Skip to content

Commit

Permalink
Extended Tour Documentation (#614)
Browse files Browse the repository at this point in the history
* tour doc

* complete tour02

* credential tour03

* tour04/01

* tour04/02-03

* tour04/04

* tour05

* tour06

* fix install requirements

* incorporate review
  • Loading branch information
mandelsoft authored Jan 4, 2024
1 parent 09e696b commit 0c66570
Show file tree
Hide file tree
Showing 51 changed files with 4,972 additions and 224 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ test:

.PHONY: generate
generate:
@$(REPO_ROOT)/hack/generate.sh $(REPO_ROOT)/pkg... $(REPO_ROOT)/cmds/ocm/... $(REPO_ROOT)/cmds/helminst/...
@$(REPO_ROOT)/hack/generate.sh $(REPO_ROOT)/pkg... $(REPO_ROOT)/cmds/ocm/... $(REPO_ROOT)/cmds/helminst/... $(REPO_ROOT)/examples/...

.PHONY: generate-deepcopy
generate-deepcopy: controller-gen
Expand Down
264 changes: 261 additions & 3 deletions examples/lib/tour/01-getting-started/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,271 @@
<!-- DO NOT MODIFY -->
<!-- this file is generated by mdref -->
<!-- from ../docsrc/01-getting-started/README.md -->
# Basic Usage of OCM Repositories

This [tour](example.go) illustrates the basic usage of the API to
access component versions in an OCM repository.

You can just call the main program with some config file argument
with the following content:
## Running the example

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

```yaml
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](../../contexts.md). 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.

```go
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`.

```go
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.

```go
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.

```go
defer repo.Close()
```

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

```go
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.

```go
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.

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

<a id="describe-version"></a>

The component version object provides access
to the component descriptor

```go
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.

```go
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.6.0
provider: ocm.software
1: name: ocmcli
extra identity: "architecture"="amd64","os"="linux"
resource type: executable
access: Local blob sha256:6672528b57fd77cefa4c5a3395431b6a5aa14dc3ddad3ffe52343a7a518c2cd3[]
2: name: ocmcli
extra identity: "architecture"="arm64","os"="linux"
resource type: executable
access: Local blob sha256:9088cb8bbef1593b905d6bd3af6652165ff82cebd0d86540a7be9637324d036b[]
3: name: ocmcli-image
extra identity:
resource type: ociImage
access: OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.6.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.

```go
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

```go
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.

```go
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:

```go
reader, err = utils.GetResourceReader(res)
```

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

```go
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.

```go
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.

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

0 comments on commit 0c66570

Please sign in to comment.