From 8bb9632df2634a4fb848c078e5f0679a272a6323 Mon Sep 17 00:00:00 2001 From: Aldo Lacuku Date: Fri, 15 Mar 2024 11:03:26 +0100 Subject: [PATCH] update(tests/push): add new tests for push command --- cmd/registry/push/push.go | 7 +- cmd/registry/push/push_plugins_test.go | 217 +++++++ cmd/registry/push/push_rulesfiles_test.go | 640 ++++++++++++++++++++ cmd/registry/push/push_test.go | 673 ---------------------- internal/utils/compress.go | 7 +- internal/utils/compress_test.go | 9 +- 6 files changed, 871 insertions(+), 682 deletions(-) create mode 100644 cmd/registry/push/push_plugins_test.go create mode 100644 cmd/registry/push/push_rulesfiles_test.go diff --git a/cmd/registry/push/push.go b/cmd/registry/push/push.go index 65dcff06..ac68296f 100644 --- a/cmd/registry/push/push.go +++ b/cmd/registry/push/push.go @@ -170,7 +170,7 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error { return err } } - path, err := utils.CreateTarGzArchive(p) + path, err := utils.CreateTarGzArchive("", p) if err != nil { return err } @@ -218,6 +218,7 @@ 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" @@ -243,10 +244,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..0b43887d --- /dev/null +++ b/cmd/registry/push/push_plugins_test.go @@ -0,0 +1,217 @@ +// 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" + "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/internal/utils" + "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, with reqs and deps", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = rulesfileyaml + 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, no reqs", func() { + BeforeEach(func() { + repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName) + pluginOne = plugintgz + 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 empty. + AssertSuccessBehaviour([]oci.ArtifactDependency{{ + Name: "my-test", + Version: "4.3.2", + Alternatives: nil, + }}, []oci.ArtifactRequirement{}, map[string]string{ + "org.opencontainers.image.source": anSource, + }, []string{ + platformAMD64, + }) + }) + }) +}) diff --git a/cmd/registry/push/push_rulesfiles_test.go b/cmd/registry/push/push_rulesfiles_test.go new file mode 100644 index 00000000..c7546fe1 --- /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" + "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/internal/utils" + "github.com/falcosecurity/falcoctl/pkg/oci" + testutils "github.com/falcosecurity/falcoctl/pkg/test" +) + +// revive:enable + +var _ = 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_test.go b/cmd/registry/push/push_test.go index 94b26231..c09d6a84 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: @@ -126,14 +121,6 @@ var _ = Describe("push", func() { pushCmd = "push" ) - 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:15" - anSource = "myrepo.com/rules.git" - ) - // Each test gets its own root command and runs it. // The err variable is asserted by each test. JustBeforeEach(func() { @@ -253,664 +240,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/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 {