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 {
|
||||
description = ''
|
||||
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 {
|
||||
description = ''
|
||||
|
@ -178,8 +189,17 @@ let
|
|||
then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name}
|
||||
else pkgs;
|
||||
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 {
|
||||
modules = [
|
||||
assertionModule
|
||||
deploymentOptions
|
||||
hive.defaults
|
||||
config
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::convert::TryInto;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio::process::Command;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
|
@ -109,10 +109,11 @@ impl Local {
|
|||
|
||||
let dest_path = key.dest_dir.join(name);
|
||||
|
||||
let mut temp = NamedTempFile::new()?;
|
||||
temp.write_all(key.text.as_bytes())?;
|
||||
|
||||
let temp = NamedTempFile::new()?;
|
||||
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
|
||||
// permission, for NixOps compatibility
|
||||
|
@ -149,7 +150,13 @@ impl Local {
|
|||
|
||||
let parent_dir = dest_path.parent().unwrap();
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -214,7 +214,8 @@ impl Ssh {
|
|||
let mut child = command.spawn()?;
|
||||
|
||||
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?;
|
||||
drop(stdin);
|
||||
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{
|
||||
io::{self, Cursor},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs::File, io::AsyncRead};
|
||||
use validator::{Validate, ValidationError};
|
||||
|
||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
|
||||
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")]
|
||||
#[serde(rename = "destDir")]
|
||||
pub(super) dest_dir: PathBuf,
|
||||
|
@ -17,6 +23,18 @@ pub struct Key {
|
|||
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> {
|
||||
let re = Regex::new(r"^[a-z][-a-z0-9]*$").unwrap();
|
||||
if re.is_match(name) {
|
||||
|
|
Loading…
Reference in a new issue