diff --git a/cmd/opm/alpha/bundle/build.go b/cmd/opm/alpha/bundle/build.go index 2bc2121b3..79901e1af 100644 --- a/cmd/opm/alpha/bundle/build.go +++ b/cmd/opm/alpha/bundle/build.go @@ -7,14 +7,14 @@ import ( ) var ( - dirBuildArgs string - tagBuildArgs string - imageBuilderArgs string - packageNameArgs string - channelsArgs string - channelDefaultArgs string - outputDirArgs string - overwriteArgs bool + buildDir string + tag string + containerTool string + pkg string + channels string + defaultChannel string + outputDir string + overwrite bool ) // newBundleBuildCmd returns a command that will build operator bundle image. @@ -44,47 +44,50 @@ func newBundleBuildCmd() *cobra.Command { RunE: buildFunc, } - bundleBuildCmd.Flags().StringVarP(&dirBuildArgs, "directory", "d", "", + bundleBuildCmd.Flags().StringVarP(&buildDir, "directory", "d", "", "The directory where bundle manifests and metadata for a specific version are located") if err := bundleBuildCmd.MarkFlagRequired("directory"); err != nil { log.Fatalf("Failed to mark `directory` flag for `build` subcommand as required") } - bundleBuildCmd.Flags().StringVarP(&tagBuildArgs, "tag", "t", "", + bundleBuildCmd.Flags().StringVarP(&tag, "tag", "t", "", "The image tag applied to the bundle image") if err := bundleBuildCmd.MarkFlagRequired("tag"); err != nil { log.Fatalf("Failed to mark `tag` flag for `build` subcommand as required") } - bundleBuildCmd.Flags().StringVarP(&packageNameArgs, "package", "p", "", + bundleBuildCmd.Flags().StringVarP(&pkg, "package", "p", "", "The name of the package that bundle image belongs to "+ "(Required if `directory` is not pointing to a bundle in the nested bundle format)") - bundleBuildCmd.Flags().StringVarP(&channelsArgs, "channels", "c", "", + bundleBuildCmd.Flags().StringVarP(&channels, "channels", "c", "", "The list of channels that bundle image belongs to"+ "(Required if `directory` is not pointing to a bundle in the nested bundle format)") - bundleBuildCmd.Flags().StringVarP(&imageBuilderArgs, "image-builder", "b", "docker", - "Tool to build container images. One of: [docker, podman, buildah]") + bundleBuildCmd.Flags().StringVarP(&containerTool, "image-builder", "b", "docker", + "Tool used to manage container images. One of: [docker, podman, buildah]") - bundleBuildCmd.Flags().StringVarP(&channelDefaultArgs, "default", "e", "", + bundleBuildCmd.Flags().StringVarP(&defaultChannel, "default", "e", "", "The default channel for the bundle image") - bundleBuildCmd.Flags().BoolVarP(&overwriteArgs, "overwrite", "o", false, + bundleBuildCmd.Flags().BoolVarP(&overwrite, "overwrite", "o", false, "To overwrite annotations.yaml locally if existed. By default, overwrite is set to `false`.") - bundleBuildCmd.Flags().StringVarP(&outputDirArgs, "output-dir", "u", "", + bundleBuildCmd.Flags().StringVarP(&outputDir, "output-dir", "u", "", "Optional output directory for operator manifests") return bundleBuildCmd } func buildFunc(cmd *cobra.Command, args []string) error { - err := bundle.BuildFunc(dirBuildArgs, outputDirArgs, tagBuildArgs, imageBuilderArgs, - packageNameArgs, channelsArgs, channelDefaultArgs, overwriteArgs) - if err != nil { - return err - } - - return nil + return bundle.BuildFunc( + buildDir, + outputDir, + tag, + containerTool, + pkg, + channels, + defaultChannel, + overwrite, + ) } diff --git a/cmd/opm/alpha/bundle/generate.go b/cmd/opm/alpha/bundle/generate.go index 9bac60247..1aaa09232 100644 --- a/cmd/opm/alpha/bundle/generate.go +++ b/cmd/opm/alpha/bundle/generate.go @@ -1,9 +1,10 @@ package bundle import ( - "github.com/operator-framework/operator-registry/pkg/lib/bundle" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/pkg/lib/bundle" ) // newBundleGenerateCmd returns a command that will generate operator bundle @@ -24,34 +25,36 @@ func newBundleGenerateCmd() *cobra.Command { RunE: generateFunc, } - bundleGenerateCmd.Flags().StringVarP(&dirBuildArgs, "directory", "d", "", + bundleGenerateCmd.Flags().StringVarP(&buildDir, "directory", "d", "", "The directory where bundle manifests for a specific version are located.") if err := bundleGenerateCmd.MarkFlagRequired("directory"); err != nil { log.Fatalf("Failed to mark `directory` flag for `generate` subcommand as required") } - bundleGenerateCmd.Flags().StringVarP(&packageNameArgs, "package", "p", "", + bundleGenerateCmd.Flags().StringVarP(&pkg, "package", "p", "", "The name of the package that bundle image belongs to "+ "(Required if `directory` is not pointing to a bundle in the nested bundle format)") - bundleGenerateCmd.Flags().StringVarP(&channelsArgs, "channels", "c", "", + bundleGenerateCmd.Flags().StringVarP(&channels, "channels", "c", "", "The list of channels that bundle image belongs to"+ "(Required if `directory` is not pointing to a bundle in the nested bundle format)") - bundleGenerateCmd.Flags().StringVarP(&channelDefaultArgs, "default", "e", "", + bundleGenerateCmd.Flags().StringVarP(&defaultChannel, "default", "e", "", "The default channel for the bundle image") - bundleGenerateCmd.Flags().StringVarP(&outputDirArgs, "output-dir", "u", "", + bundleGenerateCmd.Flags().StringVarP(&outputDir, "output-dir", "u", "", "Optional output directory for operator manifests") return bundleGenerateCmd } func generateFunc(cmd *cobra.Command, args []string) error { - err := bundle.GenerateFunc(dirBuildArgs, outputDirArgs, packageNameArgs, channelsArgs, channelDefaultArgs, true) - if err != nil { - return err - } - - return nil + return bundle.GenerateFunc( + buildDir, + outputDir, + pkg, + channels, + defaultChannel, + true, + ) } diff --git a/cmd/opm/alpha/bundle/validate.go b/cmd/opm/alpha/bundle/validate.go index de322d49a..998c391d7 100644 --- a/cmd/opm/alpha/bundle/validate.go +++ b/cmd/opm/alpha/bundle/validate.go @@ -1,13 +1,18 @@ package bundle import ( + "fmt" "io/ioutil" "os" "path/filepath" - "github.com/operator-framework/operator-registry/pkg/lib/bundle" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + "github.com/operator-framework/operator-registry/pkg/containertools" + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/image/execregistry" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" ) func newBundleValidateCmd() *cobra.Command { @@ -21,22 +26,38 @@ accurate.`, RunE: validateFunc, } - bundleValidateCmd.Flags().StringVarP(&tagBuildArgs, "tag", "t", "", + bundleValidateCmd.Flags().StringVarP(&tag, "tag", "t", "", "The path of a registry to pull from, image name and its tag that present the bundle image (e.g. quay.io/test/test-operator:latest)") if err := bundleValidateCmd.MarkFlagRequired("tag"); err != nil { log.Fatalf("Failed to mark `tag` flag for `validate` subcommand as required") } - bundleValidateCmd.Flags().StringVarP(&imageBuilderArgs, "image-builder", "b", "docker", "Tool to build container images. One of: [docker, podman]") + bundleValidateCmd.Flags().StringVarP(&containerTool, "image-builder", "b", "docker", "Tool used to pull and unpack bundle images. One of: [docker, podman]") return bundleValidateCmd } func validateFunc(cmd *cobra.Command, args []string) error { - logger := log.WithFields(log.Fields{"container-tool": imageBuilderArgs}) + logger := log.WithFields(log.Fields{"container-tool": containerTool}) log.SetLevel(log.DebugLevel) - imageValidator := bundle.NewImageValidator(imageBuilderArgs, logger) + var ( + registry image.Registry + err error + ) + + tool := containertools.NewContainerTool(containerTool, containertools.PodmanTool) + switch tool { + case containertools.PodmanTool, containertools.DockerTool: + registry, err = execregistry.NewRegistry(tool, logger) + default: + err = fmt.Errorf("unrecognized container-tool option: %s", containerTool) + } + + if err != nil { + return err + } + imageValidator := bundle.NewImageValidator(registry, logger) dir, err := ioutil.TempDir("", "bundle-") logger.Infof("Create a temp directory at %s", dir) @@ -50,7 +71,7 @@ func validateFunc(cmd *cobra.Command, args []string) error { } }() - err = imageValidator.PullBundleImage(tagBuildArgs, dir) + err = imageValidator.PullBundleImage(tag, dir) if err != nil { return err } diff --git a/manifests/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml b/manifests/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml index fb1a9e99e..0643b4223 100644 --- a/manifests/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml +++ b/manifests/prometheus/0.22.2/prometheusoperator.0.22.2.clusterserviceversion.yaml @@ -186,6 +186,15 @@ spec: beta.kubernetes.io/os: linux maturity: beta version: 0.22.2 + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: true + type: MultiNamespace + - supported: false + type: AllNamespaces customresourcedefinitions: owned: - name: prometheuses.monitoring.coreos.com diff --git a/pkg/containertools/containertoolsfakes/fake_image_reader.go b/pkg/containertools/containertoolsfakes/fake_image_reader.go deleted file mode 100644 index 2788e0a1b..000000000 --- a/pkg/containertools/containertoolsfakes/fake_image_reader.go +++ /dev/null @@ -1,114 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package containertoolsfakes - -import ( - "sync" - - "github.com/operator-framework/operator-registry/pkg/containertools" -) - -type FakeImageReader struct { - GetImageDataStub func(string, string, ...containertools.GetImageDataOption) error - getImageDataMutex sync.RWMutex - getImageDataArgsForCall []struct { - arg1 string - arg2 string - arg3 []containertools.GetImageDataOption - } - getImageDataReturns struct { - result1 error - } - getImageDataReturnsOnCall map[int]struct { - result1 error - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeImageReader) GetImageData(arg1 string, arg2 string, arg3 ...containertools.GetImageDataOption) error { - fake.getImageDataMutex.Lock() - ret, specificReturn := fake.getImageDataReturnsOnCall[len(fake.getImageDataArgsForCall)] - fake.getImageDataArgsForCall = append(fake.getImageDataArgsForCall, struct { - arg1 string - arg2 string - arg3 []containertools.GetImageDataOption - }{arg1, arg2, arg3}) - fake.recordInvocation("GetImageData", []interface{}{arg1, arg2, arg3}) - fake.getImageDataMutex.Unlock() - if fake.GetImageDataStub != nil { - return fake.GetImageDataStub(arg1, arg2, arg3...) - } - if specificReturn { - return ret.result1 - } - fakeReturns := fake.getImageDataReturns - return fakeReturns.result1 -} - -func (fake *FakeImageReader) GetImageDataCallCount() int { - fake.getImageDataMutex.RLock() - defer fake.getImageDataMutex.RUnlock() - return len(fake.getImageDataArgsForCall) -} - -func (fake *FakeImageReader) GetImageDataCalls(stub func(string, string, ...containertools.GetImageDataOption) error) { - fake.getImageDataMutex.Lock() - defer fake.getImageDataMutex.Unlock() - fake.GetImageDataStub = stub -} - -func (fake *FakeImageReader) GetImageDataArgsForCall(i int) (string, string, []containertools.GetImageDataOption) { - fake.getImageDataMutex.RLock() - defer fake.getImageDataMutex.RUnlock() - argsForCall := fake.getImageDataArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 -} - -func (fake *FakeImageReader) GetImageDataReturns(result1 error) { - fake.getImageDataMutex.Lock() - defer fake.getImageDataMutex.Unlock() - fake.GetImageDataStub = nil - fake.getImageDataReturns = struct { - result1 error - }{result1} -} - -func (fake *FakeImageReader) GetImageDataReturnsOnCall(i int, result1 error) { - fake.getImageDataMutex.Lock() - defer fake.getImageDataMutex.Unlock() - fake.GetImageDataStub = nil - if fake.getImageDataReturnsOnCall == nil { - fake.getImageDataReturnsOnCall = make(map[int]struct { - result1 error - }) - } - fake.getImageDataReturnsOnCall[i] = struct { - result1 error - }{result1} -} - -func (fake *FakeImageReader) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.getImageDataMutex.RLock() - defer fake.getImageDataMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeImageReader) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ containertools.ImageReader = new(FakeImageReader) diff --git a/pkg/containertools/imagereader.go b/pkg/containertools/imagereader.go deleted file mode 100644 index 12746d9c6..000000000 --- a/pkg/containertools/imagereader.go +++ /dev/null @@ -1,201 +0,0 @@ -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ImageReader -package containertools - -import ( - "archive/tar" - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/sirupsen/logrus" -) - -const ( - imageManifestName = "manifest.json" -) - -// imageManifest is the object format of container image manifest files -// use this type to parse manifest.json files inside container image blobs -type imageManifest struct { - Layers []string `json:”Layers”` -} - -type ImageReader interface { - GetImageData(string, string, ...GetImageDataOption) error -} - -type ImageLayerReader struct { - Cmd CommandRunner - Logger *logrus.Entry -} - -func NewImageReader(containerTool ContainerTool, logger *logrus.Entry) ImageReader { - cmd := NewCommandRunner(containerTool, logger) - - return &ImageLayerReader{ - Cmd: cmd, - Logger: logger, - } -} - -func (b ImageLayerReader) GetImageData(image, outputDir string, opts ...GetImageDataOption) error { - options := GetImageDataOptions{} - for _, o := range opts { - o(&options) - } - - // Create the output directory if it doesn't exist - if _, err := os.Stat(outputDir); os.IsNotExist(err) { - os.Mkdir(outputDir, 0777) - } - - err := b.Cmd.Pull(image) - if err != nil { - return err - } - - rootTarfile := filepath.Join(options.WorkingDir, "bundle.tar") - - if options.WorkingDir == "" { - workingDir, err := ioutil.TempDir("./", "bundle_staging_") - if err != nil { - return err - } - defer os.RemoveAll(workingDir) - - rootTarfile = filepath.Join(workingDir, "bundle.tar") - } - - err = b.Cmd.Save(image, rootTarfile) - if err != nil { - return err - } - - f, err := os.Open(rootTarfile) - if err != nil { - return err - } - defer f.Close() - - // Read the manifest.json file to find the right embedded tarball - layerTarballs, err := getManifestLayers(tar.NewReader(f)) - if err != nil { - return err - } - - // Untar the image layer tarballs and push the bundle manifests to the output directory - for _, tarball := range layerTarballs { - f, err = os.Open(rootTarfile) - if err != nil { - return err - } - defer f.Close() - - err = extractBundleManifests(tarball, outputDir, tar.NewReader(f)) - if err != nil { - return err - } - } - - return nil -} - -func getManifestLayers(tarReader *tar.Reader) ([]string, error) { - for { - header, err := tarReader.Next() - if err != nil { - if err == io.EOF { - return nil, fmt.Errorf("invalid bundle image: unable to find manifest.json") - } - return nil, err - } - - if header.Name == imageManifestName { - buf := new(bytes.Buffer) - buf.ReadFrom(tarReader) - b := buf.Bytes() - - manifests := make([]imageManifest, 0) - err := json.Unmarshal(b, &manifests) - if err != nil { - return nil, err - } - - if len(manifests) == 0 { - return nil, fmt.Errorf("invalid bundle image: manifest.json missing manifest data") - } - - topManifest := manifests[0] - - if len(topManifest.Layers) == 0 { - return nil, fmt.Errorf("invalid bundle image: manifest has no layers") - } - - return topManifest.Layers, nil - } - } -} - -func extractBundleManifests(layerTarball, outputDir string, tarReader *tar.Reader) error { - for { - header, err := tarReader.Next() - if err != nil { - if err == io.EOF { - return fmt.Errorf("Manifest error: Layer tarball does not exist in bundle") - } - return err - } - - if header.Typeflag == tar.TypeReg { - if header.Name == layerTarball { - // Found the embedded tarball for the layer - layerReader := tar.NewReader(tarReader) - - err = extractTarballToDir(outputDir, layerReader) - if err != nil { - return err - } - - return nil - } - } - } -} - -func extractTarballToDir(outputDir string, tarReader *tar.Reader) error { - for { - header, err := tarReader.Next() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - switch header.Typeflag { - case tar.TypeDir: - // Create the directory if it doesn't exist - directoryToWrite := filepath.Join(outputDir, header.Name) - if _, err := os.Stat(directoryToWrite); os.IsNotExist(err) { - os.Mkdir(directoryToWrite, 0777) - } - case tar.TypeReg: - manifestToWrite := filepath.Join(outputDir, header.Name) - - m, err := os.Create(manifestToWrite) - if err != nil { - return err - } - - _, err = io.Copy(m, tarReader) - m.Close() - if err != nil { - return err - } - } - } -} diff --git a/pkg/containertools/imagereader_test.go b/pkg/containertools/imagereader_test.go deleted file mode 100644 index cc4b0a64d..000000000 --- a/pkg/containertools/imagereader_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package containertools_test - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/operator-framework/operator-registry/pkg/containertools/containertoolsfakes" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" -) - -const ( - expectedFilePath = "testdata/expected_unpack" -) - -func TestReadImageLayersDocker(t *testing.T) { - image := "quay.io/operator-framework/example" - testWorkingDir := "testdata/docker" - testOutputDir := "testdata/output" - - expectedFiles, err := helperGetExpectedFiles() - - logger := logrus.NewEntry(logrus.New()) - mockCmd := containertoolsfakes.FakeCommandRunner{} - - mockCmd.PullReturns(nil) - mockCmd.SaveReturns(nil) - - imageReader := containertools.ImageLayerReader{ - Cmd: &mockCmd, - Logger: logger, - } - - err = imageReader.GetImageData(image, testOutputDir, containertools.WithWorkingDir(testWorkingDir)) - require.NoError(t, err) - - for _, file := range expectedFiles { - expectedFilePath := filepath.Join(expectedFilePath, file) - expectedFile, err := ioutil.ReadFile(expectedFilePath) - require.NoError(t, err) - - actualFilePath := filepath.Join(testOutputDir, file) - actualFile, err := ioutil.ReadFile(actualFilePath) - require.NoError(t, err) - - require.Equal(t, string(expectedFile), string(actualFile)) - } - - err = os.RemoveAll(testOutputDir) - require.NoError(t, err) -} - -func TestReadImageLayersPodman(t *testing.T) { - image := "quay.io/operator-framework/example" - testWorkingDir := "testdata/podman" - testOutputDir := "testdata/output" - - expectedFiles, err := helperGetExpectedFiles() - - logger := logrus.NewEntry(logrus.New()) - mockCmd := containertoolsfakes.FakeCommandRunner{} - - mockCmd.PullReturns(nil) - mockCmd.SaveReturns(nil) - - imageReader := containertools.ImageLayerReader{ - Cmd: &mockCmd, - Logger: logger, - } - - err = imageReader.GetImageData(image, testOutputDir, containertools.WithWorkingDir(testWorkingDir)) - require.NoError(t, err) - - for _, file := range expectedFiles { - expectedFilePath := filepath.Join(expectedFilePath, file) - expectedFile, err := ioutil.ReadFile(expectedFilePath) - require.NoError(t, err) - - actualFilePath := filepath.Join(testOutputDir, file) - actualFile, err := ioutil.ReadFile(actualFilePath) - require.NoError(t, err) - - require.Equal(t, string(expectedFile), string(actualFile)) - } - - err = os.RemoveAll(testOutputDir) - require.NoError(t, err) -} - -func TestReadImageLayers_PullError(t *testing.T) { - image := "quay.io/operator-framework/example" - testOutputDir := "testdata/output" - - logger := logrus.NewEntry(logrus.New()) - mockCmd := containertoolsfakes.FakeCommandRunner{} - - mockCmd.PullReturns(fmt.Errorf("Unable to pull image")) - - imageReader := containertools.ImageLayerReader{ - Cmd: &mockCmd, - Logger: logger, - } - - err := imageReader.GetImageData(image, testOutputDir) - require.Error(t, err) -} - -func TestReadImageLayers_SaveError(t *testing.T) { - image := "quay.io/operator-framework/example" - testOutputDir := "testdata/output" - - logger := logrus.NewEntry(logrus.New()) - mockCmd := containertoolsfakes.FakeCommandRunner{} - - mockCmd.PullReturns(nil) - mockCmd.SaveReturns(fmt.Errorf("Unable to save image")) - - imageReader := containertools.ImageLayerReader{ - Cmd: &mockCmd, - Logger: logger, - } - - err := imageReader.GetImageData(image, testOutputDir) - require.Error(t, err) -} - -func helperGetExpectedFiles() ([]string, error) { - var files []string - - err := filepath.Walk(expectedFilePath, func(path string, f os.FileInfo, err error) error { - if !f.IsDir() { - fileName := strings.Replace(path, expectedFilePath, "", -1) - files = append(files, fileName) - } - return nil - }) - - return files, err -} diff --git a/pkg/containertools/runner.go b/pkg/containertools/runner.go index d6d70ac02..29a40b752 100644 --- a/pkg/containertools/runner.go +++ b/pkg/containertools/runner.go @@ -4,6 +4,7 @@ package containertools import ( "fmt" "os/exec" + "strings" "github.com/sirupsen/logrus" ) @@ -13,7 +14,6 @@ type CommandRunner interface { GetToolName() string Pull(image string) error Build(dockerfile, tag string) error - Save(image, tarFile string) error Inspect(image string) ([]byte, error) } @@ -26,12 +26,12 @@ type ContainerCommandRunner struct { // NewCommandRunner takes the containerTool as an input string and returns a // CommandRunner to run commands with that cli tool -func NewCommandRunner(containerTool ContainerTool, logger *logrus.Entry) CommandRunner { - r := ContainerCommandRunner{ +func NewCommandRunner(containerTool ContainerTool, logger *logrus.Entry) *ContainerCommandRunner { + r := &ContainerCommandRunner{ logger: logger, containerTool: containerTool, } - return &r + return r } // GetToolName returns the container tool this command runner is using @@ -82,20 +82,44 @@ func (r *ContainerCommandRunner) Build(dockerfile, tag string) error { return nil } -// Save takes a local container image and runs the save commmand to convert the -// image into a specified tarball and push it to the local directory -func (r *ContainerCommandRunner) Save(image, tarFile string) error { - args := []string{"save", image, "-o", tarFile} +// Unpack copies a directory from a local container image to a directory in the local filesystem. +func (r *ContainerCommandRunner) Unpack(image, src, dst string) error { + args := []string{"create", image, ""} command := exec.Command(r.containerTool.String(), args...) - r.logger.Infof("running %s save", r.containerTool) + r.logger.Infof("running %s create", r.containerTool) r.logger.Debugf("%s", command.Args) out, err := command.CombinedOutput() if err != nil { r.logger.Errorf(string(out)) - return fmt.Errorf("error saving image: %s. %v", string(out), err) + return fmt.Errorf("error creating container %s: %v", string(out), err) + } + + id := strings.TrimSuffix(string(out), "\n") + args = []string{"cp", id + ":" + src, dst} + command = exec.Command(r.containerTool.String(), args...) + + r.logger.Infof("running %s cp", r.containerTool) + r.logger.Debugf("%s", command.Args) + + out, err = command.CombinedOutput() + if err != nil { + r.logger.Errorf(string(out)) + return fmt.Errorf("error copying container directory %s: %v", string(out), err) + } + + args = []string{"rm", id} + command = exec.Command(r.containerTool.String(), args...) + + r.logger.Infof("running %s rm", r.containerTool) + r.logger.Debugf("%s", command.Args) + + out, err = command.CombinedOutput() + if err != nil { + r.logger.Errorf(string(out)) + return fmt.Errorf("error removing container %s: %v", string(out), err) } return nil diff --git a/pkg/image/containerdregistry/options.go b/pkg/image/containerdregistry/options.go index 72e4fcc60..de0be2f70 100644 --- a/pkg/image/containerdregistry/options.go +++ b/pkg/image/containerdregistry/options.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/metadata" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/remotes" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" bolt "go.etcd.io/bbolt" ) @@ -99,7 +100,10 @@ func NewRegistry(options ...RegistryOption) (registry *Registry, err error) { destroy: destroy, log: config.Log, resolver: resolver, - platform: platforms.Only(platforms.DefaultSpec()), + platform: platforms.Ordered(platforms.DefaultSpec(), specs.Platform{ + OS: "linux", + Architecture: "amd64", + }), } return } diff --git a/pkg/image/execregistry/registry.go b/pkg/image/execregistry/registry.go index 24d75b303..9d8468f14 100644 --- a/pkg/image/execregistry/registry.go +++ b/pkg/image/execregistry/registry.go @@ -2,21 +2,30 @@ package execregistry import ( "context" - "github.com/operator-framework/operator-registry/pkg/containertools" + "github.com/sirupsen/logrus" + "github.com/operator-framework/operator-registry/pkg/containertools" "github.com/operator-framework/operator-registry/pkg/image" ) +// CommandRunner provides some basic methods for manipulating images via an external container tool. +type CommandRunner interface { + containertools.CommandRunner + + Unpack(image, src, dst string) error +} + // Registry enables manipulation of images via exec podman/docker commands. type Registry struct { - log *logrus.Entry - cmd containertools.CommandRunner + log *logrus.Entry + cmd CommandRunner } // Adapt the cmd interface to the registry interface var _ image.Registry = &Registry{} +// NewRegistry instantiates and returns a new registry which manipulates images via exec podman/docker commands. func NewRegistry(tool containertools.ContainerTool, logger *logrus.Entry) (registry *Registry, err error) { return &Registry{ log: logger, @@ -32,16 +41,13 @@ func (r *Registry) Pull(ctx context.Context, ref image.Reference) error { // Unpack writes the unpackaged content of an image to a directory. // If the referenced image does not exist in the registry, an error is returned. func (r *Registry) Unpack(ctx context.Context, ref image.Reference, dir string) error { - return containertools.ImageLayerReader{ - Cmd: r.cmd, - Logger: r.log, - }.GetImageData(ref.String(), dir) + return r.cmd.Unpack(ref.String(), "/", dir) } // Labels gets the labels for an image reference. func (r *Registry) Labels(ctx context.Context, ref image.Reference) (map[string]string, error) { return containertools.ImageLabelReader{ - Cmd: r.cmd, + Cmd: r.cmd, Logger: r.log, }.GetLabelsFromImage(ref.String()) } @@ -49,4 +55,4 @@ func (r *Registry) Labels(ctx context.Context, ref image.Reference) (map[string] // Destroy is no-op for exec tools func (r *Registry) Destroy() error { return nil -} \ No newline at end of file +} diff --git a/pkg/image/registry_test.go b/pkg/image/registry_test.go index 2d604d761..2acf93ff6 100644 --- a/pkg/image/registry_test.go +++ b/pkg/image/registry_test.go @@ -18,7 +18,6 @@ import ( libimage "github.com/operator-framework/operator-registry/pkg/lib/image" ) - // cleanupFunc is a function that cleans up after some test infra. type cleanupFunc func() diff --git a/pkg/lib/bundle/interfaces.go b/pkg/lib/bundle/interfaces.go index 3199c2a0c..426d61e08 100644 --- a/pkg/lib/bundle/interfaces.go +++ b/pkg/lib/bundle/interfaces.go @@ -1,9 +1,9 @@ package bundle import ( - "github.com/operator-framework/operator-registry/pkg/containertools" - "github.com/sirupsen/logrus" + + "github.com/operator-framework/operator-registry/pkg/image" ) // BundleImageValidator provides a toolset for pulling and then validating @@ -21,9 +21,9 @@ type BundleImageValidator interface { } // NewImageValidator is a constructor that returns an ImageValidator -func NewImageValidator(containerTool string, logger *logrus.Entry) BundleImageValidator { +func NewImageValidator(registry image.Registry, logger *logrus.Entry) BundleImageValidator { return imageValidator{ - imageReader: containertools.NewImageReader(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger), - logger: logger, + registry: registry, + logger: logger, } } diff --git a/pkg/lib/bundle/validate.go b/pkg/lib/bundle/validate.go index 0579f15b3..465bf8571 100644 --- a/pkg/lib/bundle/validate.go +++ b/pkg/lib/bundle/validate.go @@ -1,18 +1,15 @@ package bundle import ( + "context" "encoding/json" "fmt" "io/ioutil" "path/filepath" "strings" - v1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - v "github.com/operator-framework/api/pkg/validation" - "github.com/operator-framework/operator-registry/pkg/containertools" - validation "github.com/operator-framework/operator-registry/pkg/lib/validation" - "github.com/operator-framework/operator-registry/pkg/registry" - + y "github.com/ghodss/yaml" + log "github.com/sirupsen/logrus" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiValidation "k8s.io/apimachinery/pkg/api/validation" @@ -22,8 +19,11 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" k8syaml "k8s.io/apimachinery/pkg/util/yaml" - y "github.com/ghodss/yaml" - log "github.com/sirupsen/logrus" + v1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + v "github.com/operator-framework/api/pkg/validation" + "github.com/operator-framework/operator-registry/pkg/image" + validation "github.com/operator-framework/operator-registry/pkg/lib/validation" + "github.com/operator-framework/operator-registry/pkg/registry" ) const ( @@ -38,8 +38,8 @@ type Meta struct { // imageValidator is a struct implementation of the Indexer interface type imageValidator struct { - imageReader containertools.ImageReader - logger *log.Entry + registry image.Registry + logger *log.Entry } // PullBundleImage shells out to a container tool and pulls a given image tag @@ -48,7 +48,15 @@ type imageValidator struct { func (i imageValidator) PullBundleImage(imageTag, directory string) error { i.logger.Debug("Pulling and unpacking container image") - return i.imageReader.GetImageData(imageTag, directory) + var ( + ctx = context.TODO() + ref = image.SimpleReference(imageTag) + ) + if err := i.registry.Pull(ctx, ref); err != nil { + return err + } + + return i.registry.Unpack(ctx, ref, directory) } // ValidateBundle takes a directory containing the contents of a bundle and validates diff --git a/pkg/lib/bundle/validate_test.go b/pkg/lib/bundle/validate_test.go index 5ce3a8f4c..11dcfbd25 100644 --- a/pkg/lib/bundle/validate_test.go +++ b/pkg/lib/bundle/validate_test.go @@ -5,52 +5,10 @@ import ( "fmt" "testing" - "github.com/operator-framework/operator-registry/pkg/containertools/containertoolsfakes" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestPullBundle(t *testing.T) { - tag := "quay.io/example/bundle:0.0.1" - dir := "/tmp/dir" - - logger := logrus.NewEntry(logrus.New()) - - mockImgReader := containertoolsfakes.FakeImageReader{} - mockImgReader.GetImageDataReturns(nil) - - validator := imageValidator{ - imageReader: &mockImgReader, - logger: logger, - } - - err := validator.PullBundleImage(tag, dir) - require.NoError(t, err) -} - -func TestPullBundle_Error(t *testing.T) { - tag := "quay.io/example/bundle:0.0.1" - dir := "/tmp/dir" - - expectedErr := fmt.Errorf("Unable to unpack image") - - logger := logrus.NewEntry(logrus.New()) - - mockImgReader := containertoolsfakes.FakeImageReader{} - mockImgReader.GetImageDataReturns(expectedErr) - - validator := imageValidator{ - imageReader: &mockImgReader, - logger: logger, - } - - err := validator.PullBundleImage(tag, dir) - require.Error(t, err) - assert.Equal(t, expectedErr, err) -} - func TestValidateBundleFormat(t *testing.T) { dir := "./testdata/validate/valid_bundle/" diff --git a/pkg/lib/indexer/indexer.go b/pkg/lib/indexer/indexer.go index 98735fa27..4bbfeeb3f 100644 --- a/pkg/lib/indexer/indexer.go +++ b/pkg/lib/indexer/indexer.go @@ -4,24 +4,24 @@ import ( "context" "database/sql" "fmt" - "github.com/operator-framework/operator-registry/pkg/image" - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" - "github.com/operator-framework/operator-registry/pkg/image/execregistry" "io" "io/ioutil" "os" "path" "path/filepath" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "github.com/operator-framework/operator-registry/pkg/containertools" + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" + "github.com/operator-framework/operator-registry/pkg/image/execregistry" "github.com/operator-framework/operator-registry/pkg/lib/bundle" "github.com/operator-framework/operator-registry/pkg/lib/registry" pregistry "github.com/operator-framework/operator-registry/pkg/registry" "github.com/operator-framework/operator-registry/pkg/sqlite" - - "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - utilerrors "k8s.io/apimachinery/pkg/util/errors" ) const ( @@ -38,7 +38,6 @@ type ImageIndexer struct { DockerfileGenerator containertools.DockerfileGenerator CommandRunner containertools.CommandRunner LabelReader containertools.LabelReader - ImageReader containertools.ImageReader RegistryAdder registry.RegistryAdder RegistryDeleter registry.RegistryDeleter RegistryPruner registry.RegistryPruner diff --git a/pkg/lib/indexer/interfaces.go b/pkg/lib/indexer/interfaces.go index a9017ecc0..9e19dd1f5 100644 --- a/pkg/lib/indexer/interfaces.go +++ b/pkg/lib/indexer/interfaces.go @@ -21,7 +21,6 @@ func NewIndexAdder(buildTool, pullTool containertools.ContainerTool, logger *log CommandRunner: containertools.NewCommandRunner(buildTool, logger), LabelReader: containertools.NewLabelReader(pullTool, logger), RegistryAdder: registry.NewRegistryAdder(logger), - ImageReader: containertools.NewImageReader(pullTool, logger), BuildTool: buildTool, PullTool: pullTool, Logger: logger, @@ -42,7 +41,6 @@ func NewIndexDeleter(containerTool containertools.ContainerTool, logger *logrus. CommandRunner: containertools.NewCommandRunner(containerTool, logger), LabelReader: containertools.NewLabelReader(containerTool, logger), RegistryDeleter: registry.NewRegistryDeleter(logger), - ImageReader: containertools.NewImageReader(containerTool, logger), BuildTool: containerTool, PullTool: containerTool, Logger: logger, @@ -60,7 +58,6 @@ func NewIndexExporter(containerTool containertools.ContainerTool, logger *logrus DockerfileGenerator: containertools.NewDockerfileGenerator(logger), CommandRunner: containertools.NewCommandRunner(containerTool, logger), LabelReader: containertools.NewLabelReader(containerTool, logger), - ImageReader: containertools.NewImageReader(containerTool, logger), BuildTool: containerTool, PullTool: containerTool, Logger: logger, @@ -78,7 +75,6 @@ func NewIndexPruner(containerTool containertools.ContainerTool, logger *logrus.E CommandRunner: containertools.NewCommandRunner(containerTool, logger), LabelReader: containertools.NewLabelReader(containerTool, logger), RegistryPruner: registry.NewRegistryPruner(logger), - ImageReader: containertools.NewImageReader(containerTool, logger), BuildTool: containerTool, PullTool: containerTool, Logger: logger, diff --git a/test/e2e/opm_test.go b/test/e2e/opm_test.go index 2ef186611..84e7d3e0e 100644 --- a/test/e2e/opm_test.go +++ b/test/e2e/opm_test.go @@ -3,6 +3,7 @@ package e2e_test import ( "context" "database/sql" + "fmt" "io/ioutil" "os" "os/exec" @@ -15,6 +16,9 @@ import ( "k8s.io/apimachinery/pkg/util/rand" "github.com/operator-framework/operator-registry/pkg/containertools" + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" + "github.com/operator-framework/operator-registry/pkg/image/execregistry" "github.com/operator-framework/operator-registry/pkg/lib/bundle" "github.com/operator-framework/operator-registry/pkg/lib/indexer" "github.com/operator-framework/operator-registry/pkg/sqlite" @@ -69,7 +73,7 @@ func inTemporaryBuildContext(f func() error) (rerr error) { } func buildIndexWith(containerTool, indexImage, bundleImage string, bundleTags []string) error { - bundles := make([]string, len(bundleTags)) + var bundles []string for _, tag := range bundleTags { bundles = append(bundles, bundleImage+":"+tag) } @@ -133,19 +137,6 @@ func pushWith(containerTool, image string) error { return dockerpush.Run() } -func pushBundles(containerTool string) error { - err := pushWith(containerTool, bundleImage+":"+bundleTag1) - if err != nil { - return err - } - err = pushWith(containerTool, bundleImage+":"+bundleTag2) - if err != nil { - return err - } - err = pushWith(containerTool, bundleImage+":"+bundleTag3) - return err -} - func exportWith(containerTool string) error { logger := logrus.WithFields(logrus.Fields{"package": packageName}) indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger) @@ -197,22 +188,65 @@ var _ = Describe("opm", func() { Expect(err).NotTo(HaveOccurred(), "Error logging into quay.io") }) + It("builds and validates a bundle image", func() { + By("building bundle") + img := bundleImage + ":" + bundleTag3 + err := inTemporaryBuildContext(func() error { + return bundle.BuildFunc(bundlePath3, "", img, containerTool, packageName, channels, defaultChannel, false) + }) + Expect(err).NotTo(HaveOccurred()) + + By("pushing bundle") + Expect(pushWith(containerTool, img)).To(Succeed()) + + By("pulling bundle") + logger := logrus.WithFields(logrus.Fields{"image": img}) + tool := containertools.NewContainerTool(containerTool, containertools.NoneTool) + var registry image.Registry + switch tool { + case containertools.PodmanTool, containertools.DockerTool: + registry, err = execregistry.NewRegistry(tool, logger) + case containertools.NoneTool: + registry, err = containerdregistry.NewRegistry(containerdregistry.WithLog(logger)) + default: + err = fmt.Errorf("unrecognized container-tool option: %s", containerTool) + } + Expect(err).NotTo(HaveOccurred()) + + unpackDir, err := ioutil.TempDir(".", bundleTag3) + Expect(err).NotTo(HaveOccurred()) + validator := bundle.NewImageValidator(registry, logger) + Expect(validator.PullBundleImage(img, unpackDir)).To(Succeed()) + + By("validating bundle format") + Expect(validator.ValidateBundleFormat(unpackDir)).To(Succeed()) + + By("validating bundle content") + manifestsDir := filepath.Join(unpackDir, bundle.ManifestsDir) + Expect(validator.ValidateBundleContent(manifestsDir)).To(Succeed()) + Expect(os.RemoveAll(unpackDir)).To(Succeed()) + }) + It("builds and manipulates bundle and index images", func() { By("building bundles") - for tag, path := range map[string]string{ + tagPaths := map[string]string{ bundleTag1: bundlePath1, bundleTag2: bundlePath2, bundleTag3: bundlePath3, - } { - err := inTemporaryBuildContext(func() error { + } + var err error + for tag, path := range tagPaths { + err = inTemporaryBuildContext(func() error { return bundle.BuildFunc(path, "", bundleImage+":"+tag, containerTool, packageName, channels, defaultChannel, false) }) Expect(err).NotTo(HaveOccurred()) } By("pushing bundles") - err := pushBundles(containerTool) - Expect(err).NotTo(HaveOccurred()) + for tag, _ := range tagPaths { + err = pushWith(containerTool, bundleImage+":"+tag) + Expect(err).NotTo(HaveOccurred()) + } By("building an index") err = buildIndexWith(containerTool, indexImage1, bundleImage, []string{bundleTag1, bundleTag2})