Skip to content

Commit 8604bea

Browse files
authored
feat: component getter (#23)
1 parent 8b9af4a commit 8604bea

File tree

9 files changed

+269
-6
lines changed

9 files changed

+269
-6
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package ocm_cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
type ComponentGetter struct {
10+
// Location of the root component in the format <repo>//<component>:<version>.
11+
rootComponentLocation string
12+
// Path to the deployment templates resource in the format <componentRef1>/.../<componentRefN>/<resourceName>.
13+
deploymentTemplates string
14+
ocmConfig string
15+
16+
// Fields derived during InitializeComponents
17+
rootComponentVersion *ComponentVersion
18+
templatesComponentVersion *ComponentVersion
19+
templatesComponentLocation string
20+
templatesResourceName string
21+
}
22+
23+
func NewComponentGetter(rootComponentLocation, deploymentTemplates, ocmConfig string) *ComponentGetter {
24+
return &ComponentGetter{
25+
rootComponentLocation: rootComponentLocation,
26+
deploymentTemplates: deploymentTemplates,
27+
ocmConfig: ocmConfig,
28+
}
29+
}
30+
31+
func (g *ComponentGetter) InitializeComponents(ctx context.Context) error {
32+
repo, err := extractRepoFromLocation(g.rootComponentLocation)
33+
if err != nil {
34+
return err
35+
}
36+
37+
rootComponentVersion, err := GetComponentVersion(ctx, g.rootComponentLocation, g.ocmConfig)
38+
if err != nil {
39+
return fmt.Errorf("error getting root component version %s: %w", g.rootComponentLocation, err)
40+
}
41+
g.rootComponentVersion = rootComponentVersion
42+
43+
g.deploymentTemplates = strings.TrimSpace(g.deploymentTemplates)
44+
segments := strings.Split(g.deploymentTemplates, "/")
45+
if len(segments) == 0 {
46+
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)
47+
}
48+
referenceNames := segments[:len(segments)-1]
49+
g.templatesResourceName = segments[len(segments)-1]
50+
51+
cv := g.rootComponentVersion
52+
for _, refName := range referenceNames {
53+
cv, err = getReferencedComponentVersion(ctx, repo, cv, refName, g.ocmConfig)
54+
if err != nil {
55+
return fmt.Errorf("error getting referenced component version %s: %w", refName, err)
56+
}
57+
}
58+
59+
g.templatesComponentVersion = cv
60+
g.templatesComponentLocation = buildLocation(repo, cv.Component.Name, cv.Component.Version)
61+
return nil
62+
}
63+
64+
func (g *ComponentGetter) RootComponentVersion() *ComponentVersion {
65+
return g.rootComponentVersion
66+
}
67+
68+
func (g *ComponentGetter) TemplatesComponentVersion() *ComponentVersion {
69+
return g.templatesComponentVersion
70+
}
71+
72+
func (g *ComponentGetter) TemplatesResourceName() string {
73+
return g.templatesResourceName
74+
}
75+
76+
func getReferencedComponentVersion(ctx context.Context, repo string, parentCV *ComponentVersion, refName string, ocmConfig string) (*ComponentVersion, error) {
77+
ref, err := parentCV.GetComponentReference(refName)
78+
if err != nil {
79+
return nil, fmt.Errorf("error getting component reference %s: %w", refName, err)
80+
}
81+
82+
location := buildLocation(repo, ref.ComponentName, ref.Version)
83+
cv, err := GetComponentVersion(ctx, location, ocmConfig)
84+
if err != nil {
85+
return nil, fmt.Errorf("error getting component version %s: %w", location, err)
86+
}
87+
88+
return cv, nil
89+
}
90+
91+
func (g *ComponentGetter) DownloadTemplatesResource(ctx context.Context, downloadDir string) error {
92+
return downloadDirectoryResource(ctx, g.templatesComponentLocation, g.templatesResourceName, downloadDir, g.ocmConfig)
93+
}
94+
95+
func downloadDirectoryResource(ctx context.Context, componentLocation string, resourceName string, downloadDir string, ocmConfig string) error {
96+
return Execute(ctx,
97+
[]string{"download", "resources", componentLocation, resourceName},
98+
[]string{"--downloader", "ocm/dirtree", "--outfile", downloadDir},
99+
ocmConfig,
100+
)
101+
}
102+
103+
func extractRepoFromLocation(location string) (string, error) {
104+
parts := strings.SplitN(location, "//", 2)
105+
if len(parts) < 2 {
106+
return "", fmt.Errorf("invalid component location format, expected '<repo>//<component>:<version>': %s", location)
107+
}
108+
return parts[0], nil
109+
}
110+
111+
func buildLocation(repo, name, version string) string {
112+
return fmt.Sprintf("%s//%s:%s", repo, name, version)
113+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package ocm_cli_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
ocmcli "github.com/openmcp-project/bootstrapper/internal/ocm-cli"
10+
testutil "github.com/openmcp-project/bootstrapper/test/utils"
11+
)
12+
13+
func TestComponentGetter(t *testing.T) {
14+
const (
15+
componentA = "github.com/openmcp-project/bootstrapper/test-component-getter-a"
16+
componentB = "github.com/openmcp-project/bootstrapper/test-component-getter-b"
17+
componentC = "github.com/openmcp-project/bootstrapper/test-component-getter-c"
18+
version001 = "v0.0.1"
19+
templatesComponentName = "github.com/openmcp-project/gitops-templates"
20+
)
21+
22+
testutil.DownloadOCMAndAddToPath(t)
23+
ctf := testutil.BuildComponent("./testdata/02/component-constructor.yaml", t)
24+
25+
testCases := []struct {
26+
desc string
27+
rootLocation string
28+
deployTemplates string
29+
expectInitializationError bool
30+
expectedTemplatesComponentName string
31+
expectResourceError bool
32+
expectedResourceName string
33+
}{
34+
{
35+
desc: "should get a resource of the root component",
36+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
37+
deployTemplates: "test-resource-a",
38+
expectedTemplatesComponentName: componentA,
39+
expectedResourceName: "test-resource-a",
40+
},
41+
{
42+
desc: "should get a resource of a referenced component",
43+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
44+
deployTemplates: "reference-b/test-resource-b",
45+
expectedTemplatesComponentName: componentB,
46+
expectedResourceName: "test-resource-b",
47+
},
48+
{
49+
desc: "should get a resource of a nested referenced component",
50+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
51+
deployTemplates: "reference-b/reference-c/test-resource-c",
52+
expectedTemplatesComponentName: componentC,
53+
expectedResourceName: "test-resource-c",
54+
},
55+
{
56+
desc: "should fail for an unknown root component",
57+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, "unknown-component", version001),
58+
deployTemplates: "reference-b/test-resource-b",
59+
expectInitializationError: true,
60+
},
61+
{
62+
desc: "should fail for an unknown component reference",
63+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
64+
deployTemplates: "unknown-reference/test-resource-b",
65+
expectInitializationError: true,
66+
},
67+
{
68+
desc: "should fail for an unknown resource",
69+
rootLocation: fmt.Sprintf("%s//%s:%s", ctf, componentA, version001),
70+
deployTemplates: "reference-b/unknown-resource",
71+
expectedTemplatesComponentName: componentB,
72+
expectResourceError: true,
73+
},
74+
}
75+
76+
for _, tc := range testCases {
77+
t.Run(tc.desc, func(t *testing.T) {
78+
g := ocmcli.NewComponentGetter(tc.rootLocation, tc.deployTemplates, ocmcli.NoOcmConfig)
79+
80+
err := g.InitializeComponents(t.Context())
81+
if tc.expectInitializationError {
82+
assert.Error(t, err, "Expected an error initializing components")
83+
return
84+
}
85+
assert.NoError(t, err, "Error initializing components")
86+
87+
rootComponentVersion := g.RootComponentVersion()
88+
assert.NotNil(t, rootComponentVersion, "Root component version should not be nil")
89+
assert.Equal(t, componentA, rootComponentVersion.Component.Name, "Root component name should match")
90+
91+
templatesComponentVersion := g.TemplatesComponentVersion()
92+
assert.NotNil(t, templatesComponentVersion, "Templates component version should not be nil")
93+
assert.Equal(t, tc.expectedTemplatesComponentName, templatesComponentVersion.Component.Name, "Templates component name should match")
94+
95+
resource, err := templatesComponentVersion.GetResource(g.TemplatesResourceName())
96+
if tc.expectResourceError {
97+
assert.Error(t, err, "Expected an error getting resource")
98+
return
99+
}
100+
assert.NoError(t, err, "Error getting resource")
101+
assert.Equal(t, tc.expectedResourceName, resource.Name, "Resource name should match")
102+
})
103+
}
104+
}

