Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FSOC - Table for config and patch flag implemented #146

Closed
wants to merge 11 commits into from
181 changes: 162 additions & 19 deletions cmd/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ if on context name is specified, the current context is created/updated.`

setContextExample = `
# Set oauth credentials (recommended for interactive use)
fsoc config set --auth=oauth --url=https://mytenant.observe.appdynamics.com
fsoc config set auth=oauth url=https://mytenant.observe.appdynamics.com

# Set service or agent principal credentials (secret file must remain accessible)
fsoc config set --auth=service-principal --secret-file=my-service-principal.json
fsoc config set --auth=agent-principal --secret-file=agent-helm-values.yaml
fsoc config set --auth=agent-principal --secret-file=client-values.json --tenant=123456 --url=https://mytenant.observe.appdynamics.com
fsoc config set auth=service-principal secret-file=my-service-principal.json
fsoc config set auth=agent-principal secret-file=agent-helm-values.yaml
fsoc config set auth=agent-principal secret-file=client-values.json tenant=123456 url=https://mytenant.observe.appdynamics.com

# Set local access
fsoc config set --auth=local url=http://localhost --appd-pid=PID --appd-tid=TID --appd-pty=PTY
fsoc config set auth=local url=http://localhost appd-pid=PID appd-tid=TID appd-pty=PTY

# Set the token field on the "prod" context entry without touching other values
fsoc config set --profile prod --token=top-secret`
fsoc config set profile prod token=top-secret`
)

func newCmdConfigSet() *cobra.Command {
Expand All @@ -57,21 +57,30 @@ func newCmdConfigSet() *cobra.Command {
Use: "set [--profile CONTEXT] --auth=AUTH [flags]",
Short: "Create or modify a context entry in an fsoc config file",
Long: setContextLong,
Args: cobra.MaximumNArgs(1),
Args: cobra.MaximumNArgs(9),
Example: setContextExample,
Annotations: map[string]string{AnnotationForConfigBypass: ""},
Run: configSetContext,
}
cmd.Flags().String(AppdPid, "", "pid to use (local auth type only, provide raw value to be encoded)")
_ = cmd.Flags().MarkDeprecated(AppdPid, "the --"+AppdPid+" flag is deprecated, please use arguments supplied as "+AppdPid+"="+strings.ToUpper(AppdPid))
cmd.Flags().String(AppdTid, "", "tid to use (local auth type only, provide raw value to be encoded)")
_ = cmd.Flags().MarkDeprecated(AppdTid, "the --"+AppdTid+" flag is deprecated, please use arguments supplied as "+AppdTid+"="+strings.ToUpper(AppdTid))
cmd.Flags().String(AppdPty, "", "pty to use (local auth type only, provide raw value to be encoded)")
_ = cmd.Flags().MarkDeprecated(AppdPty, "the --"+AppdPty+" flag is deprecated, please use arguments supplied as "+AppdPty+"="+strings.ToUpper(AppdPty))
cmd.Flags().String("auth", "", fmt.Sprintf(`Select authentication method, one of {"%v"}`, strings.Join(GetAuthMethodsStringList(), `", "`)))
_ = cmd.Flags().MarkDeprecated("auth", "the --auth flag is deprecated, please use arguments supplied as auth=AUTH")
cmd.Flags().String("server", "", "Set server host name")
_ = cmd.Flags().MarkDeprecated("server", "The --server flag is deprecated, please use --url instead.")
_ = cmd.Flags().MarkDeprecated("server", "the --server flag is deprecated, please use arguments supplied as url=URL")
cmd.Flags().String("url", "", "Set server URL (with http or https schema)")
_ = cmd.Flags().MarkDeprecated("url", "the --url flag is deprecated, please use arguments supplied as url=URL")
cmd.Flags().String("tenant", "", "Set tenant ID")
_ = cmd.Flags().MarkDeprecated("tenant", "the --tenant flag is deprecated, please use arguments supplied as tenant=TENANT")
cmd.Flags().String("token", "", "Set token value (use --token=- to get from stdin)")
_ = cmd.Flags().MarkDeprecated("token", "the --token flag is deprecated, please use arguments supplied as token=TOKEN")
cmd.Flags().String("secret-file", "", "Set a credentials file to use for service principal (.json or .csv) or agent principal (.yaml)")
_ = cmd.Flags().MarkDeprecated("secret-file", "the --secret-file flag is deprecated, please use arguments supplied as secret-file=SECRET-TOKEN")
cmd.Flags().Bool("patch", false, "Bypass field clearing")
return cmd
}

