github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/secrets/secrets.go (about) 1 package secrets 2 3 import ( 4 "fmt" 5 6 "github.com/ActiveState/cli/internal/access" 7 "github.com/ActiveState/cli/internal/errs" 8 "github.com/ActiveState/cli/internal/keypairs" 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/ActiveState/cli/internal/logging" 11 "github.com/ActiveState/cli/internal/multilog" 12 "github.com/ActiveState/cli/internal/output" 13 "github.com/ActiveState/cli/internal/primer" 14 "github.com/ActiveState/cli/internal/runbits/rationalize" 15 "github.com/ActiveState/cli/internal/secrets" 16 secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets" 17 secretsModels "github.com/ActiveState/cli/pkg/platform/api/secrets/secrets_models" 18 "github.com/ActiveState/cli/pkg/platform/authentication" 19 "github.com/ActiveState/cli/pkg/project" 20 ) 21 22 type listPrimeable interface { 23 primer.Outputer 24 primer.Projecter 25 primer.Configurer 26 primer.Auther 27 } 28 29 // ListRunParams tracks the info required for running List. 30 type ListRunParams struct { 31 Filter string 32 } 33 34 // List manages the listing execution context. 35 type List struct { 36 secretsClient *secretsapi.Client 37 out output.Outputer 38 proj *project.Project 39 cfg keypairs.Configurable 40 auth *authentication.Auth 41 } 42 43 type secretData struct { 44 Name string `locale:"name,[HEADING]Name[/RESET]"` 45 Scope string `locale:"scope,[HEADING]Scope[/RESET]"` 46 Description string `locale:"description,[HEADING]Description[/RESET]"` 47 HasValue string `locale:"hasvalue,[HEADING]Value[/RESET]"` 48 Usage string `locale:"usage,[HEADING]Usage[/RESET]"` 49 } 50 51 // NewList prepares a list execution context for use. 52 func NewList(client *secretsapi.Client, p listPrimeable) *List { 53 return &List{ 54 secretsClient: client, 55 out: p.Output(), 56 proj: p.Project(), 57 cfg: p.Config(), 58 auth: p.Auth(), 59 } 60 } 61 62 type listOutput struct { 63 out output.Outputer 64 data []*secretData 65 } 66 67 func (o *listOutput) MarshalOutput(format output.Format) interface{} { 68 return struct { 69 Data []*secretData `opts:"verticalTable" locale:","` 70 }{ 71 o.data, 72 } 73 } 74 75 func (o *listOutput) MarshalStructured(format output.Format) interface{} { 76 output := make([]*SecretExport, len(o.data)) 77 for i, d := range o.data { 78 out := &SecretExport{ 79 Name: d.Name, 80 Scope: d.Scope, 81 Description: d.Description, 82 } 83 84 if d.HasValue == locale.T("secrets_row_value_set") { 85 out.HasValue = true 86 } 87 88 output[i] = out 89 } 90 return output 91 } 92 93 // Run executes the list behavior. 94 func (l *List) Run(params ListRunParams) error { 95 if l.proj == nil { 96 return rationalize.ErrNoProject 97 } 98 l.out.Notice(locale.Tr("operating_message", l.proj.NamespaceString(), l.proj.Dir())) 99 100 if err := checkSecretsAccess(l.proj, l.auth); err != nil { 101 return locale.WrapError(err, "secrets_err_check_access") 102 } 103 104 defs, err := definedSecrets(l.proj, l.secretsClient, l.cfg, l.auth, params.Filter) 105 if err != nil { 106 return locale.WrapError(err, "secrets_err_defined") 107 } 108 109 meta, err := defsToData(defs, l.cfg, l.proj, l.auth) 110 if err != nil { 111 return locale.WrapError(err, "secrets_err_values") 112 } 113 114 l.out.Print(&listOutput{l.out, meta}) 115 116 return nil 117 } 118 119 // checkSecretsAccess is reusable "runner-level" logic and provides a directly 120 // usable localized error. 121 func checkSecretsAccess(proj *project.Project, auth *authentication.Auth) error { 122 if proj == nil { 123 return rationalize.ErrNoProject 124 } 125 allowed, err := access.Secrets(proj.Owner(), auth) 126 if err != nil { 127 return locale.WrapError(err, "secrets_err_access") 128 } 129 if !allowed { 130 return locale.NewError("secrets_warning_no_access") 131 } 132 return nil 133 } 134 135 func definedSecrets(proj *project.Project, secCli *secretsapi.Client, cfg keypairs.Configurable, auth *authentication.Auth, filter string) ([]*secretsModels.SecretDefinition, error) { 136 logging.Debug("listing variables for org=%s, project=%s", proj.Owner(), proj.Name()) 137 138 secretDefs, err := secrets.DefsByProject(secCli, proj.Owner(), proj.Name()) 139 if err != nil { 140 return nil, err 141 } 142 143 if filter != "" { 144 secretDefs, err = filterSecrets(proj, cfg, auth, secretDefs, filter) 145 if err != nil { 146 return nil, errs.Wrap(err, "Could not filter secrets") 147 } 148 } 149 150 return secretDefs, nil 151 } 152 153 func filterSecrets(proj *project.Project, cfg keypairs.Configurable, auth *authentication.Auth, secrectDefs []*secretsModels.SecretDefinition, filter string) (defs []*secretsModels.SecretDefinition, rerr error) { 154 secrectDefsFiltered := []*secretsModels.SecretDefinition{} 155 156 oldExpander := project.RegisteredExpander("secrets") 157 if oldExpander != nil { 158 defer func() { 159 err := project.RegisterExpander("secrets", oldExpander) 160 if err != nil { 161 rerr = errs.Pack(rerr, errs.Wrap(err, "Could not register old secrets expander")) 162 } 163 }() 164 } 165 expander := project.NewSecretExpander(secretsapi.Get(auth), proj, nil, cfg, auth) 166 err := project.RegisterExpander("secrets", expander.Expand) 167 if err != nil { 168 return nil, errs.Wrap(err, "Could not register secrets expander") 169 } 170 171 _, err = project.ExpandFromProject(fmt.Sprintf("$%s", filter), proj) 172 if err != nil { 173 return nil, errs.Wrap(err, "Could not expand filter") 174 } 175 176 accessedSecrets := expander.SecretsAccessed() 177 if accessedSecrets == nil { 178 return secrectDefsFiltered, nil 179 } 180 181 for _, secretDef := range secrectDefs { 182 isUser := *secretDef.Scope == secretsModels.SecretDefinitionScopeUser 183 for _, accessedSecret := range accessedSecrets { 184 if accessedSecret.Name == *secretDef.Name && accessedSecret.IsUser == isUser { 185 secrectDefsFiltered = append(secrectDefsFiltered, secretDef) 186 } 187 } 188 } 189 190 return secrectDefsFiltered, nil 191 } 192 193 func defsToData(defs []*secretsModels.SecretDefinition, cfg keypairs.Configurable, proj *project.Project, auth *authentication.Auth) ([]*secretData, error) { 194 data := make([]*secretData, len(defs)) 195 expander := project.NewSecretExpander(secretsapi.Get(auth), proj, nil, cfg, auth) 196 197 for i, def := range defs { 198 if def.Name == nil || def.Scope == nil { 199 multilog.Error("Could not get pointer for secret name and/or scope, definition ID: %s", def.DefID) 200 continue 201 } 202 203 data[i] = &secretData{ 204 Name: *def.Name, 205 Scope: *def.Scope, 206 Description: def.Description, 207 HasValue: locale.T("secrets_row_value_unset"), 208 Usage: fmt.Sprintf("%s.%s", *def.Scope, *def.Name), 209 } 210 211 if data[i].Description == "" { 212 data[i].Description = locale.T("secrets_description_unset") 213 } 214 215 secretValue, err := expander.FindSecret(*def.Name, *def.Scope == secretsModels.SecretDefinitionScopeUser) 216 if err != nil { 217 logging.Debug("Could not determine secret value, got error: %v", err) 218 continue 219 } 220 221 if secretValue != nil { 222 data[i].HasValue = locale.T("secrets_row_value_set") 223 } 224 } 225 226 return data, nil 227 }