Skip to content

Commit 01712fa

Browse files
committed
feat add ocm templating functions
1 parent 50b09f0 commit 01712fa

File tree

10 files changed

+323
-41
lines changed

10 files changed

+323
-41
lines changed

internal/deployment-repo/deploymentRepoManager.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,12 @@ func (m *DeploymentRepoManager) ApplyTemplates(ctx context.Context) error {
270270
return fmt.Errorf("failed to apply fluxcd image automation controller template input: %w", err)
271271
}
272272

273-
err = TemplateDir(m.templatesDir, templateInput, m.gitRepo)
273+
err = util.CopyDir(m.ExtraManifestDir, filepath.Join(m.templatesDir, ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
274+
if err != nil {
275+
return fmt.Errorf("failed to copy extra manifests from %s to deployment repository: %w", m.ExtraManifestDir, err)
276+
}
277+
278+
err = TemplateDir(ctx, m.templatesDir, templateInput, m.compGetter, m.gitRepo)
274279
if err != nil {
275280
return fmt.Errorf("failed to apply templates from directory %s: %w", m.templatesDir, err)
276281
}
@@ -466,18 +471,6 @@ func (m *DeploymentRepoManager) ApplyExtraManifests(_ context.Context) error {
466471

467472
// if an extra manifest directory is specified, copy its contents to the deployment repository
468473
logger.Infof("Applying extra manifests from %s to deployment repository", m.ExtraManifestDir)
469-
err := util.CopyDir(m.ExtraManifestDir, filepath.Join(m.gitRepoDir, ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
470-
if err != nil {
471-
return fmt.Errorf("failed to copy extra manifests from %s to deployment repository: %w", m.ExtraManifestDir, err)
472-
}
473-
workTree, err := m.gitRepo.Worktree()
474-
if err != nil {
475-
return fmt.Errorf("failed to get worktree: %w", err)
476-
}
477-
_, err = workTree.Add(filepath.Join(ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
478-
if err != nil {
479-
return fmt.Errorf("failed to add extra manifests to git index: %w", err)
480-
}
481474

482475
entries, err := os.ReadDir(m.ExtraManifestDir)
483476
if err != nil {
@@ -499,6 +492,7 @@ func (m *DeploymentRepoManager) ApplyExtraManifests(_ context.Context) error {
499492
return nil
500493
}
501494

495+
// UpdateResourcesKustomization updates the resources kustomization file in the deployment repository to include all applied resources.
502496
func (m *DeploymentRepoManager) UpdateResourcesKustomization() error {
503497
logger := log.GetLogger()
504498
files := make([]string, 0,

internal/deployment-repo/deploymentRepoManager_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ func TestDeploymentRepoManager(t *testing.T) {
103103
err = deploymentRepoManager.ApplyCustomResourceDefinitions(t.Context())
104104
assert.NoError(t, err)
105105

106+
err = deploymentRepoManager.ApplyExtraManifests(t.Context())
107+
assert.NoError(t, err)
108+
106109
err = deploymentRepoManager.UpdateResourcesKustomization()
107110
assert.NoError(t, err)
108111

@@ -119,7 +122,7 @@ func TestDeploymentRepoManager(t *testing.T) {
119122
expectedRepoDir := "./testdata/01/expected-repo"
120123
actualRepoDir := originDir
121124

122-
testutils.AssertDirectoriesEqualWithNormalization(t, expectedRepoDir, actualRepoDir, createGitRepoNormalizer(originDir))
125+
testutils.AssertDirectoriesEqualWithNormalization(t, expectedRepoDir, actualRepoDir, createTestNormalizer(originDir, ctfIn))
123126

124127
fluxKustomization := &unstructured.Unstructured{}
125128
fluxKustomization.SetGroupVersionKind(schema.GroupVersionKind{
@@ -132,12 +135,16 @@ func TestDeploymentRepoManager(t *testing.T) {
132135
assert.NoError(t, err)
133136
}
134137

135-
// CreateGitRepoNormalizer creates a normalizer function that replaces dynamic git repository URLs
136-
func createGitRepoNormalizer(actualRepoURL string) func(string, string) string {
138+
// createTestNormalizer returns a function that normalizes file content by replacing actual repository URLs with placeholders.
139+
func createTestNormalizer(actualRepoURL, actualOCMRepoURL string) func(string, string) string {
137140
return func(content, filePath string) string {
138141
// For gitrepo.yaml files, replace the actual repo URL with a placeholder
139142
if strings.Contains(filePath, "gitrepo.yaml") {
140-
return strings.ReplaceAll(content, actualRepoURL, "{{GIT_REPO_URL}}")
143+
content = strings.ReplaceAll(content, actualRepoURL, "{{GIT_REPO_URL}}")
144+
}
145+
// For files that may contain OCM repository URLs, replace the actual repo URL with a placeholder
146+
if strings.Contains(filePath, ".yaml") || strings.Contains(filePath, ".yml") || strings.Contains(filePath, ".json") {
147+
content = strings.ReplaceAll(content, actualOCMRepoURL, "{{OCM_REPO_URL}}")
141148
}
142149
return content
143150
}

internal/deployment-repo/templater.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
// TemplateDir processes the template files in the specified directory and writes
2121
// the rendered content to the corresponding files in the Git repository's worktree.
2222
// It uses the provided template directory and Git repository to perform the operations.
23-
func TemplateDir(templateDirectory string, templateInput map[string]interface{}, repo *git.Repository) error {
23+
func TemplateDir(ctx context.Context, templateDirectory string, templateInput map[string]interface{}, compGetter *ocmcli.ComponentGetter, repo *git.Repository) error {
2424
logger := log.GetLogger()
2525

2626
workTree, err := repo.Worktree()
@@ -38,7 +38,7 @@ func TemplateDir(templateDirectory string, templateInput map[string]interface{},
3838
}
3939
}()
4040

41-
te := template.NewTemplateExecution().WithMissingKeyOption("zero")
41+
te := template.NewTemplateExecution().WithOCMComponentGetter(ctx, compGetter).WithMissingKeyOption("zero")
4242

4343
// Recursively walk through all files in the template directory
4444
err = filepath.WalkDir(templateDirectory, func(path string, d os.DirEntry, walkError error) error {

internal/deployment-repo/testdata/01/component-constructor.yaml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ components:
2525
name: gitops-templates
2626
version: v0.1.1
2727

28+
- componentName: github.com/openmcp-project/openmcp/releasechannel
29+
name: releasechannel
30+
version: v2.1.3
31+
2832
resources:
2933
- name: fluxcd-source-controller
3034
version: v1.6.2
@@ -152,4 +156,44 @@ components:
152156
version: v0.1.1
153157
input:
154158
type: dir
155-
path: ./templates/openmcp
159+
path: ./templates/openmcp
160+
161+
- name: github.com/openmcp-project/openmcp/releasechannel
162+
version: v2.1.3
163+
provider:
164+
name: openmcp-project
165+
166+
componentReferences:
167+
- componentName: github.com/openmcp-project/openmcp/releasechannel/crossplane
168+
name: crossplane
169+
version: v0.0.1
170+
171+
- componentName: github.com/openmcp-project/openmcp/releasechannel/crossplane
172+
name: crossplane
173+
version: v0.0.2
174+
175+
- name: github.com/openmcp-project/openmcp/releasechannel/crossplane
176+
version: v0.0.1
177+
provider:
178+
name: openmcp-project
179+
180+
resources:
181+
- name: image-crossplane
182+
type: ociImage
183+
version: v0.0.2
184+
access:
185+
type: ociArtifact
186+
imageReference: ghcr.io/openmcp-project/releasechannel/crossplane:v0.0.2
187+
188+
- name: github.com/openmcp-project/openmcp/releasechannel/crossplane
189+
version: v0.0.2
190+
provider:
191+
name: openmcp-project
192+
193+
resources:
194+
- name: image-crossplane
195+
type: ociImage
196+
version: v0.0.2
197+
access:
198+
type: ociArtifact
199+
imageReference: ghcr.io/openmcp-project/releasechannel/crossplane:v0.0.2
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: test-configmap
5+
namespace: default
6+
data:
7+
key: "value"
8+
repository: "{{OCM_REPO_URL}}"
9+
crossplaneComponentName: "github.com/openmcp-project/openmcp/releasechannel/crossplane"
10+
crossplaneComponentVersion: "v0.0.1"
11+
crossplaneImage: "ghcr.io/openmcp-project/releasechannel/crossplane"
12+
crossplaneVersions: |
13+
- v0.0.1
14+
- v0.0.2

internal/deployment-repo/testdata/01/expected-repo/resources/openmcp/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ resources:
2323
- cluster-providers/test.yaml
2424
- service-providers/test.yaml
2525
- platform-services/test.yaml
26+
- extra/test-configmap.yaml

internal/deployment-repo/testdata/01/extra-manifests/test-configmap.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,17 @@ metadata:
44
name: test-configmap
55
namespace: default
66
data:
7-
key: "value"
7+
key: "value"
8+
repository: "{{ getOCMRepository }}"
9+
{{- $releaseChannelCv := getComponentVersionByReference "releasechannel" }}
10+
{{- $crossplaneCv := getComponentVersionByReference $releaseChannelCv "crossplane" }}
11+
{{- $crossplaneImageResource := getResourceFromComponentVersion $crossplaneCv "image-crossplane" }}
12+
{{- $imageReference := dig "access" "imageReference" "none" $crossplaneImageResource }}
13+
{{- $parsedImage := parseImage $imageReference }}
14+
{{- $crossplaneCvMap := componentVersionAsMap $crossplaneCv }}
15+
crossplaneComponentName: "{{ dig "component" "name" "none" $crossplaneCvMap }}"
16+
crossplaneComponentVersion: "{{ dig "component" "version" "none" $crossplaneCvMap }}"
17+
crossplaneImage: "{{ dig "image" "none" $parsedImage }}"
18+
crossplaneVersions: |
19+
{{- $crossplaneVersions := listComponentVersions $crossplaneCv }}
20+
{{ toYaml (sortAlpha $crossplaneVersions) | indent 6 }}

internal/ocm-cli/component_getter.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ func (g *ComponentGetter) TemplatesResourceName() string {
7777
return g.templatesResourceName
7878
}
7979

80+
func (g *ComponentGetter) Repository() string {
81+
return g.repo
82+
}
83+
84+
func (g *ComponentGetter) OCMConfig() string {
85+
return g.ocmConfig
86+
}
87+
8088
func (g *ComponentGetter) GetReferencedComponentVersion(ctx context.Context, parentCV *ComponentVersion, refName string) (*ComponentVersion, error) {
8189
ref, err := parentCV.GetComponentReference(refName)
8290
if err != nil {

internal/ocm-cli/ocm.go

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package ocm_cli
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"os"
78
"os/exec"
89
"strings"
910

11+
yaml2 "k8s.io/apimachinery/pkg/util/yaml"
1012
"sigs.k8s.io/yaml"
1113
)
1214

@@ -46,16 +48,40 @@ func Execute(ctx context.Context, commands []string, args []string, ocmConfig st
4648
return fmt.Errorf("error waiting for ocm command to finish: %w", err)
4749
}
4850

49-
// get exit code
50-
if exitError, ok := cmd.ProcessState.Sys().(interface{ ExitStatus() int }); ok {
51-
if exitCode := exitError.ExitStatus(); exitCode != 0 {
52-
return fmt.Errorf("ocm command exited with code %d", exitCode)
53-
}
51+
if cmd.ProcessState.ExitCode() != 0 {
52+
return fmt.Errorf("ocm command exited with code %d", cmd.ProcessState.ExitCode())
5453
}
5554

5655
return nil
5756
}
5857

58+
func ExecuteOutput(ctx context.Context, commands []string, args []string, ocmConfig string) ([]byte, error) {
59+
var ocmArgs []string
60+
61+
if ocmConfig != NoOcmConfig {
62+
ocmArgs = append(ocmArgs, "--config", ocmConfig)
63+
64+
if err := verifyOCMConfig(ocmConfig); err != nil {
65+
return nil, fmt.Errorf("invalid OCM configuration: %w", err)
66+
}
67+
}
68+
69+
ocmArgs = append(ocmArgs, commands...)
70+
ocmArgs = append(ocmArgs, args...)
71+
72+
cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
73+
out, err := cmd.CombinedOutput()
74+
if err != nil {
75+
return nil, fmt.Errorf("error executing ocm command: %w, %q", err, out)
76+
}
77+
78+
if cmd.ProcessState.ExitCode() != 0 {
79+
return nil, fmt.Errorf("ocm command exited with code %d: %q", cmd.ProcessState.ExitCode(), out)
80+
}
81+
82+
return out, nil
83+
}
84+
5985
// ComponentVersion represents a version of an OCM component.
6086
type ComponentVersion struct {
6187
// Component is the OCM component associated with this version.
@@ -109,6 +135,11 @@ type Access struct {
109135
MediaType *string `json:"mediaType"`
110136
}
111137

138+
type ComponentListEntry struct {
139+
Name string `json:"name"`
140+
Version string `json:"version"`
141+
}
142+
112143
var (
113144
OCIImageResourceType = "ociImage"
114145
)
@@ -143,24 +174,36 @@ func (cv *ComponentVersion) GetComponentReference(name string) (*ComponentRefere
143174
return nil, fmt.Errorf("component reference %s not found in component version %s", name, cv.Component.Name)
144175
}
145176

146-
// GetComponentVersion retrieves a component version by its reference using the OCM CLI.
147-
func GetComponentVersion(ctx context.Context, componentReference string, ocmConfig string) (*ComponentVersion, error) {
148-
var ocmArgs []string
177+
func (cv *ComponentVersion) ListComponentVersions(ctx context.Context, ocmConfig string) ([]string, error) {
149178

150-
if ocmConfig != NoOcmConfig {
151-
ocmArgs = append(ocmArgs, "--config", ocmConfig)
179+
out, err := ExecuteOutput(ctx, []string{"list", "componentversion", cv.Repository + "//" + cv.Component.Name}, []string{"--output", "yaml"}, NoOcmConfig)
180+
if err != nil {
181+
return nil, err
182+
}
152183

153-
if err := verifyOCMConfig(ocmConfig); err != nil {
154-
return nil, fmt.Errorf("invalid OCM configuration: %w", err)
184+
cvList := make([]string, 0)
185+
decoder := yaml2.NewYAMLOrJSONDecoder(strings.NewReader(string(out)), 1024)
186+
for {
187+
var entry ComponentListEntry
188+
err = decoder.Decode(&entry)
189+
if err == io.EOF {
190+
break
155191
}
192+
if err != nil {
193+
return nil, fmt.Errorf("error decoding component version list: %w", err)
194+
}
195+
cvList = append(cvList, entry.Version)
156196
}
157197

158-
ocmArgs = append(ocmArgs, "get", "componentversion", "--output", "yaml", componentReference)
198+
return cvList, nil
159199

160-
cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
161-
out, err := cmd.CombinedOutput()
200+
}
201+
202+
// GetComponentVersion retrieves a component version by its reference using the OCM CLI.
203+
func GetComponentVersion(ctx context.Context, componentReference string, ocmConfig string) (*ComponentVersion, error) {
204+
out, err := ExecuteOutput(ctx, []string{"get", "componentversion", componentReference}, []string{"--output", "yaml"}, ocmConfig)
162205
if err != nil {
163-
return nil, fmt.Errorf("error executing ocm command: %w, %q", err, out)
206+
return nil, err
164207
}
165208

166209
var cv ComponentVersion

0 commit comments

Comments
 (0)