internal/ocm-cli/ocm_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestExecute(t *testing.T) {
1616

1717
testutil.DownloadOCMAndAddToPath(t)
1818

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

2121
testCases := []struct {
2222
desc string
@@ -43,15 +43,15 @@ func TestExecute(t *testing.T) {
4343
desc: "get componentversion with ocm config",
4444
commands: []string{"get", "componentversion"},
4545
arguments: []string{"--output", "yaml", ctfIn},
46-
ocmConfig: "./testdata/ocm-config.yaml",
46+
ocmConfig: "./testdata/01/ocm-config.yaml",
4747
expectedError: nil,
4848
},
4949

5050
{
5151
desc: "get componentversion with unsupported ocm config",
5252
commands: []string{"get", "componentversion"},
5353
arguments: []string{"--output", "yaml", ctfIn},
54-
ocmConfig: "./testdata/unsupported-ocm-config.yaml",
54+
ocmConfig: "./testdata/01/unsupported-ocm-config.yaml",
5555
expectedError: expectError,
5656
},
5757
}
@@ -73,7 +73,7 @@ func TestGetComponentVersion(t *testing.T) {
7373
expectError := errors.New("expected error")
7474
testutil.DownloadOCMAndAddToPath(t)
7575

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

7878
testCases := []struct {
7979
desc string
@@ -119,13 +119,13 @@ func TestGetComponentVersion(t *testing.T) {
119119
{
120120
desc: "get component version with ocm config",
121121
componentRef: ctfIn,
122-
ocmConfig: "./testdata/ocm-config.yaml",
122+
ocmConfig: "./testdata/01/ocm-config.yaml",
123123
expectedError: nil,
124124
},
125125
{
126126
desc: "get component version with unsupported ocm config",
127127
componentRef: ctfIn,
128-
ocmConfig: "./testdata/unsupported-ocm-config.yaml",
128+
ocmConfig: "./testdata/01/unsupported-ocm-config.yaml",
129129
expectedError: expectError,
130130
},
131131
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
components:
2+
3+
- name: github.com/openmcp-project/bootstrapper/test-component-getter-a
4+
version: v0.0.1
5+
provider:
6+
name: openmcp-project
7+
componentReferences:
8+
- componentName: github.com/openmcp-project/bootstrapper/test-component-getter-b
9+
name: reference-b
10+
version: v0.0.1
11+
resources:
12+
- name: test-resource-a
13+
type: blob
14+
input:
15+
type: file
16+
path: ./test-resource.yaml
17+
18+
- name: github.com/openmcp-project/bootstrapper/test-component-getter-b
19+
version: v0.0.1
20+
provider:
21+
name: openmcp-project
22+
componentReferences:
23+
- componentName: github.com/openmcp-project/bootstrapper/test-component-getter-c
24+
name: reference-c
25+
version: v0.0.1
26+
resources:
27+
- name: test-resource-b
28+
type: blob
29+
input:
30+
type: file
31+
path: ./test-resource.yaml
32+
33+
- name: github.com/openmcp-project/bootstrapper/test-component-getter-c
34+
version: v0.0.1
35+
provider:
36+
name: openmcp-project
37+
resources:
38+
- name: test-resource-c
39+
type: blob
40+
input:
41+
type: file
42+
path: ./test-resource.yaml
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
config:
2+
vars:
3+
- a: "a"
4+
- b: "b"

0 commit comments

Comments
 (0)