Skip to content
2 changes: 1 addition & 1 deletion hack/lib/test_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
source hack/lib/common.sh

function listPkgDirs() {
go list -f '{{.Dir}}' ./cmd/... ./pkg/... ./test/... ./internal/... | grep -v generated
go list -f '{{.Dir}}' ./cmd/... ./test/... ./internal/... | grep -v generated
}

function listFiles() {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/operator-sdk/cleanup/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
func NewCmd() *cobra.Command {
var timeout time.Duration
cfg := &operator.Configuration{}
cfg.Log = log.Infof
cmd := &cobra.Command{
Use: "cleanup <operatorPackageName>",
Short: "Clean up an Operator deployed with the 'run' subcommand",
Expand All @@ -41,6 +40,7 @@ func NewCmd() *cobra.Command {
u.Package = args[0]
u.DeleteAll = true
u.DeleteOperatorGroupNames = []string{operator.SDKOperatorGroupName}
u.Logf = log.Infof

ctx, cancel := context.WithTimeout(cmd.Context(), timeout)
defer cancel()
Expand Down
64 changes: 64 additions & 0 deletions internal/cmd/operator-sdk/run/bundle/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 (
"context"
"time"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/operator-framework/operator-sdk/internal/operator"
"github.com/operator-framework/operator-sdk/internal/operator/bundle"
)

func NewCmd() *cobra.Command {
var timeout time.Duration

// TODO(joelanford): move the initialization of cfg up to
// the "run" subcommand when migrating packagemanifests
// to this design.
cfg := &operator.Configuration{}

i := bundle.NewInstall(cfg)
cmd := &cobra.Command{
Use: "bundle <bundle-image>",
Short: "Deploy an Operator in the bundle format with OLM",
Args: cobra.ExactArgs(1),
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
return cfg.Load()
},
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(cmd.Context(), timeout)
defer cancel()

i.BundleImage = args[0]

// TODO(joelanford): Add cleanup logic if this fails?
csv, err := i.Run(ctx)
if err != nil {
logrus.Fatalf("Failed to run bundle: %v\n", err)
}
logrus.Infof("CSV %q installed\n", csv.Name)
},
}
cmd.Flags().SortFlags = false
cfg.BindFlags(cmd.PersistentFlags())
i.BindFlags(cmd.Flags())

cmd.Flags().DurationVar(&timeout, "timeout", 2*time.Minute, "install timeout")
return cmd
}
7 changes: 4 additions & 3 deletions internal/cmd/operator-sdk/run/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "Run an Operator in a variety of environments",
// TODO(joelanford): remove the second sentence when `run bundle` implementation is complete
Long: `This command has subcommands that will deploy your Operator with OLM.
Currently only the package manifests format is supported via the 'packagemanifests' subcommand.
Comment thread
joelanford marked this conversation as resolved.
Run 'operator-sdk run --help' for more information.
`,
Currently only the package manifests format is supported via the 'packagemanifests' subcommand.`,
}

cmd.AddCommand(
// TODO(joelanford): enable bundle command when implementation is complete
//bundle.NewCmd(),
packagemanifests.NewCmd(),
)

Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/operator-sdk/run/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var _ = Describe("Running a run command", func() {

subcommands := cmd.Commands()
Expect(len(subcommands)).To(Equal(1))
Expect(subcommands[0].Use).To(Equal("packagemanifests"))
Expect(subcommands[0].Use).To(Equal("packagemanifests <packagemanifests-root-dir>"))
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func NewCmd() *cobra.Command {
c := &packagemanifestsCmd{}

cmd := &cobra.Command{
Use: "packagemanifests",
Use: "packagemanifests <packagemanifests-root-dir>",
Short: "Deploy an Operator in the package manifests format with OLM",
Long: `'run packagemanifests' deploys an Operator's package manifests with OLM. The command's argument
must be set to a valid package manifests root directory, ex. '<project-root>/packagemanifests'.`,
Expand Down
10 changes: 1 addition & 9 deletions internal/cmd/operator-sdk/scorecard/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -204,17 +203,10 @@ func (c *scorecardCmd) validate(args []string) error {
return nil
}

// discardLogger returns a logger that throws away input.
func discardLogger() *log.Logger {
logger := log.New()
logger.SetOutput(ioutil.Discard)
return logger
}

// extractBundleImage returns bundleImage's path on disk post-extraction.
func extractBundleImage(bundleImage string) (string, error) {
// Discard bundle extraction logs unless user sets verbose mode.
logger := log.NewEntry(discardLogger())
logger := registryutil.DiscardLogger()
if viper.GetBool(flags.VerboseOpt) {
logger = log.WithFields(log.Fields{"bundle": bundleImage})
}
Expand Down
110 changes: 110 additions & 0 deletions internal/operator/bundle/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-registry/pkg/registry"
"github.com/spf13/pflag"

"github.com/operator-framework/operator-sdk/internal/operator"
"github.com/operator-framework/operator-sdk/internal/operator/internal"
registryutil "github.com/operator-framework/operator-sdk/internal/registry"
)

type Install struct {
BundleImage string

*internal.IndexImageCatalogCreator
*internal.OperatorInstaller
}

func NewInstall(cfg *operator.Configuration) Install {
i := Install{
OperatorInstaller: internal.NewOperatorInstaller(cfg),
}
i.IndexImageCatalogCreator = internal.NewIndexImageCatalogCreator(cfg)
i.CatalogCreator = i.IndexImageCatalogCreator
return i
}

const defaultIndexImage = "quay.io/operator-framework/upstream-opm-builder:latest"

func (i *Install) BindFlags(fs *pflag.FlagSet) {
fs.StringVar(&i.IndexImage, "index-image", defaultIndexImage, "index image in which to inject bundle")
fs.Var(&i.InstallMode, "install-mode", "install mode")
fs.StringVar(&i.InjectBundleMode, "mode", "", "mode to use for adding bundle to index")
_ = fs.MarkHidden("mode")
}

func (i Install) Run(ctx context.Context) (*v1alpha1.ClusterServiceVersion, error) {
if err := i.setup(ctx); err != nil {
return nil, err
}
return i.InstallOperator(ctx)
}

func (i *Install) setup(ctx context.Context) error {
labels, csv, err := loadBundle(ctx, i.BundleImage)
if err != nil {
return fmt.Errorf("load bundle: %v", err)
}

i.OperatorInstaller.PackageName = labels["operators.operatorframework.io.bundle.package.v1"]
Comment thread
rashmigottipati marked this conversation as resolved.
Copy link
Copy Markdown
Member

@estroz estroz Aug 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: I have a PR open that makes getting bundle metadata very straightforward, which we should start using once merged (no changes required now)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! Look forward to that.

i.OperatorInstaller.CatalogSourceName = fmt.Sprintf("%s-catalog", i.OperatorInstaller.PackageName)
i.OperatorInstaller.StartingCSV = csv.Name
i.OperatorInstaller.Channel = strings.Split(labels["operators.operatorframework.io.bundle.channels.v1"], ",")[0]

i.IndexImageCatalogCreator.InjectBundles = []string{i.BundleImage}
i.IndexImageCatalogCreator.InjectBundleMode = "replaces"
if i.IndexImageCatalogCreator.IndexImage == defaultIndexImage {
i.IndexImageCatalogCreator.InjectBundleMode = "semver"
}

return nil
}

func loadBundle(ctx context.Context, bundleImage string) (labels registryutil.Labels, csv *registry.ClusterServiceVersion, err error) {
bundlePath, err := registryutil.ExtractBundleImage(ctx, nil, bundleImage, false)
if err != nil {
return nil, nil, fmt.Errorf("pull bundle image: %v", err)
}
defer func() {
_ = os.RemoveAll(bundlePath)
}()

labels, _, err = registryutil.FindBundleMetadata(bundlePath)
if err != nil {
return nil, nil, fmt.Errorf("load bundle metadata: %v", err)
}

relManifestsDir, ok := labels.GetManifestsDir()
if !ok {
return nil, nil, fmt.Errorf("manifests directory not defined in bundle metadata")
}
manifestsDir := filepath.Join(bundlePath, relManifestsDir)
csv, err = registry.ReadCSVFromBundleDirectory(manifestsDir)
if err != nil {
return nil, nil, fmt.Errorf("read bundle csv: %v", err)
}

return labels, csv, nil
}
4 changes: 0 additions & 4 deletions internal/operator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ type Configuration struct {
RESTConfig *rest.Config
Client client.Client
Scheme *runtime.Scheme
Log func(string, ...interface{})

overrides *clientcmd.ConfigOverrides
}
Expand All @@ -61,9 +60,6 @@ func (c *Configuration) Load() error {
if c.overrides == nil {
c.overrides = &clientcmd.ConfigOverrides{}
}
if c.Log == nil {
c.Log = func(_ string, _ ...interface{}) {}
}
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.ExplicitPath = c.KubeconfigPath
mergedConfig, err := loadingRules.Load()
Expand Down
25 changes: 25 additions & 0 deletions internal/operator/internal/catalog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 internal

import (
"context"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

type CatalogCreator interface {
CreateCatalog(ctx context.Context, name string) (*v1alpha1.CatalogSource, error)
}
82 changes: 82 additions & 0 deletions internal/operator/internal/index_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 internal

import (
"context"
"fmt"
"strings"

"github.com/operator-framework/api/pkg/operators/v1alpha1"

"github.com/operator-framework/operator-sdk/internal/operator"
registryutil "github.com/operator-framework/operator-sdk/internal/registry"
)

type IndexImageCatalogCreator struct {
IndexImage string
InjectBundles []string
InjectBundleMode string

cfg *operator.Configuration
Comment thread
rashmigottipati marked this conversation as resolved.
}

func NewIndexImageCatalogCreator(cfg *operator.Configuration) *IndexImageCatalogCreator {
Copy link
Copy Markdown
Member

@estroz estroz Aug 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason IndexImageCatalogCreator is exported and this signature isn't

Suggested change
func NewIndexImageCatalogCreator(cfg *operator.Configuration) *IndexImageCatalogCreator {
func NewIndexImageCatalogCreator(cfg *operator.Configuration, indexImage string, injectBundles []string, injectBundleMode string) CatalogCreator {

?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a look at this for a follow-up.

Copy link
Copy Markdown
Member Author

@joelanford joelanford Aug 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, one reason is that we could make NewIndexImageCatalogCreator work with just an index image. In that case, it could skip the entire registry pod setup and just create the CatalogSource with the spec.index field set.

cc @rashmigottipati

return &IndexImageCatalogCreator{
cfg: cfg,
}
}

func (c IndexImageCatalogCreator) CreateCatalog(ctx context.Context, name string) (*v1alpha1.CatalogSource, error) {
dbPath, err := c.getDBPath(ctx)
if err != nil {
return nil, fmt.Errorf("get database path: %v", err)
}

fmt.Printf("IndexImageCatalogCreator.IndexImage: %q\n", c.IndexImage)
fmt.Printf("IndexImageCatalogCreator.IndexImageDBPath: %v\n", dbPath)
fmt.Printf("IndexImageCatalogCreator.InjectBundles: %q\n", strings.Join(c.InjectBundles, ","))
fmt.Printf("IndexImageCatalogCreator.InjectBundleMode: %q\n", c.InjectBundleMode)

// Create barebones catalog source

// Create registry pod, assigning its owner as the catalog source

// Wait for registry pod to be ready

// Update catalog source with `spec.Address = pod.status.podIP`

// Update catalog source with annotations for index image,
// injected bundle, and registry add mode

// Wait for catalog source status to indicate a successful
// connection with the registry pod

// Return the catalog source
return nil, nil
}

const defaultDBPath = "/database/index.db"

func (c IndexImageCatalogCreator) getDBPath(ctx context.Context) (string, error) {
labels, err := registryutil.GetImageLabels(ctx, nil, c.IndexImage, false)
if err != nil {
return "", fmt.Errorf("get index image labels: %v", err)
}
if dbPath, ok := labels["operators.operatorframework.io.index.database.v1"]; ok {
return dbPath, nil
}
return defaultDBPath, nil
}
Loading