Skip to content

Commit

Permalink
This commit adds scorecard bundle metadata "mediaType" and "config",
Browse files Browse the repository at this point in the history
which are written to bundle metadata on `generate bundle` when either
`--overwrite` is set or metadata files do not exist. "config.yaml"
is a hard-coded file name for the scorecard config file.

cmd/operator-sdk/generate/bundle: write scorecard bundle metadata
to annotations.yaml and bundle.Dockerfile

cmd/operator-sdk/scorecard: use scorecard metadata config path if
it exists, defaulting to `tests/scorecard/config.yaml`

internal/annotations: consolidate annotations for metrics and scorecard
in subpackages here

internal/scorecard: encode "config.yaml" as hard-coded file name for
the scorecard config, and add metadata to example annotations.yaml files
  • Loading branch information
estroz committed Jul 21, 2020
1 parent 581238d commit 079c762
Show file tree
Hide file tree
Showing 18 changed files with 490 additions and 202 deletions.
6 changes: 6 additions & 0 deletions changelog/fragments/scorecard-bundle-metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
entries:
- description: >
`generate bundle` now adds scorecard bundle metadata to bundle.Dockerfile and annotations.yaml
if `--overwrite` is set (the default in a project's `Makefile`) or both files do not exist.
kind: addition
breaking: false
119 changes: 70 additions & 49 deletions cmd/operator-sdk/generate/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ package bundle
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"

"github.com/operator-framework/operator-registry/pkg/lib/bundle"
yaml "gopkg.in/yaml.v3"
"sigs.k8s.io/kubebuilder/pkg/model/config"

genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal"
metricsannotations "github.com/operator-framework/operator-sdk/internal/annotations/metrics"
scorecardannotations "github.com/operator-framework/operator-sdk/internal/annotations/scorecard"
gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion"
"github.com/operator-framework/operator-sdk/internal/generate/collector"
"github.com/operator-framework/operator-sdk/internal/registry"
Expand Down Expand Up @@ -231,7 +236,7 @@ func (c bundleCmd) validateMetadata(*config.Config) (err error) {
}

// runMetadata generates a bundle.Dockerfile and bundle metadata.
func (c bundleCmd) runMetadata() error {
func (c bundleCmd) runMetadata(cfg *config.Config) error {

directory := c.inputDir
if directory == "" {
Expand All @@ -251,63 +256,67 @@ func (c bundleCmd) runMetadata() error {
outputDir = ""
}

return c.generateMetadata(directory, outputDir)
return c.generateMetadata(cfg, directory, outputDir)
}

// generateMetadata wraps the operator-registry bundle Dockerfile/metadata generator.
func (c bundleCmd) generateMetadata(manifestsDir, outputDir string) error {
func (c bundleCmd) generateMetadata(cfg *config.Config, manifestsDir, outputDir string) error {

metadataExists := checkMetatdataExists(outputDir, manifestsDir)
metadataExisted := checkMetatdataExists(outputDir, manifestsDir)
err := bundle.GenerateFunc(manifestsDir, outputDir, c.operatorName, c.channels, c.defaultChannel, c.overwrite)
if err != nil {
return fmt.Errorf("error generating bundle metadata: %v", err)
}

// Add SDK stamps if metadata is not present before or when overwrite is set to true.
if c.overwrite || !metadataExists {
rootDir := outputDir
if rootDir == "" {
rootDir = filepath.Dir(manifestsDir)
// Add SDK annotations/labels if metadata did not exist before or when overwrite is true.
if c.overwrite || !metadataExisted {
bundleRoot := outputDir
if bundleRoot == "" {
bundleRoot = filepath.Dir(manifestsDir)
}

if err = rewriteBundleImageContents(rootDir); err != nil {
if err = updateMetadata(cfg, bundleRoot); err != nil {
return err
}
}
return nil
}

func rewriteBundleImageContents(rootDir string) error {
metricLabels := projutil.MakeBundleMetricsLabels()

// write metric labels to bundle.Dockerfile
if err := addLabelsToDockerfile(bundle.DockerFile, metricLabels); err != nil {
return fmt.Errorf("error writing metric labels to bundle.dockerfile: %v", err)
func updateMetadata(cfg *config.Config, bundleRoot string) error {
bundleLabels := metricsannotations.MakeBundleMetadataLabels(cfg)
for key, value := range scorecardannotations.MakeBundleMetadataLabels(scorecard.DefaultConfigDir) {
if _, hasKey := bundleLabels[key]; hasKey {
return fmt.Errorf("internal error: duplicate bundle annotation key %s", key)
}
bundleLabels[key] = value
}

annotationsFilePath := getAnnotationsFilePath(rootDir)
if err := addLabelsToAnnotations(annotationsFilePath, metricLabels); err != nil {
return fmt.Errorf("error writing metric labels to annotations.yaml: %v", err)
// Write labels to bundle Dockerfile.
// NB(estroz): these "rewrites" need to be atomic because the bundle's Dockerfile and annotations.yaml
// cannot be out-of-sync.
if err := rewriteDockerfileLabels(bundle.DockerFile, bundleLabels); err != nil {
return fmt.Errorf("error writing LABEL's in %s: %v", bundle.DockerFile, err)
}
if err := rewriteAnnotations(bundleRoot, bundleLabels); err != nil {
return fmt.Errorf("error writing LABEL's in bundle metadata: %v", err)
}

// Add a COPY for the scorecard config to bundle.Dockerfile.
if err := copyScorecardConfig(); err != nil {
return fmt.Errorf("error copying scorecardConfig to bundle image, %v", err)
// Add a COPY for the scorecard config to bundle Dockerfile.
// TODO: change input config path to be a flag-based value.
err := writeDockerfileCOPYScorecardConfig(bundle.DockerFile, filepath.FromSlash(scorecard.DefaultConfigDir))
if err != nil {
return fmt.Errorf("error writing scorecard config COPY in %s: %v", bundle.DockerFile, err)
}

return nil
}

// copyScorecardConfigToBundle checks if bundle.Dockerfile and scorecard config exists in
// the operator project. If it does, it injects the scorecard configuration into bundle
// image.
// TODO: Add labels to annotations.yaml and bundle.dockerfile.
func copyScorecardConfig() error {
if isExist(bundle.DockerFile) && isExist(scorecard.ConfigDirName) {
scorecardFileContent := fmt.Sprintf("COPY %s %s\n", scorecard.ConfigDirName, scorecard.ConfigDirPath)
err := projutil.RewriteFileContents(bundle.DockerFile, "COPY", scorecardFileContent)
if err != nil {
return fmt.Errorf("error rewriting dockerfile, %v", err)
}
// writeDockerfileCOPYScorecardConfig checks if bundle.Dockerfile and scorecard config exists in
// the operator project. If it does, it injects the scorecard configuration into bundle image.
func writeDockerfileCOPYScorecardConfig(dockerfileName, localConfigDir string) error {
if isExist(bundle.DockerFile) && isExist(localConfigDir) {
scorecardFileContent := fmt.Sprintf("COPY %s %s\n", localConfigDir, "/"+scorecard.DefaultConfigDir)
return projutil.RewriteFileContents(dockerfileName, "COPY", scorecardFileContent)
}
return nil
}
Expand All @@ -328,30 +337,42 @@ func checkMetatdataExists(outputDir, manifestsDir string) bool {
return true
}

func addLabelsToDockerfile(filename string, metricAnnotation map[string]string) error {
var sdkMetricContent strings.Builder
for key, value := range metricAnnotation {
sdkMetricContent.WriteString(fmt.Sprintf("LABEL %s=%s\n", key, value))
func rewriteDockerfileLabels(dockerfileName string, kvs map[string]string) error {
var labelStrings []string
for key, value := range kvs {
labelStrings = append(labelStrings, fmt.Sprintf("LABEL %s=%s\n", key, value))
}

err := projutil.RewriteFileContents(filename, "LABEL", sdkMetricContent.String())
if err != nil {
return fmt.Errorf("error rewriting dockerfile with metric labels, %v", err)
sort.Strings(labelStrings)
var newBundleLabels strings.Builder
for _, line := range labelStrings {
newBundleLabels.WriteString(line)
}
return nil
}

// getAnnotationsFilePath return the locations of annotations.yaml.
func getAnnotationsFilePath(rootDir string) string {
return filepath.Join(rootDir, bundle.MetadataDir, bundle.AnnotationsFile)
return projutil.RewriteFileContents(dockerfileName, "LABEL", newBundleLabels.String())
}

func addLabelsToAnnotations(filename string, metricLables map[string]string) error {
err := registry.RewriteAnnotationsYaml(filename, metricLables)
func rewriteAnnotations(bundleRoot string, kvs map[string]string) error {
annotations, annotationsPath, err := registry.FindBundleMetadata(bundleRoot)
if err != nil {
return err
}
return nil

for key, value := range kvs {
annotations[key] = value
}
annotationsFile := bundle.AnnotationMetadata{
Annotations: annotations,
}
b, err := yaml.Marshal(annotationsFile)
if err != nil {
return err
}

mode := os.FileMode(0666)
if info, err := os.Stat(annotationsPath); err == nil {
mode = info.Mode()
}
return ioutil.WriteFile(annotationsPath, b, mode)
}

// isExist returns true if path exists.
Expand Down
203 changes: 203 additions & 0 deletions cmd/operator-sdk/generate/bundle/bundle_legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright 2020 The Operator-SDK 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 bundle

import (
"fmt"
"path/filepath"
"strings"

"github.com/operator-framework/operator-registry/pkg/lib/bundle"
log "github.com/sirupsen/logrus"

genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal"
gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion"
"github.com/operator-framework/operator-sdk/internal/generate/collector"
"github.com/operator-framework/operator-sdk/internal/util/projutil"
)

const (
longHelpLegacy = `
Running 'generate bundle' is the first step to publishing your operator to a catalog
and/or deploying it with OLM. This command generates a set of bundle manifests,
metadata, and a bundle.Dockerfile for your operator, and will interactively ask
for UI metadata, an important component of publishing your operator, by default unless
a bundle for your operator exists or you set '--interactive=false'.
Set '--version' to supply a semantic version for your bundle if you are creating one
for the first time or upgrading an existing one.
If '--output-dir' is set and you wish to build bundle images from that directory,
either manually update your bundle.Dockerfile or set '--overwrite'.
More information on bundles:
https://github.com/operator-framework/operator-registry/#manifest-format
`

examplesLegacy = `
# Create bundle manifests, metadata, and a bundle.Dockerfile:
$ operator-sdk generate bundle --version 0.0.1
INFO[0000] Generating bundle manifest version 0.0.1
Display name for the operator (required):
> memcached-operator
...
# After running the above commands, you should see:
$ tree deploy/olm-catalog
deploy/olm-catalog
└── memcached-operator
├── manifests
│ ├── cache.example.com_memcacheds_crd.yaml
│ └── memcached-operator.clusterserviceversion.yaml
└── metadata
└── annotations.yaml
# Then build and push your bundle image:
$ export USERNAME=<your registry username>
$ export BUNDLE_IMG=quay.io/$USERNAME/memcached-operator-bundle:v0.0.1
$ docker build -f bundle.Dockerfile -t $BUNDLE_IMG .
Sending build context to Docker daemon 42.33MB
Step 1/9 : FROM scratch
...
$ docker push $BUNDLE_IMG
`
)

// setCommonDefaultsLegacy sets defaults useful to all modes of this subcommand.
func (c *bundleCmdLegacy) setCommonDefaults() {
if c.operatorName == "" {
c.operatorName = filepath.Base(projutil.MustGetwd())
}
// A default channel can be inferred if there is only one channel. Don't infer
// default otherwise; the user must set this value.
if c.defaultChannel == "" && strings.Count(c.channels, ",") == 0 {
c.defaultChannel = c.channels
}
}

// validateManifestsLegacy validates c for bundle manifests generation for
// legacy project layouts.
func (c bundleCmdLegacy) validateManifests() error {
if c.version != "" {
if err := genutil.ValidateVersion(c.version); err != nil {
return err
}
}
return nil
}

// runManifestsLegacy generates bundle manifests for legacy project layouts.
func (c bundleCmdLegacy) runManifests() (err error) {

if !c.quiet {
if c.version == "" {
log.Info("Generating bundle manifests")
} else {
log.Infoln("Generating bundle manifests version", c.version)
}
}

if c.apisDir == "" {
c.apisDir = filepath.Join("pkg", "apis")
}
if c.deployDir == "" {
c.deployDir = "deploy"
}
if c.crdsDir == "" {
c.crdsDir = filepath.Join(c.deployDir, "crds")
}
defaultBundleDir := filepath.Join(c.deployDir, "olm-catalog", c.operatorName)
if c.inputDir == "" {
c.inputDir = defaultBundleDir
}
if c.outputDir == "" {
c.outputDir = defaultBundleDir
}

col := &collector.Manifests{}
if err := col.UpdateFromDirs(c.deployDir, c.crdsDir); err != nil {
return err
}

csvGen := gencsv.Generator{
OperatorName: c.operatorName,
OperatorType: projutil.GetOperatorType(),
Version: c.version,
Collector: col,
}

opts := []gencsv.LegacyOption{
gencsv.WithBundleBase(c.inputDir, c.apisDir, c.interactiveLevel),
gencsv.LegacyOption(gencsv.WithBundleWriter(c.outputDir)),
}
if err := csvGen.GenerateLegacy(opts...); err != nil {
return fmt.Errorf("error generating ClusterServiceVersion: %v", err)
}

var objs []interface{}
for _, crd := range col.V1CustomResourceDefinitions {
objs = append(objs, crd)
}
for _, crd := range col.V1beta1CustomResourceDefinitions {
objs = append(objs, crd)
}
dir := filepath.Join(c.outputDir, bundle.ManifestsDir)
if err := genutil.WriteObjectsToFilesLegacy(dir, objs...); err != nil {
return err
}

if !c.quiet {
log.Infoln("Bundle manifests generated successfully in", c.outputDir)
}

return nil
}

// validateMetadataLegacy validates c for bundle metadata generation for
// legacy project layouts.
func (c bundleCmdLegacy) validateMetadata() (err error) {
// Ensure a default channel is present.
if c.defaultChannel == "" {
return fmt.Errorf("--default-channel must be set if setting multiple channels")
}

return nil
}

// runMetadataLegacy generates a bundle.Dockerfile and bundle metadata for
// legacy project layouts.
func (c bundleCmdLegacy) runMetadata() error {

directory := c.inputDir
if directory == "" {
// There may be no existing bundle at the default path, so assume manifests
// were generated in the output directs.
defaultDirectory := filepath.Join("deploy", "olm-catalog", c.operatorName, bundle.ManifestsDir)
if c.outputDir != "" && genutil.IsNotExist(defaultDirectory) {
directory = filepath.Join(c.outputDir, bundle.ManifestsDir)
} else {
directory = defaultDirectory
}
} else {
directory = filepath.Join(directory, bundle.ManifestsDir)
}
outputDir := c.outputDir
if filepath.Clean(outputDir) == filepath.Clean(directory) {
outputDir = ""
}

return c.generateMetadata(nil, directory, outputDir)
}

0 comments on commit 079c762

Please sign in to comment.