diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 1f519b6c..97667cc0 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -21,6 +21,12 @@ jobs: exclude: - goarch: arm64 goos: windows + include: + - goarch: arm64 + goos: linux + cc: aarch64-linux-gnu-gcc + cgo: 1 + fail-fast: false steps: - name: Checkout commit uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -33,13 +39,17 @@ jobs: go-version: '1.21' check-latest: true + - name: Install gcc for cross-compiling + run: sudo apt update && sudo apt install gcc-aarch64-linux-gnu + - name: Build Falcoctl run: > go build -ldflags="-s -w" -o falcoctl-${{ matrix.goos }}-${{ matrix.goarch }} . env: - CGO_ENABLED: 0 + CGO_ENABLED: ${{ matrix.cgo }} GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} + CC: ${{ matrix.cc }} - name: Create Archives run: | diff --git a/cmd/registry/push/pluginLinux.go b/cmd/registry/push/pluginLinux.go new file mode 100644 index 00000000..b6481feb --- /dev/null +++ b/cmd/registry/push/pluginLinux.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2024 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. +// + +//go:build linux + +package push + +import ( + "fmt" + "runtime" + + "github.com/falcosecurity/plugin-sdk-go/pkg/loader" + "github.com/pterm/pterm" + + "github.com/falcosecurity/falcoctl/pkg/oci" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +func pluginConfigLayer(logger *pterm.Logger, filePath, platform string, artifactOptions *options.Artifact) (*oci.ArtifactConfig, error) { + config := &oci.ArtifactConfig{ + Name: artifactOptions.Name, + Version: artifactOptions.Version, + } + + // Parse the requirements. + // Check if the user has provided any. + if len(artifactOptions.Requirements) != 0 { + logger.Info("Requirements provided by user", logger.Args("plugin", filePath)) + if err := config.ParseRequirements(artifactOptions.Requirements...); err != nil { + return nil, err + } + } else { + logger.Info("Parsing requirements from: ", logger.Args("plugin", filePath)) + sysPlatform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) + // If no user provided requirements then try to parse them from the plugin. + if platform != sysPlatform { + logger.Info("Skipping, incompatible platform", logger.Args("plugin", platform, "current system", sysPlatform)) + return nil, nil + } + req, err := pluginRequirement(filePath) + if err != nil { + return nil, err + } + config.SetRequirement(req.Name, req.Version) + } + + return config, nil +} + +// pluginRequirement given a plugin as a shared library it loads it and gets the api version +// required by the plugin. +func pluginRequirement(filePath string) (*oci.ArtifactRequirement, error) { + plugin, err := loader.NewPlugin(filePath) + if err != nil { + return nil, fmt.Errorf("unable to open plugin %q: %w", filePath, err) + } + + return &oci.ArtifactRequirement{ + Name: pluginRequirementKey, + Version: plugin.Info().RequiredAPIVersion, + }, nil +} diff --git a/cmd/registry/push/pluginOthers.go b/cmd/registry/push/pluginOthers.go new file mode 100644 index 00000000..e6dbf176 --- /dev/null +++ b/cmd/registry/push/pluginOthers.go @@ -0,0 +1,46 @@ +// Copyright (C) 2024 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. +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build !linux + +package push + +import ( + "github.com/pterm/pterm" + + "github.com/falcosecurity/falcoctl/pkg/oci" + "github.com/falcosecurity/falcoctl/pkg/options" +) + +func pluginConfigLayer(logger *pterm.Logger, filePath, platform string, artifactOptions *options.Artifact) (*oci.ArtifactConfig, error) { + config := &oci.ArtifactConfig{ + Name: artifactOptions.Name, + Version: artifactOptions.Version, + } + + // Parse the requirements. + // Check if the user has provided any. + if len(artifactOptions.Requirements) != 0 { + logger.Info("Requirements provided by user", logger.Args("plugin", filePath)) + if err := config.ParseRequirements(artifactOptions.Requirements...); err != nil { + return nil, err + } + } else { + logger.Warn("Not going to parse requirements from plugin since the falcoctl build is not for linux") + } + + return config, nil +} diff --git a/cmd/registry/push/push.go b/cmd/registry/push/push.go index 65dcff06..c1b5ee87 100644 --- a/cmd/registry/push/push.go +++ b/cmd/registry/push/push.go @@ -169,8 +169,18 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error { if config, err = rulesConfigLayer(o.Printer.Logger, p, o.Artifact); err != nil { return err } + } else if o.ArtifactType == oci.Plugin { + var cfg *oci.ArtifactConfig + if cfg, err = pluginConfigLayer(o.Printer.Logger, p, o.Platforms[i], o.Artifact); err != nil { + return err + } + // This check is to prevent to overwrite the configuration with nil value when multiple platform plugin + // is processed. + if cfg != nil { + config = cfg + } } - path, err := utils.CreateTarGzArchive(p) + path, err := utils.CreateTarGzArchive("", p) if err != nil { return err } @@ -218,11 +228,14 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error { } const ( + // depsKey is the key for deps in the rulesfiles. depsKey = "required_plugin_versions" // engineKey is the key in the rulesfiles. engineKey = "required_engine_version" // engineRequirementKey is used as name for the engine requirement in the config layer for the rulesfile artifacts. engineRequirementKey = "engine_version_semver" + // pluginRequirementKey is used as name for the plugin api version requirement in the config layer for the plugin artifacts. + pluginRequirementKey = "plugin_api_version" ) func rulesConfigLayer(logger *pterm.Logger, filePath string, artifactOptions *options.Artifact) (*oci.ArtifactConfig, error) { @@ -243,10 +256,10 @@ func rulesConfigLayer(logger *pterm.Logger, filePath string, artifactOptions *op return nil, fmt.Errorf("unable to unmarshal rulesfile %s: %w", filePath, err) } - // Parse the plugin dependency. + // Parse the artifact dependencies. // Check if the user has provided any. if len(artifactOptions.Dependencies) != 0 { - logger.Info("Dependencies provided by user") + logger.Info("Dependencies provided by user", logger.Args("rulesfile", filePath)) if err = config.ParseDependencies(artifactOptions.Dependencies...); err != nil { return nil, err } diff --git a/cmd/registry/push/push_plugins_test.go b/cmd/registry/push/push_plugins_test.go new file mode 100644 index 00000000..63793d58 --- /dev/null +++ b/cmd/registry/push/push_plugins_test.go @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2024 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 push_test + +// revive:disable + +import ( + "fmt" + "github.com/falcosecurity/falcoctl/internal/utils" + "os" + "path/filepath" + "regexp" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/falcosecurity/falcoctl/cmd" + "github.com/falcosecurity/falcoctl/pkg/oci" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +// revive:enable +var _ = Describe("pushing plugins", func() { + var ( + registryCmd = "registry" + pushCmd = "push" + version = "1.1.1" + // fullRepoName is set each time before each test. + fullRepoName string + // repoName same as fullRepoName. + repoName string + // It is set in the config layer. + artifactNameInConfigLayer = "test-push-plugins" + pushedTags = []string{"tag1", "tag2", "latest"} + + // Plugin's platforms. + platformARM64 = "linux/arm64" + platformAMD64 = "linux/amd64" + + // Paths pointing to plugins that will be pushed. + // Some of the functions expect these two variable to be set to valid paths. + // They are set in beforeEach blocks by tests that need them. + pluginOne string + pluginTwo string + // Data fetched from registry and used for assertions. + pluginData *testutils.PluginArtifact + ) + + const ( + // Used as flags for all the test cases. + requirement = "plugin_api_version:3.2.1" + anSource = "myrepo.com/rules.git" + pluginsRepoBaseName = "push-plugins-tests" + ) + + var AssertSuccessBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string, platforms []string) { + It("should succeed", func() { + // We do not check the error here since we are checking it after + // pushing the artifact. + By("checking no error in output") + Expect(output).ShouldNot(gbytes.Say("ERROR")) + Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir")) + + By("checking descriptor") + Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex)) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String()))) + + By("checking index") + Expect(pluginData.Index.Manifests).Should(HaveLen(len(platforms))) + + By("checking platforms") + for _, p := range platforms { + Expect(pluginData.Platforms).Should(HaveKey(p)) + } + + By("checking config layers") + for plat, p := range pluginData.Platforms { + By(fmt.Sprintf("platform %s", plat)) + Expect(p.Config.Version).Should(Equal(version)) + Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer)) + + By("checking dependencies") + Expect(p.Config.Dependencies).Should(HaveLen(len(deps))) + for _, dep := range deps { + Expect(p.Config.Dependencies).Should(ContainElement(dep)) + } + + By("checking requirements") + Expect(p.Config.Requirements).Should(HaveLen(len(reqs))) + for _, req := range reqs { + Expect(p.Config.Requirements).Should(ContainElement(req)) + } + + By("checking annotations") + // The creation timestamp is always present. + Expect(p.Manifest.Annotations).Should(HaveLen(len(annotations) + 1)) + for key, val := range annotations { + Expect(p.Manifest.Annotations).Should(HaveKeyWithValue(key, val)) + } + } + + By("checking tags") + Expect(pluginData.Tags).Should(HaveLen(len(pushedTags))) + Expect(pluginData.Tags).Should(ContainElements(pushedTags)) + + By("checking that temporary dirs have been removed") + + Eventually(func() bool { + entries, err := os.ReadDir("/tmp") + Expect(err).ShouldNot(HaveOccurred()) + for _, e := range entries { + if e.IsDir() { + matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name())) + Expect(err).ShouldNot(HaveOccurred()) + if matched { + return true + } + } + } + return false + }).WithTimeout(5 * time.Second).Should(BeFalse()) + }) + } + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + // Reset the status after each test. + // This variable could be changed by single tests. + // Make sure to set them at their default values. + Expect(output.Clear()).ShouldNot(HaveOccurred()) + artifactNameInConfigLayer = "test-plugin" + pushedTags = []string{"tag1", "tag2", "latest"} + pluginOne = "" + pluginTwo = "" + }) + + Context("success", func() { + JustBeforeEach(func() { + // Check the returned error before proceeding. + Expect(err).ShouldNot(HaveOccurred()) + pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) + Expect(err).ShouldNot(HaveOccurred()) + }) + + When("two platforms, shared objects", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = pluginAMD64SO + pluginTwo = pluginARM64SO + + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, pluginTwo, "--type", "plugin", "--platform", + platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile, + "--plain-http", "--depends-on", "my-test:4.3.2", "--requires", requirement, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + // We expect to succeed: + // deps should be the one provided by the cli flags. + AssertSuccessBehaviour([]oci.ArtifactDependency{{ + Name: "my-test", + Version: "4.3.2", + Alternatives: nil, + }}, []oci.ArtifactRequirement{ + { + Name: "plugin_api_version", + Version: "3.2.1", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformAMD64, platformARM64, + }) + }) + + When("two platforms, shared object + tgz", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = pluginAMD64SO + pluginTwo = plugintgz + + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, pluginTwo, "--type", "plugin", "--platform", + platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile, + "--plain-http", "--depends-on", "my-test:4.3.2", "--requires", requirement, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccessBehaviour([]oci.ArtifactDependency{{ + Name: "my-test", + Version: "4.3.2", + Alternatives: nil, + }}, []oci.ArtifactRequirement{ + { + Name: "plugin_api_version", + Version: "3.2.1", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformAMD64, platformARM64, + }) + }) + + When("one platform, shared object, no reqs", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = pluginAMD64SO + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, "--type", "plugin", "--platform", + platformAMD64, "--version", version, "--config", configFile, + "--plain-http", "--depends-on", "my-test:4.3.2", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + // We expect to succeed and that the requirement is extracted by the plugin itself + AssertSuccessBehaviour([]oci.ArtifactDependency{{ + Name: "my-test", + Version: "4.3.2", + Alternatives: nil, + }}, []oci.ArtifactRequirement{ + { + Name: "plugin_api_version", + Version: "3.1.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformAMD64, + }) + }) + + When("one platform, shared object, no reqs, no plugin of same platform", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = pluginARM64SO + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, "--type", "plugin", "--platform", + platformARM64, "--version", version, "--config", configFile, + "--plain-http", "--depends-on", "my-test:4.3.2", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + // We expect to succeed and that the requirement is not set: + // 1: no user provided; + // 2: plugin can not be loaded since is of different platform than the one where we are running. + AssertSuccessBehaviour([]oci.ArtifactDependency{{ + Name: "my-test", + Version: "4.3.2", + Alternatives: nil, + }}, []oci.ArtifactRequirement{}, + map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformARM64, + }) + }) + + When("two platforms, shared object + tgz, no reqs, tgz + no plugin of same platform, no deps", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = plugintgz + pluginTwo = pluginARM64SO + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, pluginTwo, "--type", "plugin", "--platform", + platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + // We expect to succeed and that the requirement is not set: + // 1: no user provided; + // 2: plugin can not be loaded since is of different platform than the one where we are running. + // 3: the other plugin is in tgz format cannot be loaded. + AssertSuccessBehaviour([]oci.ArtifactDependency{}, + []oci.ArtifactRequirement{}, + map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformARM64, + platformAMD64, + }) + }) + }) + + Context("failure", func() { + When("one platform, shared object, no reqs, unable to load plugin", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = pluginARM64SO + args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, "--type", "plugin", "--platform", + platformAMD64, "--version", version, "--config", configFile, + "--plain-http", "--depends-on", "my-test:4.3.2", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + It("expect to fail since cannot load the shared object but the platform is the same", func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(Equal("unable to open plugin \"../../../pkg/test/data/libk8smeta-arm64.so\":" + + " can't load plugin dynamic library: ../../../pkg/test/data/libk8smeta-arm64.so: cannot open shared object file: No such file or directory")) + }) + }) + }) +}) diff --git a/cmd/registry/push/push_rulesfiles_test.go b/cmd/registry/push/push_rulesfiles_test.go new file mode 100644 index 00000000..5eb90ebb --- /dev/null +++ b/cmd/registry/push/push_rulesfiles_test.go @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2024 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 push_test + +// revive:disable + +import ( + "fmt" + "github.com/falcosecurity/falcoctl/internal/utils" + "os" + "path/filepath" + "regexp" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/falcosecurity/falcoctl/cmd" + "github.com/falcosecurity/falcoctl/pkg/oci" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +// revive:enable + +var rulesfiles = Describe("pushing rulesfiles", func() { + var ( + registryCmd = "registry" + pushCmd = "push" + version = "1.1.1" + // registry/rulesRepoBaseName-randomInt + fullRepoName string + // rulesRepoBaseName-randomInt + repoName string + // It is set in the config layer. + artifactNameInConfigLayer = "test-rulesfile" + pushedTags = []string{"tag1", "tag2", "latest"} + + // Variables passed as arguments to the push command. Each test case updates them + // to point to the file on disk living in pkg/test/data. + rulesfile string + + // Data fetched from registry and used for assertions. + rulesfileData *testutils.RulesfileArtifact + ) + + const ( + // Used as flags for all the test cases. + dep1 = "myplugin:1.2.3" + dep2 = "myplugin1:1.2.3|otherplugin:3.2.1" + req = "engine_version_semver:0.37.0" + anSource = "myrepo.com/rules.git" + rulesRepoBaseName = "push-rulesfile" + ) + + // We keep it inside the success context since need the variables of this context. + var AssertSuccesBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string) { + It("should succeed", func() { + // We do not check the error here since we are checking it after + // pushing the artifact. + By("checking no error in output") + Expect(output).ShouldNot(gbytes.Say("ERROR")) + Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir")) + + By("checking descriptor") + Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest)) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String()))) + + By("checking manifest") + Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1)) + + By("checking platforms") + Expect(rulesfileData.Descriptor.Platform).Should(BeNil()) + + By("checking config layer") + Expect(rulesfileData.Layer.Config.Version).Should(Equal(version)) + Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer)) + + By("checking dependencies") + Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(len(deps))) + for _, dep := range deps { + Expect(rulesfileData.Layer.Config.Dependencies).Should(ContainElement(dep)) + } + + By("checking requirements") + Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(len(reqs))) + for _, req := range reqs { + Expect(rulesfileData.Layer.Config.Requirements).Should(ContainElement(req)) + } + + By("checking annotations") + // The creation timestamp is always present. + Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveLen(len(annotations) + 1)) + for key, val := range annotations { + Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue(key, val)) + } + + By("checking tags") + Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags))) + Expect(rulesfileData.Tags).Should(ContainElements(pushedTags)) + + By("checking that temporary dirs have been removed") + Eventually(func() bool { + entries, err := os.ReadDir("/tmp") + Expect(err).ShouldNot(HaveOccurred()) + for _, e := range entries { + if e.IsDir() { + matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name())) + Expect(err).ShouldNot(HaveOccurred()) + if matched { + fmt.Println(e.Name()) + return true + } + } + } + return false + }).WithTimeout(5 * time.Second).Should(BeFalse()) + }) + } + + // Each test gets its own root command and runs it. + // The err variable is asserted by each test. + JustBeforeEach(func() { + rootCmd = cmd.New(ctx, opt) + err = executeRoot(args) + }) + + JustAfterEach(func() { + Expect(output.Clear()).ShouldNot(HaveOccurred()) + // This variable could be changed by single tests. + // Make sure to set them at their default values. + artifactNameInConfigLayer = "test-rulesfile" + pushedTags = []string{"tag1", "tag2", "latest"} + rulesfile = "" + }) + + Context("success", func() { + // Here we are testing all the success cases for the push command. The artifact type used here is of type + // rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin + // artifacts. So we are testing that common logic only once, and are doing it here. + + JustBeforeEach(func() { + // This runs after the push command, so check the returned error before proceeding. + Expect(err).ShouldNot(HaveOccurred()) + rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) + Expect(err).ShouldNot(HaveOccurred()) + }) + + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + }) + + When("with full flags and args", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("no --name flag provided", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]} + // Set name to the expected one. + artifactNameInConfigLayer = repoName + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("no --annotation-source provided", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{}) + }) + + When("no --tags provided", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, + "--name", artifactNameInConfigLayer} + // We expect that latest tag is pushed, so set it in the pushed tags. + pushedTags = []string{"latest"} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("no --depends-on flag provided", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{}, + []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("no --requires flag provided", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{}, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("only required flags", func() { + BeforeEach(func() { + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http"} + // Set name to the expected one. + artifactNameInConfigLayer = repoName + // We expect that latest tag is pushed, so set it in the pushed tags. + pushedTags = []string{"latest"} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{}, + []oci.ArtifactRequirement{}, + map[string]string{}) + }) + + When("with full flags and args but in tar.gz format", func() { + BeforeEach(func() { + rulesfile = rulesfiletgz + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + Context("rulesfile deps and requirements", func() { + When("user provided deps", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + rulesfile = rulesFileWithDepsAndReq + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "myplugin", + Version: "1.2.3", + Alternatives: nil, + }, { + Name: "myplugin1", + Version: "1.2.3", + Alternatives: []oci.Dependency{{ + Name: "otherplugin", + Version: "3.2.1", + }, + }, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("parsed from file deps", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + rulesfile = rulesFileWithDepsAndReq + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "cloudtrail", + Version: "0.2.3", + Alternatives: nil, + }, { + Name: "json", + Version: "0.2.2", + Alternatives: nil, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.10.0", + }, + }, + map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("parsed from file deps with alternatives", func() { + var data = ` +- required_plugin_versions: + - name: k8saudit + version: 0.7.0 + alternatives: + - name: k8saudit-eks + version: 0.4.0 + - name: json + version: 0.7.0 +` + + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + tmpDir := GinkgoT().TempDir() + rulesfile, err = testutils.WriteToTmpFile(data, tmpDir) + Expect(err).ToNot(HaveOccurred()) + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "json", + Version: "0.7.0", + Alternatives: nil, + }, { + Name: "k8saudit", + Version: "0.7.0", + Alternatives: []oci.Dependency{{ + Name: "k8saudit-eks", + Version: "0.4.0", + }, + }, + }, + }, []oci.ArtifactRequirement{}, + map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("no deps at all", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + rulesfile = rulesfileyaml + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{}, + map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("user provided requirement", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + rulesfile = rulesFileWithDepsAndReq + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--requires", req, "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "json", + Version: "0.2.2", + Alternatives: nil, + }, { + Name: "cloudtrail", + Version: "0.2.3", + Alternatives: nil, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.37.0", + }, + }, + map[string]string{ + "org.opencontainers.image.source": anSource, + }) + It("reqs should be the ones provided by the user", func() { + Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name, + rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req)) + }) + }) + + When("requirement parsed from file in semver format", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + rulesfile = rulesFileWithDepsAndReq + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{ + { + Name: "json", + Version: "0.2.2", + Alternatives: nil, + }, { + Name: "cloudtrail", + Version: "0.2.3", + Alternatives: nil, + }, + }, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.10.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + + When("requirement parsed from file in int format", func() { + var rulesfileContent = ` +- required_engine_version: 10 +` + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + tmpDir := GinkgoT().TempDir() + rulesfile, err = testutils.WriteToTmpFile(rulesfileContent, tmpDir) + Expect(err).ToNot(HaveOccurred()) + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{ + { + Name: "engine_version_semver", + Version: "0.10.0", + }, + }, map[string]string{ + "org.opencontainers.image.source": anSource, + }) + }) + }) + }) + + Context("failure", func() { + When("requirement parsed from file -- invalid format (float)", func() { + var rulesFile = ` +- required_engine_version: 10.0 +` + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + tmpDir := GinkgoT().TempDir() + rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir) + Expect(err).ToNot(HaveOccurred()) + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + }) + + It("should fail", func() { + Expect(err).Should(HaveOccurred()) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta("required_engine_version must be an int or a string respecting " + + "the semver specification, got type float64"))) + }) + }) + + When("requirement parsed from file -- invalid format (not semver)", func() { + var rulesFile = ` +- required_engine_version: 10.0notsemver +` + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) + tmpDir := GinkgoT().TempDir() + rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir) + Expect(err).ToNot(HaveOccurred()) + args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, + "--plain-http", "--annotation-source", anSource, + "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} + // Set name to the expected one. + artifactNameInConfigLayer = repoName + // We expect that latest tag is pushed, so set it in the pushed tags. + pushedTags = []string{"latest"} + }) + + It("reqs should be the ones provided by the user", func() { + Expect(err).Should(HaveOccurred()) + Expect(output).Should(gbytes.Say(regexp.QuoteMeta("10.0notsemver must be in semver format: No Major.Minor.Patch elements found"))) + }) + }) + }) +}) diff --git a/cmd/registry/push/push_suite_test.go b/cmd/registry/push/push_suite_test.go index cc5ff027..572fc785 100644 --- a/cmd/registry/push/push_suite_test.go +++ b/cmd/registry/push/push_suite_test.go @@ -18,9 +18,11 @@ package push_test import ( "context" "fmt" + "github.com/falcosecurity/falcoctl/internal/utils" "net/http" "os" "path/filepath" + "regexp" "testing" "time" @@ -42,6 +44,8 @@ const ( rulesfileyaml = "../../../pkg/test/data/rulesWithoutReqAndDeps.yaml" rulesFileWithDepsAndReq = "../../../pkg/test/data/rules.yaml" plugintgz = "../../../pkg/test/data/plugin.tar.gz" + pluginARM64SO = "../../../pkg/test/data/libk8smeta-arm64.so" + pluginAMD64SO = "../../../pkg/test/data/libk8smeta-amd64.so" ) var ( @@ -106,3 +110,23 @@ func executeRoot(args []string) error { rootCmd.SetOut(output) return cmd.Execute(rootCmd, opt) } + +var checkTmpDir = func() (bool, error) { + entries, err := os.ReadDir("/tmp") + if err != nil { + return false, err + } + + for _, e := range entries { + if e.IsDir() { + matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name())) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + } + return false, nil +} diff --git a/cmd/registry/push/push_test.go b/cmd/registry/push/push_test.go index 94b26231..c23a99e7 100644 --- a/cmd/registry/push/push_test.go +++ b/cmd/registry/push/push_test.go @@ -18,18 +18,13 @@ package push_test import ( "fmt" "math/rand" - "os" - "path/filepath" "regexp" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" - v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/falcosecurity/falcoctl/cmd" - "github.com/falcosecurity/falcoctl/internal/utils" - testutils "github.com/falcosecurity/falcoctl/pkg/test" ) var registryPushUsage = `Usage: @@ -253,664 +248,4 @@ var _ = Describe("push", func() { "flag: must be one of \"rulesfile\", \"plugin\", \"asset") }) }) - - Context("success with rules without deps and requirements", func() { - const ( - rulesRepoBaseName = "push-rulesfile" - pluginsRepoBaseName = "push-plugins" - ) - - var ( - version = "1.1.1" - // registry/rulesRepoBaseName-randomInt - fullRepoName string - // rulesRepoBaseName-randomInt - repoName string - // It is set in the config layer. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - - // Variables passed as arguments to the push command. Each test case updates them - // to point to the file on disk living in pkg/test/data. - rulesfile string - plugin string - pluginRaw string - - // Plugin's platforms. - platformARM64 = "linux/arm64" - platformAMD64 = "linux/amd64" - - // Data fetched from registry and used for assertions. - pluginData *testutils.PluginArtifact - rulesfileData *testutils.RulesfileArtifact - ) - - // We keep it inside the success context since need the variables of this context. - var AssertSuccesBehaviour = func(dependencies, requirements, annotation bool) { - It("should succeed", func() { - // We do not check the error here since we are checking it before - // pulling the artifact. - By("checking no error in output") - Expect(output).ShouldNot(gbytes.Say("ERROR")) - Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir")) - - By("checking descriptor") - Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest)) - Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String()))) - - By("checking manifest") - Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1)) - if annotation { - Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource)) - } else { - Expect(rulesfileData.Layer.Manifest.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource)) - } - - By("checking config layer") - Expect(rulesfileData.Layer.Config.Version).Should(Equal(version)) - Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer)) - if dependencies { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal(dep1)) - Expect(fmt.Sprintf("%s:%s|%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name, - rulesfileData.Layer.Config.Dependencies[1].Version, rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Name, - rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2)) - } else { - Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(0)) - } - if requirements { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name, - rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req)) - } else { - Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(0)) - } - - By("checking tags") - Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags))) - Expect(rulesfileData.Tags).Should(ContainElements(pushedTags)) - }) - } - - // Here we are testing all the success cases for the push command. The artifact type used here is of type - // rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin - // artifacts. So we are testing that common logic only once, and are doing it here. - commonFlagsAndRulesfileSpecificFlags := Context("rulesfiles and common flags", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - }) - When("with full flags and args", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - }) - AssertSuccesBehaviour(true, true, true) - }) - - When("no --name flag provided", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - }) - AssertSuccesBehaviour(true, true, true) - }) - - When("no --annotation-source provided", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - }) - AssertSuccesBehaviour(true, true, false) - }) - - When("no --tags provided", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, - "--name", artifactNameInConfigLayer} - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - AssertSuccesBehaviour(true, true, true) - }) - - When("no --depends-on flag provided", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - }) - AssertSuccesBehaviour(false, true, true) - }) - - When("no --requires flag provided", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - }) - AssertSuccesBehaviour(true, false, true) - }) - - When("only required flags", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http"} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - AssertSuccesBehaviour(false, false, false) - }) - }) - - Context("rulesfile", func() { - Context("tar.gz format", func() { - rulesfile = rulesfiletgz - var _ = commonFlagsAndRulesfileSpecificFlags - }) - - Context("raw format", func() { - rulesfile = rulesfileyaml - - // Push a raw rulesfiles using all the flags combinations. - var _ = commonFlagsAndRulesfileSpecificFlags - - Context("filesystem cleanup", func() { - // Push a raw rulesfile. - BeforeEach(func() { - // Some values such as fullRepoName is the last one set by the other tests or the default one. - // Anyway we do not really care since the tar.gz is created before. - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http"} - }) - - It("temp dir should not exist", func() { - Expect(err).ShouldNot(HaveOccurred()) - entries, err := os.ReadDir("/tmp") - Expect(err).ShouldNot(HaveOccurred()) - for _, e := range entries { - if e.IsDir() { - matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name())) - Expect(err).ShouldNot(HaveOccurred()) - Expect(matched).ShouldNot(BeTrue()) - } - } - }) - }) - - }) - }) - - // We keep it inside the success context since need the variables of this context. - var AssertSuccessBehaviourPlugins = func(dependencies, requirements, annotation bool) { - It("should succeed", func() { - // We do not check the error here since we are checking it before - // pulling the artifact. - By("checking no error in output") - Expect(output).ShouldNot(gbytes.Say("ERROR")) - Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir")) - - By("checking descriptor") - Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex)) - Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String()))) - - By("checking index") - Expect(pluginData.Index.Manifests).Should(HaveLen(2)) - - if annotation { - Expect(pluginData.Index.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource)) - } else { - Expect(pluginData.Descriptor.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource)) - } - - By("checking platforms") - Expect(pluginData.Platforms).Should(HaveKey(platformARM64)) - Expect(pluginData.Platforms).Should(HaveKey(platformAMD64)) - - By("checking config layer") - for _, p := range pluginData.Platforms { - Expect(p.Config.Version).Should(Equal(version)) - Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer)) - if dependencies { - Expect(fmt.Sprintf("%s:%s", p.Config.Dependencies[0].Name, p.Config.Dependencies[0].Version)).Should(Equal(dep1)) - Expect(fmt.Sprintf("%s:%s|%s:%s", p.Config.Dependencies[1].Name, p.Config.Dependencies[1].Version, - p.Config.Dependencies[1].Alternatives[0].Name, p.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2)) - } else { - Expect(p.Config.Dependencies).Should(HaveLen(0)) - } - if requirements { - Expect(fmt.Sprintf("%s:%s", p.Config.Requirements[0].Name, p.Config.Requirements[0].Version)).Should(Equal(req)) - } else { - Expect(p.Config.Requirements).Should(HaveLen(0)) - } - - } - - By("checking tags") - Expect(pluginData.Tags).Should(HaveLen(len(pushedTags))) - Expect(pluginData.Tags).Should(ContainElements(pushedTags)) - }) - } - - // Here we are testing the success cases for the push command using a plugin artifact and its related flags. - // Other flags related to the plugin artifacts are tested in the rulesfile artifact section. - PluginsSpecificFlags := Context("plugins specific flags", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-plugin" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) - }) - When("with full flags and args", func() { - BeforeEach(func() { - args = []string{registryCmd, pushCmd, fullRepoName, plugin, pluginRaw, "--type", "plugin", "--platform", - platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - }) - AssertSuccessBehaviourPlugins(true, true, true) - }) - }) - - Context("plugin", func() { - Context("tar.gz + raw format format", func() { - plugin = plugintgz - // We do not really care what the file is. - pluginRaw = rulesfileyaml - var _ = PluginsSpecificFlags - }) - }) - }) - - Context("rulesfile deps and requirements", func() { - const ( - rulesRepoBaseName = "push-rulesfile" - pluginsRepoBaseName = "push-plugins" - ) - - var ( - version = "1.1.1" - // registry/rulesRepoBaseName-randomInt - fullRepoName string - // rulesRepoBaseName-randomInt - repoName string - // It is set in the config layer. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - - // Variables passed as arguments to the push command. Each test case updates them - // to point to the file on disk living in pkg/test/data. - rulesfile string - rulesfileData *testutils.RulesfileArtifact - ) - - Context("user provided deps", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - rulesfile = rulesFileWithDepsAndReq - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("deps should be the ones provided by the user", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal(dep1)) - Expect(fmt.Sprintf("%s:%s|%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name, - rulesfileData.Layer.Config.Dependencies[1].Version, rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Name, - rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2)) - }) - }) - - Context("parsed from file deps", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - rulesfile = rulesFileWithDepsAndReq - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("deps should be same as in rulesfile", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal("cloudtrail:0.2.3")) - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name, - rulesfileData.Layer.Config.Dependencies[1].Version)).Should(Equal("json:0.2.2")) - }) - }) - - Context("parsed from file deps with alternatives", func() { - var data = ` -- required_plugin_versions: - - name: k8saudit - version: 0.7.0 - alternatives: - - name: k8saudit-eks - version: 0.4.0 - - name: json - version: 0.7.0 -` - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - tmpDir := GinkgoT().TempDir() - rulesfile, err = testutils.WriteToTmpFile(data, tmpDir) - Expect(err).ToNot(HaveOccurred()) - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("deps should be same as in rulesfile", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal("k8saudit:0.7.0")) - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name, - rulesfileData.Layer.Config.Dependencies[1].Version)).Should(Equal("json:0.7.0")) - Expect(fmt.Sprintf("%s:%s|%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Version, rulesfileData.Layer.Config.Dependencies[0].Alternatives[0].Name, - rulesfileData.Layer.Config.Dependencies[0].Alternatives[0].Version)).Should(Equal("k8saudit:0.7.0|k8saudit-eks:0.4.0")) - }) - }) - - Context("no deps at all", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - rulesfile = rulesfileyaml - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("deps should be same as in rulesfile", func() { - Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(0)) - }) - }) - - Context("user provided requirement", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - rulesfile = rulesFileWithDepsAndReq - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--requires", req, "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("reqs should be the ones provided by the user", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name, - rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req)) - }) - }) - - Context("requirement parsed from file in semver format", func() { - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - rulesfile = rulesFileWithDepsAndReq - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - It("reqs should be the ones provided by the user", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name, - rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal("engine_version_semver:0.10.0")) - }) - }) - - Context("requirement parsed from file in int format", func() { - var rulesfileContent = ` -- required_engine_version: 10 -` - JustBeforeEach(func() { - // This runs after the push command, so check the returned error before proceeding. - Expect(err).ShouldNot(HaveOccurred()) - rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry) - Expect(err).ShouldNot(HaveOccurred()) - }) - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - tmpDir := GinkgoT().TempDir() - rulesfile, err = testutils.WriteToTmpFile(rulesfileContent, tmpDir) - Expect(err).ToNot(HaveOccurred()) - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - AfterEach(func() { - Expect(os.RemoveAll(filepath.Dir(rulesfile))).ToNot(HaveOccurred()) - }) - - It("reqs should be the ones provided by the user", func() { - Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name, - rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal("engine_version_semver:0.10.0")) - }) - }) - - Context("requirement parsed from file -- invalid format (float)", func() { - var rulesFile = ` -- required_engine_version: 10.0 -` - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - tmpDir := GinkgoT().TempDir() - rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir) - Expect(err).ToNot(HaveOccurred()) - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - AfterEach(func() { - Expect(os.RemoveAll(filepath.Dir(rulesfile))).ToNot(HaveOccurred()) - }) - - It("reqs should be the ones provided by the user", func() { - Expect(err).Should(HaveOccurred()) - Expect(output).Should(gbytes.Say(regexp.QuoteMeta("required_engine_version must be an int or a string respecting " + - "the semver specification, got type float64"))) - }) - }) - - Context("requirement parsed from file -- invalid format (not semver)", func() { - var rulesFile = ` -- required_engine_version: 10.0notsemver -` - - JustAfterEach(func() { - // This variable could be changed by single tests. - // Make sure to set them at their default values. - artifactNameInConfigLayer = "test-rulesfile" - pushedTags = []string{"tag1", "tag2", "latest"} - }) - - BeforeEach(func() { - repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName) - tmpDir := GinkgoT().TempDir() - rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir) - Expect(err).ToNot(HaveOccurred()) - args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version, - "--plain-http", "--annotation-source", anSource, - "--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer} - // Set name to the expected one. - artifactNameInConfigLayer = repoName - // We expect that latest tag is pushed, so set it in the pushed tags. - pushedTags = []string{"latest"} - }) - - AfterEach(func() { - Expect(os.RemoveAll(filepath.Dir(rulesfile))).ToNot(HaveOccurred()) - }) - - It("reqs should be the ones provided by the user", func() { - Expect(err).Should(HaveOccurred()) - Expect(output).Should(gbytes.Say(regexp.QuoteMeta("10.0notsemver must be in semver format: No Major.Minor.Patch elements found"))) - }) - }) - }) -}) - -var _ = Describe("rulesConfigLayer", func() { - }) diff --git a/go.mod b/go.mod index afefa7d7..1b1888cb 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/docker/cli v25.0.4+incompatible github.com/docker/docker v25.0.4+incompatible github.com/falcosecurity/driverkit v0.16.3 + github.com/falcosecurity/plugin-sdk-go v0.7.3 github.com/go-oauth2/oauth2/v4 v4.5.2 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-containerregistry v0.19.1 @@ -256,6 +257,9 @@ require ( github.com/valyala/fasthttp v1.50.0 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/xanzy/go-gitlab v0.96.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zeebo/errs v1.3.0 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect diff --git a/go.sum b/go.sum index 67e2fd6b..1d498e8d 100644 --- a/go.sum +++ b/go.sum @@ -276,6 +276,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/falcosecurity/driverkit v0.16.3 h1:lvffzX//XHsXA/xOEs2bya7oLPxHGVERgUH5jgE68iE= github.com/falcosecurity/driverkit v0.16.3/go.mod h1:wIJihfqt1+hLDzEi8k4lL8eLQMrWk3x43+LcpCxHHLA= +github.com/falcosecurity/plugin-sdk-go v0.7.3 h1:nmlBUmeAgEhcEHhSDWeEYgD9WdiHR9uMWyog5Iv7GIA= +github.com/falcosecurity/plugin-sdk-go v0.7.3/go.mod h1:NP+y22DYOS+G3GDXIXNmzf0CBL3nfPPMoQuHvAzfitQ= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= diff --git a/internal/utils/compress.go b/internal/utils/compress.go index 63f5d55d..f84c258d 100644 --- a/internal/utils/compress.go +++ b/internal/utils/compress.go @@ -31,10 +31,13 @@ import ( const TmpDirPrefix = "falcoctl-registry-push-" // CreateTarGzArchive compresses and saves in a tar archive the passed file. -func CreateTarGzArchive(path string) (file string, err error) { +func CreateTarGzArchive(dir, path string) (file string, err error) { cleanedPath := filepath.Clean(path) + if dir == "" { + dir = TmpDirPrefix + } // Create output file. - tmpDir, err := os.MkdirTemp("", TmpDirPrefix) + tmpDir, err := os.MkdirTemp("", dir) if err != nil { return "", err } diff --git a/internal/utils/compress_test.go b/internal/utils/compress_test.go index 38a49725..36790567 100644 --- a/internal/utils/compress_test.go +++ b/internal/utils/compress_test.go @@ -29,6 +29,7 @@ import ( const ( filename1 = "file1" filename2 = "file2" + tmpPrefix = "testCreateTarGzArchiveFile" ) func TestCreateTarGzArchiveFile(t *testing.T) { @@ -39,11 +40,11 @@ func TestCreateTarGzArchiveFile(t *testing.T) { } defer f1.Close() - tarball, err := CreateTarGzArchive(filepath.Join(dir, filename1)) + tarball, err := CreateTarGzArchive(tmpPrefix, filepath.Join(dir, filename1)) if err != nil { t.Fatalf(err.Error()) } - defer os.Remove(tarball) + defer os.RemoveAll(filepath.Dir(tarball)) file, err := os.Open(tarball) if err != nil { @@ -82,11 +83,11 @@ func TestCreateTarGzArchiveDir(t *testing.T) { } defer f2.Close() - tarball, err := CreateTarGzArchive(dir) + tarball, err := CreateTarGzArchive(tmpPrefix, dir) if err != nil { t.Fatalf(err.Error()) } - defer os.Remove(tarball) + defer os.RemoveAll(filepath.Dir(tarball)) file, err := os.Open(tarball) if err != nil { diff --git a/pkg/test/data/libk8smeta-amd64.so b/pkg/test/data/libk8smeta-amd64.so new file mode 100755 index 00000000..8b7812e8 Binary files /dev/null and b/pkg/test/data/libk8smeta-amd64.so differ diff --git a/pkg/test/data/libk8smeta-arm64.so b/pkg/test/data/libk8smeta-arm64.so new file mode 100755 index 00000000..4344e5fb Binary files /dev/null and b/pkg/test/data/libk8smeta-arm64.so differ