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
20 changes: 7 additions & 13 deletions internal/deployment-repo/deploymentRepoManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,12 @@ func (m *DeploymentRepoManager) ApplyTemplates(ctx context.Context) error {
return fmt.Errorf("failed to apply fluxcd image automation controller template input: %w", err)
}

err = TemplateDir(m.templatesDir, templateInput, m.gitRepo)
err = util.CopyDir(m.ExtraManifestDir, filepath.Join(m.templatesDir, ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
if err != nil {
return fmt.Errorf("failed to copy extra manifests from %s to deployment repository: %w", m.ExtraManifestDir, err)
}

err = TemplateDir(ctx, m.templatesDir, templateInput, m.compGetter, m.gitRepo)
if err != nil {
return fmt.Errorf("failed to apply templates from directory %s: %w", m.templatesDir, err)
}
Expand Down Expand Up @@ -466,18 +471,6 @@ func (m *DeploymentRepoManager) ApplyExtraManifests(_ context.Context) error {

// if an extra manifest directory is specified, copy its contents to the deployment repository
logger.Infof("Applying extra manifests from %s to deployment repository", m.ExtraManifestDir)
err := util.CopyDir(m.ExtraManifestDir, filepath.Join(m.gitRepoDir, ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
if err != nil {
return fmt.Errorf("failed to copy extra manifests from %s to deployment repository: %w", m.ExtraManifestDir, err)
}
workTree, err := m.gitRepo.Worktree()
if err != nil {
return fmt.Errorf("failed to get worktree: %w", err)
}
_, err = workTree.Add(filepath.Join(ResourcesDirectoryName, OpenMCPDirectoryName, ExtraManifestsDirectory))
if err != nil {
return fmt.Errorf("failed to add extra manifests to git index: %w", err)
}

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

// UpdateResourcesKustomization updates the resources kustomization file in the deployment repository to include all applied resources.
func (m *DeploymentRepoManager) UpdateResourcesKustomization() error {
logger := log.GetLogger()
files := make([]string, 0,
Expand Down
15 changes: 11 additions & 4 deletions internal/deployment-repo/deploymentRepoManager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ func TestDeploymentRepoManager(t *testing.T) {
err = deploymentRepoManager.ApplyCustomResourceDefinitions(t.Context())
assert.NoError(t, err)

err = deploymentRepoManager.ApplyExtraManifests(t.Context())
assert.NoError(t, err)

err = deploymentRepoManager.UpdateResourcesKustomization()
assert.NoError(t, err)

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

testutils.AssertDirectoriesEqualWithNormalization(t, expectedRepoDir, actualRepoDir, createGitRepoNormalizer(originDir))
testutils.AssertDirectoriesEqualWithNormalization(t, expectedRepoDir, actualRepoDir, createTestNormalizer(originDir, ctfIn))

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

// CreateGitRepoNormalizer creates a normalizer function that replaces dynamic git repository URLs
func createGitRepoNormalizer(actualRepoURL string) func(string, string) string {
// createTestNormalizer returns a function that normalizes file content by replacing actual repository URLs with placeholders.
func createTestNormalizer(actualRepoURL, actualOCMRepoURL string) func(string, string) string {
return func(content, filePath string) string {
// For gitrepo.yaml files, replace the actual repo URL with a placeholder
if strings.Contains(filePath, "gitrepo.yaml") {
return strings.ReplaceAll(content, actualRepoURL, "{{GIT_REPO_URL}}")
content = strings.ReplaceAll(content, actualRepoURL, "{{GIT_REPO_URL}}")
}
// For files that may contain OCM repository URLs, replace the actual repo URL with a placeholder
if strings.Contains(filePath, ".yaml") || strings.Contains(filePath, ".yml") || strings.Contains(filePath, ".json") {
content = strings.ReplaceAll(content, actualOCMRepoURL, "{{OCM_REPO_URL}}")
}
return content
}
Expand Down
4 changes: 2 additions & 2 deletions internal/deployment-repo/templater.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// TemplateDir processes the template files in the specified directory and writes
// the rendered content to the corresponding files in the Git repository's worktree.
// It uses the provided template directory and Git repository to perform the operations.
func TemplateDir(templateDirectory string, templateInput map[string]interface{}, repo *git.Repository) error {
func TemplateDir(ctx context.Context, templateDirectory string, templateInput map[string]interface{}, compGetter *ocmcli.ComponentGetter, repo *git.Repository) error {
logger := log.GetLogger()

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

te := template.NewTemplateExecution().WithMissingKeyOption("zero")
te := template.NewTemplateExecution().WithOCMComponentGetter(ctx, compGetter).WithMissingKeyOption("zero")

// Recursively walk through all files in the template directory
err = filepath.WalkDir(templateDirectory, func(path string, d os.DirEntry, walkError error) error {
Expand Down
46 changes: 45 additions & 1 deletion internal/deployment-repo/testdata/01/component-constructor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ components:
name: gitops-templates
version: v0.1.1

- componentName: github.com/openmcp-project/openmcp/releasechannel
name: releasechannel
version: v2.1.3

resources:
- name: fluxcd-source-controller
version: v1.6.2
Expand Down Expand Up @@ -152,4 +156,44 @@ components:
version: v0.1.1
input:
type: dir
path: ./templates/openmcp
path: ./templates/openmcp

- name: github.com/openmcp-project/openmcp/releasechannel
version: v2.1.3
provider:
name: openmcp-project

componentReferences:
- componentName: github.com/openmcp-project/openmcp/releasechannel/crossplane
name: crossplane
version: v0.0.1

- componentName: github.com/openmcp-project/openmcp/releasechannel/crossplane
name: crossplane
version: v0.0.2

- name: github.com/openmcp-project/openmcp/releasechannel/crossplane
version: v0.0.1
provider:
name: openmcp-project

resources:
- name: image-crossplane
type: ociImage
version: v0.0.2
access:
type: ociArtifact
imageReference: ghcr.io/openmcp-project/releasechannel/crossplane:v0.0.2

- name: github.com/openmcp-project/openmcp/releasechannel/crossplane
version: v0.0.2
provider:
name: openmcp-project

resources:
- name: image-crossplane
type: ociImage
version: v0.0.2
access:
type: ociArtifact
imageReference: ghcr.io/openmcp-project/releasechannel/crossplane:v0.0.2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: test-configmap
namespace: default
data:
key: "value"
repository: "{{OCM_REPO_URL}}"
crossplaneComponentName: "github.com/openmcp-project/openmcp/releasechannel/crossplane"
crossplaneComponentVersion: "v0.0.1"
crossplaneImage: "ghcr.io/openmcp-project/releasechannel/crossplane"
crossplaneVersions: |
- v0.0.1
- v0.0.2
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ resources:
- cluster-providers/test.yaml
- service-providers/test.yaml
- platform-services/test.yaml
- extra/test-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,17 @@ metadata:
name: test-configmap
namespace: default
data:
key: "value"
key: "value"
repository: "{{ getOCMRepository }}"
{{- $releaseChannelCv := getComponentVersionByReference "releasechannel" }}
{{- $crossplaneCv := getComponentVersionByReference $releaseChannelCv "crossplane" }}
{{- $crossplaneImageResource := getResourceFromComponentVersion $crossplaneCv "image-crossplane" }}
{{- $imageReference := dig "access" "imageReference" "none" $crossplaneImageResource }}
{{- $parsedImage := parseImage $imageReference }}
{{- $crossplaneCvMap := componentVersionAsMap $crossplaneCv }}
crossplaneComponentName: "{{ dig "component" "name" "none" $crossplaneCvMap }}"
crossplaneComponentVersion: "{{ dig "component" "version" "none" $crossplaneCvMap }}"
crossplaneImage: "{{ dig "image" "none" $parsedImage }}"
crossplaneVersions: |
{{- $crossplaneVersions := listComponentVersions $crossplaneCv }}
{{ toYaml (sortAlpha $crossplaneVersions) | indent 6 }}
8 changes: 8 additions & 0 deletions internal/ocm-cli/component_getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ func (g *ComponentGetter) TemplatesResourceName() string {
return g.templatesResourceName
}

func (g *ComponentGetter) Repository() string {
return g.repo
}

func (g *ComponentGetter) OCMConfig() string {
return g.ocmConfig
}

func (g *ComponentGetter) GetReferencedComponentVersion(ctx context.Context, parentCV *ComponentVersion, refName string) (*ComponentVersion, error) {
ref, err := parentCV.GetComponentReference(refName)
if err != nil {
Expand Down
75 changes: 59 additions & 16 deletions internal/ocm-cli/ocm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package ocm_cli
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"

yaml2 "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
)

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

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

return nil
}

func ExecuteOutput(ctx context.Context, commands []string, args []string, ocmConfig string) ([]byte, error) {
var ocmArgs []string

if ocmConfig != NoOcmConfig {
ocmArgs = append(ocmArgs, "--config", ocmConfig)

if err := verifyOCMConfig(ocmConfig); err != nil {
return nil, fmt.Errorf("invalid OCM configuration: %w", err)
}
}

ocmArgs = append(ocmArgs, commands...)
ocmArgs = append(ocmArgs, args...)

cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error executing ocm command: %w, %q", err, out)
}

if cmd.ProcessState.ExitCode() != 0 {
return nil, fmt.Errorf("ocm command exited with code %d: %q", cmd.ProcessState.ExitCode(), out)
}

return out, nil
}

// ComponentVersion represents a version of an OCM component.
type ComponentVersion struct {
// Component is the OCM component associated with this version.
Expand Down Expand Up @@ -109,6 +135,11 @@ type Access struct {
MediaType *string `json:"mediaType"`
}

type ComponentListEntry struct {
Name string `json:"name"`
Version string `json:"version"`
}

var (
OCIImageResourceType = "ociImage"
)
Expand Down Expand Up @@ -143,24 +174,36 @@ func (cv *ComponentVersion) GetComponentReference(name string) (*ComponentRefere
return nil, fmt.Errorf("component reference %s not found in component version %s", name, cv.Component.Name)
}

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

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

if err := verifyOCMConfig(ocmConfig); err != nil {
return nil, fmt.Errorf("invalid OCM configuration: %w", err)
cvList := make([]string, 0)
decoder := yaml2.NewYAMLOrJSONDecoder(strings.NewReader(string(out)), 1024)
for {
var entry ComponentListEntry
err = decoder.Decode(&entry)
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("error decoding component version list: %w", err)
}
cvList = append(cvList, entry.Version)
}

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

cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
out, err := cmd.CombinedOutput()
}

// GetComponentVersion retrieves a component version by its reference using the OCM CLI.
func GetComponentVersion(ctx context.Context, componentReference string, ocmConfig string) (*ComponentVersion, error) {
out, err := ExecuteOutput(ctx, []string{"get", "componentversion", componentReference}, []string{"--output", "yaml"}, ocmConfig)
if err != nil {
return nil, fmt.Errorf("error executing ocm command: %w, %q", err, out)
return nil, err
}

var cv ComponentVersion
Expand Down
Loading