Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cli/cmd/bootstrap_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ func AddBootstrapGcpCmd(parent *cobra.Command, opts *GlobalOptions) {
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OidcIssuerURL, "oidc-issuer-url", "", "OIDC OAuth provider issuer URL (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OidcClientID, "oidc-client-id", "", "OIDC OAuth provider Client ID (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OidcClientSecret, "oidc-client-secret", "", "OIDC OAuth provider Client Secret (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelUsername, "central-otel-username", "", "Central OpenTelemetry username. Needed when sending spans to central collector (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelPassword, "central-otel-password", "", "Central OpenTelemetry password. Needed when sending spans to central collector (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.GitHubPAT, "github-pat", "", "GitHub Personal Access Token used for direct image access and fetching team SSH keys. Required when using --github-team-org/--github-team-slug. Required scopes: read:packages, read:org.")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.GitHubAppName, "github-app-name", "", "GitHub App Name (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.GitHubTeamOrg, "github-team-org", "", "GitHub organization used to fetch team SSH keys (optional, used with --github-team-slug). Requires --github-pat with at least the read:org scope.")
Expand Down Expand Up @@ -118,6 +116,12 @@ func AddBootstrapGcpCmd(parent *cobra.Command, opts *GlobalOptions) {
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoUser, "openbao-user", "admin", "OpenBao username (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.OpenBaoPassword, "openbao-password", "", "OpenBao password (optional)")

flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelEndpoint, "central-otel-endpoint", "", "Central OpenTelemetry collector endpoint. Needed when sending spans to central collector (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelUsername, "central-otel-username", "", "Central OpenTelemetry username. Needed when sending spans to central collector (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelPassword, "central-otel-password", "", "Central OpenTelemetry password. Needed when sending spans to central collector (optional)")
flags.BoolVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelSpanMetrics, "central-otel-span-metrics", false, "Enable span metrics in Central OpenTelemetry export (default: false)")
flags.BoolVar(&bootstrapGcpCmd.CodesphereEnv.LocalTraceExport, "local-trace-export", false, "Enable local trace export (default: false)")

util.MarkFlagRequired(bootstrapGcpCmd.cmd, "project-name")
util.MarkFlagRequired(bootstrapGcpCmd.cmd, "billing-account")
util.MarkFlagRequired(bootstrapGcpCmd.cmd, "base-domain")
Expand Down
3 changes: 3 additions & 0 deletions docs/oms_beta_bootstrap-gcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ oms beta bootstrap-gcp [flags]
--billing-account string GCP Billing Account ID (required)
--bitbucket-app-client-id string Bitbucket App Client ID (optional)
--bitbucket-app-client-secret string Bitbucket App Client Secret (optional)
--central-otel-endpoint string Central OpenTelemetry collector endpoint. Needed when sending spans to central collector (optional)
--central-otel-password string Central OpenTelemetry password. Needed when sending spans to central collector (optional)
--central-otel-span-metrics Enable span metrics in Central OpenTelemetry export (default: false)
--central-otel-username string Central OpenTelemetry username. Needed when sending spans to central collector (optional)
--create-test-user Create a test user with API token on the bootstrapped instance for smoke testing (default: false)
--custom-pg-ip string Custom PostgreSQL IP (optional)
Expand Down Expand Up @@ -51,6 +53,7 @@ oms beta bootstrap-gcp [flags]
--install-local string Install Codesphere from local package (default: none)
-s, --install-skip-steps stringArray Installation steps to skip during Codesphere installation (optional)
--install-version string Codesphere version to install (default: none)
--local-trace-export Enable local trace export (default: false)
--oidc-client-id string OIDC OAuth provider Client ID (optional)
--oidc-client-secret string OIDC OAuth provider Client Secret (optional)
--oidc-issuer-url string OIDC OAuth provider issuer URL (optional)
Expand Down
30 changes: 27 additions & 3 deletions internal/bootstrap/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ type CodesphereEnvironment struct {
RegistryUser string `json:"-"`
Experiments []string `json:"experiments"`
FeatureFlags map[string]bool `json:"feature_flags"`
CentralOtelUsername string `json:"-"`
CentralOtelPassword string `json:"-"`
ExternalLokiEndpoint string `json:"external_loki_endpoint,omitempty"`
ExternalLokiSecret string `json:"-"`
ExternalLokiUser string `json:"external_loki_user,omitempty"`
Expand All @@ -126,6 +124,12 @@ type CodesphereEnvironment struct {
OpenBaoUser string `json:"-"`
OpenBaoPassword string `json:"-"`

CentralOtelEndpoint string `json:"-"`
CentralOtelUsername string `json:"-"`
CentralOtelPassword string `json:"-"`
CentralOtelSpanMetrics bool `json:"-"`
LocalTraceExport bool `json:"-"`

// Config
InstallConfigPath string `json:"-"`
SecretsFilePath string `json:"-"`
Expand Down Expand Up @@ -403,7 +407,12 @@ func (b *GCPBootstrapper) ValidateInput() error {
return err
}

return b.validateExternalLokiParams()
err = b.validateExternalLokiParams()
if err != nil {
return err
}

return b.validateCentralOtelParams()
}

// validateInstallVersion checks if the specified install version exists and contains the required installer artifact
Expand Down Expand Up @@ -510,6 +519,21 @@ func (b *GCPBootstrapper) validateExternalLokiParams() error {
return nil
}

func (b *GCPBootstrapper) validateCentralOtelParams() error {
if b.Env.CentralOtelEndpoint != "" && b.Env.CentralOtelPassword == "" {
return fmt.Errorf("central OTel password is required when central OTel endpoint is set")
}

if b.Env.CentralOtelUsername != "" && b.Env.CentralOtelPassword == "" {
return fmt.Errorf("central OTel username is set but password is missing")
}
if b.Env.CentralOtelPassword != "" && b.Env.CentralOtelUsername == "" {
return fmt.Errorf("central OTel password is set but username is missing")
}

return nil
}

func (b *GCPBootstrapper) EnsureArtifactRegistry() error {
repoName := "codesphere-registry"

Expand Down
32 changes: 32 additions & 0 deletions internal/bootstrap/gcp/gcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,38 @@ var _ = Describe("GCP Bootstrapper", func() {
Expect(err).To(MatchError(ContainSubstring("external Loki endpoint is required")))
})
})

Context("When central OTel endpoint is set but password is missing", func() {
BeforeEach(func() {
csEnv.CentralOtelEndpoint = "https://otel.example.com"
csEnv.CentralOtelUsername = "otel-user"
})
It("returns an error", func() {
err := bs.ValidateInput()
Expect(err).To(MatchError(ContainSubstring("central OTel password is required when central OTel endpoint is set")))
})
})

Context("When central OTel username is set but password is missing", func() {
BeforeEach(func() {
csEnv.CentralOtelUsername = "otel-user"
})
It("returns an error", func() {
err := bs.ValidateInput()
Expect(err).To(MatchError(ContainSubstring("central OTel username is set but password is missing")))
})
})

Context("When central OTel password is set but username is missing", func() {
BeforeEach(func() {
csEnv.CentralOtelPassword = "otel-secret"
csEnv.CentralOtelEndpoint = "https://otel.example.com"
})
It("returns an error", func() {
err := bs.ValidateInput()
Expect(err).To(MatchError(ContainSubstring("central OTel password is set but username is missing")))
})
})
})

Describe("EnsureInstallConfig", func() {
Expand Down
9 changes: 9 additions & 0 deletions internal/bootstrap/gcp/install_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,15 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error {
}
}

if b.Env.CentralOtelPassword != "" || b.Env.LocalTraceExport {
b.Env.InstallConfig.Codesphere.TelemetryExport = &files.TelemetryExport{
Endpoint: b.Env.CentralOtelEndpoint,
RemoteExport: b.Env.CentralOtelPassword != "",
Traces: b.Env.LocalTraceExport,
SpanMetrics: b.Env.CentralOtelSpanMetrics,
}
}
Comment on lines +322 to +329
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some verification to give users some feedback, when these flags are partially configured but ignored by oms? Should be done atm in gcp.go.

Like password and LocalTraceExport was configured but no endpoint

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have default value in case export is enabled but no import specified. But added some check for creds and endpoint


b.Env.InstallConfig.Codesphere.Experiments = b.Env.Experiments
b.Env.InstallConfig.Codesphere.Features = b.Env.FeatureFlags
b.applyExternalLokiConfig()
Expand Down
86 changes: 86 additions & 0 deletions internal/bootstrap/gcp/install_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,92 @@ var _ = Describe("Installconfig & Secrets", func() {
Expect(loki.User).To(BeEmpty())
})
})