Expand All @@ -95,13 +104,39 @@ func validateUrl(providedUrl string) (string, error) {
return parsedUrl.String(), nil
}

func validateArgs(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()
allowedArgs := []string{AppdPid, AppdTid, AppdPty, "auth", "server", "url", "tenant", "token", "secret-file"}
for i := 0; i < len(args); i++ {
// check arg format ∑+=∑+
stringSegments := strings.Split(args[i], "=")
name, value := stringSegments[0], stringSegments[1]
if len(stringSegments) != 2 {
return fmt.Errorf("parameter name and value cannot contain \"=\"")
}
// check arg name is valid (i.e. no disallowed flags)
if !slices.Contains(allowedArgs, name) {
return fmt.Errorf("argument name %s must be one of the following values %s", name, strings.Join(allowedArgs, ", "))
}
// make sure flag isn't already set
if flags.Changed(name) {
return fmt.Errorf("cannot have both flag and argument with same name")
}
// Set flag manually
err := flags.Set(name, value)
if err != nil {
return err
}
}
return nil
}

func configSetContext(cmd *cobra.Command, args []string) {
var contextName string

// Check that either context name or current context is specified
if len(args) > 0 {
_ = cmd.Help()
log.Fatalf("Unexpected args: %v", args)
if err := validateArgs(cmd, args); err != nil {
log.Fatalf("%v", err)
}

// Check that at least one value is specified (including empty)
Expand Down Expand Up @@ -144,8 +179,22 @@ func configSetContext(cmd *cobra.Command, args []string) {
ctxPtr = &cfg.Contexts[len(cfg.Contexts)-1]
}

patch, _ := cmd.Flags().GetBool("patch")
// update only the fields for which flags were specified explicitly
if flags.Changed("auth") {
val, _ := flags.GetString("auth")
if val != "" && !slices.Contains(GetAuthMethodsStringList(), val) {
log.Fatalf(`Invalid --auth method %q; must be one of {"%v"}`, val, strings.Join(GetAuthMethodsStringList(), `", "`))
}
ctxPtr.AuthMethod = val
// Clear All fields before setting other fields
clearFields([]string{"url", "server", "tenant", "user", "token", "refresh_token", "secret-file"}, ctxPtr)
}
if flags.Changed("server") {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, "url")
if err != nil {
log.Fatal(err.Error())
}
providedServer, _ := flags.GetString("server")
constructedUrl := "https://" + providedServer
cleanedUrl, err := validateUrl(constructedUrl)
Expand All @@ -154,19 +203,40 @@ func configSetContext(cmd *cobra.Command, args []string) {
}
log.Warnf("The --server option is now deprecated. In the future, please use --url instead. We will set the url to %q for you now", cleanedUrl)
ctxPtr.URL = cleanedUrl
if !patch {
automatedFieldClearing(ctxPtr, "url")
}
}
if flags.Changed("url") {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, "url")
if err != nil {
log.Fatal(err.Error())
}
providedUrl, _ := flags.GetString("url")
cleanedUrl, err := validateUrl(providedUrl)
if err != nil {
log.Fatal(err.Error())
}
ctxPtr.URL = cleanedUrl
if !patch {
automatedFieldClearing(ctxPtr, "url")
}
}
if flags.Changed("tenant") {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, "tenant")
if err != nil {
log.Fatal(err.Error())
}
ctxPtr.Tenant, _ = flags.GetString("tenant")
if !patch {
automatedFieldClearing(ctxPtr, "tenant")
}
}
if flags.Changed("token") {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, "token")
if err != nil {
log.Fatal(err.Error())
}
value, _ := flags.GetString("token")
if value == "-" { // token to come from stdin
scanner := bufio.NewScanner(os.Stdin)
Expand All @@ -175,38 +245,60 @@ func configSetContext(cmd *cobra.Command, args []string) {
} else {
ctxPtr.Token = value
}
if !patch {
automatedFieldClearing(ctxPtr, "token")
}
}
if flags.Changed("secret-file") {

err := validateWriteReq(cmd, ctxPtr.AuthMethod, "secret-file")
if err != nil {
log.Fatal(err.Error())
}
path, _ := flags.GetString("secret-file")
path = expandHomePath(path)
var err error
ctxPtr.SecretFile, err = filepath.Abs(path)
if err != nil {
ctxPtr.SecretFile = path
}
ctxPtr.CsvFile = "" // CSV file is a backward-compatibility value only
}
if flags.Changed("auth") {
val, _ := flags.GetString("auth")
if val != "" && !slices.Contains(GetAuthMethodsStringList(), val) {
log.Fatalf(`Invalid --auth method %q; must be one of {"%v"}`, val, strings.Join(GetAuthMethodsStringList(), `", "`))
if !patch {
automatedFieldClearing(ctxPtr, "secret-file")
}
ctxPtr.AuthMethod = val
}

if ctxPtr.AuthMethod == AuthMethodLocal {
if flags.Changed(AppdPid) {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdPid)
if err != nil {
log.Fatal(err.Error())
}
pid, _ := flags.GetString(AppdPid)
ctxPtr.LocalAuthOptions.AppdPid = pid
if !patch {
automatedFieldClearing(ctxPtr, AppdPid)
}
}
if flags.Changed(AppdPty) {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdPty)
if err != nil {
log.Fatal(err.Error())
}
pty, _ := flags.GetString(AppdPty)
ctxPtr.LocalAuthOptions.AppdPty = pty
if !patch {
automatedFieldClearing(ctxPtr, AppdPty)
}
}
if flags.Changed(AppdTid) {
err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdTid)
if err != nil {
log.Fatal(err.Error())
}
tid, _ := flags.GetString(AppdTid)
ctxPtr.LocalAuthOptions.AppdTid = tid
if !patch {
automatedFieldClearing(ctxPtr, AppdTid)
}
}
}

