Skip to content

Commit

Permalink
generate: add packagemanifests subcommand for new project layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
estroz committed Jun 9, 2020
1 parent d8d9314 commit ebd8b35
Show file tree
Hide file tree
Showing 13 changed files with 1,006 additions and 16 deletions.
3 changes: 3 additions & 0 deletions changelog/fragments/generate-packagemanifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
entries:
- description: add `generate packagemanifests` subcommand for new project layouts
kind: addition
2 changes: 2 additions & 0 deletions cmd/operator-sdk/generate/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/spf13/cobra"

"github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/bundle"
"github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/packagemanifests"
)

func newCmd() *cobra.Command {
Expand All @@ -34,6 +35,7 @@ func NewCmd() *cobra.Command {
cmd := newCmd()
cmd.AddCommand(
bundle.NewCmd(),
packagemanifests.NewCmd(),
)
return cmd
}
Expand Down
138 changes: 138 additions & 0 deletions cmd/operator-sdk/generate/packagemanifests/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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 packagemanifests

import (
"fmt"

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

kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder"
"github.com/operator-framework/operator-sdk/internal/util/projutil"
)

//nolint:maligned
type packagemanifestsCmd struct {
// Options to turn on different parts of packaging.
kustomize bool
manifests bool

// Common options.
operatorName string
version string
fromVersion string
inputDir string
outputDir string
deployDir string
apisDir string
crdsDir string
updateCRDs bool
stdout bool
quiet bool

// Interactive options.
interactiveLevel projutil.InteractiveLevel
interactive bool

// Package manifest options.
channelName string
isDefaultChannel bool
}

// NewCmd returns the 'packagemanifests' command configured for the new project layout.
func NewCmd() *cobra.Command {
c := &packagemanifestsCmd{}

cmd := &cobra.Command{
Use: "packagemanifests",
Short: "Generates a package manifests format",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath())
}

// Check if the user has any specific preference to enable/disable
// interactive prompts. Default behaviour is to disable the prompt
// unless a base package does not exist.
if cmd.Flags().Changed("interactive") {
if c.interactive {
c.interactiveLevel = projutil.InteractiveOnAll
} else {
c.interactiveLevel = projutil.InteractiveHardOff
}
}

// Generate kustomize bases and manifests by default if no flags are set
// so the default behavior is "do everything".
fs := cmd.Flags()
if !fs.Changed("kustomize") && !fs.Changed("manifests") {
c.kustomize = true
c.manifests = true
}

cfg, err := kbutil.ReadConfig()
if err != nil {
log.Fatal(fmt.Errorf("error reading configuration: %v", err))
}
c.setCommonDefaults(cfg)

if c.kustomize {
if err = c.runKustomize(cfg); err != nil {
log.Fatalf("Error generating package bases: %v", err)
}
}
if c.manifests {
if err = c.validateManifests(); err != nil {
return fmt.Errorf("invalid command options: %v", err)
}
if err = c.runManifests(cfg); err != nil {
log.Fatalf("Error generating package manifests: %v", err)
}
}

return nil
},
}

cmd.Flags().BoolVar(&c.kustomize, "kustomize", false, "Generate kustomize bases")
cmd.Flags().BoolVar(&c.manifests, "manifests", false, "Generate package manifests")
cmd.Flags().BoolVar(&c.stdout, "stdout", false, "Write package to stdout")

c.addCommonFlagsTo(cmd.Flags())

return cmd
}

func (c *packagemanifestsCmd) addCommonFlagsTo(fs *pflag.FlagSet) {
fs.StringVar(&c.operatorName, "operator-name", "", "Name of the packaged operator")
fs.StringVarP(&c.version, "version", "v", "", "Semantic version of the packaged operator")
fs.StringVar(&c.inputDir, "input-dir", "", "Directory to read existing package manifests from. "+
"This directory is the parent of individual versioned package directories, and different from --deploy-dir")
fs.StringVar(&c.outputDir, "output-dir", "", "Directory in which to write package manifests")
fs.StringVar(&c.deployDir, "deploy-dir", "", "Root directory for operator manifests such as "+
"Deployments and RBAC, ex. 'deploy'. This directory is different from that passed to --input-dir")
fs.StringVar(&c.apisDir, "apis-dir", "", "Root directory for API type defintions")
fs.StringVar(&c.crdsDir, "crds-dir", "", "Root directory for CustomResoureDefinition manifests")
fs.StringVar(&c.channelName, "channel", "", "Channel name for the generated package")
fs.BoolVar(&c.isDefaultChannel, "default-channel", false, "Use the channel passed to --channel "+
"as the package manifest file's default channel")
fs.BoolVar(&c.updateCRDs, "update-crds", false, "Update CustomResoureDefinition manifests "+
"in this package")
fs.BoolVarP(&c.quiet, "quiet", "q", false, "Run in quiet mode")
fs.BoolVar(&c.interactive, "interactive", false, "When set or no package base exists, an interactive "+
"command prompt will be presented to accept package ClusterServiceVersion metadata")
}
216 changes: 216 additions & 0 deletions cmd/operator-sdk/generate/packagemanifests/packagemanifests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// 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 packagemanifests

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

