Skip to content

Commit 94e81cb

Browse files
committed
feat: add deploy-eso command, read eso resources from ocm root component, deploy OCIRepositories for image and chart, deploy HelmRelease, add tests.
On-behalf-of: Radek Schekalla (SAP) <[email protected]> Signed-off-by: Radek Schekalla (SAP) <[email protected]>
1 parent 3d47185 commit 94e81cb

File tree

15 files changed

+398
-44
lines changed

15 files changed

+398
-44
lines changed

cmd/deploy_eso.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
cfg "github.com/openmcp-project/bootstrapper/internal/config"
9+
10+
esodeployer "github.com/openmcp-project/bootstrapper/internal/eso-deployer"
11+
logging "github.com/openmcp-project/bootstrapper/internal/log"
12+
"github.com/openmcp-project/bootstrapper/internal/scheme"
13+
"github.com/openmcp-project/bootstrapper/internal/util"
14+
)
15+
16+
// deployEsoCmd represents the deploy-eso command
17+
var deployEsoCmd = &cobra.Command{
18+
Use: "deploy-eso",
19+
Short: "Deploys External Secrets Operator controllers on the target cluster",
20+
Long: "Deploys External Secrets Operator controllers on the target cluster",
21+
RunE: func(cmd *cobra.Command, args []string) error {
22+
configFilePath := args[0]
23+
config := &cfg.BootstrapperConfig{}
24+
err := config.ReadFromFile(configFilePath)
25+
if err != nil {
26+
return fmt.Errorf("failed to read config file: %w", err)
27+
}
28+
log := logging.GetLogger()
29+
log.Info("Starting deployment of external secrets operator controllers.")
30+
31+
targetCluster, err := util.GetCluster(cmd.Flag(FlagKubeConfig).Value.String(), "target-cluster", scheme.NewFluxScheme())
32+
if err != nil {
33+
return fmt.Errorf("failed to get platform cluster: %w", err)
34+
}
35+
36+
if err = esodeployer.NewEsoDeployer(config, cmd.Flag(FlagOcmConfig).Value.String(), targetCluster, log).Deploy(cmd.Context()); err != nil {
37+
return fmt.Errorf("failed deploying eso: %w", err)
38+
}
39+
40+
return nil
41+
},
42+
}
43+
44+
func init() {
45+
RootCmd.AddCommand(deployEsoCmd)
46+
deployEsoCmd.Flags().SortFlags = false
47+
deployEsoCmd.Flags().String(FlagOcmConfig, "", "OCM configuration file")
48+
deployEsoCmd.Flags().String(FlagKubeConfig, "", "Kubernetes configuration file")
49+
}

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
module github.com/openmcp-project/bootstrapper
22

3-
go 1.25.3
3+
go 1.25.1
44

