From 48cbe41007ea177421f0bad4583406a5beeea93b Mon Sep 17 00:00:00 2001 From: Igor Ignatyev Date: Thu, 25 Sep 2025 16:55:22 +0300 Subject: [PATCH 1/2] key-value item, allow to store any --- action.set.yaml | 6 ++++++ plugin.go | 15 ++++++++++++++- yaml.go | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/action.set.yaml b/action.set.yaml index ab0a81c..c82bf32 100644 --- a/action.set.yaml +++ b/action.set.yaml @@ -13,3 +13,9 @@ action: title: Value description: Value default: "" + options: + - name: format + title: Value Format + description: Format to parse the input value + enum: ["string", "yaml"] + default: string diff --git a/plugin.go b/plugin.go index c3f40a7..b1dbee4 100644 --- a/plugin.go +++ b/plugin.go @@ -175,7 +175,20 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) { key := KeyValueItem{ Key: input.Arg("key").(string), } - key.Value, _ = input.Arg("value").(string) + + userValue := input.Arg("value").(string) + format := input.Opt("format") + if format == "yaml" && userValue != "" { + yamlValue, err := parseYamlString(userValue) + if err != nil { + return fmt.Errorf("failed to parse YAML: %w", err) + } + + key.Value = yamlValue + } else { + key.Value = userValue + } + return saveKey(p.k, key) })) diff --git a/yaml.go b/yaml.go index 94bb458..37f7742 100644 --- a/yaml.go +++ b/yaml.go @@ -235,3 +235,9 @@ func (s *dataStoreYaml) Save() error { func (s *dataStoreYaml) Destroy() error { return s.file.Remove() } + +func parseYamlString(value string) (interface{}, error) { + var yamlValue interface{} + err := yaml.Unmarshal([]byte(value), &yamlValue) + return yamlValue, err +} From 6be80a9f47066331f89ec097bb3812ce33f0a83a Mon Sep 17 00:00:00 2001 From: Igor Ignatyev Date: Tue, 30 Sep 2025 17:40:13 +0300 Subject: [PATCH 2/2] fix --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ action.set.yaml | 2 +- plugin.go | 17 +++++------- yaml.go | 22 +++++++++++++--- 4 files changed, 95 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 06ac419..4d21518 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,75 @@ launchr login \ --keyring-passphrase-file=/path/to/your/secret ``` +To add a new key-value pair with an interactive shell: +```shell +launchr keyring:set your-key +``` + +It's possible to parse a user value and store it as a struct in a keyring. Supported formats are [string, yaml, json]: +```shell +launchr keyring:set your-key value +``` + +It's possible to parse a user value and store it as a struct in a keyring. Possible formats are [string, yaml, json]: +```shell +launchr keyring:set key --format yaml -- "- name: test-1 +- name: test-2" +launchr keyring:set key --format yaml -- "$(cat file.yaml)" +launchr keyring:set key --format json -- '[ + { + "name": "test-1" + }, + { + "name": "test-2" + } +]' +launchr keyring:set key --format json -- "$(cat file.json)" +``` + +You can dynamically build JSON\YAML wit structures and pass them directly to the command: `jq` +```shell +# Define your variables +TOKEN1="abc123def456" +NAME1="production-api-key" +CREATED1="2025-01-15T10:30:00Z" + +TOKEN2="xyz789uvw012" +NAME2="development-token" +CREATED2="2025-01-15T11:45:00Z" +EXPIRES2="2025-07-15T11:45:00Z" + +launchr keyring:set api-tokens-json --format json -- "$(jq -n \ + --arg t1 "$TOKEN1" --arg n1 "$NAME1" --arg c1 "$CREATED1" \ + --arg t2 "$TOKEN2" --arg n2 "$NAME2" --arg c2 "$CREATED2" --arg e2 "$EXPIRES2" \ + '[ + { + tokenhash: $t1, + name: $n1, + created: $c1, + expires: null + }, + { + tokenhash: $t2, + name: $n2, + created: $c2, + expires: $e2 + } + ]')" +``` +`yq` using same variables: +```shell +launchr keyring:set api-tokens-yaml --format yaml -- "$(yq -n \ + '.[0].tokenhash = env(TOKEN1) | + .[0].name = env(NAME1) | + .[0].created = env(CREATED1) | + .[0].expires = null | + .[1].tokenhash = env(TOKEN2) | + .[1].name = env(NAME2) | + .[1].created = env(CREATED2) | + .[1].expires = env(EXPIRES2)')" +``` + Flags `--keyring-passphrase` and `--keyring-passphrase-file` are available for all launchr commands, for example: ```shell launchr compose --keyring-passphrase=YOURPASSHRPASE diff --git a/action.set.yaml b/action.set.yaml index c82bf32..f0bd6ef 100644 --- a/action.set.yaml +++ b/action.set.yaml @@ -17,5 +17,5 @@ action: - name: format title: Value Format description: Format to parse the input value - enum: ["string", "yaml"] + enum: ["string", "yaml", "json"] default: string diff --git a/plugin.go b/plugin.go index b1dbee4..95534f3 100644 --- a/plugin.go +++ b/plugin.go @@ -177,16 +177,13 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) { } userValue := input.Arg("value").(string) - format := input.Opt("format") - if format == "yaml" && userValue != "" { - yamlValue, err := parseYamlString(userValue) - if err != nil { - return fmt.Errorf("failed to parse YAML: %w", err) - } - - key.Value = yamlValue - } else { - key.Value = userValue + format := input.Opt("format").(string) + var err error + + // @TODO cover with tests + key.Value, err = parseFromString(format, userValue) + if err != nil { + return fmt.Errorf("failed to parse %s: %w", format, err) } return saveKey(p.k, key) diff --git a/yaml.go b/yaml.go index 37f7742..c31bd0d 100644 --- a/yaml.go +++ b/yaml.go @@ -1,6 +1,7 @@ package keyring import ( + "encoding/json" "errors" "os" "strings" @@ -236,8 +237,21 @@ func (s *dataStoreYaml) Destroy() error { return s.file.Remove() } -func parseYamlString(value string) (interface{}, error) { - var yamlValue interface{} - err := yaml.Unmarshal([]byte(value), &yamlValue) - return yamlValue, err +func parseFromString(format, value string) (interface{}, error) { + if format == "string" || value == "" { + return value, nil + } + + var parsed interface{} + var err error + switch format { + case "json": + err = json.Unmarshal([]byte(value), &parsed) + case "yaml": + err = yaml.Unmarshal([]byte(value), &parsed) + default: + panic("unsupported format: " + format) + } + + return parsed, err }