01-getting-started

command
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2024 License: Apache-2.0 Imports: 14 Imported by: 0

README

Basic Usage of OCM Repositories

This tour illustrates the basic usage of the API to access component versions in an OCM repository.

Running the example

You can call the main program with a config file argument (--config <file>), where the config file has the following content:

component: github.com/mandelsoft/examples/cred1
repository: ghcr.io/mandelsoft/ocm
version: 0.1.0

Walkthrough

The basic entry point for using the OCM library is always an OCM Context object. It bundles all configuration settings and type registrations, like access methods, repository types, etc, and configuration settings, like credentials, which should be used when working with the OCM ecosystem.

Therefore, the first step is always to get access to such a context object. Our example uses the default context provided by the library, which covers the complete type registration contained in the executable.

It can be accessed by a function of the pkg/contexts/ocm package.

	ctx := ocm.DefaultContext()

The context acts as the central entry point to get access to OCM elements. First, we get a repository, to look for component versions. We use the OCM repository hosted on ghcr.io, which is providing the standard OCM components.

For every storage technology used to store OCM components, there is a serializable descriptor object, the repository specification. It describes the information required to access the repository and can be used to store the serialized form as part of other resources, for example Kubernetes resources or configuration settings. The available repository implementations can be found under .../pkg/contexts/ocm/repositories.

	spec := ocireg.NewRepositorySpec("ghcr.io/open-component-model/ocm")

The context can now be used to map the descriptor into a repository object, which then provides access to the OCM elements stored in this repository.

	repo, err := ctx.RepositoryForSpec(spec)
	if err != nil {
		return errors.Wrapf(err, "cannot setup repository")
	}

To release potentially allocated temporary resources, many objects must be closed, if they are not used anymore. This is typically done by a defer statement placed after a successful object retrieval.

	defer repo.Close()

All kinds of repositories, regardless of their type feature the same interface to work with OCM content. It can be used to access stored elements. First of all, a repository hosts component versions. They are stored for components. Components are not necessarily explicit objects stored in an OCM repository. But they have features like a name and versions. Therefore, the repository abstraction provided by the library offers a component object, which can be retrieved from a repository object. A component has a name and acts as namespace for versions.

	c, err := repo.LookupComponent("ocm.software/ocmcli")
	if err != nil {
		return errors.Wrapf(err, "cannot lookup component")
	}
	defer c.Close()

Now we look for the versions of the component available in this repository.

	versions, err := c.ListVersions()
	if err != nil {
		return errors.Wrapf(err, "cannot query version names")
	}

OCM version names must follow the SemVer rules. Therefore, we can simply order the versions and print them.

	err = semverutils.SortVersions(versions)
	if err != nil {
		return errors.Wrapf(err, "cannot sort versions")
	}
	fmt.Printf("versions for component ocm.software/ocmcli: %s\n", strings.Join(versions, ", "))

Now, we have a look at the latest version. It is the last one in the list.

	cv, err := c.LookupVersion(versions[len(versions)-1])
	if err != nil {
		return errors.Wrapf(err, "cannot get latest version")
	}
	defer cv.Close()

The component version object provides access to the component descriptor

	cd := cv.GetDescriptor()
	fmt.Printf("resources of the latest version:\n")
	fmt.Printf("  version:  %s\n", cv.GetVersion())
	fmt.Printf("  provider: %s\n", cd.Provider.Name)

and the resources described by the component version.

	for i, r := range cv.GetResources() {
		fmt.Printf("  %2d: name:           %s\n", i+1, r.Meta().GetName())
		fmt.Printf("      extra identity: %s\n", r.Meta().GetExtraIdentity())
		fmt.Printf("      resource type:  %s\n", r.Meta().GetType())
		acc, err := r.Access()
		if err != nil {
			fmt.Printf("      access:         error: %s\n", err)
		} else {
			fmt.Printf("      access:         %s\n", acc.Describe(ctx))
		}
	}

This results in the following output (the shown version might differ, because the code always describes the latest version):

