Merge pull request #5 from justinas/keys-keyfile

Add 'deployment.keys.<key>.keyFile' option
This commit is contained in:
Zhaofeng Li 2021-02-10 17:20:28 -08:00 committed by GitHub
commit 9d59a6a288
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 10 deletions

View file

@ -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

View file

@ -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(())
}

View file

@ -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);

View file

@ -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) {