Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- The printout for the compatible Kubernetes Version [#2446](https://github.com/operator-framework/operator-sdk/pull/2446)
- The `--output-dir` flag instructs [`operator-sdk bundle create`](./doc/cli/operator-sdk_bundle_create.md) to write manifests and metadata to a non-default directory. ([#2715](https://github.com/operator-framework/operator-sdk/pull/2715))
- The `--overwrite` flag instructs [`operator-sdk bundle create`](./doc/cli/operator-sdk_bundle_create.md) to overwrite metadata, manifests, and `bundle.Dockerfile`. ([#2715](https://github.com/operator-framework/operator-sdk/pull/2715))
- [`operator-sdk bundle validate`](./doc/cli/operator-sdk_bundle_validate.md) now accepts either an image tag or a directory arg. If the arg is a directory, its children must contain a `manifests/` and a `metadata/` directory. [#2737](https://github.com/operator-framework/operator-sdk/pull/2737)

### Changed

Expand All @@ -30,7 +31,7 @@
### Removed

- **Breaking Change:** remove `pkg/restmapper` which was deprecated in `v0.14.0`. Projects that use this package must switch to the `DynamicRESTMapper` implementation in [controller-runtime](https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/client/apiutil#NewDynamicRESTMapper). ([#2544](https://github.com/operator-framework/operator-sdk/pull/2544))
- **Breaking Change:** remove deprecated `operator-sdk generate openapi` subcommand. ([#2740](https://github.com/operator-framework/operator-sdk/pull/2740))
- **Breaking Change:** remove deprecated `operator-sdk generate openapi` subcommand. ([#2740](https://github.com/operator-framework/operator-sdk/pull/2740))
- **Breaking Change:** Removed CSV configuration file support (defaulting to deploy/olm-catalog/csv-config.yaml) in favor of specifying inputs to the generator via [`generate csv --deploy-dir --apis-dir --crd-dir`](doc/cli/operator-sdk_generate_csv.md#options), and configuring output locations via [`generate csv --output-dir`](doc/cli/operator-sdk_generate_csv.md#options). ([#2511](https://github.com/operator-framework/operator-sdk/pull/2511))

### Bug Fixes
Expand Down
25 changes: 14 additions & 11 deletions cmd/operator-sdk/bundle/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ building the image:
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
if err = c.setDefaults(); err != nil {
return fmt.Errorf("error setting default args: %v", err)
log.Fatalf("Failed to default args: %v", err)
}

if err = c.validate(args); err != nil {
Expand Down Expand Up @@ -183,29 +183,32 @@ func (c *bundleCreateCmd) setDefaults() (err error) {
// by only assuming the operator dir is the packageName if directory isn't set.
c.packageName = projectName
}

// Clean and make paths relative for less verbose error messages.
if c.directory, err = filepath.Abs(c.directory); err != nil {
if c.directory, err = relDir(c.directory); err != nil {
return err
}
// Set outputDir in any case so we make the operator-registry file generator
// write 'manifests/' every time, and handle cleanup logic in runBuild().
if c.outputDir == "" {
c.outputDir = filepath.Dir(c.directory)
}
if c.outputDir, err = filepath.Abs(c.outputDir); err != nil {
if c.outputDir, err = relDir(c.outputDir); err != nil {
return err
}

return nil
}

func relDir(dir string) (out string, err error) {
if out, err = filepath.Abs(dir); err != nil {
return "", err
}
wd, err := os.Getwd()
if err != nil {
return err
}
if c.directory, err = filepath.Rel(wd, c.directory); err != nil {
return err
return "", err
}
if c.outputDir, err = filepath.Rel(wd, c.outputDir); err != nil {
return err
}
return nil
return filepath.Rel(wd, out)
}

func (c bundleCreateCmd) validate(args []string) error {
Expand Down
165 changes: 113 additions & 52 deletions cmd/operator-sdk/bundle/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,88 +16,149 @@ package bundle

import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"github.com/operator-framework/operator-sdk/internal/flags"

"github.com/operator-framework/operator-registry/pkg/lib/bundle"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

type bundleValidateCmd struct {
bundleCmd
}

// newValidateCmd returns a command that will validate an operator bundle image.
func newValidateCmd() *cobra.Command {
c := bundleCmd{}
c := bundleValidateCmd{}
cmd := &cobra.Command{
Use: "validate",
Short: "Validate an operator bundle image",
Long: `The 'operator-sdk bundle validate' command will validate both content and
format of an operator bundle image containing operator metadata and manifests.
This command will exit with a non-zero exit code if any validation tests fail.

Note: the image being validated must exist in a remote registry, not just locally.`,
Long: `The 'operator-sdk bundle validate' command can validate both content and
format of an operator bundle image or an operator bundles directory on-disk
containing operator metadata and manifests. This command will exit with a non-zero
exit code if any validation tests fail.

Note: if validating an image, the image must exist in a remote registry, not
just locally.
`,
Example: `The following command flow will generate test-operator bundle image manifests
and validate that image:
and validate them, assuming a bundle for 'test-operator' version v0.1.0 exists at
<project-root>/deploy/olm-catalog/test-operator/0.1.0:

$ cd ${HOME}/go/test-operator
# Generate manifests locally.
$ operator-sdk bundle create \
--generate-only \
--directory ./deploy/olm-catalog/test-operator/0.1.0

# Generate manifests locally.
$ operator-sdk bundle create --generate-only
# Validate the directory containing manifests and metadata.
$ operator-sdk bundle validate ./deploy/olm-catalog/test-operator

# Modify the metadata and Dockerfile.
$ cd ./deploy/olm-catalog/test-operator
$ vim ./metadata/annotations.yaml
$ vim ./Dockerfile
To build and validate an image:

# Build and push the image using the docker CLI.
$ docker build -t quay.io/example/test-operator:v0.1.0 .
$ docker push quay.io/example/test-operator:v0.1.0
# Build and push the image using the docker CLI.
$ operator-sdk bundle create quay.io/example/test-operator:v0.1.0 \
--directory ./deploy/olm-catalog/test-operator/0.1.0
$ docker push quay.io/example/test-operator:v0.1.0

# Ensure the image with modified metadata/Dockerfile is valid.
$ operator-sdk bundle validate quay.io/example/test-operator:v0.1.0`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("a bundle image tag is a required argument, ex. example.com/test-operator:v0.1.0")
}
c.imageTag = args[0]
# Ensure the image with modified metadata and Dockerfile is valid.
$ operator-sdk bundle validate quay.io/example/test-operator:v0.1.0

dir, err := ioutil.TempDir("", "bundle-")
if err != nil {
log.Fatal(err)
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
if err = c.validate(args); err != nil {
return fmt.Errorf("error validating args: %v", err)
}
defer func() {
if err = os.RemoveAll(dir); err != nil {
log.Error(err.Error())
// If the argument isn't a directory, assume it's an image.
if isExist(args[0]) {
if c.directory, err = relDir(args[0]); err != nil {
log.Fatal(err)
}
}()
logger := log.WithFields(log.Fields{
"container-tool": c.imageBuilder,
"bundle-dir": dir,
})
log.SetLevel(log.DebugLevel)
val := bundle.NewImageValidator(c.imageBuilder, logger)
if err = val.PullBundleImage(c.imageTag, dir); err != nil {
log.Fatalf("Error to unpacking image: %v", err)
} else {
c.imageTag = args[0]
}
if err = c.run(); err != nil {
log.Fatal(err)
}
return nil
},
}

log.Info("Validating bundle image format and contents")
c.addToFlagSet(cmd.Flags())

if err = val.ValidateBundleFormat(dir); err != nil {
log.Fatalf("Bundle format validation failed: %v", err)
}
manifestsDir := filepath.Join(dir, bundle.ManifestsDir)
if err = val.ValidateBundleContent(manifestsDir); err != nil {
log.Fatalf("Bundle content validation failed: %v", err)
return cmd
}

func (c bundleValidateCmd) validate(args []string) error {
if len(args) != 1 {
return errors.New("an image tag or directory is a required argument")
}
return nil
}

func (c *bundleValidateCmd) addToFlagSet(fs *pflag.FlagSet) {
fs.StringVarP(&c.imageBuilder, "image-builder", "b", "docker",
"Tool to extract container images. One of: [docker, podman]")
}

func (c bundleValidateCmd) run() (err error) {
// Set directory, either supplied directly or a temp dir used to unpack
// the image.
dir := c.directory
if c.imageTag != "" {
dir, err = ioutil.TempDir("", "bundle-")
if err != nil {
return err
}
defer func() {
if err = os.RemoveAll(dir); err != nil {
log.Errorf("Error removing temp bundle dir: %v", err)
}
}()
}
if dir, err = filepath.Abs(dir); err != nil {
return err
}

log.Info("All validation tests have completed successfully")
// Set up logger.
fields := log.Fields{"bundle-dir": dir}
if c.imageTag != "" {
fields["container-tool"] = c.imageBuilder
}
logger := log.WithFields(fields)
if viper.GetBool(flags.VerboseOpt) {
log.SetLevel(log.DebugLevel)
}

return nil
},
val := bundle.NewImageValidator(c.imageBuilder, logger)

// Pull image if a tag was passed.
if c.imageTag != "" {
logger.Info("Unpacked image layers")
err = val.PullBundleImage(c.imageTag, dir)
if err != nil {
logger.Fatalf("Error to unpacking image: %v", err)
}
}

cmd.Flags().StringVarP(&c.imageBuilder, "image-builder", "b", "docker",
"Tool to extract container images. One of: [docker, podman]")
// Validate bundle format.
if err = val.ValidateBundleFormat(dir); err != nil {
logger.Fatalf("Bundle format validation failed: %v", err)
}

return cmd
// Validate bundle content.
manifestsDir := filepath.Join(dir, bundle.ManifestsDir)
if err = val.ValidateBundleContent(manifestsDir); err != nil {
logger.Fatalf("Bundle content validation failed: %v", err)
}

logger.Info("All validation tests have completed successfully")

return nil
}
41 changes: 24 additions & 17 deletions doc/cli/operator-sdk_bundle_validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ Validate an operator bundle image

### Synopsis

The 'operator-sdk bundle validate' command will validate both content and
format of an operator bundle image containing operator metadata and manifests.
This command will exit with a non-zero exit code if any validation tests fail.
The 'operator-sdk bundle validate' command can validate both content and
format of an operator bundle image or an operator bundles directory on-disk
containing operator metadata and manifests. This command will exit with a non-zero
exit code if any validation tests fail.

Note: if validating an image, the image must exist in a remote registry, not
just locally.

Note: the image being validated must exist in a remote registry, not just locally.

```
operator-sdk bundle validate [flags]
Expand All @@ -18,24 +21,28 @@ operator-sdk bundle validate [flags]

```
The following command flow will generate test-operator bundle image manifests
and validate that image:
and validate them, assuming a bundle for 'test-operator' version v0.1.0 exists at
<project-root>/deploy/olm-catalog/test-operator/0.1.0:

# Generate manifests locally.
$ operator-sdk bundle create \
--generate-only \
--directory ./deploy/olm-catalog/test-operator/0.1.0

# Validate the directory containing manifests and metadata.
$ operator-sdk bundle validate ./deploy/olm-catalog/test-operator

$ cd ${HOME}/go/test-operator
To build and validate an image:

# Generate manifests locally.
$ operator-sdk bundle create --generate-only
# Build and push the image using the docker CLI.
$ operator-sdk bundle create quay.io/example/test-operator:v0.1.0 \
--directory ./deploy/olm-catalog/test-operator/0.1.0
$ docker push quay.io/example/test-operator:v0.1.0

# Modify the metadata and Dockerfile.
$ cd ./deploy/olm-catalog/test-operator
$ vim ./metadata/annotations.yaml
$ vim ./Dockerfile
# Ensure the image with modified metadata and Dockerfile is valid.
$ operator-sdk bundle validate quay.io/example/test-operator:v0.1.0

# Build and push the image using the docker CLI.
$ docker build -t quay.io/example/test-operator:v0.1.0 .
$ docker push quay.io/example/test-operator:v0.1.0

# Ensure the image with modified metadata/Dockerfile is valid.
$ operator-sdk bundle validate quay.io/example/test-operator:v0.1.0
```

### Options
Expand Down
1 change: 1 addition & 0 deletions doc/user/olm-catalog/bundle-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The following `operator-sdk` subcommands create or interact with Operator on-dis
| **operator-sdk** | **opm** |
|--- |--- |
| `operator-sdk bundle validate <image-tag>` | `opm alpha bundle validate --tag <image-tag>` |
| `operator-sdk bundle validate <directory>` | no equivalent |

[sdk-generate-csv]:./generating-a-csv.md
[registry-bundle]:https://github.com/operator-framework/operator-registry/tree/v1.5.3#manifest-format
Expand Down
38 changes: 38 additions & 0 deletions hack/lib/test_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,41 @@ function add_go_mod_replace() {
fi
echo "$replace" >> go.mod
}

# check_dir accepts 3 args:
# 1: test case string
# 2: directory to test for existence
# 3: either 0 or 1, where 0 means "dir should exist", and 1 means
# "dir should not exist". The command fails if the condition is not met.
function check_dir() {
if [[ $3 == 0 ]]; then
if [[ -d "$2" ]]; then
error_text "${1}: directory ${2} should not exist"
exit 1
fi
else
if [[ ! -d "$2" ]]; then
error_text "${1}: directory ${2} should exist"
exit 1
fi
fi
}

# check_file accepts 3 args:
# 1: test case string
# 2: file to test for existence
# 3: either 0 or 1, where 0 means "file should exist", and 1 means
# "file should not exist". The command fails if the condition is not met.
function check_file() {
if [[ $3 == 0 ]]; then
if [[ -f "$2" ]]; then
error_text "${1}: file ${2} should not exist"
exit 1
fi
else
if [[ ! -f "$2" ]]; then
error_text "${1}: file ${2} should exist"
exit 1
fi
fi
}
Loading