Skip to content

Commit

Permalink
feat(artifact/config): fetch config layer for a specific platform
Browse files Browse the repository at this point in the history
Signed-off-by: Aldo Lacuku <[email protected]>
  • Loading branch information
alacuku committed Nov 16, 2023
1 parent cdccbeb commit 4594749
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 29 deletions.
15 changes: 14 additions & 1 deletion cmd/artifact/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ package config

import (
"context"
"fmt"
"runtime"
"strings"

"github.com/spf13/cobra"

Expand All @@ -28,6 +31,7 @@ import (
type artifactConfigOptions struct {
*options.Common
*options.Registry
platform string
}

// NewArtifactConfigCmd returns the artifact config command.
Expand All @@ -48,6 +52,8 @@ func NewArtifactConfigCmd(ctx context.Context, opt *options.Common) *cobra.Comma
}

o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
"os and architecture of the artifact in OS/ARCH format")

return cmd
}
Expand All @@ -70,7 +76,14 @@ func (o *artifactConfigOptions) RunArtifactConfig(ctx context.Context, args []st
return err
}

if config, err = puller.PullConfigLayer(ctx, ref); err != nil {
// TODO: implement two new flags (platforms, platform) based on the oci platform struct.
// Split the platform.
tokens := strings.Split(o.platform, "/")
if len(tokens) != 2 {
return fmt.Errorf("invalid platform format: %s", o.platform)
}

if config, err = puller.PullConfigLayer(ctx, ref, tokens[0], tokens[1]); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/artifact/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ var _ = Describe("Config", func() {
args = []string{artifactCmd, configCmd, "noregistry/noartifact", plaingHTTP, configFlag, configDir}
})

assertFailedBehavior(usage, "ERROR unable to fetch reference")
assertFailedBehavior(usage, "ERROR unable to get manifest: unable to fetch reference")
})