55
require (
66
github.com/Masterminds/sprig/v3 v3.3.0
7+
github.com/fluxcd/helm-controller/api v1.4.2
78
github.com/fluxcd/kustomize-controller/api v1.7.1
9+
github.com/fluxcd/source-controller/api v1.7.2
810
github.com/go-git/go-billy/v5 v5.6.2
911
github.com/go-git/go-git/v5 v5.16.3
1012
github.com/go-logr/logr v1.4.3
@@ -13,6 +15,7 @@ require (
1315
github.com/spf13/cobra v1.10.1
1416
github.com/stretchr/testify v1.11.1
1517
k8s.io/api v0.34.1
18+
k8s.io/apiextensions-apiserver v0.34.1
1619
k8s.io/apimachinery v0.34.1
1720
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
1821
sigs.k8s.io/controller-runtime v0.22.3
@@ -36,6 +39,7 @@ require (
3639
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
3740
github.com/emirpasic/gods v1.18.1 // indirect
3841
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
42+
github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect
3943
github.com/fluxcd/pkg/apis/kustomize v1.13.0 // indirect
4044
github.com/fluxcd/pkg/apis/meta v1.22.0 // indirect
4145
github.com/fsnotify/fsnotify v1.9.0 // indirect
@@ -94,7 +98,6 @@ require (
9498
gopkg.in/inf.v0 v0.9.1 // indirect
9599
gopkg.in/warnings.v0 v0.1.2 // indirect
96100
gopkg.in/yaml.v3 v3.0.1 // indirect
97-
k8s.io/apiextensions-apiserver v0.34.1 // indirect
98101
k8s.io/client-go v0.34.1 // indirect
99102
k8s.io/klog/v2 v2.130.1 // indirect
100103
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,18 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH
4040
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
4141
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
4242
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
43+
github.com/fluxcd/helm-controller/api v1.4.2 h1:2+D3kX3UJhYlr+1rzOkQ/YbIQ96R/olmdfjaYS+okNg=
44+
github.com/fluxcd/helm-controller/api v1.4.2/go.mod h1:0XrBhKEaqvxyDj/FziG1Q8Fmx2UATdaqLgYqmZh6wW4=
4345
github.com/fluxcd/kustomize-controller/api v1.7.1 h1:wFevRoziJcQEcJtNL2/NrQfAA1lrrOnFSmFIZrBBfBc=
4446
github.com/fluxcd/kustomize-controller/api v1.7.1/go.mod h1:77OSly9kxQli7Nmcln0OqZDjVpRMc6eLKED0CiJHYz8=
47+
github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA=
48+
github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4=
4549
github.com/fluxcd/pkg/apis/kustomize v1.13.0 h1:GGf0UBVRIku+gebY944icVeEIhyg1P/KE3IrhOyJJnE=
4650
github.com/fluxcd/pkg/apis/kustomize v1.13.0/go.mod h1:TLKVqbtnzkhDuhWnAsN35977HvRfIjs+lgMuNro/LEc=
4751
github.com/fluxcd/pkg/apis/meta v1.22.0 h1:EHWQH5ZWml7i8eZ/AMjm1jxid3j/PQ31p+hIwCt6crM=
4852
github.com/fluxcd/pkg/apis/meta v1.22.0/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg=
53+
github.com/fluxcd/source-controller/api v1.7.2 h1:/lg/xoyRjxwdhHKqjTxQS2o1cp+DMKJ8W4rpm+ZLemQ=
54+
github.com/fluxcd/source-controller/api v1.7.2/go.mod h1:2JtCeUVpl0aqKImS19jUz9EEnMdzgqNWHkllrIhV004=
4955
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
5056
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
5157
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=

internal/flux_deployer/component_manager.go renamed to internal/component/component_manager.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package flux_deployer
1+
package component
22

33
import (
44
"context"
@@ -9,7 +9,7 @@ import (
99

1010
// ComponentManager bundles the OCM logic required by the FluxDeployer.
1111
type ComponentManager interface {
12-
GetComponentWithImageResources(ctx context.Context) (*ocm_cli.ComponentVersion, error)
12+
GetComponentWithImageResources(ctx context.Context, resourceName string) (*ocm_cli.ComponentVersion, error)
1313
DownloadTemplatesResource(ctx context.Context, downloadDir string) error
1414
}
1515

@@ -35,8 +35,8 @@ func NewComponentManager(ctx context.Context, config *cfg.BootstrapperConfig, oc
3535
return m, nil
3636
}
3737

38-
func (m *ComponentManagerImpl) GetComponentWithImageResources(ctx context.Context) (*ocm_cli.ComponentVersion, error) {
39-
return m.ComponentGetter.GetComponentVersionForResourceRecursive(ctx, m.ComponentGetter.RootComponentVersion(), FluxCDSourceControllerResourceName)
38+
func (m *ComponentManagerImpl) GetComponentWithImageResources(ctx context.Context, resourceName string) (*ocm_cli.ComponentVersion, error) {
39+
return m.ComponentGetter.GetComponentVersionForResourceRecursive(ctx, m.ComponentGetter.RootComponentVersion(), resourceName)
4040
}
4141

4242
func (m *ComponentManagerImpl) DownloadTemplatesResource(ctx context.Context, downloadDir string) error {

internal/flux_deployer/component_manager_test.go renamed to internal/component/component_manager_mock.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package flux_deployer_test
1+
package component
22

33
import (
44
"context"
@@ -7,7 +7,6 @@ import (
77

88
"sigs.k8s.io/yaml"
99

10-
"github.com/openmcp-project/bootstrapper/internal/flux_deployer"
1110
ocmcli "github.com/openmcp-project/bootstrapper/internal/ocm-cli"
1211
"github.com/openmcp-project/bootstrapper/internal/util"
1312
)
@@ -18,13 +17,13 @@ type MockComponentManager struct {
1817
TemplatesPath string
1918
}
2019

21-
var _ flux_deployer.ComponentManager = (*MockComponentManager)(nil)
20+
var _ ComponentManager = (*MockComponentManager)(nil)
2221

23-
func (m MockComponentManager) GetComponentWithImageResources(_ context.Context) (*ocmcli.ComponentVersion, error) {
22+
func (m MockComponentManager) GetComponentWithImageResources(_ context.Context, _ string) (*ocmcli.ComponentVersion, error) {
2423
return loadComponentVersion(m.ComponentPath)
2524
}
2625

27-
func (m MockComponentManager) DownloadTemplatesResource(ctx context.Context, downloadDir string) error {
26+
func (m MockComponentManager) DownloadTemplatesResource(_ context.Context, downloadDir string) error {
2827
return util.CopyDir(m.TemplatesPath, downloadDir)
2928
}
3029

internal/deployment-repo/deploymentRepoManager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ func (m *DeploymentRepoManager) RunKustomizeAndApply(ctx context.Context) error
599599
for _, manifest := range manifests {
600600
if manifest.GetKind() == "Kustomization" && strings.Contains(manifest.GetAPIVersion(), "kustomize.toolkit.fluxcd.io") {
601601
logger.Infof("Applying Kustomization manifest: %s/%s", manifest.GetNamespace(), manifest.GetName())
602-
err = util.ApplyUnstructuredObject(ctx, m.TargetCluster, manifest)
602+
err = util.CreateOrUpdate(ctx, m.TargetCluster, manifest)
603603
if err != nil {
604604
return fmt.Errorf("failed to apply Kustomization manifest %s/%s: %w", manifest.GetNamespace(), manifest.GetName(), err)
605605
}

internal/eso-deployer/constants.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package eso_deployer
2+
3+
const (
4+
esoNamespace = "external-secrets-system"
5+
esoImageRepoName = "external-secrets-image"
6+
esoChartRepoName = "external-secrets-chart"
7+
esoHelmReleaseName = "external-secrets-operator"
8+
)

internal/eso-deployer/deployer.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package eso_deployer
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"time"
8+
9+
helmv2 "github.com/fluxcd/helm-controller/api/v2"
10+
sourcev1 "github.com/fluxcd/source-controller/api/v1"
11+
"github.com/openmcp-project/controller-utils/pkg/clusters"
12+
"github.com/sirupsen/logrus"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
15+
ocmcli "github.com/openmcp-project/bootstrapper/internal/ocm-cli"
16+
17+
"github.com/openmcp-project/bootstrapper/internal/component"
18+
cfg "github.com/openmcp-project/bootstrapper/internal/config"
19+
20+
"github.com/openmcp-project/bootstrapper/internal/flux_deployer"
21+
"github.com/openmcp-project/bootstrapper/internal/util"
22+
23+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
24+
)
25+
26+
type EsoDeployer struct {
27+
Config *cfg.BootstrapperConfig
28+
29+
// OcmConfigPath is the path to the OCM configuration file
30+
OcmConfigPath string
31+
32+
platformCluster *clusters.Cluster
33+
log *logrus.Logger
34+
}
35+
36+
func NewEsoDeployer(config *cfg.BootstrapperConfig, ocmConfigPath string, platformCluster *clusters.Cluster, log *logrus.Logger) *EsoDeployer {
37+
return &EsoDeployer{
38+
Config: config,
39+
OcmConfigPath: ocmConfigPath,
40+
platformCluster: platformCluster,
41+
log: log,
42+
}
43+
}
44+
45+
func (d *EsoDeployer) Deploy(ctx context.Context) error {
46+
componentManager, err := component.NewComponentManager(ctx, d.Config, d.OcmConfigPath)
47+
if err != nil {
48+
return fmt.Errorf("error creating component manager: %w", err)
49+
}
50+
51+
return d.DeployWithComponentManager(ctx, componentManager)
52+
}
53+
54+
func (d *EsoDeployer) DeployWithComponentManager(ctx context.Context, componentManager component.ComponentManager) error {
55+
d.log.Info("Getting OCM component containing ESO resources.")
56+
esoComponent, err := componentManager.GetComponentWithImageResources(ctx, "external-secrets-operator-image")
57+
if err != nil {
58+
return fmt.Errorf("failed to get external-secrets-operator-image component: %w", err)
59+
}
60+
61+
esoChartRes, err := esoComponent.GetResource("external-secrets-operator-chart")
62+
if err != nil {
63+
return fmt.Errorf("failed to get external-secrets-operator-chart resource: %w", err)
64+
}
65+
d.log.Info("Deploying OCIRepo for ESO chart.")
66+
if err = deployRepo(ctx, d, esoChartRes, esoChartRepoName); err != nil {
67+
return fmt.Errorf("failed to create helm chart repo: %w", err)
68+
}
69+
70+
esoImageRes, err := esoComponent.GetResource("external-secrets-operator-image")
71+
if err != nil {
72+
return fmt.Errorf("failed to get external-secrets-operator-image resource: %w", err)
73+
}
74+
d.log.Info("Deploying OCIRepo for ESO image.")
75+
if err = deployRepo(ctx, d, esoImageRes, esoImageRepoName); err != nil {
76+
return fmt.Errorf("failed to create helm image repo: %w", err)
77+
}
78+
79+
d.log.Info("Deploying HelmRelease for ESO.")
80+
if err = deployHelmRelease(ctx, d, esoImageRes); err != nil {
81+
return fmt.Errorf("failed to deploy helm release: %w", err)
82+
}
83+
84+
d.log.Info("Done.")
85+
return nil
86+
}
87+
88+
func deployHelmRelease(ctx context.Context, d *EsoDeployer, res *ocmcli.Resource) error {
89+
name, _, _, err := util.ParseImageVersionAndTag(*res.Access.ImageReference)
90+
if err != nil {
91+
return fmt.Errorf("failed to parse image resource: %w", err)
92+
}
93+
94+
values := map[string]any{
95+
"image": map[string]any{"repository": name},
96+
}
97+
encoded, err := json.Marshal(values)
98+
if err != nil {
99+
return fmt.Errorf("failed to marshal ESO Helm values: %w", err)
100+
}
101+
jsonVals := &apiextensionsv1.JSON{Raw: encoded}
102+
103+
helmRelease := &helmv2.HelmRelease{
104+
ObjectMeta: metav1.ObjectMeta{
105+
Name: esoHelmReleaseName,
106+
Namespace: flux_deployer.FluxSystemNamespace,
107+
},
108+
Spec: helmv2.HelmReleaseSpec{
109+
ChartRef: &helmv2.CrossNamespaceSourceReference{
110+
Kind: "OCIRepository",
111+
Name: esoChartRepoName,
112+
Namespace: flux_deployer.FluxSystemNamespace,
113+
},
114+
ReleaseName: "eso",
115+
TargetNamespace: esoNamespace,
116+
Install: &helmv2.Install{
117+
CreateNamespace: true,
118+
},
119+
Values: jsonVals,
120+
},
121+
}
122+
return util.CreateOrUpdate(ctx, d.platformCluster, helmRelease)
123+
}
124+
125+
func deployRepo(ctx context.Context, d *EsoDeployer, res *ocmcli.Resource, repoName string) error {
126+
imageName, tag, digest, err := util.ParseImageVersionAndTag(*res.Access.ImageReference)
127+
if err != nil {
128+
return err
129+
}
130+
131+
ociRepo := &sourcev1.OCIRepository{
132+
ObjectMeta: metav1.ObjectMeta{
133+
Name: repoName,
134+
Namespace: flux_deployer.FluxSystemNamespace,
135+
},
136+
Spec: sourcev1.OCIRepositorySpec{
137+
URL: fmt.Sprintf("oci://%s", imageName),
138+
Reference: &sourcev1.OCIRepositoryRef{
139+
Tag: tag,
140+
Digest: digest,
141+
},
142+
Timeout: &metav1.Duration{Duration: 1 * time.Minute},
143+
},
144+
}
145+
return util.CreateOrUpdate(ctx, d.platformCluster, ociRepo)
146+
}

0 commit comments

Comments
 (0)