From 3bf83e090c5c3de8d1dc8ea491ef8f62e62e4064 Mon Sep 17 00:00:00 2001 From: hasbro17 Date: Wed, 25 Apr 2018 10:49:06 -0700 Subject: [PATCH] *: add generate alm-catalog command --- commands/operator-sdk/cmd/build.go | 2 +- commands/operator-sdk/cmd/generate.go | 1 + .../operator-sdk/cmd/generate/alm_catalog.go | 95 ++++++++++++++ pkg/generator/alm_catalog_tmpl.go | 120 ++++++++++++++++++ pkg/generator/gen_alm_catalog.go | 120 ++++++++++++++++++ pkg/generator/generator.go | 96 ++++++++++---- 6 files changed, 408 insertions(+), 26 deletions(-) create mode 100644 commands/operator-sdk/cmd/generate/alm_catalog.go create mode 100644 pkg/generator/alm_catalog_tmpl.go create mode 100644 pkg/generator/gen_alm_catalog.go diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 08b97b67b74..fc32353d244 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -55,7 +55,7 @@ const ( func buildFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { - cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("new command needs 1 argument.")) + cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("build command needs 1 argument.")) } bcmd := exec.Command(build) diff --git a/commands/operator-sdk/cmd/generate.go b/commands/operator-sdk/cmd/generate.go index c5601867f00..d0742251935 100644 --- a/commands/operator-sdk/cmd/generate.go +++ b/commands/operator-sdk/cmd/generate.go @@ -28,5 +28,6 @@ func NewGenerateCmd() *cobra.Command { `, } cmd.AddCommand(generate.NewGenerateK8SCmd()) + cmd.AddCommand(generate.NewGenerateAlmCatalogCmd()) return cmd } diff --git a/commands/operator-sdk/cmd/generate/alm_catalog.go b/commands/operator-sdk/cmd/generate/alm_catalog.go new file mode 100644 index 00000000000..f6dfb39b86f --- /dev/null +++ b/commands/operator-sdk/cmd/generate/alm_catalog.go @@ -0,0 +1,95 @@ +// Copyright 2018 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 generate + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + + cmdError "github.com/coreos/operator-sdk/commands/operator-sdk/error" + "github.com/coreos/operator-sdk/pkg/generator" + yaml "gopkg.in/yaml.v2" + + "github.com/spf13/cobra" +) + +const ( + configYaml = "./config/config.yaml" +) + +var ( + image string + version string +) + +func NewGenerateAlmCatalogCmd() *cobra.Command { + almCatalogCmd := &cobra.Command{ + Use: "alm-catalog", + Short: "Generates ALM Catalog manifests", + Long: `alm-catalog generator generates the following ALM Catalog manifests needed to create a catalog entry in ALM: +- Cluster Service Version: deploy/alm-catalog/csv.yaml +- Package: deploy/alm-catalog/package.yaml +- Custom Resource Definition: deploy/alm-catalog/crd.yaml + +The following flags are required: +--image: The container image name to set in the CSV to deploy the operator +--version: The version of the current CSV + +For example: + $ operator-sdk generate alm-catalog --image=quay.io/example/operator:v0.0.1 --version=0.0.1 +`, + Run: almCatalogFunc, + } + almCatalogCmd.Flags().StringVar(&image, "image", "", "The container image name to set in the CSV to deploy the operator e.g: quay.io/example/operator:v0.0.1") + almCatalogCmd.MarkFlagRequired("image") + almCatalogCmd.Flags().StringVar(&version, "version", "", "The version of the current CSV e.g: 0.0.1") + almCatalogCmd.MarkFlagRequired("version") + + return almCatalogCmd +} + +func almCatalogFunc(cmd *cobra.Command, args []string) { + if len(args) != 0 { + cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("alm-catalog command doesn't accept any arguments.")) + } + verifyFlags() + + fmt.Fprintln(os.Stdout, "Generating ALM catalog manifests") + + c := &generator.Config{} + fp, err := ioutil.ReadFile(configYaml) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to read config file %v: (%v)", configYaml, err)) + } + if err = yaml.Unmarshal(fp, c); err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to unmarshal config file %v: (%v)", configYaml, err)) + } + + // Generate ALM catalog manifests + if err = generator.RenderAlmCatalog(c, image, version); err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/alm-catalog: (%v)", err)) + } +} + +func verifyFlags() { + if len(image) == 0 { + cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--image must not have empty value")) + } + if len(version) == 0 { + cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--version must not have empty value")) + } +} diff --git a/pkg/generator/alm_catalog_tmpl.go b/pkg/generator/alm_catalog_tmpl.go new file mode 100644 index 00000000000..d9d050ff491 --- /dev/null +++ b/pkg/generator/alm_catalog_tmpl.go @@ -0,0 +1,120 @@ +// Copyright 2018 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 generator + +const catalogPackageTmpl = `packageName: {{.PackageName}} +channels: +- name: {{.ChannelName}} + currentCSV: {{.CurrentCSV}} +` + +const crdTmpl = `apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: {{.KindPlural}}.{{.GroupName}} +spec: + group: {{.GroupName}} + names: + kind: {{.Kind}} + listKind: {{.Kind}}List + plural: {{.KindPlural}} + singular: {{.KindSingular}} + scope: Namespaced + version: {{.Version}} +` + +const catalogCSVTmpl = `apiVersion: app.coreos.com/v1alpha1 +kind: ClusterServiceVersion-v1 +metadata: + name: {{.CSVName}} + namespace: placeholder +spec: + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: {{.ProjectName}} + rules: + - apiGroups: + - {{.GroupName}} + resources: + - "*" + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - "*" + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - "*" + deployments: + - name: {{.ProjectName}} + spec: + replicas: 1 + selector: + matchLabels: + app: {{.ProjectName}} + template: + metadata: + labels: + app: {{.ProjectName}} + spec: + containers: + - name: {{.ProjectName}}-alm-owned + image: {{.Image}} + command: + - {{.ProjectName}} + imagePullPolicy: Always + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + restartPolicy: Always + terminationGracePeriodSeconds: 5 + serviceAccountName: {{.ProjectName}} + serviceAccount: {{.ProjectName}} + customresourcedefinitions: + owned: + - description: Represents an instance of a {{.Kind}} application + displayName: {{.Kind}} Application + kind: {{.Kind}} + name: {{.KindPlural}}.{{.GroupName}} + version: {{.CRDVersion}} + version: {{.CatalogVersion}} + displayName: {{.Kind}} + labels: + alm-owner-enterprise-app: {{.ProjectName}} + alm-status-descriptors: {{.CSVName}} +` diff --git a/pkg/generator/gen_alm_catalog.go b/pkg/generator/gen_alm_catalog.go new file mode 100644 index 00000000000..15b1e02ed31 --- /dev/null +++ b/pkg/generator/gen_alm_catalog.go @@ -0,0 +1,120 @@ +// Copyright 2018 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 generator + +import ( + "fmt" + "io" + "strings" + "text/template" +) + +const ( + // Sample catalog resource values + // TODO: Make this configurable + packageChannel = "alpha" +) + +// CatalogPackageConfig contains the data needed to generate deploy/alm-catalog/package.yaml +type CatalogPackageConfig struct { + PackageName string + ChannelName string + CurrentCSV string +} + +// renderCatalogPackage generates deploy/alm-catalog/package.yaml +func renderCatalogPackage(w io.Writer, config *Config, catalogVersion string) error { + t := template.New(catalogPackageYaml) + t, err := t.Parse(catalogPackageTmpl) + if err != nil { + return fmt.Errorf("failed to parse catalog package template: %v", err) + } + + name := strings.ToLower(config.Kind) + cpConfig := CatalogPackageConfig{ + PackageName: name, + ChannelName: packageChannel, + CurrentCSV: getCSVName(name, catalogVersion), + } + return t.Execute(w, cpConfig) +} + +// CRDConfig contains the data needed to generate deploy/alm-catalog/crd.yaml +type CRDConfig struct { + Kind string + KindSingular string + KindPlural string + GroupName string + Version string +} + +// renderCRD generates deploy/alm-catalog/crd.yaml +func renderCRD(w io.Writer, config *Config) error { + t := template.New(catalogCRDYaml) + t, err := t.Parse(crdTmpl) + if err != nil { + return fmt.Errorf("failed to parse catalog CRD template: %v", err) + } + + kindSingular := strings.ToLower(config.Kind) + crdConfig := CRDConfig{ + Kind: config.Kind, + KindSingular: kindSingular, + KindPlural: kindSingular + "s", + GroupName: groupName(config.APIVersion), + Version: version(config.APIVersion), + } + return t.Execute(w, crdConfig) +} + +// CSVConfig contains the data needed to generate deploy/alm-catalog/csv.yaml +type CSVConfig struct { + Kind string + KindSingular string + KindPlural string + GroupName string + CRDVersion string + ProjectName string + CSVName string + Image string + CatalogVersion string +} + +// renderCatalogCSV generates deploy/alm-catalog/csv.yaml +func renderCatalogCSV(w io.Writer, config *Config, image, catalogVersion string) error { + t := template.New(catalogCSVYaml) + t, err := t.Parse(catalogCSVTmpl) + if err != nil { + return fmt.Errorf("failed to parse catalog CSV template: %v", err) + } + + kindSingular := strings.ToLower(config.Kind) + csvConfig := CSVConfig{ + Kind: config.Kind, + KindSingular: kindSingular, + KindPlural: kindSingular + "s", + GroupName: groupName(config.APIVersion), + CRDVersion: version(config.APIVersion), + CSVName: getCSVName(kindSingular, catalogVersion), + Image: image, + CatalogVersion: catalogVersion, + ProjectName: config.ProjectName, + } + return t.Execute(w, csvConfig) +} + +func getCSVName(name, version string) string { + return name + ".v" + version +} diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index b38e02cb89e..130273b0e87 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -28,33 +28,37 @@ const ( defaultFileMode = 0644 defaultExecFileMode = 0744 // dirs - cmdDir = "cmd" - deployDir = "deploy" - configDir = "config" - tmpDir = "tmp" - buildDir = tmpDir + "/build" - codegenDir = tmpDir + "/codegen" - pkgDir = "pkg" - apisDir = pkgDir + "/apis" - stubDir = pkgDir + "/stub" + cmdDir = "cmd" + deployDir = "deploy" + almCatalogDir = deployDir + "/alm-catalog" + configDir = "config" + tmpDir = "tmp" + buildDir = tmpDir + "/build" + codegenDir = tmpDir + "/codegen" + pkgDir = "pkg" + apisDir = pkgDir + "/apis" + stubDir = pkgDir + "/stub" // files - main = "main.go" - handler = "handler.go" - doc = "doc.go" - register = "register.go" - types = "types.go" - build = "build.sh" - dockerBuild = "docker_build.sh" - dockerfile = "Dockerfile" - boilerplate = "boilerplate.go.txt" - updateGenerated = "update-generated.sh" - gopkgtoml = "Gopkg.toml" - gopkglock = "Gopkg.lock" - config = "config.yaml" - operatorYaml = deployDir + "/operator.yaml" - rbacYaml = "rbac.yaml" - crYaml = "cr.yaml" + main = "main.go" + handler = "handler.go" + doc = "doc.go" + register = "register.go" + types = "types.go" + build = "build.sh" + dockerBuild = "docker_build.sh" + dockerfile = "Dockerfile" + boilerplate = "boilerplate.go.txt" + updateGenerated = "update-generated.sh" + gopkgtoml = "Gopkg.toml" + gopkglock = "Gopkg.lock" + config = "config.yaml" + operatorYaml = deployDir + "/operator.yaml" + rbacYaml = "rbac.yaml" + crYaml = "cr.yaml" + catalogPackageYaml = "package.yaml" + catalogCSVYaml = "csv.yaml" + catalogCRDYaml = "crd.yaml" ) type Generator struct { @@ -196,6 +200,48 @@ func RenderOperatorYaml(c *Config, image string) error { return ioutil.WriteFile(operatorYaml, buf.Bytes(), defaultFileMode) } +// RenderAlmCatalog generates catalog manifests "deploy/alm-catalog/*" +// The current working directory must be the project repository root +func RenderAlmCatalog(c *Config, image, version string) error { + // mkdir deploy/alm-catalog + repoPath, err := os.Getwd() + if err != nil { + return err + } + almDir := filepath.Join(repoPath, almCatalogDir) + if err := os.MkdirAll(almDir, defaultDirFileMode); err != nil { + return err + } + + // deploy/alm-catalog/package.yaml + buf := &bytes.Buffer{} + if err := renderCatalogPackage(buf, c, version); err != nil { + return err + } + path := filepath.Join(almDir, catalogPackageYaml) + if err := ioutil.WriteFile(path, buf.Bytes(), defaultFileMode); err != nil { + return err + } + + // deploy/alm-catalog/crd.yaml + buf = &bytes.Buffer{} + if err := renderCRD(buf, c); err != nil { + return err + } + path = filepath.Join(almDir, catalogCRDYaml) + if err := ioutil.WriteFile(path, buf.Bytes(), defaultFileMode); err != nil { + return err + } + + // deploy/alm-catalog/csv.yaml + buf = &bytes.Buffer{} + if err := renderCatalogCSV(buf, c, image, version); err != nil { + return err + } + path = filepath.Join(almDir, catalogCSVYaml) + return ioutil.WriteFile(path, buf.Bytes(), defaultFileMode) +} + func (g *Generator) renderTmp() error { bDir := filepath.Join(g.projectName, buildDir) if err := os.MkdirAll(bDir, defaultDirFileMode); err != nil {