Skip to content

Commit c7a896e

Browse files
authored
refactor: improved ocm apis and tests (#10)
* refactoring and tests * fix readme * add missing code dirs * fix readme * fix verify * handle symlink already exists
1 parent e2e3285 commit c7a896e

File tree

11 files changed

+311
-71
lines changed

11 files changed

+311
-71
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,32 @@
77
The openmcp bootstrapper is a command line tool that is able to set up an openmcp landscape initially and to update existing openmcp landscapes with new versions of the openmcp project.
88

99
Supported commands:
10-
* `ocmTransfer`: Transfers the specified OCM component version from the source location to the destination location.
10+
* `ocmTransfer`: Transfers the specified OCM component version from the source location to the target location.
1111

1212
### `ocmTransfer`
1313

14-
The `ocmTransfer` command is used to transfer an OCM component version from a source location to a destination location.
14+
The `ocmTransfer` command is used to transfer an OCM component version from a source location to a target location.
1515
The `ocmTransfer` requires the following parameters:
1616
* `source`: The source location of the OCM component version to be transferred.
17-
* `destination`: The destination location where the OCM component version should be transferred to.
17+
* `target`: The target location where the OCM component version should be transferred to.
1818

1919
Optional parameters:
2020
* `--config`: Path to the OCM configuration file.
2121

2222
```shell
23-
bootstrapper ocmTransfer --source <source-location> --destination <destination-location> --config <path-to-ocm-config>
23+
openmcp-bootstrapper ocmTransfer <source-location> <target-location> --config <path-to-ocm-config>
2424
```
2525

2626
This command internally calls the OCM cli with the following command and arguments:
2727

2828
```shell
29-
ocm transfer componentversion --recursive --copy-resources --copy-sources <source-location> <destination-location> --config <path-to-ocm-config>
29+
ocm --config <path-to-ocm-config> transfer componentversion --recursive --copy-resources --copy-sources <source-location> <target-location>
3030
```
3131

3232
Example:
3333
```shell
34-
ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ./ctf
35-
ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ghcr.io/my-github-user
34+
openmcp-bootstrapper ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ./ctf
35+
openmcp-bootstrapper ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ghcr.io/my-github-user
3636
```
3737

3838

Taskfile.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ includes:
99
NESTED_MODULES: ''
1010
API_DIRS: ''
1111
MANIFEST_OUT: ''
12-
CODE_DIRS: '{{.ROOT_DIR}}/cmd/...'
12+
CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/test/...'
1313
COMPONENTS: 'openmcp-bootstrapper'
1414
REPO_URL: 'https://github.com/openmcp-project/bootstrapper'
1515
GENERATE_DOCS_INDEX: "true"

cmd/ocmTransfer.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import (
88

99
// ocmTransferCmd represents the "ocm transfer componentversion" command
1010
var ocmTransferCmd = &cobra.Command{
11-
Use: "ocmTransfer source destination",
12-
Short: "Transfer an OCM component from a source to a destination",
13-
Long: `Transfers the specified OCM component version from the source location to the destination location.`,
11+
Use: "ocmTransfer source target",
12+
Short: "Transfer an OCM component from a source to a target location",
13+
Long: `Transfers the specified OCM component version from the source location to the target location.`,
1414
Aliases: []string{
1515
"transfer",
1616
},
1717
Args: cobra.ExactArgs(2),
1818
ArgAliases: []string{
1919
"source",
20-
"destination",
20+
"target",
2121
},
2222
RunE: func(cmd *cobra.Command, args []string) error {
2323
transferCommands := []string{
@@ -30,7 +30,7 @@ var ocmTransferCmd = &cobra.Command{
3030
"--copy-resources",
3131
"--copy-sources",
3232
args[0], // source
33-
args[1], // destination
33+
args[1], // target
3434
}
3535

3636
return ocmcli.Execute(cmd.Context(), transferCommands, transferArgs, cmd.Flag("config").Value.String())

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ go 1.24.5
55
require (
66
github.com/spf13/cobra v1.9.1
77
github.com/stretchr/testify v1.10.0
8-
gopkg.in/yaml.v3 v3.0.1
8+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
9+
sigs.k8s.io/yaml v1.6.0
910
)
1011

1112
require (
1213
github.com/davecgh/go-spew v1.1.1 // indirect
1314
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1415
github.com/pmezard/go-difflib v1.0.0 // indirect
1516
github.com/spf13/pflag v1.0.7 // indirect
17+
go.yaml.in/yaml/v2 v2.4.2 // indirect
18+
gopkg.in/yaml.v3 v3.0.1 // indirect
1619
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
22
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
5+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
46
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
57
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
68
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -13,7 +15,15 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
1315
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1416
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1517
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
18+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
19+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
20+
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
21+
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
1622
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1723
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1824
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1925
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
26+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
27+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
28+
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
29+
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

internal/ocm-cli/ocm.go

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package ocm_cli
33
import (
44
"context"
55
"fmt"
6-
"gopkg.in/yaml.v3"
76
"os"
87
"os/exec"
8+
9+
"sigs.k8s.io/yaml"
910
)
1011

1112
const (
@@ -19,16 +20,16 @@ const (
1920
// The `args` parameter is a slice of strings representing the arguments to the command.
2021
// The `ocmConfig` parameter is a string representing the path to the OCM configuration file. Passing `NoOcmConfig` indicates that no configuration file should be used.
2122
func Execute(ctx context.Context, commands []string, args []string, ocmConfig string) error {
22-
var flags []string
23-
24-
flags = append(flags, commands...)
25-
flags = append(flags, args...)
23+
var ocmArgs []string
2624

2725
if ocmConfig != NoOcmConfig {
28-
flags = append(flags, "--config", ocmConfig)
26+
ocmArgs = append(ocmArgs, "--config", ocmConfig)
2927
}
3028

31-
cmd := exec.CommandContext(ctx, "ocm", flags...)
29+
ocmArgs = append(ocmArgs, commands...)
30+
ocmArgs = append(ocmArgs, args...)
31+
32+
cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
3233
cmd.Stdout = os.Stdout
3334
cmd.Stderr = os.Stderr
3435

@@ -52,43 +53,47 @@ type ComponentVersion struct {
5253
// Component represents an OCM component with its name, version, references to other components, and resources.
5354
type Component struct {
5455
// Name is the name of the component.
55-
Name string `yaml:"name"`
56+
Name string `json:"name"`
5657
// Version is the version of the component.
57-
Version string `yaml:"version"`
58+
Version string `json:"version"`
5859
// ComponentReferences is a list of references to other components that this component depends on.
5960
ComponentReferences []ComponentReference `yaml:"componentReferences"`
6061
// Resources is a list of resources associated with this component, including their names, versions, types, and access information.
61-
Resources []Resource `yaml:"resources"`
62+
Resources []Resource `json:"resources"`
6263
}
6364

6465
// ComponentReference represents a reference to another component, including its name, version, and the name of the component it refers to.
6566
type ComponentReference struct {
6667
// Name is the name of the component reference.
67-
Name string `yaml:"name"`
68+
Name string `json:"name"`
6869
// Version is the version of the component reference.
69-
Version string `yaml:"version"`
70+
Version string `json:"version"`
7071
// ComponentName is the name of the component that this reference points to.
71-
ComponentName string `yaml:"componentName"`
72+
ComponentName string `json:"componentName"`
7273
}
7374

7475
// Resource represents a resource associated with a component, including its name, version, type, and access information.
7576
type Resource struct {
7677
// Name is the name of the resource.
77-
Name string `yaml:"name"`
78+
Name string `json:"name"`
7879
// Version is the version of the resource.
79-
Version string `yaml:"version"`
80+
Version string `json:"version"`
8081
// Type is the content type of the resource.
81-
Type string `yaml:"type"`
82+
Type string `json:"type"`
8283
// Access contains the information on how to access the resource.
83-
Access Access `yaml:"access"`
84+
Access Access `json:"access"`
8485
}
8586

8687
// Access represents the access information for a resource, including the type of access.
8788
type Access struct {
88-
// Type is the content type of access to the resource.
89-
Type string `yaml:"type"`
89+
// Type specifies the access type of the resource.
90+
Type string `json:"type"`
9091
// ImageReference is the reference to the image if the Type is "ociArtifact".
91-
ImageReference string `yaml:"imageReference"`
92+
ImageReference *string `json:"imageReference"`
93+
// LocalReference specifies a component local access
94+
LocalReference *string `json:"localReference"`
95+
// MediaType is the media type of the resource
96+
MediaType *string `json:"mediaType"`
9297
}
9398

9499
// GetResource retrieves a resource by its name from the component version.
@@ -113,21 +118,18 @@ func (cv *ComponentVersion) GetComponentReference(name string) (*ComponentRefere
113118

114119
// GetComponentVersion retrieves a component version by its reference using the OCM CLI.
115120
func GetComponentVersion(ctx context.Context, componentReference string, ocmConfig string) (*ComponentVersion, error) {
116-
flags := []string{
117-
"get",
118-
"componentversion",
119-
"--output", "yaml",
120-
componentReference,
121-
}
121+
var ocmArgs []string
122122

123123
if ocmConfig != NoOcmConfig {
124-
flags = append(flags, "--config", ocmConfig)
124+
ocmArgs = append(ocmArgs, "--config", ocmConfig)
125125
}
126126

127-
cmd := exec.CommandContext(ctx, "ocm", flags...)
127+
ocmArgs = append(ocmArgs, "get", "componentversion", "--output", "yaml", componentReference)
128+
129+
cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
128130
out, err := cmd.CombinedOutput()
129131
if err != nil {
130-
return nil, fmt.Errorf("error executing ocm command: %w", err)
132+
return nil, fmt.Errorf("error executing ocm command: %w, %q", err, out)
131133
}
132134

133135
var cv ComponentVersion

internal/ocm-cli/ocm_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package ocm_cli_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"k8s.io/utils/ptr"
9+
10+
ocmcli "github.com/openmcp-project/bootstrapper/internal/ocm-cli"
11+
testutil "github.com/openmcp-project/bootstrapper/test/utils"
12+
)
13+
14+
func TestExecute(t *testing.T) {
15+
expectError := errors.New("expected error")
16+
17+
testutil.DownloadOCMAndAddToPath(t)
18+
19+
ctfIn := testutil.BuildComponent("./testdata/component-constructor.yaml", t)
20+
21+
testCases := []struct {
22+
desc string
23+
commands []string
24+
arguments []string
25+
ocmConfig string
26+
expectedError error
27+
}{
28+
{
29+
desc: "get componentversion",
30+
commands: []string{"get", "componentversion"},
31+
arguments: []string{"--output", "yaml", ctfIn},
32+
ocmConfig: ocmcli.NoOcmConfig,
33+
expectedError: nil,
34+
},
35+
{
36+
desc: "get componentversion with invalid argument",
37+
commands: []string{"get", "componentversion"},
38+
arguments: []string{"--output", "yaml", "invalid-argument"},
39+
ocmConfig: ocmcli.NoOcmConfig,
40+
expectedError: expectError,
41+
},
42+
{
43+
desc: "get componentversion with ocm config",
44+
commands: []string{"get", "componentversion"},
45+
arguments: []string{"--output", "yaml", ctfIn},
46+
ocmConfig: "./testdata/ocm-config.yaml",
47+
expectedError: nil,
48+
},
49+
}
50+
51+
for _, tc := range testCases {
52+
t.Run(tc.desc, func(t *testing.T) {
53+
err := ocmcli.Execute(t.Context(), tc.commands, tc.arguments, tc.ocmConfig)
54+
55+
if tc.expectedError != nil {
56+
assert.Error(t, err)
57+
} else {
58+
assert.NoError(t, err)
59+
}
60+
})
61+
}
62+
}
63+
64+
func TestGetComponentVersion(t *testing.T) {
65+
expectError := errors.New("expected error")
66+
testutil.DownloadOCMAndAddToPath(t)
67+
68+
ctfIn := testutil.BuildComponent("./testdata/component-constructor.yaml", t)
69+
70+
testCases := []struct {
71+
desc string
72+
componentRef string
73+
ocmConfig string
74+
expectedError error
75+
verify func(cv *ocmcli.ComponentVersion)
76+
}{
77+
{
78+
desc: "get component version",
79+
componentRef: ctfIn,
80+
ocmConfig: ocmcli.NoOcmConfig,
81+
expectedError: nil,
82+
verify: func(cv *ocmcli.ComponentVersion) {
83+
assert.Equal(t, cv.Component.Name, "github.com/openmcp-project/bootstrapper/test")
84+
assert.Equal(t, cv.Component.Version, "v0.0.1")
85+
assert.Len(t, cv.Component.ComponentReferences, 2)
86+
assert.Len(t, cv.Component.Resources, 1)
87+
88+
assert.Contains(t, cv.Component.ComponentReferences, ocmcli.ComponentReference{
89+
Name: "bootstrapper-dependency-a",
90+
Version: "v0.2.0",
91+
ComponentName: "github.com/openmcp-project/bootstrapper-dependency-a",
92+
})
93+
assert.Contains(t, cv.Component.ComponentReferences, ocmcli.ComponentReference{
94+
Name: "bootstrapper-dependency-b",
95+
Version: "v0.3.0",
96+
ComponentName: "github.com/openmcp-project/bootstrapper-dependency-b",
97+
})
98+
99+
assert.Contains(t, cv.Component.Resources, ocmcli.Resource{
100+
Name: "test-resource",
101+
Version: "v0.0.1",
102+
Type: "blob",
103+
Access: ocmcli.Access{
104+
Type: "localBlob",
105+
LocalReference: cv.Component.Resources[0].Access.LocalReference,
106+
MediaType: ptr.To("application/octet-stream"),
107+
},
108+
})
109+
},
110+
},
111+
{
112+
desc: "get component version with ocm config",
113+
componentRef: ctfIn,
114+
ocmConfig: "./testdata/ocm-config.yaml",
115+
expectedError: nil,
116+
},
117+
{
118+
desc: "get component version with invalid reference",
119+
componentRef: "invalid-component-ref",
120+
ocmConfig: ocmcli.NoOcmConfig,
121+
expectedError: expectError,
122+
},
123+
}
124+
125+
for _, tc := range testCases {
126+
t.Run(tc.desc, func(t *testing.T) {
127+
cv, err := ocmcli.GetComponentVersion(t.Context(), tc.componentRef, tc.ocmConfig)
128+
129+
if tc.expectedError != nil {
130+
assert.Error(t, err)
131+
} else {
132+
assert.NoError(t, err)
133+
}
134+
135+
if tc.verify != nil {
136+
tc.verify(cv)
137+
}
138+
})
139+
}
140+
}

0 commit comments

Comments
 (0)