Context("When CentralOtelPassword is set", func() {
BeforeEach(func() {
csEnv.CentralOtelPassword = "otel-secret"
csEnv.CentralOtelEndpoint = "https://otel.example.com"
csEnv.CentralOtelSpanMetrics = true
})
It("sets TelemetryExport with RemoteExport true", 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())

te := bs.Env.InstallConfig.Codesphere.TelemetryExport
Expect(te).NotTo(BeNil())
Expect(te.Endpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeTrue())
Expect(te.Traces).To(BeFalse())
Expect(te.SpanMetrics).To(BeTrue())
})
})

Context("When LocalTraceExport is true (no password)", func() {
BeforeEach(func() {
csEnv.LocalTraceExport = true
csEnv.CentralOtelEndpoint = "https://otel.example.com"
})
It("sets TelemetryExport with Traces true and RemoteExport false", 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())

te := bs.Env.InstallConfig.Codesphere.TelemetryExport
Expect(te).NotTo(BeNil())
Expect(te.Endpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeFalse())
Expect(te.Traces).To(BeTrue())
Expect(te.SpanMetrics).To(BeFalse())
})
})

Context("When both CentralOtelPassword and LocalTraceExport are set", func() {
BeforeEach(func() {
csEnv.CentralOtelPassword = "otel-secret"
csEnv.LocalTraceExport = true
csEnv.CentralOtelEndpoint = "https://otel.example.com"
csEnv.CentralOtelSpanMetrics = true
})
It("sets TelemetryExport with both RemoteExport and Traces true", 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())

te := bs.Env.InstallConfig.Codesphere.TelemetryExport
Expect(te).NotTo(BeNil())
Expect(te.Endpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeTrue())
Expect(te.Traces).To(BeTrue())
Expect(te.SpanMetrics).To(BeTrue())
})
})

Context("When neither CentralOtelPassword nor LocalTraceExport are set", func() {
It("leaves TelemetryExport nil", 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.TelemetryExport).To(BeNil())
})
})
})

Describe("Invalid cases", func() {
Expand Down
8 changes: 8 additions & 0 deletions internal/installer/files/config_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ type CodesphereConfig struct {
ManagedServices []ManagedServiceConfig `yaml:"managedServices,omitempty"`
OpenBao *OpenBaoConfig `yaml:"openBao,omitempty"`
Migration *MigrationConfig `yaml:"migration,omitempty"`
TelemetryExport *TelemetryExport `yaml:"telemetryExport,omitempty"`
Override ChartOverride `yaml:"override,omitempty"`

DomainAuthPrivateKey string `yaml:"-"`
Expand Down Expand Up @@ -399,6 +400,13 @@ type FlavorConfig struct {
Pool map[int]int `yaml:"pool"`
}

type TelemetryExport struct {
Endpoint string `yaml:"endpoint"`
RemoteExport bool `yaml:"remoteExport"`
Traces bool `yaml:"traces"`
SpanMetrics bool `yaml:"spanMetrics"`
}

type ChartOverride = map[string]interface{}

type ImageRef struct {
Expand Down
Loading