diff --git a/cli/cmd/bootstrap_gcp.go b/cli/cmd/bootstrap_gcp.go index 91378e0c..3770c79e 100644 --- a/cli/cmd/bootstrap_gcp.go +++ b/cli/cmd/bootstrap_gcp.go @@ -87,6 +87,11 @@ func AddBootstrapGcpCmd(parent *cobra.Command, opts *GlobalOptions) { flags.StringArrayVar(&bootstrapGcpCmd.CodesphereEnv.Experiments, "experiments", gcp.DefaultExperiments, "Experiments to enable in Codesphere installation (optional)") flags.StringArrayVar(&bootstrapGcpCmd.CodesphereEnv.FeatureFlags, "feature-flags", []string{}, "Feature flags to enable in Codesphere installation (optional)") + flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoURI, "openbao-uri", "", "URI for OpenBao (optional)") + flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoEngine, "openbao-engine", "cs-secrets-engine", "OpenBao engine name (default: cs-secrets-engine)") + flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoUser, "openbao-user", "admin", "OpenBao username (optional)") + flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoPassword, "openbao-password", "", "OpenBao password (optional)") + util.MarkFlagRequired(bootstrapGcpCmd.cmd, "project-name") util.MarkFlagRequired(bootstrapGcpCmd.cmd, "billing-account") util.MarkFlagRequired(bootstrapGcpCmd.cmd, "base-domain") @@ -143,7 +148,6 @@ func (c *BootstrapGcpCmd) BootstrapGcp() error { } log.Println("\nšŸŽ‰šŸŽ‰šŸŽ‰ GCP infrastructure bootstrapped successfully!") - log.Println(envString) log.Printf("Infrastructure details written to %s", infraFilePath) log.Printf("Access the jumpbox using:\nssh-add $SSH_KEY_PATH; ssh -o StrictHostKeyChecking=no -o ForwardAgent=yes -o SendEnv=OMS_PORTAL_API_KEY root@%s", bs.Env.Jumpbox.GetExternalIP()) if bs.Env.InstallVersion != "" { diff --git a/cli/cmd/init_install_config.go b/cli/cmd/init_install_config.go index e0b232e7..b1409628 100644 --- a/cli/cmd/init_install_config.go +++ b/cli/cmd/init_install_config.go @@ -88,6 +88,11 @@ type InitInstallConfigOpts struct { CodesphereHostingPlanTempStorageMb int CodesphereWorkspacePlanName string CodesphereWorkspacePlanMaxReplicas int + + CodesphereOpenBaoUri string + CodesphereOpenBaoEngine string + CodesphereOpenBaoUser string + CodesphereOpenBaoPassword string } func (c *InitInstallConfigCmd) RunE(_ *cobra.Command, args []string) error { @@ -154,6 +159,11 @@ func AddInitInstallConfigCmd(init *cobra.Command, opts *GlobalOptions) { c.cmd.Flags().StringVar(&c.Opts.CodesphereDomain, "domain", "", "Main Codesphere domain") + c.cmd.Flags().StringVar(&c.Opts.CodesphereOpenBaoUri, "openbao-uri", "", "URI for OpenBao (e.g., https://openbao.example.com)") + c.cmd.Flags().StringVar(&c.Opts.CodesphereOpenBaoEngine, "openbao-engine", "cs-secrets-engine", "Engine for OpenBao") + c.cmd.Flags().StringVar(&c.Opts.CodesphereOpenBaoUser, "openbao-user", "admin", "Username for OpenBao authentication") + c.cmd.Flags().StringVar(&c.Opts.CodesphereOpenBaoPassword, "openbao-password", "", "Password for OpenBao authentication") + util.MarkFlagRequired(c.cmd, "config") util.MarkFlagRequired(c.cmd, "vault") @@ -448,6 +458,16 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig) *f } } + if c.Opts.CodesphereOpenBaoUri != "" { + if config.Codesphere.OpenBao == nil { + config.Codesphere.OpenBao = &files.OpenBaoConfig{} + } + config.Codesphere.OpenBao.URI = c.Opts.CodesphereOpenBaoUri + config.Codesphere.OpenBao.Engine = c.Opts.CodesphereOpenBaoEngine + config.Codesphere.OpenBao.User = c.Opts.CodesphereOpenBaoUser + config.Codesphere.OpenBao.Password = c.Opts.CodesphereOpenBaoPassword + } + // Plans if c.Opts.CodesphereHostingPlanCPUTenth != 0 || c.Opts.CodesphereHostingPlanMemoryMb != 0 || c.Opts.CodesphereHostingPlanStorageMb != 0 || c.Opts.CodesphereHostingPlanTempStorageMb != 0 { diff --git a/docs/oms-cli_beta_bootstrap-gcp.md b/docs/oms-cli_beta_bootstrap-gcp.md index 09d3ca90..47f1b596 100644 --- a/docs/oms-cli_beta_bootstrap-gcp.md +++ b/docs/oms-cli_beta_bootstrap-gcp.md @@ -35,6 +35,10 @@ oms-cli beta bootstrap-gcp [flags] --install-hash string Codesphere package hash to install (default: none) -s, --install-skip-steps stringArray Installation steps to skip during Codesphere installation (optional) --install-version string Codesphere version to install (default: none) + --openbao-engine string OpenBao engine name (default: cs-secrets-engine) (default "cs-secrets-engine") + --openbao-password string OpenBao password (optional) + --openbao-uri string URI for OpenBao (optional) + --openbao-user string OpenBao username (optional) (default "admin") --preemptible Use preemptible VMs for Codesphere infrastructure (default: false) --project-name string Unique GCP Project Name (required) --region string GCP Region (default: europe-west4) (default "europe-west4") diff --git a/docs/oms-cli_init_install-config.md b/docs/oms-cli_init_install-config.md index 92d0e3a1..52b7c0c7 100644 --- a/docs/oms-cli_init_install-config.md +++ b/docs/oms-cli_init_install-config.md @@ -58,6 +58,10 @@ $ oms-cli init install-config --validate -c config.yaml --vault prod.vault.yaml --interactive Enable interactive prompting (when true, other config flags are ignored) (default true) --k8s-control-plane strings K8s control plane IPs (comma-separated) --k8s-managed Use Codesphere-managed Kubernetes (default true) + --openbao-engine string Engine for OpenBao (default "cs-secrets-engine") + --openbao-password string Password for OpenBao authentication + --openbao-uri string URI for OpenBao (e.g., https://openbao.example.com) + --openbao-user string Username for OpenBao authentication (default "admin") --postgres-mode string PostgreSQL setup mode (install/external) --postgres-primary-ip string Primary PostgreSQL server IP --profile string Use a predefined configuration profile (dev, production, minimal) diff --git a/internal/bootstrap/gcp/gcp.go b/internal/bootstrap/gcp/gcp.go index e3cb8c8a..726bf3d1 100644 --- a/internal/bootstrap/gcp/gcp.go +++ b/internal/bootstrap/gcp/gcp.go @@ -102,6 +102,12 @@ type CodesphereEnvironment struct { Experiments []string `json:"experiments"` FeatureFlags []string `json:"feature_flags"` + // OpenBao + OpenBaoURI string `json:"-"` + OpenBaoEngine string `json:"-"` + OpenBaoUser string `json:"-"` + OpenBaoPassword string `json:"-"` + // Config InstallConfigPath string `json:"-"` SecretsFilePath string `json:"-"` @@ -1271,6 +1277,30 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error { } } + b.Env.InstallConfig.Codesphere.ManagedServices = []files.ManagedServiceConfig{ + { + Name: "postgres", + Version: "v1", + }, + { + Name: "babelfish", + Version: "v1", + }, + { + Name: "s3", + Version: "v1", + }, + } + + if b.Env.OpenBaoURI != "" { + b.Env.InstallConfig.Codesphere.OpenBao = &files.OpenBaoConfig{ + Engine: b.Env.OpenBaoEngine, + URI: b.Env.OpenBaoURI, + User: b.Env.OpenBaoUser, + Password: b.Env.OpenBaoPassword, + } + } + if err := b.icg.WriteInstallConfig(b.Env.InstallConfigPath, true); err != nil { return fmt.Errorf("failed to write config file: %w", err) } diff --git a/internal/bootstrap/gcp/gcp_test.go b/internal/bootstrap/gcp/gcp_test.go index cf230ff6..e2fa35b6 100644 --- a/internal/bootstrap/gcp/gcp_test.go +++ b/internal/bootstrap/gcp/gcp_test.go @@ -1137,6 +1137,9 @@ var _ = Describe("GCP Bootstrapper", func() { dnsConfig := dnsIssuer["config"].(map[string]interface{}) cloudDns := dnsConfig["cloudDNS"].(map[string]interface{}) Expect(cloudDns["project"]).To(Equal(bs.Env.DNSProjectID)) + + Expect(bs.Env.InstallConfig.Codesphere.OpenBao).To(BeNil()) + Expect(len(bs.Env.InstallConfig.Codesphere.ManagedServices)).To(Equal(3)) }) Context("When Experiments are set in CodesphereEnvironment", func() { BeforeEach(func() { @@ -1190,6 +1193,30 @@ var _ = Describe("GCP Bootstrapper", func() { }) }) + + Context("When OpenBao config is set", func() { + BeforeEach(func() { + csEnv.OpenBaoURI = "https://openbao.example.com" + csEnv.OpenBaoPassword = "fake-password" + csEnv.OpenBaoUser = "fake-username" + csEnv.OpenBaoEngine = "fake-engine" + }) + It("sets OpenBao config in install config", func() { + icg.EXPECT().GenerateSecrets().Return(nil) + icg.EXPECT().WriteInstallConfig("fake-config-file", true).Return(nil) + icg.EXPECT().WriteVault("fake-secret", true).Return(nil) + + nodeClient.EXPECT().CopyFile(mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice() + + err := bs.UpdateInstallConfig() + Expect(err).NotTo(HaveOccurred()) + + Expect(bs.Env.InstallConfig.Codesphere.OpenBao.URI).To(Equal("https://openbao.example.com")) + Expect(bs.Env.InstallConfig.Codesphere.OpenBao.Password).To(Equal("fake-password")) + Expect(bs.Env.InstallConfig.Codesphere.OpenBao.User).To(Equal("fake-username")) + Expect(bs.Env.InstallConfig.Codesphere.OpenBao.Engine).To(Equal("fake-engine")) + }) + }) }) Describe("Invalid cases", func() { diff --git a/internal/installer/config_generator_collector.go b/internal/installer/config_generator_collector.go index bd8e2a1f..71bc0108 100644 --- a/internal/installer/config_generator_collector.go +++ b/internal/installer/config_generator_collector.go @@ -346,4 +346,24 @@ func (g *InstallConfig) collectCodesphereConfig(prompter *Prompter) { 1: workspacePlan, }, } + + g.collectOpenBaoConfig(prompter) +} + +func (g *InstallConfig) collectOpenBaoConfig(prompter *Prompter) { + log.Println("\n=== OpenBao Configuration (Optional) ===") + hasOpenBao := prompter.Bool("Configure OpenBao integration", g.Config.Codesphere.OpenBao != nil && g.Config.Codesphere.OpenBao.URI != "") + if !hasOpenBao { + g.Config.Codesphere.OpenBao = nil + return + } + + if g.Config.Codesphere.OpenBao == nil { + g.Config.Codesphere.OpenBao = &files.OpenBaoConfig{} + } + + g.Config.Codesphere.OpenBao.URI = g.collectString(prompter, "OpenBao URI (e.g., https://openbao.example.com)", "") + g.Config.Codesphere.OpenBao.Engine = g.collectString(prompter, "OpenBao engine name", "cs-secrets-engine") + g.Config.Codesphere.OpenBao.User = g.collectString(prompter, "OpenBao username", "admin") + g.Config.Codesphere.OpenBao.Password = g.collectString(prompter, "OpenBao password", "") } diff --git a/internal/installer/config_manager.go b/internal/installer/config_manager.go index 814e77e2..97166bda 100644 --- a/internal/installer/config_manager.go +++ b/internal/installer/config_manager.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net" + "net/url" "github.com/codesphere-cloud/oms/internal/installer/files" "github.com/codesphere-cloud/oms/internal/util" @@ -155,6 +156,21 @@ func (g *InstallConfig) ValidateInstallConfig() []string { errors = append(errors, "Codesphere domain is required") } + if g.Config.Codesphere.OpenBao != nil { + if g.Config.Codesphere.OpenBao.URI == "" { + errors = append(errors, "OpenBao URI is required when OpenBao integration is enabled") + } + if _, err := url.ParseRequestURI(g.Config.Codesphere.OpenBao.URI); err != nil { + errors = append(errors, "OpenBao URI must be a valid URL") + } + if g.Config.Codesphere.OpenBao.Engine == "" { + errors = append(errors, "OpenBao engine name is required when OpenBao integration is enabled") + } + if g.Config.Codesphere.OpenBao.User == "" { + errors = append(errors, "OpenBao username is required when OpenBao integration is enabled") + } + } + return errors } @@ -177,6 +193,12 @@ func (g *InstallConfig) ValidateVault() []string { } } + if g.Config.Codesphere.OpenBao != nil { + if !foundSecrets["openBaoPassword"] { + errors = append(errors, "required OpenBao secret missing: openBaoPassword") + } + } + return errors } @@ -358,5 +380,12 @@ func (g *InstallConfig) MergeVaultIntoConfig() error { } } + // OpenBao secrets + if g.Config.Codesphere.OpenBao != nil { + if secret, ok := secretsMap["openBaoPassword"]; ok && secret.Fields != nil { + g.Config.Codesphere.OpenBao.Password = secret.Fields.Password + } + } + return nil } diff --git a/internal/installer/config_manager_test.go b/internal/installer/config_manager_test.go index eeb21d46..7b1c6810 100644 --- a/internal/installer/config_manager_test.go +++ b/internal/installer/config_manager_test.go @@ -218,6 +218,51 @@ var _ = Describe("ConfigManager", func() { Expect(errors).To(ContainElement(ContainSubstring("postgres server address is required"))) }) }) + + }) + + Context("openBao validation", func() { + BeforeEach(func() { + configManager.Config.Codesphere.OpenBao = &files.OpenBaoConfig{ + URI: "https://openbao.example.com", + Engine: "openbao-engine", + User: "fake-user", + } + }) + + It("should require OpenBao URI", func() { + configManager.Config.Codesphere.OpenBao.URI = "" + errors := configManager.ValidateInstallConfig() + Expect(errors).To(ContainElement(ContainSubstring("OpenBao URI is required"))) + }) + + It("should require OpenBao engine", func() { + configManager.Config.Codesphere.OpenBao.Engine = "" + errors := configManager.ValidateInstallConfig() + Expect(errors).To(ContainElement(ContainSubstring("OpenBao engine name is required"))) + }) + + It("should require OpenBao user", func() { + configManager.Config.Codesphere.OpenBao.User = "" + errors := configManager.ValidateInstallConfig() + Expect(errors).To(ContainElement(ContainSubstring("OpenBao username is required"))) + }) + + It("should validate OpenBao URI format", func() { + configManager.Config.Codesphere.OpenBao.URI = "not-a-valid-url" + errors := configManager.ValidateInstallConfig() + Expect(errors).To(ContainElement(ContainSubstring("OpenBao URI must be a valid URL"))) + }) + + It("should require OpenBao password in vault", func() { + configManager.Vault = &files.InstallVault{ + Secrets: []files.SecretEntry{ + {Name: "cephSshPrivateKey"}, + }, + } + errors := configManager.ValidateVault() + Expect(errors).To(ContainElement(ContainSubstring("required OpenBao secret missing: openBaoPassword"))) + }) }) Context("ceph validation", func() { diff --git a/internal/installer/files/config_yaml.go b/internal/installer/files/config_yaml.go index c3cb3e7f..df130af5 100644 --- a/internal/installer/files/config_yaml.go +++ b/internal/installer/files/config_yaml.go @@ -263,11 +263,20 @@ type CodesphereConfig struct { UnderprovisionFactors *UnderprovisionFactors `yaml:"underprovisionFactors,omitempty"` GitProviders *GitProvidersConfig `yaml:"gitProviders,omitempty"` ManagedServices []ManagedServiceConfig `yaml:"managedServices,omitempty"` + OpenBao *OpenBaoConfig `yaml:"openBao,omitempty"` DomainAuthPrivateKey string `yaml:"-"` DomainAuthPublicKey string `yaml:"-"` } +type OpenBaoConfig struct { + Engine string `yaml:"engine,omitempty"` + URI string `yaml:"uri,omitempty"` + User string `yaml:"user,omitempty"` + + Password string `yaml:"-"` +} + type CertIssuerType string const ( @@ -417,16 +426,16 @@ type OAuthConfig struct { type ManagedServiceConfig struct { Name string `yaml:"name"` - API ManagedServiceAPI `yaml:"api"` - Author string `yaml:"author"` - Category string `yaml:"category"` - ConfigSchema map[string]interface{} `yaml:"configSchema"` - DetailsSchema map[string]interface{} `yaml:"detailsSchema"` - SecretsSchema map[string]interface{} `yaml:"secretsSchema"` - Description string `yaml:"description"` - DisplayName string `yaml:"displayName"` - IconURL string `yaml:"iconUrl"` - Plans []ServicePlan `yaml:"plans"` + API ManagedServiceAPI `yaml:"api,omitempty"` + Author string `yaml:"author,omitempty"` + Category string `yaml:"category,omitempty"` + ConfigSchema map[string]interface{} `yaml:"configSchema,omitempty"` + DetailsSchema map[string]interface{} `yaml:"detailsSchema,omitempty"` + SecretsSchema map[string]interface{} `yaml:"secretsSchema,omitempty"` + Description string `yaml:"description,omitempty"` + DisplayName string `yaml:"displayName,omitempty"` + IconURL string `yaml:"iconUrl,omitempty"` + Plans []ServicePlan `yaml:"plans,omitempty"` Version string `yaml:"version"` } @@ -530,6 +539,7 @@ func (c *RootConfig) ExtractVault() *InstallVault { c.addManagedServiceSecrets(vault) c.addRegistrySecrets(vault) c.addKubeConfigSecret(vault) + c.addOpenBaoSecrets(vault) return vault } @@ -744,6 +754,17 @@ func (c *RootConfig) addKubeConfigSecret(vault *InstallVault) { } } +func (c *RootConfig) addOpenBaoSecrets(vault *InstallVault) { + if c.Codesphere.OpenBao != nil && c.Codesphere.OpenBao.Password != "" { + vault.Secrets = append(vault.Secrets, SecretEntry{ + Name: "openBaoPassword", + Fields: &SecretFields{ + Password: c.Codesphere.OpenBao.Password, + }, + }) + } +} + func Capitalize(s string) string { if s == "" { return ""