diff --git a/cli/cmd/init_install_config.go b/cli/cmd/init_install_config.go index 0be2637f..3a0ab720 100644 --- a/cli/cmd/init_install_config.go +++ b/cli/cmd/init_install_config.go @@ -97,6 +97,14 @@ type InitInstallConfigOpts struct { CodesphereOpenBaoEngine string CodesphereOpenBaoUser string CodesphereOpenBaoPassword string + + OpenfgaBackupsEnabled bool + OpenfgaBackupsDestinationPath string + OpenfgaBackupsEndpointURL string + OpenfgaBackupsSchedule string + OpenfgaBackupsRetentionPolicy string + OpenfgaBackupsAccessKeyID string + OpenfgaBackupsSecretAccessKey string } func (c *InitInstallConfigCmd) RunE(_ *cobra.Command, args []string) error { @@ -191,6 +199,15 @@ func AddInitInstallConfigCmd(init *cobra.Command, opts *util.GlobalOptions) { 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") + // OpenFGA database backups + c.cmd.Flags().BoolVar(&c.Opts.OpenfgaBackupsEnabled, "openfga-backups-enabled", false, "Enable OpenFGA database backups") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsDestinationPath, "openfga-backups-destination", "", "Backup destination (S3 URL, e.g. s3://backup-openfga)") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsEndpointURL, "openfga-backups-endpoint", "", "S3-compatible endpoint URL (e.g. https://storage.googleapis.com)") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsSchedule, "openfga-backups-schedule", "", "Backup schedule (6-field cron, empty for chart default)") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsRetentionPolicy, "openfga-backups-retention", "", "Retention policy (e.g. 7d, empty for chart default)") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsAccessKeyID, "openfga-backups-access-key-id", "", "S3 access key ID for OpenFGA backups") + c.cmd.Flags().StringVar(&c.Opts.OpenfgaBackupsSecretAccessKey, "openfga-backups-secret-access-key", "", "S3 secret access key for OpenFGA backups") + util.MarkFlagRequired(c.cmd, "config") util.MarkFlagRequired(c.cmd, "vault") @@ -533,6 +550,32 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig, va } } + // OpenFGA database backups + if c.Opts.OpenfgaBackupsEnabled { + if config.Codesphere.OpenfgaBackups == nil { + config.Codesphere.OpenfgaBackups = &files.OpenfgaBackupsConfig{} + } + config.Codesphere.OpenfgaBackups.Enabled = true + if c.Opts.OpenfgaBackupsDestinationPath != "" { + config.Codesphere.OpenfgaBackups.DestinationPath = c.Opts.OpenfgaBackupsDestinationPath + } + if c.Opts.OpenfgaBackupsEndpointURL != "" { + config.Codesphere.OpenfgaBackups.EndpointURL = c.Opts.OpenfgaBackupsEndpointURL + } + if c.Opts.OpenfgaBackupsSchedule != "" { + config.Codesphere.OpenfgaBackups.Schedule = c.Opts.OpenfgaBackupsSchedule + } + if c.Opts.OpenfgaBackupsRetentionPolicy != "" { + config.Codesphere.OpenfgaBackups.RetentionPolicy = c.Opts.OpenfgaBackupsRetentionPolicy + } + if c.Opts.OpenfgaBackupsAccessKeyID != "" { + vault.SetSecret(files.SecretEntry{Name: files.SecretOpenfgaDbBackupAccessKeyId, Fields: &files.SecretFields{Password: c.Opts.OpenfgaBackupsAccessKeyID}}) + } + if c.Opts.OpenfgaBackupsSecretAccessKey != "" { + vault.SetSecret(files.SecretEntry{Name: files.SecretOpenfgaDbBackupSecretAccessKey, Fields: &files.SecretFields{Password: c.Opts.OpenfgaBackupsSecretAccessKey}}) + } + } + // Plans if c.Opts.CodesphereHostingPlanCPUTenth != 0 || c.Opts.CodesphereHostingPlanMemoryMb != 0 || c.Opts.CodesphereHostingPlanStorageMb != 0 || c.Opts.CodesphereHostingPlanTempStorageMb != 0 { diff --git a/cli/cmd/init_install_config_test.go b/cli/cmd/init_install_config_test.go index d098b641..4512acbe 100644 --- a/cli/cmd/init_install_config_test.go +++ b/cli/cmd/init_install_config_test.go @@ -93,6 +93,47 @@ var _ = Describe("UpdateConfigFromOpts", func() { Expect(config.Postgres.ServerAddress).To(Equal("postgres.example.com:5432")) }) + + It("sets openfga backups config and vault secrets when enabled", func() { + config := &files.RootConfig{} + vault := &files.InstallVault{} + command := &InitInstallConfigCmd{Opts: &InitInstallConfigOpts{ + OpenfgaBackupsEnabled: true, + OpenfgaBackupsDestinationPath: "s3://backup-openfga", + OpenfgaBackupsEndpointURL: "https://storage.googleapis.com", + OpenfgaBackupsSchedule: "0 */30 * * * *", + OpenfgaBackupsRetentionPolicy: "7d", + OpenfgaBackupsAccessKeyID: "access-id", + OpenfgaBackupsSecretAccessKey: "secret-key", + }} + + command.updateConfigFromOpts(config, vault) + + Expect(config.Codesphere.OpenfgaBackups).NotTo(BeNil()) + Expect(config.Codesphere.OpenfgaBackups.Enabled).To(BeTrue()) + Expect(config.Codesphere.OpenfgaBackups.DestinationPath).To(Equal("s3://backup-openfga")) + Expect(config.Codesphere.OpenfgaBackups.EndpointURL).To(Equal("https://storage.googleapis.com")) + Expect(config.Codesphere.OpenfgaBackups.Schedule).To(Equal("0 */30 * * * *")) + Expect(config.Codesphere.OpenfgaBackups.RetentionPolicy).To(Equal("7d")) + + accessKey := vault.GetSecret(files.SecretOpenfgaDbBackupAccessKeyId) + Expect(accessKey).NotTo(BeNil()) + Expect(accessKey.Fields.Password).To(Equal("access-id")) + secretKey := vault.GetSecret(files.SecretOpenfgaDbBackupSecretAccessKey) + Expect(secretKey).NotTo(BeNil()) + Expect(secretKey.Fields.Password).To(Equal("secret-key")) + }) + + It("does not set openfga backups config when not enabled", func() { + config := &files.RootConfig{} + command := &InitInstallConfigCmd{Opts: &InitInstallConfigOpts{ + OpenfgaBackupsDestinationPath: "s3://ignored", + }} + + command.updateConfigFromOpts(config, &files.InstallVault{}) + + Expect(config.Codesphere.OpenfgaBackups).To(BeNil()) + }) }) var _ = Describe("ValidateConfig", func() { diff --git a/docs/oms_init_install-config.md b/docs/oms_init_install-config.md index 71a94595..663f02c8 100644 --- a/docs/oms_init_install-config.md +++ b/docs/oms_init_install-config.md @@ -50,40 +50,47 @@ $ oms init install-config --validate -c config.yaml --vault prod.vault.yaml ### Options ``` - --acme-dns01-provider string DNS provider for DNS-01 solver (e.g., cloudflare) - --acme-eab-key-id string External Account Binding key ID (required by some ACME providers) - --acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers) - --acme-email string Email address for ACME account registration - --acme-enabled Enable ACME certificate issuer - --acme-issuer-name string Name for the ACME ClusterIssuer (default "acme-issuer") - --acme-server string ACME server URL (default "https://acme-v02.api.letsencrypt.org/directory") - --ansible-inventory string Path to Ansible inventory file to import host information from - --ceph-csi-kubelet-dir string Directory of kubelet for ceph csi. Required for some cloud providers - --ceph-nodes-subnet string CIDR subnet for ceph nodes - -c, --config string Output file path for config.yaml (default "config.yaml") - --dc-city string Datacenter city - --dc-country-code string Datacenter country code - --dc-id int Datacenter ID - --dc-name string Datacenter name - --domain string Main Codesphere domain - --generate-keys Generate SSH keys and certificates (default true) - -h, --help help for install-config - --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 - --postgres-server string PostgreSQL server hostname for install mode or address for external mode - --profile string Use a predefined configuration profile (dev, production, minimal) - --registry-server string Server for container registry - --secrets-dir string Secrets base directory (default "/root/secrets") - --validate Validate existing config files instead of creating new ones - --vault string Output file path for prod.vault.yaml (default "prod.vault.yaml") - --with-comments Add helpful comments to the generated YAML files + --acme-dns01-provider string DNS provider for DNS-01 solver (e.g., cloudflare) + --acme-eab-key-id string External Account Binding key ID (required by some ACME providers) + --acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers) + --acme-email string Email address for ACME account registration + --acme-enabled Enable ACME certificate issuer + --acme-issuer-name string Name for the ACME ClusterIssuer (default "acme-issuer") + --acme-server string ACME server URL (default "https://acme-v02.api.letsencrypt.org/directory") + --ansible-inventory string Path to Ansible inventory file to import host information from + --ceph-csi-kubelet-dir string Directory of kubelet for ceph csi. Required for some cloud providers + --ceph-nodes-subnet string CIDR subnet for ceph nodes + -c, --config string Output file path for config.yaml (default "config.yaml") + --dc-city string Datacenter city + --dc-country-code string Datacenter country code + --dc-id int Datacenter ID + --dc-name string Datacenter name + --domain string Main Codesphere domain + --generate-keys Generate SSH keys and certificates (default true) + -h, --help help for install-config + --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") + --openfga-backups-access-key-id string S3 access key ID for OpenFGA backups + --openfga-backups-destination string Backup destination (S3 URL, e.g. s3://backup-openfga) + --openfga-backups-enabled Enable OpenFGA database backups + --openfga-backups-endpoint string S3-compatible endpoint URL (e.g. https://storage.googleapis.com) + --openfga-backups-retention string Retention policy (e.g. 7d, empty for chart default) + --openfga-backups-schedule string Backup schedule (6-field cron, empty for chart default) + --openfga-backups-secret-access-key string S3 secret access key for OpenFGA backups + --postgres-mode string PostgreSQL setup mode (install/external) + --postgres-primary-ip string Primary PostgreSQL server IP + --postgres-server string PostgreSQL server hostname for install mode or address for external mode + --profile string Use a predefined configuration profile (dev, production, minimal) + --registry-server string Server for container registry + --secrets-dir string Secrets base directory (default "/root/secrets") + --validate Validate existing config files instead of creating new ones + --vault string Output file path for prod.vault.yaml (default "prod.vault.yaml") + --with-comments Add helpful comments to the generated YAML files ``` ### SEE ALSO diff --git a/internal/installer/config_generator_collector.go b/internal/installer/config_generator_collector.go index 4920890b..bd22ef75 100644 --- a/internal/installer/config_generator_collector.go +++ b/internal/installer/config_generator_collector.go @@ -361,6 +361,52 @@ func (g *InstallConfig) collectCodesphereConfig(prompter *Prompter) { } g.collectOpenBaoConfig(prompter) + g.collectOpenfgaBackupsConfig(prompter) +} + +func (g *InstallConfig) collectOpenfgaBackupsConfig(prompter *Prompter) { + log.Println("\n=== OpenFGA Database Backups (Optional) ===") + hasBackups := prompter.Bool("Configure OpenFGA database backups", g.Config.Codesphere.OpenfgaBackups != nil && g.Config.Codesphere.OpenfgaBackups.Enabled) + if !hasBackups { + g.Config.Codesphere.OpenfgaBackups = nil + return + } + + if g.Config.Codesphere.OpenfgaBackups == nil { + g.Config.Codesphere.OpenfgaBackups = &files.OpenfgaBackupsConfig{} + } + ob := g.Config.Codesphere.OpenfgaBackups + ob.Enabled = true + + ob.DestinationPath = g.collectString(prompter, "Backup destination (S3 URL, e.g. s3://backup-openfga)", ob.DestinationPath) + ob.EndpointURL = g.collectString(prompter, "S3-compatible endpoint URL (e.g. https://storage.googleapis.com)", ob.EndpointURL) + ob.Schedule = g.collectString(prompter, "Backup schedule (6-field cron, empty for chart default)", ob.Schedule) + ob.RetentionPolicy = g.collectString(prompter, "Retention policy (e.g. 7d, empty for chart default)", ob.RetentionPolicy) + + accessKeyID := "" + secretAccessKey := "" + if g.Vault != nil { + if s := g.Vault.GetSecret(files.SecretOpenfgaDbBackupAccessKeyId); s != nil && s.Fields != nil { + accessKeyID = s.Fields.Password + } + if s := g.Vault.GetSecret(files.SecretOpenfgaDbBackupSecretAccessKey); s != nil && s.Fields != nil { + secretAccessKey = s.Fields.Password + } + } + accessKeyID = g.collectString(prompter, "S3 access key ID", accessKeyID) + secretAccessKey = g.collectString(prompter, "S3 secret access key", secretAccessKey) + + if accessKeyID != "" || secretAccessKey != "" { + if g.Vault == nil { + g.Vault = &files.InstallVault{} + } + if accessKeyID != "" { + g.Vault.SetSecret(files.SecretEntry{Name: files.SecretOpenfgaDbBackupAccessKeyId, Fields: &files.SecretFields{Password: accessKeyID}}) + } + if secretAccessKey != "" { + g.Vault.SetSecret(files.SecretEntry{Name: files.SecretOpenfgaDbBackupSecretAccessKey, Fields: &files.SecretFields{Password: secretAccessKey}}) + } + } } func (g *InstallConfig) collectOpenBaoConfig(prompter *Prompter) { diff --git a/internal/installer/files/config_yaml.go b/internal/installer/files/config_yaml.go index 8f60066e..bfdf274b 100644 --- a/internal/installer/files/config_yaml.go +++ b/internal/installer/files/config_yaml.go @@ -341,8 +341,8 @@ type OpenBaoConfig struct { } // OpenfgaBackupsConfig is the friendly representation of the OpenFGA database -// backup settings. On marshal it is translated into the openfga subchart values -// under codesphere.override (see buildOpenfgaBackupOverride). +// backup settings. On marshal it is translated into the openfga application +// values under pcApps.applications.openfga (see buildOpenfgaBackupValues). type OpenfgaBackupsConfig struct { Enabled bool `yaml:"enabled"` // Schedule is an optional 6-field cron expression. When empty the chart default applies.