diff --git a/go.mod b/go.mod index 75183edc..dd9cc653 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/aws/smithy-go v1.20.3 github.com/conductorone/baton-sdk v0.2.18 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 go.uber.org/zap v1.27.0 google.golang.org/protobuf v1.34.2 ) @@ -68,8 +68,8 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/config/config.go b/vendor/github.com/conductorone/baton-sdk/pkg/config/config.go new file mode 100644 index 00000000..45d2b093 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/config/config.go @@ -0,0 +1,228 @@ +package config + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/conductorone/baton-sdk/pkg/cli" + "github.com/conductorone/baton-sdk/pkg/connectorrunner" + "github.com/conductorone/baton-sdk/pkg/field" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +func DefineConfiguration( + ctx context.Context, + connectorName string, + connector cli.GetConnectorFunc, + schema field.Configuration, + options ...connectorrunner.Option, +) (*viper.Viper, *cobra.Command, error) { + v := viper.New() + v.SetConfigType("yaml") + + path, name, err := cleanOrGetConfigPath(os.Getenv("BATON_CONFIG_PATH")) + if err != nil { + return nil, nil, err + } + + v.SetConfigName(name) + v.AddConfigPath(path) + if err := v.ReadInConfig(); err != nil { + if errors.Is(err, viper.ConfigFileNotFoundError{}) { + return nil, nil, err + } + } + v.SetEnvPrefix("baton") + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + v.AutomaticEnv() + + // add default fields and constrains + schema.Fields = field.EnsureDefaultFieldsExists(schema.Fields) + schema.Constraints = field.EnsureDefaultRelationships(schema.Constraints) + + // setup CLI with cobra + mainCMD := &cobra.Command{ + Use: connectorName, + Short: connectorName, + SilenceErrors: true, + SilenceUsage: true, + RunE: cli.MakeMainCommand(ctx, connectorName, v, schema, connector, options...), + } + + // add options to the main command + for _, field := range schema.Fields { + switch field.FieldType { + case reflect.Bool: + value, err := field.Bool() + if err != nil { + return nil, nil, fmt.Errorf( + "field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + mainCMD.PersistentFlags(). + BoolP(field.FieldName, field.CLIShortHand, value, field.GetDescription()) + case reflect.Int: + value, err := field.Int() + if err != nil { + return nil, nil, fmt.Errorf( + "field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + mainCMD.PersistentFlags(). + IntP(field.FieldName, field.CLIShortHand, value, field.GetDescription()) + case reflect.String: + value, err := field.String() + if err != nil { + return nil, nil, fmt.Errorf( + "field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + mainCMD.PersistentFlags(). + StringP(field.FieldName, field.CLIShortHand, value, field.GetDescription()) + case reflect.Slice: + value, err := field.StringSlice() + if err != nil { + return nil, nil, fmt.Errorf( + "field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + mainCMD.PersistentFlags(). + StringSliceP(field.FieldName, field.CLIShortHand, value, field.GetDescription()) + default: + return nil, nil, fmt.Errorf( + "field %s, %s is not yet supported", + field.FieldName, + field.FieldType, + ) + } + + // mark hidden + if field.Hidden { + err := mainCMD.PersistentFlags().MarkHidden(field.FieldName) + if err != nil { + return nil, nil, fmt.Errorf( + "cannot hide field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + } + + // mark required + if field.Required { + if field.FieldType == reflect.Bool { + return nil, nil, fmt.Errorf("requiring %s of type %s does not make sense", field.FieldName, field.FieldType) + } + + err := mainCMD.MarkPersistentFlagRequired(field.FieldName) + if err != nil { + return nil, nil, fmt.Errorf( + "cannot require field %s, %s: %w", + field.FieldName, + field.FieldType, + err, + ) + } + } + } + + // apply constrains + for _, constrain := range schema.Constraints { + switch constrain.Kind { + case field.MutuallyExclusive: + mainCMD.MarkFlagsMutuallyExclusive(listFieldConstrainsAsStrings(constrain)...) + case field.RequiredTogether: + mainCMD.MarkFlagsRequiredTogether(listFieldConstrainsAsStrings(constrain)...) + case field.AtLeastOne: + mainCMD.MarkFlagsOneRequired(listFieldConstrainsAsStrings(constrain)...) + case field.Dependents: + // do nothing + } + } + + if err := v.BindPFlags(mainCMD.PersistentFlags()); err != nil { + return nil, nil, err + } + if err := v.BindPFlags(mainCMD.Flags()); err != nil { + return nil, nil, err + } + + grpcServerCmd := &cobra.Command{ + Use: "_connector-service", + Short: "Start the connector service", + Hidden: true, + RunE: cli.MakeGRPCServerCommand(ctx, connectorName, v, schema, connector), + } + mainCMD.AddCommand(grpcServerCmd) + + capabilitiesCmd := &cobra.Command{ + Use: "capabilities", + Short: "Get connector capabilities", + RunE: cli.MakeCapabilitiesCommand(ctx, connectorName, v, connector), + } + mainCMD.AddCommand(capabilitiesCmd) + + mainCMD.AddCommand(cli.AdditionalCommands(name, schema.Fields)...) + + // NOTE (shackra): we don't check subcommands (i.e.: grpcServerCmd and capabilitiesCmd) + mainCMD.PersistentFlags().VisitAll(func(f *pflag.Flag) { + if v.IsSet(f.Name) { + _ = mainCMD.Flags().Set(f.Name, v.GetString(f.Name)) + } + }) + + return v, mainCMD, nil +} + +func listFieldConstrainsAsStrings(constrains field.SchemaFieldRelationship) []string { + var fields []string + for _, v := range constrains.Fields { + fields = append(fields, v.FieldName) + } + + return fields +} + +func cleanOrGetConfigPath(customPath string) (string, string, error) { + if customPath != "" { + cfgDir, cfgFile := filepath.Split(filepath.Clean(customPath)) + if cfgDir == "" { + cfgDir = "." + } + + ext := filepath.Ext(cfgFile) + if ext == "" || (ext != ".yaml" && ext != ".yml") { + return "", "", errors.New("expected config file to have .yaml or .yml extension") + } + + return strings.TrimSuffix( + cfgDir, + string(filepath.Separator), + ), strings.TrimSuffix( + cfgFile, + ext, + ), nil + } + + return ".", ".baton", nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3220704b..ee527e0e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -183,6 +183,7 @@ github.com/conductorone/baton-sdk/pb/c1/reader/v2 github.com/conductorone/baton-sdk/pb/c1/utls/v1 github.com/conductorone/baton-sdk/pkg/annotations github.com/conductorone/baton-sdk/pkg/cli +github.com/conductorone/baton-sdk/pkg/config github.com/conductorone/baton-sdk/pkg/connectorbuilder github.com/conductorone/baton-sdk/pkg/connectorrunner github.com/conductorone/baton-sdk/pkg/connectorstore