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: 3 additions & 0 deletions changelog/fragments/interactive-csv-cmd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
entries:
- description: Add flag `--interactive` to the command `operator-sdk generate csv` in order to enable working with interactive prompts while generating CSV.
kind: "addition"
57 changes: 37 additions & 20 deletions cmd/operator-sdk/generate/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ import (
)

type csvCmd struct {
csvVersion string
csvChannel string
fromVersion string
operatorName string
outputDir string
deployDir string
apisDir string
crdDir string
updateCRDs bool
defaultChannel bool
makeManifests bool
csvVersion string
csvChannel string
fromVersion string
operatorName string
outputDir string
deployDir string
apisDir string
crdDir string
interactivelevel projutil.InteractiveLevel
updateCRDs bool
defaultChannel bool
makeManifests bool
interactive bool
}
Comment thread
varshaprasad96 marked this conversation as resolved.

//nolint:lll
Expand Down Expand Up @@ -171,6 +173,16 @@ Flags that change project default paths:
c.updateCRDs = false
}

// Check if the user has any specific preference to enable / disable interactive prompts.
// Default behaviour is to disable the prompts.
if cmd.Flags().Changed("interactive") {
if c.interactive {
c.interactivelevel = projutil.InteractiveOnAll
} else {
c.interactivelevel = projutil.InteractiveHardOff
}
}

if err := c.run(); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -230,6 +242,9 @@ Flags that change project default paths:
"directory. This directory is intended to be used for your latest bundle manifests. "+
"The default location is deploy/olm-catalog/<operator-name>/manifests. "+
"If --output-dir is set, the directory will be <output-dir>/manifests")
cmd.Flags().BoolVar(&c.interactive, "interactive", false,
"When set, will enable the interactive command prompt feature to fill the UI "+
"metadata fields in CSV")

