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
113 changes: 113 additions & 0 deletions internal/ocm-cli/component_getter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package ocm_cli

import (
"context"
"fmt"
"strings"
)

type ComponentGetter struct {
// Location of the root component in the format <repo>//<component>:<version>.
rootComponentLocation string
// Path to the deployment templates resource in the format <componentRef1>/.../<componentRefN>/<resourceName>.
deploymentTemplates string
ocmConfig string

// Fields derived during InitializeComponents
rootComponentVersion *ComponentVersion
templatesComponentVersion *ComponentVersion
templatesComponentLocation string
templatesResourceName string
}

func NewComponentGetter(rootComponentLocation, deploymentTemplates, ocmConfig string) *ComponentGetter {
return &ComponentGetter{
rootComponentLocation: rootComponentLocation,
deploymentTemplates: deploymentTemplates,
ocmConfig: ocmConfig,
}
}

func (g *ComponentGetter) InitializeComponents(ctx context.Context) error {
repo, err := extractRepoFromLocation(g.rootComponentLocation)
if err != nil {
return err
}

rootComponentVersion, err := GetComponentVersion(ctx, g.rootComponentLocation, g.ocmConfig)
if err != nil {
return fmt.Errorf("error getting root component version %s: %w", g.rootComponentLocation, err)
}
g.rootComponentVersion = rootComponentVersion

g.deploymentTemplates = strings.TrimSpace(g.deploymentTemplates)
segments := strings.Split(g.deploymentTemplates, "/")
if len(segments) == 0 {
return fmt.Errorf("deploymentTemplates path must contain a resource name or component references and a resource name separated by slashes (ref1/.../refN/resource): %s", g.deploymentTemplates)
}
referenceNames := segments[:len(segments)-1]
g.templatesResourceName = segments[len(segments)-1]

cv := g.rootComponentVersion
for _, refName := range referenceNames {
cv, err = getReferencedComponentVersion(ctx, repo, cv, refName, g.ocmConfig)
if err != nil {
return fmt.Errorf("error getting referenced component version %s: %w", refName, err)
}
}

g.templatesComponentVersion = cv
g.templatesComponentLocation = buildLocation(repo, cv.Component.Name, cv.Component.Version)
return nil
}

func (g *ComponentGetter) RootComponentVersion() *ComponentVersion {
return g.rootComponentVersion
}

func (g *ComponentGetter) TemplatesComponentVersion() *ComponentVersion {
return g.templatesComponentVersion
}

func (g *ComponentGetter) TemplatesResourceName() string {
return g.templatesResourceName
}

func getReferencedComponentVersion(ctx context.Context, repo string, parentCV *ComponentVersion, refName string, ocmConfig string) (*ComponentVersion, error) {
ref, err := parentCV.GetComponentReference(refName)
if err != nil {
return nil, fmt.Errorf("error getting component reference %s: %w", refName, err)
}

location := buildLocation(repo, ref.ComponentName, ref.Version)
cv, err := GetComponentVersion(ctx, location, ocmConfig)
if err != nil {
return nil, fmt.Errorf("error getting component version %s: %w", location, err)
}

return cv, nil
}

func (g *ComponentGetter) DownloadTemplatesResource(ctx context.Context, downloadDir string) error {
return downloadDirectoryResource(ctx, g.templatesComponentLocation, g.templatesResourceName, downloadDir, g.ocmConfig)
}

func downloadDirectoryResource(ctx context.Context, componentLocation string, resourceName string, downloadDir string, ocmConfig string) error {
return Execute(ctx,
[]string{"download", "resources", componentLocation, resourceName},
[]string{"--downloader", "ocm/dirtree", "--outfile", downloadDir},
ocmConfig,
)
}

func extractRepoFromLocation(location string) (string, error) {
parts := strings.SplitN(location, "//", 2)
if len(parts) < 2 {
return "", fmt.Errorf("invalid component location format, expected '<repo>//<component>:<version>': %s", location)
}
return parts[0], nil
}

func buildLocation(repo, name, version string) string {
return fmt.Sprintf("%s//%s:%s", repo, name, version)
}
104 changes: 104 additions & 0 deletions internal/ocm-cli/component_getter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ocm_cli_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

ocmcli "github.com/openmcp-project/bootstrapper/internal/ocm-cli"
testutil "github.com/openmcp-project/bootstrapper/test/utils"
)