When("non existing repository", func() {
Expand Down
4 changes: 2 additions & 2 deletions cmd/artifact/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
return nil, err
}

artifactConfig, err := puller.GetArtifactConfig(ctx, ref)
artifactConfig, err := puller.GetArtifactConfig(ctx, ref, runtime.GOOS, runtime.GOARCH)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -265,7 +265,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []

logger.Info("Preparing to pull artifact", logger.Args("ref", ref))

if err := puller.CheckAllowedType(ctx, ref, o.allowedTypes.Types); err != nil {
if err := puller.CheckAllowedType(ctx, ref, runtime.GOOS, runtime.GOARCH, o.allowedTypes.Types); err != nil {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/artifact/install/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ var artifactInstallTests = Describe("install", func() {
Expect(err).To(BeNil())
args = []string{artifactCmd, installCmd, "noregistry/testrules", "--plain-http", "--config", configFile}
})
installAssertFailedBehavior(artifactInstallUsage, `ERROR unable to fetch reference`)
installAssertFailedBehavior(artifactInstallUsage, `ERROR unable to get manifest: unable to fetch reference`)
})

When("invalid repository", func() {
Expand All @@ -193,7 +193,7 @@ var artifactInstallTests = Describe("install", func() {
Expect(err).To(BeNil())
args = []string{artifactCmd, installCmd, newReg, "--plain-http", "--config", configFile}
})
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERROR unable to fetch reference %q", newReg))
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERROR unable to get manifest: unable to fetch reference %q", newReg))
})

When("with disallowed types (rulesfile)", func() {
Expand Down
4 changes: 2 additions & 2 deletions internal/follower/follower.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (f *Follower) follow(ctx context.Context) {
f.logger.Info("Found new artifact version", f.logger.Args("followerName", f.ref, "tag", f.tag))

// Pull config layer to check falco versions
artifactConfig, err := f.GetArtifactConfig(ctx, f.ref)
artifactConfig, err := f.GetArtifactConfig(ctx, f.ref, runtime.GOOS, runtime.GOARCH)
if err != nil {
f.logger.Error("Unable to pull config layer", f.logger.Args("followerName", f.ref, "reason", err.Error()))
return
Expand Down Expand Up @@ -252,7 +252,7 @@ func (f *Follower) follow(ctx context.Context) {
// pull downloads, extracts, and installs the artifact.
func (f *Follower) pull(ctx context.Context) (filePaths []string, res *oci.RegistryResult, err error) {
f.logger.Debug("Check if pulling an allowed type of artifact", f.logger.Args("followerName", f.ref))
if err := f.Puller.CheckAllowedType(ctx, f.ref, f.Config.AllowedTypes.Types); err != nil {
if err := f.Puller.CheckAllowedType(ctx, f.ref, runtime.GOOS, runtime.GOARCH, f.Config.AllowedTypes.Types); err != nil {
return nil, nil, err
}

Expand Down
55 changes: 36 additions & 19 deletions pkg/oci/puller/puller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"io"
"runtime"

v1 "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
Expand Down Expand Up @@ -161,8 +160,28 @@ func manifestFromDesc(ctx context.Context, target oras.Target, desc *v1.Descript
return &manifest, nil
}

// manifestFromRef retieves the manifest of an artifact, also taking care of resolving to it walking through indexes.
func (p *Puller) manifestFromRef(ctx context.Context, ref string) (*v1.Manifest, error) {
// manifest retieves the manifest of an artifact, also taking care of resolving to it walking through indexes.
// If the artifact has a v1.MediaTypeImageIndex descriptor then it fetches the manifest for the
// specified platform.
func (p *Puller) manifest(ctx context.Context, ref, os, arch string) (*v1.Manifest, error) {
var manifest v1.Manifest

manifestBytes, err := p.RawManifest(ctx, ref, os, arch)
if err != nil {
return nil, fmt.Errorf("unable to get manifest: %w", err)
}

if err = json.Unmarshal(manifestBytes, &manifest); err != nil {
return nil, fmt.Errorf("unable to unmarshal manifest: %w", err)
}

return &manifest, nil
}

// RawManifest fetches the manifest layer from a given reference.
// If the artifact has a v1.MediaTypeImageIndex descriptor then it fetches the manifest for the
// specified platform.
func (p *Puller) RawManifest(ctx context.Context, ref, os, arch string) ([]byte, error) {
repo, err := repository.NewRepository(ref, repository.WithClient(p.Client), repository.WithPlainHTTP(p.plainHTTP))
if err != nil {
return nil, err
Expand All @@ -188,19 +207,18 @@ func (p *Puller) manifestFromRef(ctx context.Context, ref string) (*v1.Manifest,
return nil, fmt.Errorf("unable to unmarshal manifest: %w", err)
}

// todo: decide if goos or arch should be passed to this function
found := false
for _, manifest := range index.Manifests {
if manifest.Platform.OS == runtime.GOOS &&
manifest.Platform.Architecture == runtime.GOARCH {
if manifest.Platform.OS == os &&
manifest.Platform.Architecture == arch {
desc = manifest
found = true
break
}
}

if !found {
return nil, fmt.Errorf("unable to find a manifest matching the given platform: %s %s", runtime.GOOS, runtime.GOARCH)
return nil, fmt.Errorf("unable to find a manifest matching the given platform: %s/%s", os, arch)
}

manifestReader, err = repo.Fetch(ctx, desc)
Expand All @@ -209,22 +227,19 @@ func (p *Puller) manifestFromRef(ctx context.Context, ref string) (*v1.Manifest,
}
}

var manifest v1.Manifest
manifestBytes, err := io.ReadAll(manifestReader)
if err != nil {
return nil, fmt.Errorf("unable to read bytes from manifest reader for ref %q: %w", ref, err)
}

if err = json.Unmarshal(manifestBytes, &manifest); err != nil {
return nil, fmt.Errorf("unable to unmarshal manifest: %w", err)
}

return &manifest, nil
return manifestBytes, nil
}

// GetArtifactConfig fetches only the config layer from a given ref.
func (p *Puller) GetArtifactConfig(ctx context.Context, ref string) (*oci.ArtifactConfig, error) {
configBytes, err := p.PullConfigLayer(ctx, ref)
// If the artifact has a v1.MediaTypeImageIndex descriptor then it fetches the config layer for the
// specified platform.
func (p *Puller) GetArtifactConfig(ctx context.Context, ref, os, arch string) (*oci.ArtifactConfig, error) {
configBytes, err := p.PullConfigLayer(ctx, ref, os, arch)
if err != nil {
return nil, err
}
Expand All @@ -238,13 +253,15 @@ func (p *Puller) GetArtifactConfig(ctx context.Context, ref string) (*oci.Artifa
}

// PullConfigLayer fetches only the config layer from a given ref.
func (p *Puller) PullConfigLayer(ctx context.Context, ref string) ([]byte, error) {
// If the artifact has a v1.MediaTypeImageIndex descriptor then it fetches the config layer for the
// specified platform.
func (p *Puller) PullConfigLayer(ctx context.Context, ref, os, arch string) ([]byte, error) {
repo, err := repository.NewRepository(ref, repository.WithClient(p.Client), repository.WithPlainHTTP(p.plainHTTP))
if err != nil {
return nil, err
}

manifest, err := p.manifestFromRef(ctx, ref)
manifest, err := p.manifest(ctx, ref, os, arch)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -276,12 +293,12 @@ func (p *Puller) PullConfigLayer(ctx context.Context, ref string) ([]byte, error
// CheckAllowedType does a preliminary check on the manifest to state whether we are allowed
// or not to download this type of artifact. If allowedTypes is empty, everything is allowed,
// else it is used to perform the check.
func (p *Puller) CheckAllowedType(ctx context.Context, ref string, allowedTypes []oci.ArtifactType) error {
func (p *Puller) CheckAllowedType(ctx context.Context, ref, os, arch string, allowedTypes []oci.ArtifactType) error {
if len(allowedTypes) == 0 {
return nil
}

manifest, err := p.manifestFromRef(ctx, ref)
manifest, err := p.manifest(ctx, ref, os, arch)
if err != nil {
return err
}
Expand Down
54 changes: 52 additions & 2 deletions pkg/oci/puller/puller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -146,17 +147,21 @@ var _ = Describe("Puller", func() {
Context("PullConfigLayer func", func() {
var (
ref string
os string
arch string
cfgLayer []byte
err error
)
JustBeforeEach(func() {
puller = ocipuller.NewPuller(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker)
cfgLayer, err = puller.PullConfigLayer(ctx, ref)
cfgLayer, err = puller.PullConfigLayer(ctx, ref, os, arch)
})

JustAfterEach(func() {
cfgLayer = nil
err = nil
os = ""
arch = ""
})

When("Artifact does not exist", func() {
Expand All @@ -173,6 +178,9 @@ var _ = Describe("Puller", func() {
When("config layer is set", func() {
BeforeEach(func() {
ref = pluginMultiPlatformRef
tokens := strings.Split(testPluginPlatform1, "/")
os = tokens[0]
arch = tokens[1]
})

It("should get config layer", func() {
Expand All @@ -191,6 +199,48 @@ var _ = Describe("Puller", func() {
Expect(string(cfgLayer)).Should(Equal("{}"))
})
})

When("config layer for linux/arm64", func() {
BeforeEach(func() {
ref = pluginMultiPlatformRef
tokens := strings.Split(testPluginPlatform1, "/")
os = tokens[0]
arch = tokens[1]
})

It("should get config layer", func() {
Expect(err).ShouldNot(HaveOccurred())
Expect(cfgLayer).ShouldNot(BeNil())
})
})

When("config layer artifact without platform", func() {
BeforeEach(func() {
ref = rulesRef
tokens := strings.Split(testPluginPlatform1, "/")
os = tokens[0]
arch = tokens[1]
})

It("should get config layer", func() {
Expect(err).ShouldNot(HaveOccurred())
Expect(cfgLayer).ShouldNot(BeNil())
})
})

When("config layer for non existing platform", func() {
BeforeEach(func() {
ref = pluginMultiPlatformRef
os = "linux"
arch = "non-existing"
})

It("should error", func() {
Expect(err).Should(HaveOccurred())
Expect(cfgLayer).Should(BeNil())
})
})

})

Context("Descriptor func", func() {
Expand Down Expand Up @@ -251,7 +301,7 @@ var _ = Describe("Puller", func() {
)
JustBeforeEach(func() {
puller = ocipuller.NewPuller(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), plainHTTP, tracker)
err = puller.CheckAllowedType(ctx, ref, allowedTypes)
err = puller.CheckAllowedType(ctx, ref, runtime.GOOS, runtime.GOARCH, allowedTypes)
})

JustAfterEach(func() {
Expand Down

0 comments on commit 4594749

Please sign in to comment.