Skip to content

Commit

Permalink
new(artifact/manifest): add manifest command
Browse files Browse the repository at this point in the history
It allows to fetch the manifest for a given artifact based on it's platform.

Signed-off-by: Aldo Lacuku <aldo@lacuku.eu>
  • Loading branch information
alacuku committed Nov 16, 2023
1 parent 5cfe057 commit f2858b4
Show file tree
Hide file tree
Showing 5 changed files with 452 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/falcosecurity/falcoctl/cmd/artifact/info"
"github.com/falcosecurity/falcoctl/cmd/artifact/install"
"github.com/falcosecurity/falcoctl/cmd/artifact/list"
"github.com/falcosecurity/falcoctl/cmd/artifact/manifest"
"github.com/falcosecurity/falcoctl/cmd/artifact/search"
"github.com/falcosecurity/falcoctl/internal/config"
"github.com/falcosecurity/falcoctl/pkg/index/cache"
Expand Down Expand Up @@ -71,6 +72,7 @@ func NewArtifactCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Comma
cmd.AddCommand(info.NewArtifactInfoCmd(ctx, opt))
cmd.AddCommand(follow.NewArtifactFollowCmd(ctx, opt))
cmd.AddCommand(artifactconfig.NewArtifactConfigCmd(ctx, opt))
cmd.AddCommand(manifest.NewArtifactManifestCmd(ctx, opt))

return cmd
}
17 changes: 17 additions & 0 deletions cmd/artifact/manifest/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package manifest defines the business logic to fetch manifest layer for artifacts.
package manifest
93 changes: 93 additions & 0 deletions cmd/artifact/manifest/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package manifest

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

"github.com/spf13/cobra"

ocipuller "github.com/falcosecurity/falcoctl/pkg/oci/puller"
ociutils "github.com/falcosecurity/falcoctl/pkg/oci/utils"
"github.com/falcosecurity/falcoctl/pkg/options"
)

type artifactManifestOptions struct {
*options.Common
*options.Registry
platform string
}

// NewArtifactManifestCmd returns the artifact manifest command.
func NewArtifactManifestCmd(ctx context.Context, opt *options.Common) *cobra.Command {
o := artifactManifestOptions{
Common: opt,
Registry: &options.Registry{},
}

cmd := &cobra.Command{
Use: "manifest [ref] [flags]",
Short: "Get the manifest layer of an artifact",
Long: "Get the manifest layer of an artifact",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return o.RunArtifactManifest(ctx, args)
},
}

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
}

func (o *artifactManifestOptions) RunArtifactManifest(ctx context.Context, args []string) error {
var (
puller *ocipuller.Puller
ref string
manifest []byte
err error
)

// Create puller with auto login enabled.
if puller, err = ociutils.Puller(o.PlainHTTP, o.Printer); err != nil {
return err
}

// Resolve the artifact reference.
if ref, err = o.IndexCache.ResolveReference(args[0]); err != nil {
return err
}

// 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 manifest, err = puller.RawManifest(ctx, ref, tokens[0], tokens[1]); err != nil {
return err
}

o.Printer.DefaultText.Println(string(manifest))

return nil
}
135 changes: 135 additions & 0 deletions cmd/artifact/manifest/manifest_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package manifest_test

import (
"context"
"fmt"
"net/http"
"testing"
"time"

"github.com/distribution/distribution/v3/configuration"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/spf13/cobra"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"

"github.com/falcosecurity/falcoctl/cmd"
"github.com/falcosecurity/falcoctl/pkg/oci"
"github.com/falcosecurity/falcoctl/pkg/oci/authn"
ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher"
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
testutils "github.com/falcosecurity/falcoctl/pkg/test"
)

var (
localRegistryHost string
localRegistry *remote.Registry
testRuleTarball = "../../../pkg/test/data/rules.tar.gz"
testPluginTarball = "../../../pkg/test/data/plugin.tar.gz"
testPluginPlatform1 = "linux/amd64"
testPluginPlatform2 = "windows/amd64"
testPluginPlatform3 = "linux/arm64"
ctx = context.Background()
pluginMultiPlatformRef string
rulesRef string
output = gbytes.NewBuffer()
rootCmd *cobra.Command
opt *commonoptions.Common
)

func TestManifest(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Manifest Suite")
}

var _ = BeforeSuite(func() {
var err error
config := &configuration.Configuration{}
// Get a free port to be used by the registry.
port, err := testutils.FreePort()
Expect(err).ToNot(HaveOccurred())
// Create the registry address to which will bind.
config.HTTP.Addr = fmt.Sprintf("localhost:%d", port)
localRegistryHost = config.HTTP.Addr

// Create the oras registry.
localRegistry, err = testutils.NewOrasRegistry(localRegistryHost, true)
Expect(err).ToNot(HaveOccurred())

// Start the local registry.
go func() {
err := testutils.StartRegistry(context.Background(), config)
Expect(err).ToNot(BeNil())
}()

// Check that the registry is up and accepting connections.
Eventually(func(g Gomega) error {
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
return err
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())

// Initialize options for command.
opt = commonoptions.NewOptions()
opt.Initialize(commonoptions.WithWriter(output))

// Push the artifacts to the registry.
// Same artifacts will be used to test the puller code.
pusher := ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), true, nil)

// Push plugin artifact with multiple architectures.
filePathsAndPlatforms := ocipusher.WithFilepathsAndPlatforms([]string{testPluginTarball, testPluginTarball, testPluginTarball},
[]string{testPluginPlatform1, testPluginPlatform2, testPluginPlatform3})
pluginMultiPlatformRef = localRegistryHost + "/plugins:multiplatform"
artConfig := oci.ArtifactConfig{}
Expect(artConfig.ParseDependencies("my-dep:1.2.3|my-alt-dep:1.4.5")).ToNot(HaveOccurred())
Expect(artConfig.ParseRequirements("my-req:7.8.9")).ToNot(HaveOccurred())
artifactConfig := ocipusher.WithArtifactConfig(artConfig)

// Build options slice.
options := []ocipusher.Option{filePathsAndPlatforms, artifactConfig}

// Push the plugin artifact.
_, err = pusher.Push(ctx, oci.Plugin, pluginMultiPlatformRef, options...)
Expect(err).ShouldNot(HaveOccurred())

// Prepare and push artifact without config layer.
filePaths := ocipusher.WithFilepaths([]string{testRuleTarball})
artConfig = oci.ArtifactConfig{}
Expect(artConfig.ParseDependencies("dep1:1.2.3", "dep2:2.3.1")).ToNot(HaveOccurred())
options = []ocipusher.Option{
filePaths,
ocipusher.WithTags("latest"),
}

// Push a rulesfile artifact
options = append(options, ocipusher.WithArtifactConfig(artConfig))
rulesRef = localRegistryHost + "/rulesfiles:regular"
_, err = pusher.Push(ctx, oci.Rulesfile, rulesRef, options...)
Expect(err).ShouldNot(HaveOccurred())
})

func executeRoot(args []string) error {
rootCmd.SetArgs(args)
rootCmd.SetOut(output)
return cmd.Execute(rootCmd, opt)
}

0 comments on commit f2858b4

Please sign in to comment.