Expand Down Expand Up @@ -239,3 +331,54 @@ func expandHomePath(file string) string {
}
return file
}

func getAuthFieldConfigRow(authService string) AuthFieldConfigRow {
return getAuthFieldWritePermissions()[authService]
}

func validateWriteReq(cmd *cobra.Command, authService string, field string) error {
flags := cmd.Flags()
authProvider := authService
if flags.Changed("auth") {
authProvider, _ = flags.GetString("auth")
}
if authProvider == "" {
return fmt.Errorf("must provide an authentication type before or while writing to other context fields")
}
if getAuthFieldConfigRow(authProvider)[field] == 0 {
return fmt.Errorf("cannot write to field %s because it is not allowed for authentication method %s", field, authProvider)
}
return nil
}

func clearFields(fields []string, ctxPtr *Context) {
if slices.Contains(fields, "auth") {
ctxPtr.AuthMethod = ""
}
if slices.Contains(fields, "url") {
ctxPtr.URL = ""
}
if slices.Contains(fields, "server") {
ctxPtr.Server = ""
}
if slices.Contains(fields, "tenant") {
ctxPtr.Tenant = ""
}
if slices.Contains(fields, "user") {
ctxPtr.User = ""
}
if slices.Contains(fields, "token") {
ctxPtr.Token = ""
}
if slices.Contains(fields, "refresh_token") {
ctxPtr.RefreshToken = ""
}
if slices.Contains(fields, "secret-file") {
ctxPtr.SecretFile = ""
}
}

func automatedFieldClearing(ctxPtr *Context, field string) {
table := getAuthFieldClearConfig()
clearFields(table[ctxPtr.AuthMethod][field], ctxPtr)
}
Loading