"sigs.k8s.io/kubebuilder/pkg/model/config"

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"
genpkg "github.com/operator-framework/operator-sdk/internal/generate/packagemanifest"
)

// setCommonDefaults sets defaults useful to all modes of this subcommand.
func (c *packagemanifestsCmd) setCommonDefaults(cfg *config.Config) {
if c.operatorName == "" {
c.operatorName = filepath.Base(cfg.Repo)
}
}

// runKustomize generates kustomize package bases.
func (c packagemanifestsCmd) runKustomize(cfg *config.Config) error {

if !c.quiet {
fmt.Println("Generating package manifests kustomize bases")
}

defaultDir := filepath.Join("config", "packages")
if c.inputDir == "" {
c.inputDir = defaultDir
}
if c.outputDir == "" {
c.outputDir = defaultDir
}
if c.apisDir == "" {
if cfg.MultiGroup {
c.apisDir = "apis"
} else {
c.apisDir = "api"
}
}

csvGen := gencsv.Generator{
OperatorName: c.operatorName,
OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout),
}
opts := []gencsv.Option{
gencsv.WithBase(c.inputDir, c.apisDir, c.interactiveLevel),
gencsv.WithBaseWriter(c.outputDir),
}
if err := csvGen.Generate(cfg, opts...); err != nil {
return fmt.Errorf("error generating ClusterServiceVersion: %v", err)
}

if !c.quiet {
fmt.Println("Bases generated successfully in", c.outputDir)
}

return nil
}

// validateManifests validates c for package manifests generation.
func (c packagemanifestsCmd) validateManifests() error {

if err := genutil.ValidateVersion(c.version); err != nil {
return err
}

if c.fromVersion != "" {
return errors.New("--from-version cannot be set for PROJECT configured projects")
}

if !genutil.IsPipeReader() {
if c.deployDir == "" {
return errors.New("--deploy-dir must be set if not reading from stdin")
}
if c.crdsDir == "" {
return errors.New("--crd-dir must be set if not reading from stdin")
}
}

if c.stdout {
if c.outputDir != "" {
return errors.New("--output-dir cannot be set if writing to stdout")
}
}

if c.isDefaultChannel && c.channelName == "" {
return fmt.Errorf("--default-channel can only be set if --channel is set")
}

return nil
}

// runManifests generates package manifests.
func (c packagemanifestsCmd) runManifests(cfg *config.Config) error {

if !c.quiet && !c.stdout {
fmt.Println("Generating package manifests version", c.version)
}

defaultDir := filepath.Join("config", "packages")
if c.inputDir == "" {
c.inputDir = defaultDir
}
if !c.stdout {
if c.outputDir == "" {
c.outputDir = defaultDir
}
}
// Only regenerate API definitions once.
if c.apisDir == "" && !c.kustomize {
if cfg.MultiGroup {
c.apisDir = "apis"
} else {
c.apisDir = "api"
}
}

if err := c.generatePackageManifest(); err != nil {
return err
}

col := &collector.Manifests{}
if genutil.IsPipeReader() {
if err := col.UpdateFromReader(os.Stdin); err != nil {
return err
}
}
if c.deployDir != "" {
if err := col.UpdateFromDirs(c.deployDir, c.crdsDir); err != nil {
return err
}
}

csvGen := gencsv.Generator{
OperatorName: c.operatorName,
OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout),
Version: c.version,
Collector: col,
}

stdout := genutil.NewMultiManifestWriter(os.Stdout)
opts := []gencsv.Option{
gencsv.WithBase(c.inputDir, c.apisDir, c.interactiveLevel),
}
if c.stdout {
opts = append(opts, gencsv.WithWriter(stdout))
} else {
opts = append(opts, gencsv.WithPackageWriter(c.outputDir))
}

if err := csvGen.Generate(cfg, opts...); err != nil {
return fmt.Errorf("error generating ClusterServiceVersion: %v", err)
}

if c.updateCRDs {
var objs []interface{}
for _, crd := range col.V1CustomResourceDefinitions {
objs = append(objs, crd)
}
for _, crd := range col.V1beta1CustomResourceDefinitions {
objs = append(objs, crd)
}
if c.stdout {
if err := genutil.WriteObjects(stdout, objs...); err != nil {
return err
}
} else {
dir := filepath.Join(c.outputDir, c.version)
if err := genutil.WriteObjectsToFiles(dir, objs...); err != nil {
return err
}
}
}

if !c.quiet && !c.stdout {
fmt.Println("Package manifests generated successfully in", c.outputDir)
}

return nil
}

func (c packagemanifestsCmd) generatePackageManifest() error {
pkgGen := genpkg.Generator{
OperatorName: c.operatorName,
Version: c.version,
ChannelName: c.channelName,
IsDefaultChannel: c.isDefaultChannel,
}
opts := []genpkg.Option{
genpkg.WithBase(c.inputDir),
genpkg.WithFileWriter(c.outputDir),
}
if err := pkgGen.Generate(opts...); err != nil {
return err
}
return nil
}
Loading

0 comments on commit ebd8b35

Please sign in to comment.