Skip to content

Commit a947a51

Browse files
dymurrayShawn Hurley
authored andcommitted
commands/operator-sdk: Add CLI support for Ansible Operator (#486)
- operator-sdk new --type ansible - operator-sdk build support. Autodetect type of operator by looking for golang files. - operator-sdk generate CRD support.
1 parent 581d7c7 commit a947a51

File tree

7 files changed

+528
-47
lines changed

7 files changed

+528
-47
lines changed

commands/operator-sdk/cmd/build.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"log"
2323
"os"
2424
"os/exec"
25+
"strings"
2526

2627
"github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/cmdutil"
2728
cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error"
@@ -135,29 +136,33 @@ func renderTestManifest(image string) {
135136
const (
136137
build = "./tmp/build/build.sh"
137138
configYaml = "./config/config.yaml"
139+
mainGo = "./cmd/%s/main.go"
138140
)
139141

140142
func buildFunc(cmd *cobra.Command, args []string) {
141143
if len(args) != 1 {
142144
cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("build command needs exactly 1 argument"))
143145
}
144146

145-
bcmd := exec.Command(build)
146-
bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild))
147-
bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests))
148-
o, err := bcmd.CombinedOutput()
149-
if err != nil {
150-
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build: (%v)", string(o)))
147+
// Don't need to buld go code if Ansible Operator
148+
if mainExists() {
149+
bcmd := exec.Command(build)
150+
bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild))
151+
bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests))
152+
o, err := bcmd.CombinedOutput()
153+
if err != nil {
154+
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build: (%v)", string(o)))
155+
}
156+
fmt.Fprintln(os.Stdout, string(o))
151157
}
152-
fmt.Fprintln(os.Stdout, string(o))
153158

154159
image := args[0]
155160
baseImageName := image
156161
if enableTests {
157162
baseImageName += "-intermediate"
158163
}
159164
dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", baseImageName)
160-
o, err = dbcmd.CombinedOutput()
165+
o, err := dbcmd.CombinedOutput()
161166
if err != nil {
162167
if enableTests {
163168
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build intermediate image for %s image: (%s)", image, string(o)))
@@ -178,3 +183,16 @@ func buildFunc(cmd *cobra.Command, args []string) {
178183
renderTestManifest(image)
179184
}
180185
}
186+
187+
func mainExists() bool {
188+
dir, err := os.Getwd()
189+
if err != nil {
190+
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get current working dir: %v", err))
191+
}
192+
dirSplit := strings.Split(dir, "/")
193+
projectName := dirSplit[len(dirSplit)-1]
194+
if _, err = os.Stat(fmt.Sprintf(mainGo, projectName)); err == nil {
195+
return true
196+
}
197+
return false
198+
}

commands/operator-sdk/cmd/generate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ func NewGenerateCmd() *cobra.Command {
2929
}
3030
cmd.AddCommand(generate.NewGenerateK8SCmd())
3131
cmd.AddCommand(generate.NewGenerateOlmCatalogCmd())
32+
cmd.AddCommand(generate.NewGenerateCrdCmd())
3233
return cmd
3334
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package generate
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"os"
21+
"path/filepath"
22+
"strings"
23+
24+
cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error"
25+
"github.com/operator-framework/operator-sdk/pkg/generator"
26+
27+
"github.com/spf13/cobra"
28+
)
29+
30+
var (
31+
apiVersion string
32+
kind string
33+
)
34+
35+
const (
36+
goDir = "GOPATH"
37+
deployCrdDir = "deploy"
38+
)
39+
40+
func NewGenerateCrdCmd() *cobra.Command {
41+
crdCmd := &cobra.Command{
42+
Use: "crd",
43+
Short: "Generates a custom resource definition (CRD) and the custom resource (CR) files",
44+
Long: `The operator-sdk generate command will create a custom resource definition (CRD) and the custom resource (CR) files for the specified api-version and kind.
45+
46+
Generated CRD filename: <project-name>/deploy/<group>_<version>_<kind>_crd.yaml
47+
Generated CR filename: <project-name>/deploy/<group>_<version>_<kind>_cr.yaml
48+
49+
<project-name>/deploy path must already exist
50+
--api-version and --kind are required flags to generate the new operator application.
51+
`,
52+
Run: crdFunc,
53+
}
54+
crdCmd.Flags().StringVar(&apiVersion, "api-version", "", "Kubernetes apiVersion and has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)")
55+
crdCmd.MarkFlagRequired("api-version")
56+
crdCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService)")
57+
crdCmd.MarkFlagRequired("kind")
58+
return crdCmd
59+
}
60+
61+
func crdFunc(cmd *cobra.Command, args []string) {
62+
if len(args) != 0 {
63+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("crd command doesn't accept any arguments."))
64+
}
65+
verifyCrdFlags()
66+
verifyCrdDeployPath()
67+
68+
fmt.Fprintln(os.Stdout, "Generating custom resource definition (CRD) file")
69+
70+
// generate CR/CRD file
71+
wd, err := os.Getwd()
72+
if err != nil {
73+
cmdError.ExitWithError(cmdError.ExitError, err)
74+
}
75+
if err := generator.RenderDeployCrdFiles(filepath.Join(wd, deployCrdDir), apiVersion, kind); err != nil {
76+
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate CRD and CR files: (%v)", err))
77+
}
78+
}
79+
80+
func verifyCrdFlags() {
81+
if len(apiVersion) == 0 {
82+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--api-version must not have empty value"))
83+
}
84+
if len(kind) == 0 {
85+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must not have empty value"))
86+
}
87+
kindFirstLetter := string(kind[0])
88+
if kindFirstLetter != strings.ToUpper(kindFirstLetter) {
89+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must start with an uppercase letter"))
90+
}
91+
if strings.Count(apiVersion, "/") != 1 {
92+
cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("api-version has wrong format (%v); format must be $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)", apiVersion))
93+
}
94+
}
95+
96+
// verifyCrdDeployPath checks if the path <project-name>/deploy sub-directory is exists, and that is rooted under $GOPATH
97+
func verifyCrdDeployPath() {
98+
wd, err := os.Getwd()
99+
if err != nil {
100+
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to determine the full path of the current directory: %v", err))
101+
}
102+
// check if the deploy sub-directory exist
103+
_, err = os.Stat(filepath.Join(wd, deployCrdDir))
104+
if err != nil {
105+
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("the path (./%v) does not exist. run this command in your project directory", deployCrdDir))
106+
}
107+
}