func TestComponentGetter(t *testing.T) {
const (
componentA = "github.com/openmcp-project/bootstrapper/test-component-getter-a"
componentB = "github.com/openmcp-project/bootstrapper/test-component-getter-b"
componentC = "github.com/openmcp-project/bootstrapper/test-component-getter-c"
version001 = "v0.0.1"
templatesComponentName = "github.com/openmcp-project/gitops-templates"
)

testutil.DownloadOCMAndAddToPath(t)
ctf := testutil.BuildComponent("./testdata/02/component-constructor.yaml", t)

testCases := []struct {
desc string
rootLocation string
deployTemplates string
expectInitializationError bool
expectedTemplatesComponentName string
expectResourceError bool
expectedResourceName string
}{
{
desc: "should get a resource of the root component",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
deployTemplates: "test-resource-a",
expectedTemplatesComponentName: componentA,
expectedResourceName: "test-resource-a",
},
{
desc: "should get a resource of a referenced component",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
deployTemplates: "reference-b/test-resource-b",
expectedTemplatesComponentName: componentB,
expectedResourceName: "test-resource-b",
},
{
desc: "should get a resource of a nested referenced component",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
deployTemplates: "reference-b/reference-c/test-resource-c",
expectedTemplatesComponentName: componentC,
expectedResourceName: "test-resource-c",
},
{
desc: "should fail for an unknown root component",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, "unknown-component", version001),
deployTemplates: "reference-b/test-resource-b",
expectInitializationError: true,
},
{
desc: "should fail for an unknown component reference",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
deployTemplates: "unknown-reference/test-resource-b",
expectInitializationError: true,
},
{
desc: "should fail for an unknown resource",
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
deployTemplates: "reference-b/unknown-resource",
expectedTemplatesComponentName: componentB,
expectResourceError: true,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
g := ocmcli.NewComponentGetter(tc.rootLocation, tc.deployTemplates, ocmcli.NoOcmConfig)

err := g.InitializeComponents(t.Context())
if tc.expectInitializationError {
assert.Error(t, err, "Expected an error initializing components")
return
}
assert.NoError(t, err, "Error initializing components")

rootComponentVersion := g.RootComponentVersion()
assert.NotNil(t, rootComponentVersion, "Root component version should not be nil")
assert.Equal(t, componentA, rootComponentVersion.Component.Name, "Root component name should match")

templatesComponentVersion := g.TemplatesComponentVersion()
assert.NotNil(t, templatesComponentVersion, "Templates component version should not be nil")
assert.Equal(t, tc.expectedTemplatesComponentName, templatesComponentVersion.Component.Name, "Templates component name should match")

resource, err := templatesComponentVersion.GetResource(g.TemplatesResourceName())
if tc.expectResourceError {
assert.Error(t, err, "Expected an error getting resource")
return
}
assert.NoError(t, err, "Error getting resource")
assert.Equal(t, tc.expectedResourceName, resource.Name, "Resource name should match")
})
}
}
12 changes: 6 additions & 6 deletions internal/ocm-cli/ocm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestExecute(t *testing.T) {

testutil.DownloadOCMAndAddToPath(t)

ctfIn := testutil.BuildComponent("./testdata/component-constructor.yaml", t)
ctfIn := testutil.BuildComponent("./testdata/01/component-constructor.yaml", t)

testCases := []struct {
desc string
Expand All @@ -43,15 +43,15 @@ func TestExecute(t *testing.T) {
desc: "get componentversion with ocm config",
commands: []string{"get", "componentversion"},
arguments: []string{"--output", "yaml", ctfIn},
ocmConfig: "./testdata/ocm-config.yaml",
ocmConfig: "./testdata/01/ocm-config.yaml",
expectedError: nil,
},

{
desc: "get componentversion with unsupported ocm config",
commands: []string{"get", "componentversion"},
arguments: []string{"--output", "yaml", ctfIn},
ocmConfig: "./testdata/unsupported-ocm-config.yaml",
ocmConfig: "./testdata/01/unsupported-ocm-config.yaml",
expectedError: expectError,
},
}
Expand All @@ -73,7 +73,7 @@ func TestGetComponentVersion(t *testing.T) {
expectError := errors.New("expected error")
testutil.DownloadOCMAndAddToPath(t)

ctfIn := testutil.BuildComponent("./testdata/component-constructor.yaml", t)
ctfIn := testutil.BuildComponent("./testdata/01/component-constructor.yaml", t)

testCases := []struct {
desc string
Expand Down Expand Up @@ -119,13 +119,13 @@ func TestGetComponentVersion(t *testing.T) {
{
desc: "get component version with ocm config",
componentRef: ctfIn,
ocmConfig: "./testdata/ocm-config.yaml",
ocmConfig: "./testdata/01/ocm-config.yaml",
expectedError: nil,
},
{
desc: "get component version with unsupported ocm config",
componentRef: ctfIn,
ocmConfig: "./testdata/unsupported-ocm-config.yaml",
ocmConfig: "./testdata/01/unsupported-ocm-config.yaml",
expectedError: expectError,
},
{
Expand Down
42 changes: 42 additions & 0 deletions internal/ocm-cli/testdata/02/component-constructor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
components:

- name: github.com/openmcp-project/bootstrapper/test-component-getter-a
version: v0.0.1
provider:
name: openmcp-project
componentReferences:
- componentName: github.com/openmcp-project/bootstrapper/test-component-getter-b
name: reference-b
version: v0.0.1
resources:
- name: test-resource-a
type: blob
input:
type: file
path: ./test-resource.yaml

- name: github.com/openmcp-project/bootstrapper/test-component-getter-b
version: v0.0.1
provider:
name: openmcp-project
componentReferences:
- componentName: github.com/openmcp-project/bootstrapper/test-component-getter-c
name: reference-c
version: v0.0.1
resources:
- name: test-resource-b
type: blob
input:
type: file
path: ./test-resource.yaml

- name: github.com/openmcp-project/bootstrapper/test-component-getter-c
version: v0.0.1
provider:
name: openmcp-project
resources:
- name: test-resource-c
type: blob
input:
type: file
path: ./test-resource.yaml
4 changes: 4 additions & 0 deletions internal/ocm-cli/testdata/02/test-resource.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
config:
vars:
- a: "a"
- b: "b"