Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework implementation support for OCM storage backends #569

Merged
merged 18 commits into from
Nov 29, 2023
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
4 changes: 2 additions & 2 deletions cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,15 @@ Error: signing: github.com/mandelsoft/ref:v1: failed resolving component referen
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component "github.com/mandelsoft/test" not found in ComponentArchive
`))
})

It("sign archive", func() {
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, ARCH)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component "github.com/mandelsoft/test" not found in ComponentArchive
`))
})
})
Expand Down
16 changes: 8 additions & 8 deletions examples/lib/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import (
)

type Config struct {
Username string `json:"username"`
Password string `json:"password"`
Component string `json:"component"`
Repository string `json:"repository"`
Version string `json:"version"`

Target json.RawMessage `json:"targetRepository"`
OCMConfig string `json:"ocmConfig"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Component string `json:"component,omitempty"`
Repository string `json:"repository,omitempty"`
Version string `json:"version,omitempty"`

Target json.RawMessage `json:"targetRepository,omitempty"`
OCMConfig string `json:"ocmConfig,omitempty"`
}

func ReadConfig(path string) (*Config, error) {
Expand Down
13 changes: 13 additions & 0 deletions examples/lib/tour/01-getting-started/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 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:

```yaml
component: github.com/mandelsoft/examples/cred1
repository: ghcr.io/mandelsoft/ocm
version: 0.1.0
```
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ func listVersions(repo ocm.Repository, list ...string) error {
}
return nil
}

func ComposingAComponentVersionA() error {
// yes, we need an OCM context, again
ctx := ocm.DefaultContext()
Expand Down
10 changes: 10 additions & 0 deletions examples/lib/tour/02-composing-a-component-version/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Composing a Component Version

This tor illustrates the basic usage of the API to
create/compose component versions.

It covers two basic scenarios:
- [`basic`](01-basic-componentversion-creation.go) Create a component version stored in the filesystem
- [`compose`](02-composition-version.go) Create a component version stored in memory using a non-persistent composition version.

You can just call the main program with the scenario as argument.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"os"

"github.com/open-component-model/ocm/examples/lib/helper"
"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/contexts/credentials"
ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity"
"github.com/open-component-model/ocm/pkg/contexts/oci"
Expand All @@ -19,19 +18,6 @@ import (
"github.com/open-component-model/ocm/pkg/errors"
)

func obfuscate(props common.Properties) string {
if pw, ok := props[credentials.ATTR_PASSWORD]; ok {
if len(pw) > 5 {
pw = pw[:5] + "***"
} else {
pw = "***"
}
props = props.Copy()
props[credentials.ATTR_PASSWORD] = pw
}
return props.String()
}

func UsingCredentialsB(cfg *helper.Config, create bool) error {
ctx := ocm.DefaultContext()

Expand Down Expand Up @@ -137,6 +123,11 @@ func UsingCredentialsB(cfg *helper.Config, create bool) error {
if err != nil {
return errors.Wrapf(err, "no credentials")
}
// an error is only provided if something went wrong while determining
// the credentials. Delivering NO credentials is a valid result.
if creds == nil {
return fmt.Errorf("no credentials found")
}
fmt.Printf("credentials: %s\n", obfuscate(creds.Properties()))

// Now we can continue with our basic component version composition
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"

"github.com/open-component-model/ocm/examples/lib/helper"
"github.com/open-component-model/ocm/pkg/contexts/credentials"
ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity"
"github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig"
"github.com/open-component-model/ocm/pkg/contexts/oci"
"github.com/open-component-model/ocm/pkg/contexts/ocm"
"github.com/open-component-model/ocm/pkg/errors"
)

func UsingCredentialsRepositories(cfg *helper.Config) error {
ctx := ocm.DefaultContext()
credctx := ctx.CredentialsContext()

// The OCM toolset embraces multiple storage
// backend technologies, for OCM meta data as well
// as for artifacts described by a component version.
// All those technologies typically have their own
// way to configure credentials for command line
// tools or servers.
//
// The credential management provides so-called
// credential repositories. Such a repository
// is able to provide any number of names
// credential sets. This way any special
// credential store can be connected to the
// OCM credential management jsu by providing
// an own implementation for the repository interface.

// One such case is the docker config json, a config
// file used by <code>docker login</code> to store
// credentials for dedicatd OCI regsitries.
dspec := dockerconfig.NewRepositorySpec("~/.docker/config.json")

// There are general credential stores, like a HashiCorp Vault
// or type-specific ones, like the docker config json
// used to configure credentials for the docker client.
// (working with OCI registries).
// Those specialized repository implementation are not only able to
// provide credential sets, they also know about the usage context.
// Such repository implementations are able to provide credential
// mappings for consumer ids, also.

// The docker config is such a case, so we can instruct the
// repository to automatically propagate appropriate the consumer id
// mappings.
dspec = dspec.WithConsumerPropagation(true)

// now we can jsut add the repository for this specification to
// the credential context.
_, err := credctx.RepositoryForSpec(dspec)
if err != nil {
return errors.Wrapf(err, "invalid credential repository")
}
// we are not interested in the repository object, so we just ignore
// the result.

// so, if you have done the appropriate docker login for your
// OCI registry, it should be possible now to get the credentials
// for the configured repository.
id, err := oci.GetConsumerIdForRef(cfg.Repository)
if err != nil {
return errors.Wrapf(err, "invalid consumer")
}

// the returned credentials are provided via an interface, which might change its
// content, if the underlying credential source changes.
creds, err := credentials.CredentialsForConsumer(credctx, id, ociidentity.IdentityMatcher)
if err != nil {
return errors.Wrapf(err, "no credentials")
}
// an error is only provided if something went wrong while determining
// the credentials. Delivering NO credentials is a valid result.
if creds == nil {
return fmt.Errorf("no credentials found")
}
fmt.Printf("credentials: %s\n", obfuscate(creds.Properties()))

return nil
}
21 changes: 21 additions & 0 deletions examples/lib/tour/03-working-with-credentials/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Working with Credentials

This tour illustrates the basic handling of credentials
using the OCM library. The library provides
an extensible framework to bring together credential providers
and credential cosunmers in a technology-agnostic way.

It covers four basic scenarios:
- [`basic`](01-using-credentials.go) Writing to a repository with directly specified credentials.
- [`generic`](02-basic-credential-management.go) Using credentials via the credential management.
- [`read`](02-basic-credential-management.go) Read the previously component version using the credential management.
- [`credrepo`](03-credential-repositories.go) Providing credentials via credential repositories.

You can just call the main program with some config file option (`--config <file>`) and the name of the scenario.
The config file should have the following content:

```yaml
repository: ghcr.io/mandelsoft/ocm
username:
password:
```
14 changes: 14 additions & 0 deletions examples/lib/tour/03-working-with-credentials/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/open-component-model/ocm/pkg/blobaccess"
"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/contexts/credentials"
"github.com/open-component-model/ocm/pkg/contexts/ocm"
"github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact"
"github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc"
Expand Down Expand Up @@ -278,3 +279,16 @@ func listVersions(repo ocm.Repository, list ...string) error {
}
return nil
}

func obfuscate(props common.Properties) string {
if pw, ok := props[credentials.ATTR_PASSWORD]; ok {
if len(pw) > 5 {
pw = pw[:5] + "***"
} else {
pw = "***"
}
props = props.Copy()
props[credentials.ATTR_PASSWORD] = pw
}
return props.String()
}
2 changes: 2 additions & 0 deletions examples/lib/tour/03-working-with-credentials/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ func main() {
err = UsingCredentialsB(cfg, true)
case "read":
err = UsingCredentialsB(cfg, false)
case "credrepo":
err = UsingCredentialsRepositories(cfg)
default:
err = fmt.Errorf("unknown example %q", cmd)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
"encoding/json"
"fmt"

"github.com/go-test/deep"
"github.com/open-component-model/ocm/examples/lib/helper"
"github.com/open-component-model/ocm/pkg/contexts/config"
"github.com/open-component-model/ocm/pkg/contexts/credentials"
credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config"
"github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds"
"github.com/open-component-model/ocm/pkg/contexts/oci"
"github.com/open-component-model/ocm/pkg/errors"
)

func BasicConfigurationHandling(cfg *helper.Config) error {
// configuration is handled by the configuration context.
ctx := config.DefaultContext()

// the configuration context handles configuration objects.
// a configuration object is any object implementing
// the config.Config interface.

// one such object is the configuration object for
// credentials.

creds := credcfg.New()

// here we can configure credential settings:
// credential repositories and consumer is mappings.
id, err := oci.GetConsumerIdForRef(cfg.Repository)
if err != nil {
return errors.Wrapf(err, "invalid consumer")
}
creds.AddConsumer(
id,
directcreds.NewRepositorySpec(cfg.GetCredentials().Properties()),
)

// credential objects are typically serializable and deserializable.

spec, err := json.MarshalIndent(creds, " ", " ")
if err != nil {
return errors.Wrapf(err, "marshal credential config")
}

fmt.Printf("this a a credential configuration object:\n%s\n", string(spec))

// like all the other maifest based description this format always includes
// a type field, which can be used to deserialize a specification into
// the appropriate object.
// This can ebe done by the config context. It accepts YAML or JSON.

o, err := ctx.GetConfigForData(spec, nil)
if err != nil {
return errors.Wrapf(err, "deserialize config")
}

if diff := deep.Equal(o, creds); len(diff) != 0 {
fmt.Printf("diff:\n%v\n", diff)
return fmt.Errorf("invalid des/erialization")
}

// regardless what variant is used (direct object or descriptor)
// the config object can be added to a config context.
err = ctx.ApplyConfig(creds, "explicit cred setting")
if err != nil {
return errors.Wrapf(err, "cannot apply config")
}

// Every config object implements the
// ApplyTo(ctx config.Context, target interface{}) error method.
// It takes an object, which wants to be configured.
// The config object then decides, whether it provides
// settings for the given object and calls the appropriate
// methods on this object (after a type cast).
//
// This way the config mechanism reverts the configuration
// request, it does not actively configure something, instead
// an object, which wants to be configured calls the config
// context to apply pending configs.
// The config context manages a queue of config objects
// and applys them to an object to be configured.

// If ask he credential context now for credentials,
// it asks the config context for pending config objects
// and apply them.
// Theregore, we now should the configured creentials, here.

credctx := credentials.DefaultContext()

found, err := credentials.CredentialsForConsumer(credctx, id)
if err != nil {
return errors.Wrapf(err, "cannot get credentials")
}
// an error is only provided if something went wrong while determining
// the credentials. Delivering NO credentials is a valid result.
if found == nil {
return fmt.Errorf("no credentials found")
}
fmt.Printf("consumer id: %s\n", id)
fmt.Printf("credentials: %s\n", obfuscate(found))

if found.GetProperty(credentials.ATTR_USERNAME) != cfg.Username {
return fmt.Errorf("password mismatch")
}
if found.GetProperty(credentials.ATTR_PASSWORD) != cfg.Password {
return fmt.Errorf("password mismatch")
}
return nil
}
Loading
Loading