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
1 change: 1 addition & 0 deletions cli/cmd/bootstrap_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func AddBootstrapGcpCmd(parent *cobra.Command, opts *util.GlobalOptions) {
flags.StringArrayVarP(&bootstrapGcpCmd.CodesphereEnv.InstallSkipSteps, "install-skip-steps", "s", []string{}, "Installation steps to skip during Codesphere installation (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.RemoteOmsBinaryPath, "remote-oms-binary", "", "Path to a local Linux amd64 OMS binary to copy to and use on the jumpbox instead of downloading a release (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.RegistryUser, "registry-user", "", "Custom Registry username (only for GitHub registry type) (optional)")
flags.StringVar(&bootstrapGcpCmd.CodesphereEnv.ContainerRegistryURL, "registry", "", "Alternative container registry used for Codesphere images and charts")
flags.StringVar(&bootstrapGcpCmd.InputRegistryType, "registry-type", "local-container", "Container registry type to use (options: local-container, artifact-registry) (default: local-container)")
flags.StringArrayVar(&bootstrapGcpCmd.CodesphereEnv.InternalFlags, "internal-flags", gcp.DefaultInternalFlags, "Internal flags to enable in Codesphere installation (optional)")
flags.StringArrayVar(&bootstrapGcpCmd.experiments, "experiments", []string{}, "Deprecated: use --internal-flags instead. Values are added to the internal flags.")
Expand Down
3 changes: 1 addition & 2 deletions cli/cmd/bootstrap_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func AddBootstrapLocalCmd(parent *cobra.Command) {
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.InstallLocal, "install-local", "", "Path to a local installer package (tar.gz or unpacked directory)")
// Registry
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.RegistryUser, "registry-user", "", "Custom Registry username")
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.ContainerRegistryURL, "registry", "", "Alternative container registry used for Codesphere images and charts")

// Codesphere Environment
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.BaseDomain, "base-domain", "cs.local", "Base domain for Codesphere")
Expand All @@ -97,8 +98,6 @@ func AddBootstrapLocalCmd(parent *cobra.Command) {
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.SecretsFilePath, "secrets-file", "", "Path to secrets file (default: <install-dir>/prod.vault.yaml)")
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.CephDeviceFilter, "ceph-device-filter", "", "Regular expression selecting Ceph block devices by name")
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.CephDevicePathFilter, "ceph-device-path-filter", "", "Regular expression selecting Ceph block devices by path")
// ArgoCD integration
flags.StringVar(&bootstrapLocalCmd.CodesphereEnv.ArgoCDRegistryURL, "registry-url", "oci://ghcr.io/codesphere-cloud/charts", "OCI registry URL used for the ArgoCD helm pull secret")
bootstrapLocalCmd.cmd.RunE = bootstrapLocalCmd.RunE

util.MarkFlagRequired(bootstrapLocalCmd.cmd, "registry-user")
Expand Down
15 changes: 13 additions & 2 deletions cli/cmd/codesphere/install_codesphere_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"os"
"runtime"
"strings"

argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/codesphere-cloud/cs-go/pkg/io"
Expand Down Expand Up @@ -117,6 +118,15 @@ func installArgoCDAndApps(opts *InstallCodesphereOpts, cfg files.RootConfig, pm
if err != nil {
return fmt.Errorf("failed to parse installer BOM: %w", err)
}
configuredRegistryURL := ""
if cfg.Registry != nil {
configuredRegistryURL = strings.TrimSuffix(strings.TrimPrefix(cfg.Registry.Server, "oci://"), "/")
if configuredRegistryURL != "" && configuredRegistryURL != "ghcr.io" {
if err := bomConfig.UseRegistry(configuredRegistryURL); err != nil {
return fmt.Errorf("failed to configure installer BOM registry: %w", err)
}
}
}

var install *argocdinstaller.AppInstaller

Expand All @@ -133,8 +143,8 @@ func installArgoCDAndApps(opts *InstallCodesphereOpts, cfg files.RootConfig, pm
return fmt.Errorf("registry password not found in vault (secret %q)", files.SecretRegistryPassword)
}
registryURL := opts.ArgoCDRegistryURL
if registryURL == "" && cfg.Registry != nil {
registryURL = cfg.Registry.Server + "/codesphere-cloud/charts"
if registryURL == "" && configuredRegistryURL != "" {
registryURL = configuredRegistryURL + "/codesphere-cloud/charts"
}
argoCDInstall, err := argocdinstaller.NewInstaller(argocdinstaller.InstallerConfig{
Version: opts.ArgoCDVersion,
Expand All @@ -145,6 +155,7 @@ func installArgoCDAndApps(opts *InstallCodesphereOpts, cfg files.RootConfig, pm
FullInstall: true,
ForceConflicts: opts.ArgoCDForceConflicts,
RepoURL: opts.ArgoCDRepoURL,
BOM: bomConfig,
ValueFiles: opts.ArgoCDValues,
RESTConfig: restConfig,
})
Expand Down
1 change: 1 addition & 0 deletions docs/oms_beta_bootstrap-gcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ oms beta bootstrap-gcp [flags]
--prometheus-remote-write-user string Prometheus remote write username (optional)
--recover-config Recover previously generated install config from the jumpbox. This will overwrite the local config! (default: false)
--region string GCP Region (default: europe-west4) (default "europe-west4")
--registry string Alternative container registry used for Codesphere images and charts
--registry-type string Container registry type to use (options: local-container, artifact-registry) (default: local-container) (default "local-container")
--registry-user string Custom Registry username (only for GitHub registry type) (optional)
--remote-oms-binary string Path to a local Linux amd64 OMS binary to copy to and use on the jumpbox instead of downloading a release (optional)
Expand Down
2 changes: 1 addition & 1 deletion docs/oms_beta_bootstrap-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ oms beta bootstrap-local [flags]
--pod-cidr string Service CIDR of the Kubernetes cluster. If not specified, OMS will try to determine it.
--preview-flags stringArray Preview flags to enable in Codesphere installation (optional) (default [openfga-authz,cluster-admin,secret-management,sub-path-mount,workspace-ssh])
--profile string Profile to apply to the install config like resources (supported: dev, minimal, prod) (default "dev")
--registry-url string OCI registry URL used for the ArgoCD helm pull secret (default "oci://ghcr.io/codesphere-cloud/charts")
--registry string Alternative container registry used for Codesphere images and charts
--registry-user string Custom Registry username
--secrets-file string Path to secrets file (default: <install-dir>/prod.vault.yaml)
--service-cidr string Service CIDR of the Kubernetes cluster. If not specified, OMS will try to determine it.
Expand Down
5 changes: 4 additions & 1 deletion internal/bootstrap/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,10 @@ func (b *GCPBootstrapper) EnsureGitHubAccessConfigured() error {
if b.Env.GitHubPAT == "" {
return fmt.Errorf("GitHub PAT is not set")
}
b.Env.InstallConfig.Registry.Server = "ghcr.io"
registryURL := strings.TrimSuffix(strings.TrimPrefix(b.Env.ContainerRegistryURL, "oci://"), "/")
if registryURL != "" {
b.Env.InstallConfig.Registry.Server = registryURL
}
b.icg.GetVault().SetSecret(files.SecretEntry{Name: files.SecretRegistryUsername, Fields: &files.SecretFields{Password: b.Env.RegistryUser}})
b.icg.GetVault().SetSecret(files.SecretEntry{Name: files.SecretRegistryPassword, Fields: &files.SecretFields{Password: b.Env.GitHubPAT}})
b.Env.InstallConfig.Registry.ReplaceImagesInBom = false
Expand Down
10 changes: 9 additions & 1 deletion internal/bootstrap/gcp/gcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,13 +991,21 @@ var _ = Describe("GCP Bootstrapper", func() {

err := bs.EnsureGitHubAccessConfigured()
Expect(err).NotTo(HaveOccurred())
Expect(bs.Env.InstallConfig.Registry.Server).To(Equal("ghcr.io"))
Expect(bs.Env.InstallConfig.Registry.Server).To(BeEmpty())
Expect(vault.GetSecret(files.SecretRegistryUsername).Fields.Password).To(Equal(csEnv.RegistryUser))
Expect(vault.GetSecret(files.SecretRegistryPassword).Fields.Password).To(Equal(csEnv.GitHubPAT))
Expect(bs.Env.InstallConfig.Registry.LoadContainerImages).To(BeFalse())
Expect(bs.Env.InstallConfig.Registry.ReplaceImagesInBom).To(BeFalse())
})

It("uses the configured registry URL", func() {
csEnv.ContainerRegistryURL = "oci://registry.example.com/mirror/"
icg.EXPECT().GetVault().Return(&files.InstallVault{})

Expect(bs.EnsureGitHubAccessConfigured()).To(Succeed())
Expect(bs.Env.InstallConfig.Registry.Server).To(Equal("registry.example.com/mirror"))
})

Context("When GitHub PAT is missing", func() {
BeforeEach(func() {
csEnv.GitHubPAT = ""
Expand Down
38 changes: 31 additions & 7 deletions internal/bootstrap/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ type CodesphereEnvironment struct {
InstallHash string `json:"install_hash"`
InstallLocal string `json:"install_local"`
// Registry
RegistryUser string `json:"-"`
RegistryPassword string `json:"-"`
RegistryUser string `json:"-"`
RegistryPassword string `json:"-"`
ContainerRegistryURL string `json:"container_registry_url,omitempty"`
// Config
InstallDir string `json:"-"`
ExistingConfigUsed bool `json:"-"`
Expand All @@ -99,8 +100,6 @@ type CodesphereEnvironment struct {
ServiceCIDR string `json:"service_cidr"`
CephDeviceFilter string `json:"-"`
CephDevicePathFilter string `json:"-"`
// ArgoCD integration
ArgoCDRegistryURL string `json:"-"`
}

// NewLocalBootstrapper creates a bootstrapper for a local Codesphere cluster.
Expand Down Expand Up @@ -237,13 +236,23 @@ func (b *LocalBootstrapper) Bootstrap() error {
}

func (b *LocalBootstrapper) newArgoCDAndAppsInstall() (*argocd.AppInstaller, error) {
version := "9.5.21"
if b.installerBOM != nil {
version = ""
}
registryURL := ""
if b.Env.InstallConfig.Registry != nil && b.Env.InstallConfig.Registry.Server != "" {
registryURL = strings.TrimSuffix(b.Env.InstallConfig.Registry.Server, "/") + "/codesphere-cloud/charts"
}

// renovate: datasource=helm depName=argo-cd registryUrl=https://argoproj.github.io/argo-helm
argoCDInstall, err := argocd.NewInstaller(argocd.InstallerConfig{
Version: "9.5.21",
Version: version,
OciPassword: b.Env.RegistryPassword,
OciRegistryURL: strings.TrimPrefix(b.Env.ArgoCDRegistryURL, "oci://"),
OciRegistryURL: strings.TrimPrefix(registryURL, "oci://"),
FullInstall: true,
ForceConflicts: true,
BOM: b.installerBOM,
RESTConfig: b.restConfig,
})
if err != nil {
Expand Down Expand Up @@ -511,6 +520,22 @@ func (b *LocalBootstrapper) EnsureInstallConfig() error {
}

b.Env.InstallConfig = b.icg.GetInstallConfig()
configuredRegistry := strings.TrimSuffix(strings.TrimPrefix(b.Env.ContainerRegistryURL, "oci://"), "/")
if configuredRegistry != "" {
if b.Env.InstallConfig.Registry == nil {
b.Env.InstallConfig.Registry = &files.RegistryConfig{}
}
b.Env.InstallConfig.Registry.Server = configuredRegistry
}
effectiveRegistry := ""
if b.Env.InstallConfig.Registry != nil {
effectiveRegistry = strings.TrimSuffix(strings.TrimPrefix(b.Env.InstallConfig.Registry.Server, "oci://"), "/")
}
if b.installerBOM != nil && effectiveRegistry != "" && effectiveRegistry != "ghcr.io" {
if err := b.installerBOM.UseRegistry(effectiveRegistry); err != nil {
return fmt.Errorf("failed to configure installer BOM registry: %w", err)
}
}

return nil
}
Expand Down Expand Up @@ -688,7 +713,6 @@ func (b *LocalBootstrapper) EnsureGitHubAccessConfigured() error {
if b.Env.RegistryPassword == "" {
return fmt.Errorf("registry password is not set")
}
b.Env.InstallConfig.Registry.Server = "ghcr.io"
b.icg.GetVault().SetSecret(files.SecretEntry{Name: files.SecretRegistryUsername, Fields: &files.SecretFields{Password: b.Env.RegistryUser}})
b.icg.GetVault().SetSecret(files.SecretEntry{Name: files.SecretRegistryPassword, Fields: &files.SecretFields{Password: b.Env.RegistryPassword}})
b.Env.InstallConfig.Registry.ReplaceImagesInBom = false
Expand Down
7 changes: 7 additions & 0 deletions internal/installer/argocd/install_and_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ func (i *AppInstaller) InstallPCApps(ctx context.Context, bomConfig *bom.Config)
// Values derived from the install config form the base; an explicit pcApps block in
// config.yaml wins over them, and the --pc-apps-values files win over both.
values := util.DeepMergeMaps(installer.OpenFgaPcAppsValues(&i.cfg.Config, i.cfg.Vault), i.cfg.Config.PcApps)
if i.cfg.Config.Registry != nil && i.cfg.Config.Registry.Server != "" {
values = util.DeepMergeMaps(map[string]any{
"global": map[string]any{
"imageRegistry": i.cfg.Config.Registry.Server,
},
}, values)
}

pcApps, err := installer.NewPcAppsFromBom(
i.cfg.KubeClient,
Expand Down
37 changes: 37 additions & 0 deletions internal/installer/argocd/install_and_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@
package argocd_test

import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"

argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/codesphere-cloud/oms/internal/installer"
"github.com/codesphere-cloud/oms/internal/installer/argocd"
"github.com/codesphere-cloud/oms/internal/installer/bom"
"github.com/codesphere-cloud/oms/internal/installer/files"
"github.com/codesphere-cloud/oms/internal/installer/vault"
"github.com/codesphere-cloud/oms/internal/installer/vault/sops"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func sopsAndAgeAvailable() bool {
Expand All @@ -42,6 +52,33 @@ var _ = Describe("AppInstaller", func() {
Expect(install.InstallArgoCD()).To(Succeed())
Expect(argoCDInstall.called).To(BeTrue())
})

It("configures the pc-applications global image registry", func() {
scheme := runtime.NewScheme()
Expect(clientgoscheme.AddToScheme(scheme)).To(Succeed())
Expect(argov1alpha1.AddToScheme(scheme)).To(Succeed())
kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "argocd-codesphere-oci-read", Namespace: "argocd"},
Data: map[string][]byte{"url": []byte("registry.example.com/mirror/codesphere-cloud/charts")},
}).Build()
install := argocd.NewAppInstaller(argocd.AppInstallerConfig{
Config: files.RootConfig{Registry: &files.RegistryConfig{Server: "registry.example.com/mirror"}},
Vault: &files.InstallVault{},
KubeClient: kubeClient,
})
bomConfig := &bom.Config{Components: map[string]bom.ComponentConfig{
"pc-applications": {Files: map[string]bom.FileRef{
"chart": {OciRef: "oci://registry.example.com/mirror/codesphere-cloud/charts/pc-applications:1.2.3"},
}},
}}

Expect(install.InstallPCApps(context.Background(), bomConfig)).To(Succeed())
app := &argov1alpha1.Application{}
Expect(kubeClient.Get(context.Background(), client.ObjectKey{Name: "pc-applications", Namespace: "argocd"}, app)).To(Succeed())
values := map[string]any{}
Expect(json.Unmarshal(app.Spec.Source.Helm.ValuesObject.Raw, &values)).To(Succeed())
Expect(values).To(HaveKeyWithValue("global", map[string]any{"imageRegistry": "registry.example.com/mirror"}))
})
})

var _ = Describe("VaultAndRESTConfig", func() {
Expand Down
27 changes: 26 additions & 1 deletion internal/installer/argocd/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/Masterminds/semver/v3"
"github.com/codesphere-cloud/oms/internal/installer"
"github.com/codesphere-cloud/oms/internal/installer/bom"
k8s "github.com/codesphere-cloud/oms/internal/util"
"helm.sh/helm/v4/pkg/chart/common/util"
"helm.sh/helm/v4/pkg/cli/values"
Expand All @@ -34,6 +35,7 @@ type InstallerConfig struct {
FullInstall bool
ForceConflicts bool
RepoURL string
BOM *bom.Config
ValueFiles []string
RESTConfig *rest.Config
}
Expand Down Expand Up @@ -89,6 +91,17 @@ func NewInstaller(cfg InstallerConfig) (*Installer, error) {
// Install is the top-level orchestrator. It delegates every Helm interaction
// to the HelmClient interface, keeping this function short and testable.
func (a *Installer) Install() error {
chartName := "argo-cd"
usingBOMChart := false
if a.BOM != nil && a.RepoURL == "" && a.Version == "" {
if chart, ok := a.BOM.GetChart("argocd"); ok {
chartName = "oci://" + chart.Name()
usingBOMChart = true
a.Version = chart.Tag()
log.Printf("Using ArgoCD chart %s:%s from BOM\n", chart.Name(), chart.Tag())
}
}

if err := a.validateRepoURL(); err != nil {
return err
}
Expand All @@ -111,9 +124,18 @@ func (a *Installer) Install() error {
defaults := map[string]any{
"dex": map[string]any{"enabled": false},
}
if usingBOMChart {
// The Codesphere argocd chart is a wrapper around the upstream
// argo-cd chart. Helm passes dependency values through the dependency
// name, so upstream defaults must be nested under "argo-cd". The
// upstream chart installed directly expects the same values at root.
defaults = map[string]any{
"argo-cd": defaults,
}
}
vals = util.MergeTables(vals, defaults)

chartName, repoURL := a.resolveChartRef("argo-cd")
chartName, repoURL := a.resolveChartRef(chartName)
cfg := installer.ChartConfig{
ReleaseName: "argocd",
ChartName: chartName,
Expand Down Expand Up @@ -217,6 +239,9 @@ func (a *Installer) validateRepoURL() error {
}

func (a *Installer) resolveChartRef(chartName string) (string, string) {
if strings.HasPrefix(chartName, "oci://") {
return chartName, ""
}
repoURL := a.RepoURL
if repoURL == "" {
repoURL = DefaultRepoURL
Expand Down
35 changes: 35 additions & 0 deletions internal/installer/argocd/installer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/codesphere-cloud/oms/internal/installer"
"github.com/codesphere-cloud/oms/internal/installer/argocd"
"github.com/codesphere-cloud/oms/internal/installer/bom"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -173,6 +174,40 @@ var _ = Describe("Installer.Install", func() {
})
})

Context("BOM chart", func() {
It("uses the argocd OCI chart and version from the BOM", func() {
bomPath := filepath.Join(GinkgoT().TempDir(), "bom.json")
Expect(os.WriteFile(bomPath, []byte(`{"components":{"argocd":{"files":{"chart":{"ociRef":"ghcr.io/codesphere-cloud/charts/argocd:1.2.3"}}}}}`), 0o600)).To(Succeed())
bomConfig, err := bom.Parse(bomPath)
Expect(err).NotTo(HaveOccurred())

helmMock.EXPECT().FindRelease("argocd", "argocd").Return(nil, nil)
helmMock.EXPECT().InstallChart(mock.Anything, mock.MatchedBy(func(cfg installer.ChartConfig) bool {
argoValues, ok := cfg.Values["argo-cd"].(map[string]interface{})
if !ok {
return false
}
dex, ok := argoValues["dex"].(map[string]interface{})
return cfg.ChartName == "oci://ghcr.io/codesphere-cloud/charts/argocd" &&
cfg.RepoURL == "" && cfg.Version == "1.2.3" &&
ok && dex["enabled"] == false && cfg.Values["dex"] == nil
}), mock.Anything).Return(nil)

a = &argocd.Installer{InstallerConfig: argocd.InstallerConfig{BOM: bomConfig}, Helm: helmMock}
Expect(a.Install()).To(Succeed())
})

It("falls back to the upstream chart when no BOM is provided", func() {
helmMock.EXPECT().FindRelease("argocd", "argocd").Return(nil, nil)
helmMock.EXPECT().InstallChart(mock.Anything, mock.MatchedBy(func(cfg installer.ChartConfig) bool {
return cfg.ChartName == "argo-cd" && cfg.RepoURL == argocd.DefaultRepoURL
}), mock.Anything).Return(nil)

a = &argocd.Installer{Helm: helmMock}
Expect(a.Install()).To(Succeed())
})
})

Context("values overrides", func() {
BeforeEach(func() {
helmMock.EXPECT().FindRelease("argocd", "argocd").Return(nil, nil)
Expand Down
Loading