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
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,32 @@
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.

Supported commands:
* `ocmTransfer`: Transfers the specified OCM component version from the source location to the destination location.
* `ocmTransfer`: Transfers the specified OCM component version from the source location to the target location.

### `ocmTransfer`

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

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

```shell
bootstrapper ocmTransfer --source <source-location> --destination <destination-location> --config <path-to-ocm-config>
openmcp-bootstrapper ocmTransfer <source-location> <target-location> --config <path-to-ocm-config>
```

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

```shell
ocm transfer componentversion --recursive --copy-resources --copy-sources <source-location> <destination-location> --config <path-to-ocm-config>
ocm --config <path-to-ocm-config> transfer componentversion --recursive --copy-resources --copy-sources <source-location> <target-location>
```

Example:
```shell
ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ./ctf
ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ghcr.io/my-github-user
openmcp-bootstrapper ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ./ctf
openmcp-bootstrapper ocmTransfer ghcr.io/openmcp-project/components//github.com/openmcp-project/openmcp:v0.0.11 ghcr.io/my-github-user
```


Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ includes:
NESTED_MODULES: ''
API_DIRS: ''
MANIFEST_OUT: ''
CODE_DIRS: '{{.ROOT_DIR}}/cmd/...'
CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/test/...'
COMPONENTS: 'openmcp-bootstrapper'
REPO_URL: 'https://github.com/openmcp-project/bootstrapper'
GENERATE_DOCS_INDEX: "true"
Expand Down
10 changes: 5 additions & 5 deletions cmd/ocmTransfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import (

// ocmTransferCmd represents the "ocm transfer componentversion" command
var ocmTransferCmd = &cobra.Command{
Use: "ocmTransfer source destination",
Short: "Transfer an OCM component from a source to a destination",
Long: `Transfers the specified OCM component version from the source location to the destination location.`,
Use: "ocmTransfer source target",
Short: "Transfer an OCM component from a source to a target location",
Long: `Transfers the specified OCM component version from the source location to the target location.`,
Aliases: []string{
"transfer",
},
Args: cobra.ExactArgs(2),
ArgAliases: []string{
"source",
"destination",
"target",
},
RunE: func(cmd *cobra.Command, args []string) error {
transferCommands := []string{
Expand All @@ -30,7 +30,7 @@ var ocmTransferCmd = &cobra.Command{
"--copy-resources",
"--copy-sources",
args[0], // source
args[1], // destination
args[1], // target
}

return ocmcli.Execute(cmd.Context(), transferCommands, transferArgs, cmd.Flag("config").Value.String())
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ go 1.24.5
require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/yaml v1.6.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.7 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -13,7 +15,15 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
60 changes: 31 additions & 29 deletions internal/ocm-cli/ocm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package ocm_cli
import (
"context"
"fmt"
"gopkg.in/yaml.v3"
"os"
"os/exec"

"sigs.k8s.io/yaml"
)

const (
Expand All @@ -19,16 +20,16 @@ const (
// The `args` parameter is a slice of strings representing the arguments to the command.
// The `ocmConfig` parameter is a string representing the path to the OCM configuration file. Passing `NoOcmConfig` indicates that no configuration file should be used.
func Execute(ctx context.Context, commands []string, args []string, ocmConfig string) error {
var flags []string

flags = append(flags, commands...)
flags = append(flags, args...)
var ocmArgs []string

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

cmd := exec.CommandContext(ctx, "ocm", flags...)
ocmArgs = append(ocmArgs, commands...)
ocmArgs = append(ocmArgs, args...)

cmd := exec.CommandContext(ctx, "ocm", ocmArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

Expand All @@ -52,43 +53,47 @@ type ComponentVersion struct {
// Component represents an OCM component with its name, version, references to other components, and resources.
type Component struct {
// Name is the name of the component.
Name string `yaml:"name"`
Name string `json:"name"`
// Version is the version of the component.
Version string `yaml:"version"`
Version string `json:"version"`
// ComponentReferences is a list of references to other components that this component depends on.
ComponentReferences []ComponentReference `yaml:"componentReferences"`
// Resources is a list of resources associated with this component, including their names, versions, types, and access information.
Resources []Resource `yaml:"resources"`
Resources []Resource `json:"resources"`
}

// ComponentReference represents a reference to another component, including its name, version, and the name of the component it refers to.
type ComponentReference struct {
// Name is the name of the component reference.
Name string `yaml:"name"`
Name string `json:"name"`
// Version is the version of the component reference.
Version string `yaml:"version"`
Version string `json:"version"`
// ComponentName is the name of the component that this reference points to.
ComponentName string `yaml:"componentName"`
ComponentName string `json:"componentName"`
}

// Resource represents a resource associated with a component, including its name, version, type, and access information.
type Resource struct {
// Name is the name of the resource.
Name string `yaml:"name"`
Name string `json:"name"`
// Version is the version of the resource.
Version string `yaml:"version"`
Version string `json:"version"`
// Type is the content type of the resource.
Type string `yaml:"type"`
Type string `json:"type"`
// Access contains the information on how to access the resource.
Access Access `yaml:"access"`
Access Access `json:"access"`
}

// Access represents the access information for a resource, including the type of access.
type Access struct {
// Type is the content type of access to the resource.
Type string `yaml:"type"`
// Type specifies the access type of the resource.
Type string `json:"type"`
// ImageReference is the reference to the image if the Type is "ociArtifact".
ImageReference string `yaml:"imageReference"`
ImageReference *string `json:"imageReference"`
// LocalReference specifies a component local access
LocalReference *string `json:"localReference"`
// MediaType is the media type of the resource
MediaType *string `json:"mediaType"`
}

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

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

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

cmd := exec.CommandContext(ctx, "ocm", flags...)
ocmArgs = append(ocmArgs, "get", "componentversion", "--output", "yaml", componentReference)

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

var cv ComponentVersion
Expand Down
140 changes: 140 additions & 0 deletions internal/ocm-cli/ocm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package ocm_cli_test

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/utils/ptr"

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

func TestExecute(t *testing.T) {
expectError := errors.New("expected error")

testutil.DownloadOCMAndAddToPath(t)

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

testCases := []struct {
desc string
commands []string
arguments []string
ocmConfig string
expectedError error
}{
{
desc: "get componentversion",
commands: []string{"get", "componentversion"},
arguments: []string{"--output", "yaml", ctfIn},
ocmConfig: ocmcli.NoOcmConfig,
expectedError: nil,
},
{
desc: "get componentversion with invalid argument",
commands: []string{"get", "componentversion"},
arguments: []string{"--output", "yaml", "invalid-argument"},
ocmConfig: ocmcli.NoOcmConfig,
expectedError: expectError,
},
{
desc: "get componentversion with ocm config",
commands: []string{"get", "componentversion"},
arguments: []string{"--output", "yaml", ctfIn},
ocmConfig: "./testdata/ocm-config.yaml",
expectedError: nil,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
err := ocmcli.Execute(t.Context(), tc.commands, tc.arguments, tc.ocmConfig)

if tc.expectedError != nil {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}

func TestGetComponentVersion(t *testing.T) {
expectError := errors.New("expected error")
testutil.DownloadOCMAndAddToPath(t)

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

testCases := []struct {
desc string
componentRef string
ocmConfig string
expectedError error
verify func(cv *ocmcli.ComponentVersion)
}{
{
desc: "get component version",
componentRef: ctfIn,
ocmConfig: ocmcli.NoOcmConfig,
expectedError: nil,
verify: func(cv *ocmcli.ComponentVersion) {
assert.Equal(t, cv.Component.Name, "github.com/openmcp-project/bootstrapper/test")
assert.Equal(t, cv.Component.Version, "v0.0.1")
assert.Len(t, cv.Component.ComponentReferences, 2)
assert.Len(t, cv.Component.Resources, 1)

assert.Contains(t, cv.Component.ComponentReferences, ocmcli.ComponentReference{
Name: "bootstrapper-dependency-a",
Version: "v0.2.0",
ComponentName: "github.com/openmcp-project/bootstrapper-dependency-a",
})
assert.Contains(t, cv.Component.ComponentReferences, ocmcli.ComponentReference{
Name: "bootstrapper-dependency-b",
Version: "v0.3.0",
ComponentName: "github.com/openmcp-project/bootstrapper-dependency-b",
})

assert.Contains(t, cv.Component.Resources, ocmcli.Resource{
Name: "test-resource",
Version: "v0.0.1",
Type: "blob",
Access: ocmcli.Access{
Type: "localBlob",
LocalReference: cv.Component.Resources[0].Access.LocalReference,
MediaType: ptr.To("application/octet-stream"),
},
})
},
},
{
desc: "get component version with ocm config",
componentRef: ctfIn,
ocmConfig: "./testdata/ocm-config.yaml",
expectedError: nil,
},
{
desc: "get component version with invalid reference",
componentRef: "invalid-component-ref",
ocmConfig: ocmcli.NoOcmConfig,
expectedError: expectError,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
cv, err := ocmcli.GetComponentVersion(t.Context(), tc.componentRef, tc.ocmConfig)

if tc.expectedError != nil {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}

if tc.verify != nil {
tc.verify(cv)
}
})
}
}
Loading