commands/operator-sdk/cmd/new.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,29 @@ generates a skeletal app-operator application in $GOPATH/src/github.com/example.
5353
newCmd.MarkFlagRequired("api-version")
5454
newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService)")
5555
newCmd.MarkFlagRequired("kind")
56+
newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (e.g \"ansible\")")
5657
newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository")
58+
newCmd.Flags().BoolVar(&generatePlaybook, "generate-playbook", false, "Generate a playbook skeleton. (Only used for --type ansible)")
5759

5860
return newCmd
5961
}
6062

6163
var (
62-
apiVersion string
63-
kind string
64-
projectName string
65-
skipGit bool
64+
apiVersion string
65+
kind string
66+
operatorType string
67+
projectName string
68+
skipGit bool
69+
generatePlaybook bool
6670
)
6771

6872
const (
69-
gopath = "GOPATH"
70-
src = "src"
71-
dep = "dep"
72-
ensureCmd = "ensure"
73+
gopath = "GOPATH"
74+
src = "src"
75+
dep = "dep"
76+
ensureCmd = "ensure"
77+
goOperatorType = "go"
78+
ansibleOperatorType = "ansible"
7379
)
7480

7581
func newFunc(cmd *cobra.Command, args []string) {
@@ -79,13 +85,15 @@ func newFunc(cmd *cobra.Command, args []string) {
7985
parse(args)
8086
mustBeNewProject()
8187
verifyFlags()
82-
g := generator.NewGenerator(apiVersion, kind, projectName, repoPath())
88+
g := generator.NewGenerator(apiVersion, kind, operatorType, projectName, repoPath(), generatePlaybook)
8389
err := g.Render()
8490
if err != nil {
8591
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create project %v: %v", projectName, err))
8692
}
87-
pullDep()
88-
generate.K8sCodegen(projectName)
93+
if operatorType == goOperatorType {
94+
pullDep()
95+
generate.K8sCodegen(projectName)
96+
}
8997
initGit()
9098
}
9199

@@ -136,6 +144,12 @@ func verifyFlags() {
136144
if len(kind) == 0 {
137145
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must not have empty value"))
138146
}
147+
if operatorType != goOperatorType && operatorType != ansibleOperatorType {
148+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--type can only be `go` or `ansible`"))
149+
}
150+
if operatorType != ansibleOperatorType && generatePlaybook {
151+
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--generate-playbook can only be used with --type `ansible`"))
152+
}
139153
kindFirstLetter := string(kind[0])
140154
if kindFirstLetter != strings.ToUpper(kindFirstLetter) {
141155
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must start with an uppercase letter"))

0 commit comments

Comments
 (0)