-
Notifications
You must be signed in to change notification settings - Fork 2
feat(fga): Cluster Admin Email assignment gets part of bootstrap command #555
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,10 +9,14 @@ import ( | |
| "runtime" | ||
|
|
||
| "github.com/codesphere-cloud/cs-go/pkg/io" | ||
| "github.com/codesphere-cloud/oms/internal/clusteradmin" | ||
| "github.com/codesphere-cloud/oms/internal/env" | ||
| "github.com/codesphere-cloud/oms/internal/installer" | ||
| "github.com/codesphere-cloud/oms/internal/installer/files" | ||
| "github.com/codesphere-cloud/oms/internal/system" | ||
| "github.com/codesphere-cloud/oms/internal/util" | ||
| "github.com/spf13/cobra" | ||
| "k8s.io/client-go/tools/clientcmd" | ||
| ) | ||
|
|
||
| // InstallCodespherePlatformCmd runs only the Codesphere platform step (Phase 3). | ||
|
|
@@ -23,16 +27,20 @@ type InstallCodespherePlatformCmd struct { | |
| } | ||
|
|
||
| func (c *InstallCodespherePlatformCmd) RunE(_ *cobra.Command, _ []string) error { | ||
| effectiveOpts, _, cleanup, err := prepareInstallConfig(c.Opts, installer.NewConfig()) | ||
| effectiveOpts, cfg, cleanup, err := prepareInstallConfig(c.Opts, installer.NewConfig()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer cleanup() | ||
|
|
||
| return installCodespherePlatform(effectiveOpts, c.Env) | ||
| return installCodespherePlatform(effectiveOpts, cfg, c.Env) | ||
| } | ||
|
|
||
| func installCodespherePlatform(opts *InstallCodesphereOpts, env env.Env) error { | ||
| func installCodespherePlatform(opts *InstallCodesphereOpts, cfg files.RootConfig, env env.Env) error { | ||
| if err := ensureClusterAdminSecret(context.Background(), opts, cfg); err != nil { | ||
| return fmt.Errorf("failed to set cluster admin email: %w", err) | ||
| } | ||
|
|
||
| workdir := env.GetOmsWorkdir() | ||
| pm := installer.NewPackage(workdir, opts.Package) | ||
| cm := installer.NewConfig() | ||
|
|
@@ -55,6 +63,45 @@ func installCodespherePlatform(opts *InstallCodesphereOpts, env env.Env) error { | |
| return nil | ||
| } | ||
|
|
||
| // ensureClusterAdminSecret applies the cluster admin email configured via | ||
| // codesphere.clusterAdminEmail to the cluster-admin-email secret before the | ||
| // platform is installed, so the auth-service finds it on first start. | ||
| // It is a no-op when the config does not set an email. | ||
| func ensureClusterAdminSecret(ctx context.Context, opts *InstallCodesphereOpts, cfg files.RootConfig) error { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like it duplicates the vault-loading and kubeconfig-extraction pattern that already exists in loadVaultData in install_codesphere_dependencies.go extract a shared helper maybe?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this function does a lot of infrastructure stuff. So maybe this function would make more sense in the installer or clusteradmin package and not the cli command layer |
||
| email := cfg.Codesphere.ClusterAdminEmail | ||
| if email == "" { | ||
| return nil | ||
| } | ||
|
|
||
| vaultPath, err := resolveVaultPath(opts.Vault, cfg) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vault, err := installer.LoadVaultData(vaultPath, opts.PrivKey) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to load vault %s: %w", vaultPath, err) | ||
| } | ||
| kubeConfigContent, err := kubeConfigContentFromVault(vault) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeConfigContent)) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to load kubernetes config from vault: %w", err) | ||
| } | ||
| clientset, _, err := util.NewClientsFromRESTConfig(restConfig) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create kubernetes client: %w", err) | ||
| } | ||
|
|
||
| return clusteradmin.AddClusterAdmin(ctx, clientset, clusteradmin.Opts{ | ||
| Email: email, | ||
| Namespace: clusteradmin.DefaultNamespace, | ||
| SecretName: clusteradmin.DefaultSecretName, | ||
| CreateNamespace: true, | ||
| }) | ||
| } | ||
|
|
||
| func AddInstallCodespherePlatformCmd(codesphere *cobra.Command, opts *InstallCodesphereOpts) { | ||
| platform := InstallCodespherePlatformCmd{ | ||
| cmd: &cobra.Command{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ import ( | |
|
|
||
| "cloud.google.com/go/compute/apiv1/computepb" | ||
| "github.com/codesphere-cloud/oms/internal/bootstrap" | ||
| "github.com/codesphere-cloud/oms/internal/clusteradmin" | ||
| "github.com/codesphere-cloud/oms/internal/env" | ||
| "github.com/codesphere-cloud/oms/internal/github" | ||
| "github.com/codesphere-cloud/oms/internal/installer" | ||
|
|
@@ -134,6 +135,7 @@ type CodesphereEnvironment struct { | |
| PrometheusRemoteWriteUser string `json:"prometheus_remote_write_user,omitempty"` | ||
| PrometheusRemoteWritePassword string `json:"-"` | ||
| PrometheusRemoteWriteURL string `json:"prometheus_remote_write_url,omitempty"` | ||
| ClusterAdminEmail string `json:"cluster_admin_email,omitempty"` | ||
|
|
||
| // ACME Issuer | ||
| GoogleACMEIssuer bool `json:"google_acme_issuer,omitempty"` | ||
|
|
@@ -438,9 +440,34 @@ func (b *GCPBootstrapper) ValidateInput() error { | |
| return err | ||
| } | ||
|
|
||
| err = b.validateClusterAdminEmail() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return b.validateTelemetryExportParams() | ||
| } | ||
|
|
||
| func (b *GCPBootstrapper) validateClusterAdminEmail() error { | ||
| if b.Env.ClusterAdminEmail == "" { | ||
| return nil | ||
| } | ||
|
|
||
| // The email reaches the cluster via the install config, which is only | ||
| // updated when the config is written. | ||
| if !b.Env.WriteConfig { | ||
| return fmt.Errorf("cluster admin email requires write-config to be enabled") | ||
| } | ||
|
|
||
| email, err := clusteradmin.NormalizeEmail(b.Env.ClusterAdminEmail) | ||
| if err != nil { | ||
| return fmt.Errorf("invalid cluster admin email: %w", err) | ||
| } | ||
| b.Env.ClusterAdminEmail = email | ||
|
|
||
| return nil | ||
| } | ||
|
Comment on lines
+451
to
+469
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we allow the flag without --write-config when --recover-config is also set? |
||
|
|
||
| // validateInstallVersion checks if the specified install version exists and contains the required installer artifact | ||
| func (b *GCPBootstrapper) validateInstallVersion() error { | ||
| if b.Env.InstallLocal != "" { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,10 @@ type Opts struct { | |
| Email string | ||
| Namespace string | ||
| SecretName string | ||
| // CreateNamespace creates the target namespace if it does not exist yet | ||
| // instead of failing. Used during installation, where the secret may be | ||
| // written before the platform charts have created the namespace. | ||
| CreateNamespace bool | ||
| } | ||
|
|
||
| // AddClusterAdmin writes the given email to the cluster-admin-email secret in | ||
|
|
@@ -40,7 +44,7 @@ type Opts struct { | |
| // The email is stored under the EmailKey data key. Running the command again | ||
| // with a different email overwrites the previous value. | ||
| func AddClusterAdmin(ctx context.Context, clientset kubernetes.Interface, opts Opts) error { | ||
| email, err := normalizeEmail(opts.Email) | ||
| email, err := NormalizeEmail(opts.Email) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
@@ -52,6 +56,12 @@ func AddClusterAdmin(ctx context.Context, clientset kubernetes.Interface, opts O | |
| return fmt.Errorf("secret name must not be empty") | ||
| } | ||
|
|
||
| if opts.CreateNamespace { | ||
| if err := ensureNamespace(ctx, clientset, opts.Namespace); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| secrets := clientset.CoreV1().Secrets(opts.Namespace) | ||
|
|
||
| existing, err := secrets.Get(ctx, opts.SecretName, metav1.GetOptions{}) | ||
|
|
@@ -92,8 +102,19 @@ func AddClusterAdmin(ctx context.Context, clientset kubernetes.Interface, opts O | |
| return nil | ||
| } | ||
|
|
||
| // normalizeEmail validates and canonicalizes an email address. | ||
| func normalizeEmail(raw string) (string, error) { | ||
| // ensureNamespace creates the namespace if it does not exist yet. | ||
| func ensureNamespace(ctx context.Context, clientset kubernetes.Interface, namespace string) error { | ||
| ns := &corev1.Namespace{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: namespace}, | ||
| } | ||
| if _, err := clientset.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { | ||
| return fmt.Errorf("creating namespace %s: %w", namespace, err) | ||
| } | ||
| return nil | ||
| } | ||
|
Comment on lines
+106
to
+114
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't wait for the namespace, so the namespace might not be visible yet when secrets.Create is called. Is that a problem? |
||
|
|
||
| // NormalizeEmail validates and canonicalizes an email address. | ||
| func NormalizeEmail(raw string) (string, error) { | ||
| trimmed := strings.TrimSpace(raw) | ||
| if trimmed == "" { | ||
| return "", fmt.Errorf("email must not be empty") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we thread a context.Context through installCodespherePlatform instead of using a fixed context.Background()?