resources of the latest version:
  version:  0.9.0
  provider: ocm.software
   1: name:           ocmcli
      extra identity: "architecture"="amd64","os"="linux"
      resource type:  executable
      access:         Local blob sha256:1de1c90f23d0a3dbb8d8646f09380f1da257f9d10796b42dc4ef85e8df93a135[]
   2: name:           ocmcli
      extra identity: "architecture"="arm64","os"="linux"
      resource type:  executable
      access:         Local blob sha256:ca049bb09399020ce0822fd18c0a534ae0d02c3e0180f05dd4faccf61176a267[]
   3: name:           ocmcli
      extra identity: "architecture"="arm64","os"="darwin"
      resource type:  executable
      access:         Local blob sha256:1e32b3f1a08c72e3187b247f8931ea9d0554240fd452a4df129d6036c62b0476[]
   4: name:           ocmcli
      extra identity: "architecture"="amd64","os"="darwin"
      resource type:  executable
      access:         Local blob sha256:04708d2f9845dd6d52f2b8f94e930f3a74a1a098b7ee401e001307d4b4fcc703[]
   5: name:           ocmcli
      extra identity: "architecture"="amd64","os"="windows"
      resource type:  executable
      access:         Local blob sha256:e8cf5dfd1ab02ab982e6f1a425d426fc1f7dc83e6385d26d0477525a4a66c629[]
   6: name:           ocmcli-image
      extra identity: 
      resource type:  ociImage
      access:         OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.9.0

Resources have some metadata, like their identity and a resource type. And, most importantly, they describe how the content of the resource (as blob) can be accessed. This is done by an access specification, again a serializable descriptor, like the repository specification.

The component version used here contains the executables for the OCM CLI for various platforms. The next step is to get the executable for the actual environment. The identity of a resource described by a component version consists of a set of properties. The property name is mandatory. But there may be more identity attributes finally stored as ``extraIdentity` in the component descriptor.

A convention is to use dedicated identity properties to indicate the operating system and the architecture for executables.

	id := metav1.NewIdentity("ocmcli",
		extraid.ExecutableOperatingSystem, runtime.GOOS,
		extraid.ExecutableArchitecture, runtime.GOARCH,
	)

	res, err := cv.GetResource(id)
	if err != nil {
		return errors.Wrapf(err, "resource %s", id)
	}

Now we want to retrieve the executable. The library provides two basic ways to do this.

First, there is the direct way to gain access to the blob by using the basic model operations to get a reader for the resource blob. Therefore, in a first step we get the access method for the resource

		var m ocm.AccessMethod
		m, err = res.AccessMethod()
		if err != nil {
			return errors.Wrapf(err, "cannot get access method")
		}
		defer m.Close()

The method needs to be closed, because the method object may cache the technical blob representation generated by accessing the underlying access technology. (for example, accessing an OCI image requires a sequence of backend requests for the manifest, the layers, etc, which will then be packaged into a tar archive returned as blob). This caching may not be required, if the backend directly returns a blob.

Now, we get access to the reader providing the blob content. The blob features a mime type, which can be used to understand the format of the blob. Here, we have a plain octet stream.

		fmt.Printf("  found blob with mime type %s\n", m.MimeType())
		reader, err = m.Reader()

Because this code sequence is a common operation, there is a utility function handling this sequence. A shorter way to get a resource reader is as follows:

		reader, err = utils.GetResourceReader(res)

Before we download the content we check the error and prepare closing the reader, again

	if err != nil {
		return errors.Wrapf(err, "cannot get resource reader")
	}
	defer reader.Close()

Now, we just read the content and copy it to the intended output file (/tmp/ocmcli).

	file, err := os.OpenFile("/tmp/ocmcli", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0766)
	if err != nil {
		return errors.Wrapf(err, "cannot open output file")
	}
	defer file.Close()

	n, err := io.Copy(file, reader)
	if err != nil {
		return errors.Wrapf(err, "write executable")
	}
	fmt.Printf("%d bytes written\n", n)

Another way to download a resource is to use registered downloaders. download.DownloadResource is used to download resources with specific handlers for selected resource and mime type combinations. The executable downloader is registered by default and automatically sets the X flag for the written file.

	_, err = download.DownloadResource(ctx, res, "/tmp/ocmcli", download.WithPrinter(common.NewPrinter(os.Stdout)))
	if err != nil {
		return errors.Wrapf(err, "download failed")
	}

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL