diff --git a/README.md b/README.md index a889f2b..a38b28b 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,46 @@ This repository runs the OCM integration tests. It installs a local OCI registry This project uses Python 3.9+ to run the tests against the [OCM CLI](https://github.com/open-component-model/ocm). It is targeted to be executed in a Github action workflow. You can also run the tests locally: -* Install Python 3.9+ +* Install Python 3.9+ `sudo apt install -y python3 python3.10-venv` * Create a virtual environment, e.g `python -m venv /ocmtest` * Install pip: `python -m pip install --upgrade pip` -* Activate environment: `. /ocmtest/bin/activate` +* Activate environment: `. /ocmtest/bin/activate` (on Windows: `. \ocmtest\Scripts\Activate.ps1`) -- . D:\pip\ocm-integrationtest\Scripts\Activate.ps1 * Install requirements: `pip install -r requirements.txt` * Install docker -* Install htpasswd -* Install [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md) +* Install htpasswd - `sudo apt -y install apache2-utils` +* Install [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md) - `go install github.com/google/go-containerregistry/cmd/crane@latest` * Create SSL certificates and store them in `./certs` directory (see `create-cert.sh` for instructions [Link](create-cert.sh) (be sure to have a hostname with a fully qualified domain name) * Create a user for the OCI registry and passwd file: `htpasswd -b -v certs/htpasswd ocmuser ` * Set environment variables: `export FDQN_NAME=:4430; export USER_NAME=ocmuser; export PASSWD=` -* If you user alternative container runtimes to docker (like e.g. colima) you may need to set DOCKER_HOST env var. e.g.: `export DOCKER_HOST=unix:///Users//.colima/default/docker.sock` +* If you use alternative container runtimes to docker (like e.g. colima) you may need to set DOCKER_HOST env var. e.g.: `export DOCKER_HOST=unix:///Users//.colima/default/docker.sock` * Run local OCI registry in docker: `./start_docker.sh` * Build local test binaries: `./build.sh` * Run tests: `pytest tests` * Stop and remove container: `./stop_docker.sh` +```bash +sudo apt -y install apache2-utils python3 python3.10-venv +go install github.com/google/go-containerregistry/cmd/crane@latest +python3 -m venv /mnt/d/pip/ocm-integrationtest/ +python3 -m pip install --upgrade pip +. /mnt/d/pip/ocm-integrationtest/bin/activate +pip install -r requirements.txt +# start here +cd /mnt/d/git/ocm/ocm-integrationtest/ +. /mnt/d/pip/ocm-integrationtest/bin/activate +mkdir -p +export FDQN_NAME=`hostname --fqdn`:4430 + +htpasswd -b -c certs/htpasswd ocmuser ocmpass +htpasswd -b -v certs/htpasswd ocmuser ocmpass +``` + +```powershell +cd D:\git\ocm\ocm-integrationtest\ +. D:\pip\ocm-integrationtest\Scripts\Activate.ps1 +pip install -r requirements.txt +``` + ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/open-component-model/ocm-integrationtest/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..4b7ff66 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,4 @@ +go build -o local/hello.exe -ldflags '-extldflags "-static"' main.go +#env GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o local/hello.amd64 -ldflags '-extldflags "-static"' main.go +docker build -f Dockerfile.arm64 --tag hello-arm64:0.1.0 --tag eu.gcr.io/sap-cp-k8s-ocm-gcp-eu30-dev/dev/d058463/images/hello-arm64:0.1.0 . +docker build -f Dockerfile.amd64 --tag hello-amd64:0.1.0 --tag eu.gcr.io/sap-cp-k8s-ocm-gcp-eu30-dev/dev/d058463/images/hello-amd64:0.1.0 . diff --git a/cases/multiple-arguments.golden b/cases/multiple-arguments.golden new file mode 100644 index 0000000..506e2d7 --- /dev/null +++ b/cases/multiple-arguments.golden @@ -0,0 +1 @@ +ocm version 0.17.0-dev diff --git a/cases/no-args.golden b/cases/no-args.golden new file mode 100644 index 0000000..38bb201 --- /dev/null +++ b/cases/no-args.golden @@ -0,0 +1,348 @@ +ocm — Open Component Model Command Line Client + +Synopsis: + ocm [] help + +Available Commands: + add Add elements to a component repository or component version + bootstrap bootstrap components + cache Cache related commands + check check components in OCM repository + clean Cleanup/re-organize elements + completion Generate the autocompletion script for the specified shell + controller Commands acting on the ocm-controller + create Create transport or component archive + credentials Commands acting on credentials + describe Describe various elements by using appropriate sub commands. + download Download oci artifacts, resources or complete components + execute Execute an element. + get Get information about artifacts and components + hash Hash and normalization operations + help Help about any command + install Install elements. + list List information about components + oci Dedicated command flavors for the OCI layer + ocm Dedicated command flavors for the Open Component Model + set Set information about OCM repositories + show Show tags or versions + sign Sign components or hashes + toi Dedicated command flavors for the TOI layer + transfer Transfer artifacts or components + verify Verify component version signatures + version displays the version + +Flags: + -X, --attribute stringArray attribute setting + --ca-cert stringArray additional root certificate authorities (for signing certificates) + --config stringArray configuration file + --config-set strings apply configuration set + -C, --cred stringArray credential setting + -h, --help help for ocm + -I, --issuer stringArray issuer name or distinguished name (DN) (optionally for dedicated signature) ([:=]) + --logJson log as json instead of human readable logs + --logconfig string log config + -L, --logfile string set log file + --logkeys stringArray log tags/realms(with leading /) to be enabled ([/[+]]name{,[/[+]]name}[=level]) + -l, --loglevel string set log level + -K, --private-key stringArray private key setting + -k, --public-key stringArray public key setting + -v, --verbose deprecated: enable logrus verbose logging + --version show version + +Description: + + The Open Component Model command line client supports the work with OCM + artifacts, like Component Archives, Common Transport Archive, + Component Repositories, and Component Versions. + + Additionally it provides some limited support for the docker daemon, OCI artifacts and + registries. + + It can be used in two ways: + - *verb/operation first*: here the sub commands follow the pattern * * + - *area/kind first*: here the area and/or object kind is given first followed by the operation according to the pattern + *[] * + + The command accepts some top level options, they can only be given before the sub commands. + + A configuration according to «ocm configfile» is read from a «.ocmconfig» file + located in the «HOME» directory. With the option «--config» other + file locations can be specified. If nothing is specified and no file is found at the default + location a default configuration is composed according to known type specific + configuration files. + + The following configuration sources are used: + - The docker configuration file at «~/.docker/config.json» is + read to feed in the configured credentials for OCI registries. + + - The npm configuration file at «~/.npmrc» is + read to feed in the configured credentials for NPM registries. + + With the option «--cred» it is possible to specify arbitrary credentials + for various environments on the command line. Nevertheless it is always preferable + to use the cli config file. + Every credential setting is related to a dedicated consumer and provides a set of + credential attributes. All this can be specified by a sequence of «--cred» + options. + + Every option value has the format + + --cred [:]= + + Consumer identity attributes are prefixed with the colon ':'. A credential settings + always start with a sequence of at least one identity attributes, followed by a + sequence of credential attributes. + If a credential attribute is followed by an identity attribute a new credential setting + is started. + + The first credential setting may omit identity attributes. In this case it is used as + default credential, always used if no dedicated match is found. + + For example: + + --cred :type=OCIRegistry --cred :hostname=ghcr.io --cred username=mandelsoft --cred password=xyz + + With the option «-X» it is possible to pass global settings of the + form + + -X = + + The «--log*» options can be used to configure the logging behaviour. + For details see «ocm logging». + + There is a quick config option «--logkeys» to configure simple + tag/realm based condition rules. The comma-separated names build an AND rule. + Hereby, names starting with a slash («/») denote a realm (without + the leading slash). A realm is a slash separated sequence of identifiers. If + the realm name starts with a plus («+») character the generated rule + will match the realm and all its sub-realms, otherwise, only the dedicated + realm is affected. For example «/+ocm=trace» will enable all log output of the + OCM library. + + A tag directly matches the logging tags. Used tags and realms can be found under + topic «ocm logging». The ocm coding basically uses the realm «ocm». + The default level to enable is «info». Separated by an equal sign («=») + optionally a dedicated level can be specified. Log levels can be («error», + «warn», «info», «debug» and «trace». + The default level is «warn». + The «--logconfig*» options can be used to configure a complete + logging configuration (yaml/json) via command line. If the argument starts with + an «@», the logging configuration is taken from a file. + + The value can be a simple type or a JSON/YAML string for complex values + (see «ocm attributes»). The following attributes are supported: + - «github.com/mandelsoft/logforward» [«logfwd»]: *logconfig* Logging config structure used for config forwarding + This attribute is used to specify a logging configuration intended + to be forwarded to other tools. + (For example: TOI passes this config to the executor) + + - «github.com/mandelsoft/oci/cache» [«cache»]: *string* + Filesystem folder to use for caching OCI blobs + + - «github.com/mandelsoft/ocm/compat» [«compat»]: *bool* + Compatibility mode: Avoid generic local access methods and prefer type specific ones. + + - «github.com/mandelsoft/ocm/hasher»: *JSON* + Preferred hash algorithm to calculate resource digests. The following + digesters are supported: + - «NO-DIGEST» + - «SHA-256» (default) + - «SHA-512» + + - «github.com/mandelsoft/ocm/keeplocalblob» [«keeplocalblob»]: *bool* + Keep local blobs when importing OCI artifacts to OCI registries from «localBlob» + access methods. By default, they will be expanded to OCI artifacts with the + access method «ociRegistry». If this option is set to true, they will be stored + as local blobs, also. The access method will still be «localBlob» but with a nested + «ociRegistry» access method for describing the global access. + + - «github.com/mandelsoft/ocm/mapocirepo» [«mapocirepo»]: *bool|YAML* + When uploading an OCI artifact blob to an OCI based OCM repository and the + artifact is uploaded as OCI artifact, the repository path part is shortened, + either by hashing all but the last repository name part or by executing + some prefix based name mappings. + + If a boolean is given the short hash or none mode is enabled. + The YAML flavor uses the following fields: + - *«mode»* *string*: «hash», «shortHash», «prefixMapping» + or «none». If unset, no mapping is done. + - *«prefixMappings»*: *map[string]string* repository path prefix mapping. + - *«prefix»*: *string* repository prefix to use (replaces potential sub path of OCM repo). + or «none». + - *«prefixMapping»*: *map[string]string* repository path prefix mapping. + Notes: + + - The mapping only occurs in transfer commands and only when transferring to OCI registries (e.g. + when transferring to a CTF archive this option will be ignored). + - The mapping in mode «prefixMapping» requires a full prefix of the composed final name. + Partial matches are not supported. The host name of the target will be skipped. + - The artifact name of the component-descriptor is not mapped. + - If the mapping is provided on the command line it must be JSON format and needs to be properly + escaped (see example below). + + Example: + + Assume a component named «github.com/my_org/myexamplewithalongname» and a chart name + «echo» in the «Charts.yaml» of the chart archive. The following input to a + «resource.yaml» creates a component version: + + name: mychart + type: helmChart + input: + type: helm + path: charts/mychart.tgz + --- + name: myimage + type: ociImage + version: 0.1.0 + input: + type: ociImage + repository: ocm/ocm.software/ocmcli/ocmcli-image + path: ghcr.io/acme/ocm/ocm.software/ocmcli/ocmcli-image:0.1.0 + + The following command: + + ocm "-X mapocirepo={\"mode\":\"mapping\",\"prefixMappings\":{\"acme/github.com/my_org/myexamplewithalongname/ocm/ocm.software/ocmcli\":\"acme/cli\", \"acme/github.com/my_org/myexamplewithalongnameabc123\":\"acme/mychart\"}}" transfer ctf -f --copy-resources ./ctf ghcr.io/acme + + will result in the following artifacts in «ghcr.io/my_org»: + + mychart/echo + cli/ocmcli-image + + Note that the host name part of the transfer target «ghcr.io/acme» is excluded from the + prefix but the path «acme» is considered. + + The same using a config file «.ocmconfig»: + + type: generic.config.ocm.software/v1 + configurations: + ... + - type: attributes.config.ocm.software + attributes: + ... + mapocirepo: + mode: mapping + prefixMappings: + acme/github.com/my\_org/myexamplewithalongname/ocm/ocm.software/ocmcli: acme/cli + acme/github.com/my\_org/myexamplewithalongnameabc123: acme/mychart + + ocm transfer ca -f --copy-resources ./ca ghcr.io/acme + + - «github.com/mandelsoft/ocm/ociuploadrepo» [«ociuploadrepo»]: *oci base repository ref* + Upload local OCI artifact blobs to a dedicated repository. + + - «github.com/mandelsoft/ocm/plugindir» [«plugindir»]: *plugin directory* + Directory to look for OCM plugin executables. + + - «github.com/mandelsoft/ocm/rootcerts» [«rootcerts»]: *JSON* + General root certificate settings given as JSON document with the following + format: + + { + "rootCertificates": [ + { + "data": """ + }, + { + "path": """ + } + ] + } + + One of following data fields are possible: + - «data»: base64 encoded binary data + - «stringdata»: plain text data + - «path»: a file path to read the data from + + - «github.com/mandelsoft/ocm/signing»: *JSON* + Public and private Key settings given as JSON document with the following + format: + + { + "publicKeys": [ + "": { + "data": """ + } + ], + "privateKeys"": [ + "": { + "path": """ + } + ] + } + + One of following data fields are possible: + - «data»: base64 encoded binary data + - «stringdata»: plain text data + - «path»: a file path to read the data from + + - «github.com/mandelsoft/tempblobcache» [«blobcache»]: *string* Foldername for temporary blob cache + The temporary blob cache is used to accessing large blobs from remote systems. + The are temporarily stored in the filesystem, instead of the memory, to avoid + blowing up the memory consumption. + + - «ocm.software/cliconfig» [«cliconfig»]: *cliconfig* Configuration Object passed to command line plugin. + - «ocm.software/compositionmode» [«compositionmode»]: *bool* (default: false) + Composition mode decouples a component version provided by a repository + implementation from the backend persistence. Added local blobs will + and other changes will not be forwarded to the backend repository until + an AddVersion is called on the component. + If composition mode is disabled blobs will directly be forwarded to + the backend and descriptor updated will be persisted on AddVersion + or closing a provided existing component version. + + - «ocm.software/signing/sigstore» [«sigstore»]: *sigstore config* Configuration to use for sigstore based signing. + The following fields are used. + - *«fulcioURL»* *string* default is https://v1.fulcio.sigstore.dev + - *«rekorURL»* *string* default is https://rekor.sigstore.dev + - *«OIDCIssuer»* *string* default is https://oauth2.sigstore.dev/auth + - *«OIDCClientID»* *string* default is sigstore + For several options (like «-X») it is possible to pass complex values + using JSON or YAML syntax. To pass those arguments the escaping of the used shell + must be used to pass quotes, commas, curly brackets or newlines. for the *bash* + the easiest way to achieve this is to put the complete value into single quotes. + + «-X 'mapocirepo={"mode": "shortHash"}'». + + Alternatively, quotes and opening curly brackets can be escaped by using a + backslash («\»). + Often a tagged value can also be substituted from a file with the syntax + + «=@» + + The «--public-key» and «--private-key» options can be + used to define public and private keys on the command line. The options have an + argument of the form «=». The name is the name + of the key and represents the context is used for (For example the signature + name of a component version) + + Alternatively a key can be specified as base64 encoded string if the argument + start with the prefix «!» or as direct string with the prefix + «=». + + With «--issuer» it is possible to declare expected issuer + constraints for public key certificates provided as part of a signature + required to accept the provisioned public key (besides the successful + validation of the certificate). By default, the issuer constraint is + derived from the signature name. If it is not a formal distinguished name, + it is assumed to be a plain common name. + + With «--ca-cert» it is possible to define additional root + certificates for signature verification, if public keys are provided + by a certificate delivered with the signature. + Use ocm -h for additional help. + + +Additional help topics: + ocm attributes configuration attributes used to control the behaviour + ocm configfile configuration file + ocm credential-handling Provisioning of credentials for credential consumers + ocm logging Configured logging keys + ocm oci-references notation for OCI references + ocm ocm-accessmethods List of all supported access methods + ocm ocm-downloadhandlers List of all available download handlers + ocm ocm-labels Labels and Label Merging + ocm ocm-references notation for OCM references + ocm ocm-uploadhandlers List of all available upload handlers + ocm toi-bootstrapping Tiny OCM Installer based on component versions + diff --git a/cases/one-argument.golden b/cases/one-argument.golden new file mode 100644 index 0000000..ffc4910 --- /dev/null +++ b/cases/one-argument.golden @@ -0,0 +1 @@ +{"Major":"0", "Minor":"17", "Patch":"0", "PreRelease":"dev", "Meta":"", "GitVersion":"0.17.0-dev", "GitCommit":"", "GitTreeState":"", "BuildDate":"1970-01-01T00:00:00Z", "GoVersion":"go1.23.2", "Compiler":"gc", "Platform":"windows/amd64"} diff --git a/go.mod b/go.mod index b486f3a..b522b05 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/open-component-model/ocm-integrationtest go 1.23.2 + +require github.com/sergi/go-diff v1.3.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..10eff93 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/ocm_test.go b/ocm_test.go new file mode 100644 index 0000000..0e5287c --- /dev/null +++ b/ocm_test.go @@ -0,0 +1,141 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + "text/template" + + "github.com/sergi/go-diff/diffmatchpatch" +) + +var binaryName = "ocm" + +var binaryPath = "D:\\git\\ocm\\ocm\\cmds\\ocm\\" + +var update = flag.Bool("update", false, "update golden case files") + +func TestMain(m *testing.M) { + err := os.Chdir(binaryPath) + if err != nil { + fmt.Printf("could not change dir: %v", err) + os.Exit(1) + } + + dir, err := os.Getwd() + if err != nil { + fmt.Printf("could not get current dir: %v", err) + } + + binaryPath = filepath.Join(dir, binaryName) + + os.Exit(m.Run()) +} + +func runBinary(args []string) ([]byte, error) { + cmd := exec.Command(binaryPath, args...) + cmd.Env = append(os.Environ(), "GOCOVERDIR=.coverdata") + return cmd.CombinedOutput() +} + +func fixturePath(t *testing.T, fixture string) string { + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Fatalf("problems recovering caller information") + } + return filepath.Join(filepath.Dir(filename), "cases", fixture) +} + +func writeFixture(t *testing.T, fixture string, content []byte) { + err := os.WriteFile(fixturePath(t, fixture), content, 0o644) + if err != nil { + t.Fatal(err) + } +} + +func loadFixture(t *testing.T, fixture string) string { + tmpl, err := template.ParseFiles(fixturePath(t, fixture)) + if err != nil { + t.Fatal(err) + } + var tmplBytes bytes.Buffer + err = tmpl.Execute(&tmplBytes, envToMap()) + if err != nil { + t.Fatal(err) + } + return tmplBytes.String() +} + +func envToMap() map[string]string { + evpMap := map[string]string{} + + for _, v := range os.Environ() { + split := strings.Split(v, "=") + if len(split) == 2 { + evpMap[split[0]] = split[1] + } + } + return evpMap +} + +func TestCliArgs(t *testing.T) { + tests := []struct { + name string + args []string + fixture string + }{ + {"no arguments", []string{}, "no-args.golden"}, + {"one argument", []string{"version"}, "one-argument.golden"}, + {"multiple arguments", []string{"--version"}, "multiple-arguments.golden"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output, err := runBinary(tt.args) + + if err != nil { + t.Fatal(err) + } + + if *update { + writeFixture(t, tt.fixture, output) + } + + actual := string(output) + + expected := loadFixture(t, tt.fixture) + + if !reflect.DeepEqual(actual, expected) { + // t.Fatalf("actual = %s, expected = %s", actual, expected) + //diff.Diff("actual", []byte(actual), "expected", []byte(expected)) + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(actual, expected, true) + //fmt.Println(dmp.DiffPrettyText(diff)) + var newDiffs []diffmatchpatch.Diff + for i, item := range diffs { + if item.Type != diffmatchpatch.DiffEqual { + if i-1 >= 0 && diffs[i-1].Type == diffmatchpatch.DiffEqual { + newDiffs = append(newDiffs, diffs[i-1]) + } + newDiffs = append(newDiffs, item) + if i+1 <= len(diffs) && diffs[i+1].Type == diffmatchpatch.DiffEqual { + newDiffs = append(newDiffs, diffs[i+1]) + } + } + } + + fmt.Printf("len(diff) = %v", len(diffs)) + fmt.Printf("len(newDiffs) = %v", len(newDiffs)) + //t.Errorf("diff = %s", dmp.DiffToDelta(diffs)) + t.Errorf("diff = %s", dmp.DiffPrettyText(newDiffs)) + } + }) + } +}