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  }