Skip to content
Merged
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.StringVar(&bootstrapGcpCmd.CodesphereEnv.LocalTraceEndpoint, "local-trace-endpoint", "", "Endpoint for exporting traces to an in-cluster storage (optional)")
flags.BoolVar(&bootstrapGcpCmd.CodesphereEnv.CentralOtelSpanMetrics, "central-otel-span-metrics", false, "Enable span metrics in Central OpenTelemetry 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-endpoint string Endpoint for exporting traces to an in-cluster storage (optional)
--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:"-"`
LocalTraceEndpoint string `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.validateTelemetryExportParams()
}

// 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) validateTelemetryExportParams() 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
}
Comment thread
BBesrour marked this conversation as resolved.

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
10 changes: 10 additions & 0 deletions internal/bootstrap/gcp/install_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,16 @@ func (b *GCPBootstrapper) UpdateInstallConfig() error {
}
}

if b.Env.CentralOtelPassword != "" || b.Env.LocalTraceEndpoint != "" {
b.Env.InstallConfig.Codesphere.TelemetryExport = &files.TelemetryExport{
RemoteEndpoint: b.Env.CentralOtelEndpoint,
RemoteExport: b.Env.CentralOtelPassword != "",
Traces: b.Env.LocalTraceEndpoint != "",
TraceEndpoint: b.Env.LocalTraceEndpoint,
SpanMetrics: b.Env.CentralOtelSpanMetrics,
}
}

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.RemoteEndpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeTrue())
Expect(te.Traces).To(BeFalse())
Expect(te.SpanMetrics).To(BeTrue())
})
})

Context("When LocalTraceEndpoint is set (no password)", func() {
BeforeEach(func() {
csEnv.LocalTraceEndpoint = "http://localhost:4318"
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.RemoteEndpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeFalse())
Expect(te.Traces).To(BeTrue())
Expect(te.TraceEndpoint).To(Equal("http://localhost:4318"))
Expect(te.SpanMetrics).To(BeFalse())
})
})

Context("When both CentralOtelPassword and CentralOtelEndpoint are set", func() {
BeforeEach(func() {
csEnv.CentralOtelPassword = "otel-secret"
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.RemoteEndpoint).To(Equal("https://otel.example.com"))
Expect(te.RemoteExport).To(BeTrue())
Expect(te.Traces).To(BeFalse())
Expect(te.SpanMetrics).To(BeTrue())
})
})

Context("When neither CentralOtelPassword nor LocalTraceEndpoint 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
9 changes: 9 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,14 @@ type FlavorConfig struct {
Pool map[int]int `yaml:"pool"`
}

type TelemetryExport struct {
RemoteEndpoint string `yaml:"remoteEndpoint"`
RemoteExport bool `yaml:"remoteExport"`
Traces bool `yaml:"traces"`
TraceEndpoint string `yaml:"traceEndpoint,omitempty"`
SpanMetrics bool `yaml:"spanMetrics"`
}

type ChartOverride = map[string]interface{}

type ImageRef struct {
Expand Down
Loading