Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,17 @@ The value must be formatted as json.

$ sops set ~/git/svc/sops/example.yaml '["an_array"][1]' '{"uid1":null,"uid2":1000,"uid3":["bob"]}'

You can also provide the value from a file or stdin:

.. code:: sh

# Provide the value from a file
$ echo '{"uid1":null,"uid2":1000,"uid3":["bob"]}' > /tmp/example-value
$ sops set ~/git/svc/sops/example.yaml --value-file '["an_array"][1]' /tmp/example-value

# Provide the value from stdin
$ echo '{"uid1":null,"uid2":1000,"uid3":["bob"]}' | sops set ~/git/svc/sops/example.yaml --value-stdin '["an_array"][1]'

Unset a sub-part in a document tree
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
32 changes: 26 additions & 6 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
encodingjson "encoding/json"
"fmt"
"io"
"net"
"net/url"
"os"
Expand Down Expand Up @@ -1374,8 +1375,8 @@ func main() {
},
{
Name: "set",
Usage: `set a specific key or branch in the input document. value must be a json encoded string. eg. '/path/to/file ["somekey"][0] {"somevalue":true}'`,
ArgsUsage: `file index value`,
Usage: `set a specific key or branch in the input document. value must be a JSON encoded string, for example '/path/to/file ["somekey"][0] {"somevalue":true}', or a path if --value-file is used, or omitted if --value-stdin is used`,
ArgsUsage: `file index [ value ]`,
Flags: append([]cli.Flag{
cli.StringFlag{
Name: "input-type",
Expand All @@ -1387,7 +1388,11 @@ func main() {
},
cli.BoolFlag{
Name: "value-file",
Usage: "treat 'value' as a file to read the actual value from (avoids leaking secrets in process listings)",
Usage: "treat 'value' as a file to read the actual value from (avoids leaking secrets in process listings). Mutually exclusive with --value-stdin",
},
cli.BoolFlag{
Name: "value-stdin",
Usage: "treat 'value' as a file to read the actual value from (avoids leaking secrets in process listings). Mutually exclusive with --value-file",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This help text is wrong: --value-stdin doesn't take a value, only --value-file does that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither option takes a value. They both modify what the value parameter of sops set means.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither option takes a value. They both modify what the value parameter of sops set means.

Oh, now I see what you mean. Yes, the option doesn't take a value, but sops set, the sub-command, has an optional [value] positional parameter at the end, and that is not used/read when --value-stdin is used. The help text makes it sound otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, and now I also see what you mean :D I've created #1963 to fix this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

},
cli.IntFlag{
Name: "shamir-secret-sharing-threshold",
Expand All @@ -1411,8 +1416,17 @@ func main() {
if c.Bool("verbose") {
logging.SetLevel(logrus.DebugLevel)
}
if c.NArg() != 3 {
return common.NewExitError("Error: no file specified, or index and value are missing", codes.NoFileSpecified)
if c.Bool("value-file") && c.Bool("value-stdin") {
return common.NewExitError("Error: cannot use both --value-file and --value-stdin", codes.ErrorGeneric)
}
if c.Bool("value-stdin") {
if c.NArg() != 2 {
return common.NewExitError("Error: file specified, or index and value are missing. Need precisely 2 positional arguments since --value-stdin is used.", codes.NoFileSpecified)
}
} else {
if c.NArg() != 3 {
return common.NewExitError("Error: no file specified, or index and value are missing. Need precisely 3 positional arguments.", codes.NoFileSpecified)
}
}
fileName, err := filepath.Abs(c.Args()[0])
if err != nil {
Expand All @@ -1435,7 +1449,13 @@ func main() {
}

var data string
if c.Bool("value-file") {
if c.Bool("value-stdin") {
content, err := io.ReadAll(os.Stdin)
if err != nil {
return toExitError(err)
}
data = string(content)
} else if c.Bool("value-file") {
filename := c.Args()[2]
content, err := os.ReadFile(filename)
if err != nil {
Expand Down
51 changes: 51 additions & 0 deletions functional-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,57 @@ bar: baz",
panic!("Output JSON does not have the expected structure");
}

#[test]
fn set_json_file_insert_with_value_stdin() {
let file_path = prepare_temp_file(
"test_set_json_file_insert_with_value_stdin.json",
r#"{"a": 2, "b": "ba"}"#.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops")
.status
.success(),
"sops didn't exit successfully"
);
// let value_file = prepare_temp_file("insert_value_file.json", r#"{"cc": "ccc"}"#.as_bytes());
let process = Command::new(SOPS_BINARY_PATH)
.arg("set")
.arg("--value-stdin")
.arg(file_path.clone())
.arg(r#"["c"]"#)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Error running sops");
write_to_stdin(&process, b"{\"cc\": \"ccc\"}");
let output = process.wait_with_output().expect("Failed to wait on sops");
println!(
"stdout: {}, stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success(), "sops didn't exit successfully");
let mut s = String::new();
File::open(file_path)
.unwrap()
.read_to_string(&mut s)
.unwrap();
let data: Value = serde_json::from_str(&s).expect("Error parsing sops's JSON output");
if let Value::Mapping(data) = data {
let a = data.get(&Value::String("c".to_owned())).unwrap();
if let &Value::Mapping(ref a) = a {
assert_encrypted!(&a, Value::String("cc".to_owned()));
return;
}
}
panic!("Output JSON does not have the expected structure");
}

#[test]
fn set_yaml_file_update() {
let file_path = prepare_temp_file(
Expand Down
Loading