@@ -18,9 +18,10 @@ import (
1818)
1919
2020const (
21- procGetKeyValue = "keyring.GetKeyValue"
22- errTplNotFoundURL = "%s not found in keyring. Use `%s keyring:login` to add it."
23- errTplNotFoundKey = "%s not found in keyring. Use `%s keyring:set` to add it."
21+ procGetKeyValue = "keyring.GetKeyValue"
22+ procGetCredentials = "keyring.GetCredentials" //nolint:gosec // G101: Processor name with masked credentials.
23+ errTplNotFoundURL = "%s not found in keyring. Use `%s keyring:login` to add it."
24+ errTplNotFoundKey = "%s not found in keyring. Use `%s keyring:set` to add it."
2425
2526 envVarPassphrase = launchr .EnvVar ("keyring_passphrase" )
2627 envVarPassphraseFile = launchr .EnvVar ("keyring_passphrase_file" )
@@ -72,7 +73,7 @@ func (p *Plugin) OnAppInit(app launchr.App) error {
7273 var m action.Manager
7374 app .GetService (& m )
7475
75- addValueProcessors (m , p .k )
76+ addValueProcessors (m , p .k , p . cfg )
7677 return nil
7778}
7879
@@ -81,14 +82,27 @@ type GetKeyValueProcessorOptions = *action.GenericValueProcessorOptions[struct {
8182 Key string `yaml:"key" validate:"not-empty"`
8283}]
8384
85+ // GetCredentialsProcessorOptions is a [action.ValueProcessorOptions] struct.
86+ type GetCredentialsProcessorOptions = * action.GenericValueProcessorOptions [struct {
87+ URLFromKey string `yaml:"url_from_key"`
88+ URLFromConfig string `yaml:"url_from_config"`
89+ }]
90+
8491// addValueProcessors adds a keyring [action.ValueProcessor] to [action.Manager].
85- func addValueProcessors (m action.Manager , keyring Keyring ) {
92+ func addValueProcessors (m action.Manager , keyring Keyring , cfg launchr. Config ) {
8693 m .AddValueProcessor (procGetKeyValue , action.GenericValueProcessor [GetKeyValueProcessorOptions ]{
8794 Types : []jsonschema.Type {jsonschema .String },
8895 Fn : func (v any , opts GetKeyValueProcessorOptions , ctx action.ValueProcessorContext ) (any , error ) {
8996 return processGetByKey (v , opts , ctx , keyring )
9097 },
9198 })
99+
100+ m .AddValueProcessor (procGetCredentials , action.GenericValueProcessor [GetCredentialsProcessorOptions ]{
101+ Types : []jsonschema.Type {jsonschema .Object },
102+ Fn : func (v any , opts GetCredentialsProcessorOptions , ctx action.ValueProcessorContext ) (any , error ) {
103+ return processGetCredentials (v , opts , ctx , keyring , cfg )
104+ },
105+ })
92106}
93107
94108func processGetByKey (value any , opts GetKeyValueProcessorOptions , ctx action.ValueProcessorContext , k Keyring ) (any , error ) {
@@ -131,6 +145,76 @@ func processGetByKey(value any, opts GetKeyValueProcessorOptions, ctx action.Val
131145 return value , buildNotFoundError (opts .Fields .Key , errTplNotFoundKey , err )
132146}
133147
148+ func processGetCredentials (value any , opts GetCredentialsProcessorOptions , ctx action.ValueProcessorContext , k Keyring , cfg launchr.Config ) (any , error ) {
149+ // Init object
150+ if value == nil {
151+ value = make (map [string ]interface {})
152+ }
153+
154+ m , ok := value .(map [string ]interface {})
155+ if ! ok {
156+ return value , fmt .Errorf ("%s: invalid value type submitted: %T" , procGetCredentials , value )
157+ }
158+
159+ var url string
160+ if opts .Fields .URLFromKey != "" {
161+ url , ok = m [opts .Fields .URLFromKey ].(string )
162+ if ! ok {
163+ launchr .Term ().Warning ().Printfln ("%s: specified key `%s` doesn't exist in passed object" , procGetCredentials , opts .Fields .URLFromKey )
164+ }
165+ }
166+
167+ if url == "" && opts .Fields .URLFromConfig != "" {
168+ err := cfg .Get (opts .Fields .URLFromConfig , & url )
169+ if err != nil {
170+ return value , err
171+ }
172+ }
173+
174+ if url == "" {
175+ return value , fmt .Errorf ("%s: credentials URL is not provided in processor options or submitted data" , procGetCredentials )
176+ }
177+
178+ ci , err := k .GetForURL (url )
179+ if err == nil {
180+ m ["url" ] = ci .URL
181+ m ["username" ] = ci .Username
182+ m ["password" ] = ci .Password
183+
184+ return value , nil
185+ }
186+
187+ streams := ctx .Input .Streams ()
188+ isTerminal := streams != nil && streams .In ().IsTerminal ()
189+ if errors .Is (err , ErrNotFound ) && isTerminal {
190+ item := CredentialsItem {URL : url }
191+ err = RequestCredentialsFromTty (& item )
192+ if err != nil {
193+ return value , err
194+ }
195+
196+ err = k .AddItem (item )
197+ if err != nil {
198+ return value , err
199+ }
200+
201+ // Ensure keyring storage will be accessible after save.
202+ defer k .ResetStorage ()
203+ err = k .Save ()
204+ if err != nil {
205+ return value , err
206+ }
207+ launchr .Term ().Info ().Printfln ("URL credentials %q has been added to keyring" , item .URL )
208+
209+ m ["url" ] = ci .URL
210+ m ["username" ] = ci .Username
211+ m ["password" ] = ci .Password
212+ return value , nil
213+ }
214+
215+ return value , buildNotFoundError (url , errTplNotFoundURL , err )
216+ }
217+
134218// DiscoverActions implements [launchr.ActionDiscoveryPlugin] interface.
135219func (p * Plugin ) DiscoverActions (_ context.Context ) ([]* action.Action , error ) {
136220 // Action list.
0 commit comments