forked from DGNum/colmena
Merge pull request #5 from justinas/keys-keyfile
Add 'deployment.keys.<key>.keyFile' option
This commit is contained in:
commit
9d59a6a288
4 changed files with 56 additions and 10 deletions
|
@ -103,8 +103,19 @@ let
|
||||||
text = lib.mkOption {
|
text = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Content of the key.
|
Content of the key.
|
||||||
|
Either `keyFile` or `text` must be set.
|
||||||
'';
|
'';
|
||||||
type = types.str;
|
default = null;
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
};
|
||||||
|
keyFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path of the local file to read the key from.
|
||||||
|
Either `keyFile` or `text` must be set.
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
apply = value: if value == null then null else toString value;
|
||||||
|
type = types.nullOr types.path;
|
||||||
};
|
};
|
||||||
destDir = lib.mkOption {
|
destDir = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -178,8 +189,17 @@ let
|
||||||
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
|
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
|
||||||
else pkgs;
|
else pkgs;
|
||||||
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
|
evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix");
|
||||||
|
assertionModule = { config, ... }: {
|
||||||
|
assertions = lib.mapAttrsToList (key: opts: {
|
||||||
|
assertion = (opts.text == null) != (opts.keyFile == null);
|
||||||
|
message =
|
||||||
|
let prefix = "${name}.deployment.keys.${key}";
|
||||||
|
in "Exactly one of `${prefix}.text` and `${prefix}.keyFile` must be set.";
|
||||||
|
}) config.deployment.keys;
|
||||||
|
};
|
||||||
in evalConfig {
|
in evalConfig {
|
||||||
modules = [
|
modules = [
|
||||||
|
assertionModule
|
||||||
deploymentOptions
|
deploymentOptions
|
||||||
hive.defaults
|
hive.defaults
|
||||||
config
|
config
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use tokio::fs::OpenOptions;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
@ -109,10 +109,11 @@ impl Local {
|
||||||
|
|
||||||
let dest_path = key.dest_dir.join(name);
|
let dest_path = key.dest_dir.join(name);
|
||||||
|
|
||||||
let mut temp = NamedTempFile::new()?;
|
let temp = NamedTempFile::new()?;
|
||||||
temp.write_all(key.text.as_bytes())?;
|
|
||||||
|
|
||||||
let (_, temp_path) = temp.keep().map_err(|pe| pe.error)?;
|
let (_, temp_path) = temp.keep().map_err(|pe| pe.error)?;
|
||||||
|
let mut reader = key.reader().await?;
|
||||||
|
let mut writer = OpenOptions::new().write(true).open(&temp_path).await?;
|
||||||
|
tokio::io::copy(reader.as_mut(), &mut writer).await?;
|
||||||
|
|
||||||
// Well, we need the userspace chmod program to parse the
|
// Well, we need the userspace chmod program to parse the
|
||||||
// permission, for NixOps compatibility
|
// permission, for NixOps compatibility
|
||||||
|
@ -149,7 +150,13 @@ impl Local {
|
||||||
|
|
||||||
let parent_dir = dest_path.parent().unwrap();
|
let parent_dir = dest_path.parent().unwrap();
|
||||||
fs::create_dir_all(parent_dir)?;
|
fs::create_dir_all(parent_dir)?;
|
||||||
fs::rename(temp_path, dest_path)?;
|
|
||||||
|
if fs::rename(&temp_path, &dest_path).is_err() {
|
||||||
|
// Linux can not rename across different filesystems, try copy-then-remove
|
||||||
|
let copy_result = fs::copy(&temp_path, &dest_path);
|
||||||
|
fs::remove_file(&temp_path)?;
|
||||||
|
copy_result?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,7 +214,8 @@ impl Ssh {
|
||||||
let mut child = command.spawn()?;
|
let mut child = command.spawn()?;
|
||||||
|
|
||||||
let mut stdin = child.stdin.take().unwrap();
|
let mut stdin = child.stdin.take().unwrap();
|
||||||
stdin.write_all(key.text.as_bytes()).await?;
|
let mut reader = key.reader().await?;
|
||||||
|
tokio::io::copy(reader.as_mut(), &mut stdin).await?;
|
||||||
stdin.flush().await?;
|
stdin.flush().await?;
|
||||||
drop(stdin);
|
drop(stdin);
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
use std::path::PathBuf;
|
use std::{
|
||||||
|
io::{self, Cursor},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{fs::File, io::AsyncRead};
|
||||||
use validator::{Validate, ValidationError};
|
use validator::{Validate, ValidationError};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
pub(crate) text: String,
|
pub(crate) text: Option<String>,
|
||||||
|
#[serde(rename = "keyFile")]
|
||||||
|
pub(crate) key_file: Option<String>,
|
||||||
#[validate(custom = "validate_dest_dir")]
|
#[validate(custom = "validate_dest_dir")]
|
||||||
#[serde(rename = "destDir")]
|
#[serde(rename = "destDir")]
|
||||||
pub(super) dest_dir: PathBuf,
|
pub(super) dest_dir: PathBuf,
|
||||||
|
@ -17,6 +23,18 @@ pub struct Key {
|
||||||
pub(super) permissions: String,
|
pub(super) permissions: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
pub(crate) async fn reader(&'_ self,) -> Result<Box<dyn AsyncRead + Send + Unpin + '_>, io::Error> {
|
||||||
|
if let Some(ref t) = self.text {
|
||||||
|
Ok(Box::new(Cursor::new(t)))
|
||||||
|
} else if let Some(ref p) = self.key_file {
|
||||||
|
Ok(Box::new(File::open(p).await?))
|
||||||
|
} else {
|
||||||
|
unreachable!("Neither `text` nor `keyFile` set. This should have been validated by Nix assertions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_unix_name(name: &str) -> Result<(), ValidationError> {
|
fn validate_unix_name(name: &str) -> Result<(), ValidationError> {
|
||||||
let re = Regex::new(r"^[a-z][-a-z0-9]*$").unwrap();
|
let re = Regex::new(r"^[a-z][-a-z0-9]*$").unwrap();
|
||||||
if re.is_match(name) {
|
if re.is_match(name) {
|
||||||
|
|
Loading…
Reference in a new issue