package bannerctl import ( "context" "database/sql" "errors" "flag" "fmt" "time" "github.com/peterbourgon/ff/v3" bsltypes "edge-infra.dev/pkg/edge/api/bsl/types" "edge-infra.dev/pkg/edge/bsl" "edge-infra.dev/pkg/edge/gcpinfra/constants" "edge-infra.dev/pkg/lib/gcp/cloudsql" "edge-infra.dev/pkg/lib/logging" ) var ( // ErrNoBillingAccount occurs when no billing account is provided ErrNoBillingAccount = errors.New("no billing account provided") // ErrNoFolderID occurs when no folder id is provided ErrNoFolderID = errors.New("no folder id provided") // ErrNoDomain occurs when no domain name is provided ErrNoDomain = errors.New("no domain provided") // ErrNoDatasyncDNSName occurs when no datasync dns name is provided ErrNoDatasyncDNSName = errors.New("datasync dns name is not provided") // ErrNoDatasyncDNSZone occurs when no datasync dns zone is provided ErrNoDatasyncDNSZone = errors.New("datasync dns zone is not provided") ) // Config represents bannerctl configuration type Config struct { ProjectBootstrapping *ProjectBootstrappingConfig ForemanProjectID string PlatInfraProjectID string EdgeAPI string TotpSecret string Domain string DatasyncDNSName string DatasyncDNSZone string MetricsAddr string DB *sql.DB DatabaseName string BSLAccessKey bsl.AccessKey BSLConfig bsltypes.BSPConfig GCPRegion string GCPZone string GCPForemanProjectNumber string EdgeSecOptInCompliance bool EdgeSecMaxLeasePeriod string EdgeSecMaxValidityPeriod string RequeueTime time.Duration IntervalTime time.Duration ResourceTimeout time.Duration } // ProjectBootstrappingConfig represents required configuration for bannerctl to // bootstrap GCP projects for Edge: // - billing account for new projects // - folder id to add project to type ProjectBootstrappingConfig struct { BillingAccount string FolderID string } // NewConfig adds the flags required for bannerctl to a FlagSet, which is // parsed by peterbourgon/ff to add support for environment variables, // returning a valid Config struct and the created FlagSet so it can be // bound to other consumers func NewConfig(args []string) (Config, *flag.FlagSet, error) { // default values config := Config{ ProjectBootstrapping: &ProjectBootstrappingConfig{}, } fs := flag.NewFlagSet("bannerctl", flag.ExitOnError) var SQLUser, SQLConnection string fs.StringVar( &config.ProjectBootstrapping.FolderID, "gcp-tenants-folder-id", "", "folder id to store created gcp projects in", ) fs.StringVar( &config.ProjectBootstrapping.BillingAccount, "gcp-billing-account", constants.DefaultBillingAccountID, "billing account id to use for created gcp projects", ) fs.StringVar( &config.ForemanProjectID, "gcp-foreman-project-id", "", "foreman gcp project id that bannerctl will be running from -- not required "+ "for enterprise cluster deployments", ) fs.StringVar( &config.PlatInfraProjectID, "gcp-plat-infra-project-id", "", "platform infrastructure (dns, artifact registry, etc) project", ) fs.StringVar( &config.EdgeAPI, "edge-api", "", "api for edge env", ) fs.StringVar( &config.TotpSecret, "totp-secret-key", "", "totp secret for validating totp token", ) fs.StringVar( &config.Domain, "domain", "", "top level edge domain name", ) fs.StringVar( &config.DatasyncDNSName, "datasync-dns-name", "", "exact dns name know as shown in gcp, remove the ending dot(.). e.g.. 'datasync-preprod.dev'", ) fs.StringVar( &config.DatasyncDNSZone, "datasync-dns-zone", "", "datasync dns zone as shown in gcp console. e.g.. 'edge-preprod-datasync-dns-zone'", ) fs.StringVar( &config.MetricsAddr, "metrics-address", ":8080", "Address to bind metrics endpoint (/metrics) to", ) fs.StringVar( &config.DatabaseName, "sql-db-name", "", "SQL Database Name", ) fs.StringVar( &SQLUser, "sql-user", "", "SQL User", ) fs.StringVar( &SQLConnection, "sql-connection-name", "", "SQL Connection Name", ) fs.StringVar(&config.BSLAccessKey.SecretKey, "edge-bsl-secret-key", "", "secret key for access the bsl", ) fs.StringVar(&config.BSLAccessKey.SharedKey, "edge-bsl-shared-key", "", "shared key for access the bsl", ) fs.StringVar( &config.BSLConfig.Endpoint, "bsl-endpoint", "", "bsl endpoint for a specific environment", ) fs.StringVar( &config.BSLConfig.Root, "bsl-root-org", "", "bsl root org", ) fs.StringVar( &config.BSLConfig.OrganizationPrefix, "bsp-organization-prefix", "", "bsl organization prefix for a specific environment", ) fs.StringVar( &config.GCPRegion, "gcp-region", "", "GCP region - ex: us-east1", ) fs.StringVar( &config.GCPZone, "gcp-zone", "", "GCP zone - ex: a/b/c", ) fs.StringVar( &config.GCPForemanProjectNumber, "gcp-foreman-project-number", "", "GCP Project Number for all Foreman Projects", ) fs.BoolVar( &config.EdgeSecOptInCompliance, "edge-sec-opt-in-compliance", false, "Edge Security compliance setting", ) fs.StringVar( &config.EdgeSecMaxLeasePeriod, "edge-sec-max-lease-period", "", "Maximum secret lease period", ) fs.StringVar( &config.EdgeSecMaxValidityPeriod, "edge-sec-max-validity-period", "", "Maximum secret validity period", ) fs.DurationVar( &config.IntervalTime, "interval-time", 2*time.Minute, "interval for processing", ) fs.DurationVar( &config.RequeueTime, "requeue-time", 20*time.Second, "requeue interval for errors", ) fs.DurationVar( &config.ResourceTimeout, "resource-timeout", 20*time.Second, "timeout for kms resource", ) if err := ff.Parse(fs, args[1:], ff.WithEnvVarNoPrefix()); err != nil { return Config{}, &flag.FlagSet{}, err } // Connect bannerctl to database log := logging.NewLogger().WithName("bannerctl") db, err := cloudsql.GCPPostgresConnection(SQLConnection). DBName(config.DatabaseName). Username(SQLUser). NewConnection() // Log database errors, without exiting, for alerting purposes. Infra status recording is not a critical feature. if err != nil { log.Error(err, "could not create sql connection") } else { var pingCtx, cancel = context.WithTimeout(context.Background(), 3*time.Second) if err := db.PingContext(pingCtx); err != nil { log.Error(err, "could not ping sql connection") } else { config.DB = db } cancel() } log.Info("Infra status recording feature", "enabled", config.DB != nil) if err := config.Validate(); err != nil { return Config{}, &flag.FlagSet{}, err } return config, fs, nil } // Validation checks for configuration func (f Config) Validate() error { if f.Domain == "" { return fmt.Errorf("%w", ErrNoDomain) } if f.DatasyncDNSName == "" { return ErrNoDatasyncDNSName } if f.DatasyncDNSZone == "" { return ErrNoDatasyncDNSZone } if f.BSLAccessKey.SecretKey == "" { return fmt.Errorf("misisng bsl access secret key") } if f.BSLAccessKey.SharedKey == "" { return fmt.Errorf("missing bsl access shared key") } if f.BSLConfig.Endpoint == "" { return fmt.Errorf("missing bsl endpoint") } if f.BSLConfig.Root == "" { return fmt.Errorf("missing bsl root org") } if f.BSLConfig.OrganizationPrefix == "" { return fmt.Errorf("missing bsl org prefix") } if f.GCPRegion == "" { return fmt.Errorf("missing gcp region") } if f.GCPZone == "" { return fmt.Errorf("missing gcp zone") } if f.GCPForemanProjectNumber == "" { return fmt.Errorf("missing gcp foreman project number") } if f.EdgeSecMaxLeasePeriod == "" { return fmt.Errorf("missing edge security max secret lease period") } if f.EdgeSecMaxValidityPeriod == "" { return fmt.Errorf("missing edge security max secret validity period") } if f.IntervalTime == 0 { return fmt.Errorf("missing INTERVAL_TIME environment variable") } if f.RequeueTime == 0 { return fmt.Errorf("missing REQUEUE_TIME environment variable") } if f.ResourceTimeout == 0 { return fmt.Errorf("missing RESOURCE_TIMEOUT environment variable") } return f.ProjectBootstrapping.Validate() } // Validate validates the config required for bootstrapping tenant projects. // If it isn't enabled, it performs no validation. func (f ProjectBootstrappingConfig) Validate() error { if f.BillingAccount == "" { return fmt.Errorf("%w", ErrNoBillingAccount) } if f.FolderID == "" { return fmt.Errorf("%w", ErrNoFolderID) } return nil }