return cmd
}
Expand All @@ -250,16 +265,18 @@ func (c csvCmd) run() error {
if c.operatorName == "" {
c.operatorName = filepath.Base(projutil.MustGetwd())
}

csv := gencatalog.BundleGenerator{
OperatorName: c.operatorName,
CSVVersion: c.csvVersion,
FromVersion: c.fromVersion,
UpdateCRDs: c.updateCRDs,
MakeManifests: c.makeManifests,
DeployDir: c.deployDir,
ApisDir: c.apisDir,
CRDsDir: c.crdDir,
OutputDir: c.outputDir,
OperatorName: c.operatorName,
CSVVersion: c.csvVersion,
FromVersion: c.fromVersion,
UpdateCRDs: c.updateCRDs,
MakeManifests: c.makeManifests,
DeployDir: c.deployDir,
ApisDir: c.apisDir,
CRDsDir: c.crdDir,
OutputDir: c.outputDir,
InteractivePreference: c.interactivelevel,
}

if err := csv.Generate(); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions hack/tests/subcommand-generate-csv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ function check_crd_files() {
}

function generate_csv() {
echo "operator-sdk generate csv --operator-name $OPERATOR_NAME $@"
operator-sdk generate csv --operator-name $OPERATOR_NAME $@
echo "operator-sdk generate csv --operator-name $OPERATOR_NAME --interactive=false $@"
operator-sdk generate csv --operator-name $OPERATOR_NAME --interactive=false $@
}

pushd "$TEST_DIR" > /dev/null
Expand Down
28 changes: 25 additions & 3 deletions internal/generate/olm-catalog/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ type BundleGenerator struct {
CSVVersion string
// These directories specify where to retrieve manifests from.
DeployDir, ApisDir, CRDsDir string
// Interactivepreference refers to the user preference to enable/disable
// interactive prompts.
InteractivePreference projutil.InteractiveLevel
// updateCRDs directs the generator to also add CustomResourceDefinition
// manifests to the bundle.
UpdateCRDs bool
// makeManifests directs the generator to use 'manifests' as the bundle
// dir name.
MakeManifests bool

// noUpdate is for testing the generator's update capabilities.
noUpdate bool
// fromBundleDir is set if the generator needs to update from
Expand All @@ -71,6 +73,9 @@ type BundleGenerator struct {
// toBundleDir is the bundle directory filepath where the CSV will be generated
// This is set according to the generator's OutputDir
toBundleDir string
// Subcommand includes the list of csv metadata fields which the user
// provides to the interactive prompts which appear while generating csv.
interactiveCSVCmd interactiveCSVCmd
}

// getBundleDirs gets directory names of the new bundle and, if it exists,
Expand Down Expand Up @@ -180,6 +185,14 @@ func (g *BundleGenerator) setDefaults() {
// olmapiv1alpha1.ClusterServiceVersion instead of writing to a template.
func (g BundleGenerator) Generate() error {
g.setDefaults()

csvPath := g.getCSVPath(g.OperatorName)

if (g.InteractivePreference == projutil.InteractiveSoftOff && !isFileExist(csvPath)) ||
g.InteractivePreference == projutil.InteractiveOnAll {
g.interactiveCSVCmd.generateInteractivePrompt()
}

fileMap, err := g.generateCSV()
if err != nil {
return err
Expand All @@ -195,13 +208,14 @@ func (g BundleGenerator) Generate() error {
}
}

if err = os.MkdirAll(g.toBundleDir, fileutil.DefaultDirFileMode); err != nil {
if err := os.MkdirAll(g.toBundleDir, fileutil.DefaultDirFileMode); err != nil {
return fmt.Errorf("error mkdir %s: %v", g.toBundleDir, err)
}

for fileName, b := range fileMap {
path := filepath.Join(g.toBundleDir, fileName)
log.Debugf("CSV generator writing %s", path)
if err = ioutil.WriteFile(path, b, fileutil.DefaultFileMode); err != nil {
if err := ioutil.WriteFile(path, b, fileutil.DefaultFileMode); err != nil {
return fmt.Errorf("error writing bundle file: %v", err)
}
}
Expand All @@ -220,6 +234,11 @@ func getCSVFileNameLegacy(name, version string) string {
return getCSVName(strings.ToLower(name), version) + csvYamlFileExt
}

// getCSVPath returns the location of CSV in the project.
func (g BundleGenerator) getCSVPath(operatorName string) string {
Comment thread
varshaprasad96 marked this conversation as resolved.
return filepath.Join(g.fromBundleDir, getCSVFileName(operatorName))
}

func (g BundleGenerator) generateCSV() (fileMap map[string][]byte, err error) {
// Get current CSV to update, otherwise start with a fresh CSV.
var csv *olmapiv1alpha1.ClusterServiceVersion
Expand All @@ -243,6 +262,9 @@ func (g BundleGenerator) generateCSV() (fileMap map[string][]byte, err error) {
return nil, err
}

// populate the csv with the metadata obtained from the user.
g.interactiveCSVCmd.addUImetadata(csv)

path := ""
if g.MakeManifests {
path = getCSVFileName(g.OperatorName)
Expand Down
91 changes: 91 additions & 0 deletions internal/generate/olm-catalog/csv_cmd_prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 olmcatalog

import (
"strings"

olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-sdk/internal/util/projutil"
)

// InteractiveCSVCmd includes the list of CSV fields which would be asked
// to the user while generating CSV.
type interactiveCSVCmd struct {
// DisplayName is the name of the crd.
DisplayName string
// Keyword is a list of keywords describing the operator.
Keywords []string
// Description of the operator. Can include the features, limitations or
// use-cases of the operator.
Description string
// Name of the publishing entity behind the operator.
ProviderName string
// URL related to the publishing entity behind the operator.
ProviderURL string
// Maintainers is the list of organizational entities maintaining the operator.
Maintainers []string
}

// generateInteractivePrompt generates the prompts for user to provide input to the CSV
// fields.
func (s *interactiveCSVCmd) generateInteractivePrompt() {
s.DisplayName = projutil.GetRequiredInput("Display name for the operator")
s.Keywords = projutil.GetStringArray("Comma-separated list of keywords for your operator")
s.Description = projutil.GetRequiredInput("Description for the operator")
s.ProviderName = projutil.GetRequiredInput("Provider's name for the operator")
s.ProviderURL = projutil.GetOptionalInput("Any relevant URL for the provider name")
s.Maintainers = projutil.GetStringArray("Comma-separated list of maintainers and their emails" +
" (e.g. 'name1:email1, name2:email2')")
}

// addUImetadata populates the CSV with the data obtained from the interactive
// prompts which appear while generating CSV.
func (s *interactiveCSVCmd) addUImetadata(csv *olmapiv1alpha1.ClusterServiceVersion) {
if s.DisplayName != "" {
csv.Spec.DisplayName = s.DisplayName
}

if len(s.Keywords) != 0 {
csv.Spec.Keywords = s.Keywords
}

if s.Description != "" {
csv.Spec.Description = s.Description
}

if len(s.Maintainers) != 0 {
maintainers := make([]olmapiv1alpha1.Maintainer, 0)
for _, entity := range s.Maintainers {
entityDetails := strings.Split(entity, ":")
if len(entityDetails) == 2 {
m := olmapiv1alpha1.Maintainer{}
m.Name, m.Email = entityDetails[0], entityDetails[1]
maintainers = append(maintainers, m)
}
}
csv.Spec.Maintainers = maintainers
}

if s.ProviderName != "" {
provider := olmapiv1alpha1.AppLink{}
provider.Name = s.ProviderName
if s.ProviderURL != "" {
provider.URL = s.ProviderURL
}
csv.Spec.Provider = provider
}

}
83 changes: 65 additions & 18 deletions internal/generate/olm-catalog/csv_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,20 @@ func TestGoCSVNewWithInputsToOutput(t *testing.T) {

csvVersion := "0.0.1"
g := BundleGenerator{
OperatorName: testProjectName,
DeployDir: "config",
ApisDir: "api",
CRDsDir: filepath.Join("config", "crds"),
OutputDir: outputDir,
CSVVersion: csvVersion,
FromVersion: "",
UpdateCRDs: false,
MakeManifests: false,
OperatorName: testProjectName,
DeployDir: "config",
ApisDir: "api",
CRDsDir: filepath.Join("config", "crds"),
OutputDir: outputDir,
CSVVersion: csvVersion,
FromVersion: "",
UpdateCRDs: false,
MakeManifests: false,
InteractivePreference: projutil.InteractiveHardOff,
}

g.noUpdate = true

if err := g.Generate(); err != nil {
t.Fatalf("Failed to execute CSV generator: %v", err)
}
Expand Down Expand Up @@ -151,16 +153,18 @@ func TestGoCSVUpgradeWithInputsToOutput(t *testing.T) {
}

g := BundleGenerator{
OperatorName: testProjectName,
DeployDir: "config",
ApisDir: "api",
CRDsDir: filepath.Join("config", "crds"),
OutputDir: outputDir,
CSVVersion: csvVersion,
FromVersion: fromVersion,
UpdateCRDs: false,
MakeManifests: false,
OperatorName: testProjectName,
DeployDir: "config",
ApisDir: "api",
CRDsDir: filepath.Join("config", "crds"),
OutputDir: outputDir,
CSVVersion: csvVersion,
FromVersion: fromVersion,
UpdateCRDs: false,
MakeManifests: false,
InteractivePreference: projutil.InteractiveHardOff,
}

if err := g.Generate(); err != nil {
t.Fatalf("Failed to execute CSV generator: %v", err)
}
Expand Down Expand Up @@ -392,6 +396,49 @@ func TestGoCSVNewWithEmptyDeployDir(t *testing.T) {
}
}

func TestCSVPrompt(t *testing.T) {
cleanupFunc := chDirWithCleanup(t, testGoDataDir)
defer cleanupFunc()

s := interactiveCSVCmd{
DisplayName: "Memcached Application",
Keywords: []string{"memcached", "app"},
Description: "Main enterprise application providing business critical features with " +
"high availability and no manual intervention.",
ProviderName: "Example",
ProviderURL: "www.example.com",
Maintainers: []string{"Some Corp:corp@example.com"},
}

g := BundleGenerator{
OperatorName: testProjectName,
DeployDir: "deploy",
ApisDir: filepath.Join("pkg", "apis"),
CRDsDir: filepath.Join("deploy", "crds_v1beta1"),
OutputDir: "deploy",
CSVVersion: "0.0.2",
FromVersion: "",
UpdateCRDs: false,
MakeManifests: true,
interactiveCSVCmd: s,
}

g.setDefaults()
fileMap, err := g.generateCSV()
if err != nil {
t.Fatalf("Failed to execute CSV generator: %v", err)
}

csvExpFile := getCSVFileName(testProjectName)
csvExpBytes := readFile(t, filepath.Join(OLMCatalogDir, testProjectName, "manifests", csvExpFile))
if b, ok := fileMap[csvExpFile]; !ok {
t.Errorf("Failed to generate CSV for version %s", csvVersion)
} else {
assert.Equal(t, string(csvExpBytes), string(b))
}

}

func TestUpdateCSVVersion(t *testing.T) {
cleanupFunc := chDirWithCleanup(t, testGoDataDir)
defer cleanupFunc()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ spec:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: memcached-operator
image: quay.io/example/memcached-operator:v0.0.2
image: quay.io/example/memcached-operator:v0.0.3
Comment thread
varshaprasad96 marked this conversation as resolved.
imagePullPolicy: Never
name: memcached-operator
resources: {}
Expand Down
Loading