Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

commands/operator-sdk: Add CLI support for Ansible Operator #486

Merged
merged 16 commits into from
Oct 2, 2018
Merged
Show file tree
Hide file tree
Changes from 8 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
34 changes: 26 additions & 8 deletions commands/operator-sdk/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"log"
"os"
"os/exec"
"strings"

"github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/cmdutil"
cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error"
Expand Down Expand Up @@ -135,29 +136,33 @@ func renderTestManifest(image string) {
const (
build = "./tmp/build/build.sh"
configYaml = "./config/config.yaml"
mainGo = "./cmd/%s/main.go"
)

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

bcmd := exec.Command(build)
bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild))
bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests))
o, err := bcmd.CombinedOutput()
if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build: (%v)", string(o)))
// Don't need to buld go code if Ansible Operator
if buildCmd() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you change the name to be more representative of the actual function?
Right now it's just checking if cmd/<project-name>/main.go exists to determine if it's an Ansible or Go type project.
So perhaps mainExists() or isGoProject().

bcmd := exec.Command(build)
bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild))
bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests))
o, err := bcmd.CombinedOutput()
Copy link
Contributor

Choose a reason for hiding this comment

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

As @AlexNPavel mentioned: You need to pass the envs from L146-147 to the build command.

bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild))
bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests))

We made this change in #469

Although we're planning to remove the build script altogether in #541 so you might need to rebase again if that gets done first.

Copy link
Contributor Author

@dymurray dymurray Sep 27, 2018

Choose a reason for hiding this comment

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

Thanks a lot for updating that @johnkim76. Appreciate the heads up on #541 as well

if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build: (%v)", string(o)))
}
fmt.Fprintln(os.Stdout, string(o))
}
fmt.Fprintln(os.Stdout, string(o))

image := args[0]
baseImageName := image
if enableTests {
baseImageName += "-intermediate"
}
dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", baseImageName)
o, err = dbcmd.CombinedOutput()
o, err := dbcmd.CombinedOutput()
if err != nil {
if enableTests {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build intermediate image for %s image: (%s)", image, string(o)))
Expand All @@ -178,3 +183,16 @@ func buildFunc(cmd *cobra.Command, args []string) {
renderTestManifest(image)
}
}

func buildCmd() bool {
dir, err := os.Getwd()
if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get current working dir: %v", err))
}
dirSplit := strings.Split(dir, "/")
projectName := dirSplit[len(dirSplit)-1]
if _, err = os.Stat(fmt.Sprintf(mainGo, projectName)); err == nil {
return true
}
return false
}
1 change: 1 addition & 0 deletions commands/operator-sdk/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ func NewGenerateCmd() *cobra.Command {
}
cmd.AddCommand(generate.NewGenerateK8SCmd())
cmd.AddCommand(generate.NewGenerateOlmCatalogCmd())
cmd.AddCommand(generate.NewGenerateCrdCmd())
return cmd
}
113 changes: 113 additions & 0 deletions commands/operator-sdk/cmd/generate/crd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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"
"os"
"path/filepath"
"strings"

cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error"
"github.com/operator-framework/operator-sdk/pkg/generator"

"github.com/spf13/cobra"
dymurray marked this conversation as resolved.
Show resolved Hide resolved
)

var (
apiVersion string
kind string
)

const (
goDir = "GOPATH"
deployCrdDir = "deploy"
)

func NewGenerateCrdCmd() *cobra.Command {
crdCmd := &cobra.Command{
Use: "crd",
Short: "Generates a custom resource definition (CRD) and the custom resource (CR) files",
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.

Generated CRD filename: <project-name>/deploy/<kind>_crd.yaml
Copy link
Contributor

Choose a reason for hiding this comment

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

@shawn-hurley Just wanted to point out that if you try to generate a second CRD with the same Kind but different APIVersion then that will overwrite the previous <kind>_crd.yaml. Not sure if that's a legitimate use case in an ansible type project but we account for multiple CRDs in the refactor branch by using the format deploy/crds/<group>_<version>_<kind>_crd.yaml

https://github.com/operator-framework/operator-sdk/blob/refactor/controller-runtime/commands/operator-sdk/cmd/add/api.go#L146-L168

We'll soon add the changes from this PR into the refactor branch as well(to fit the style of the scaffold).
Right now we don't have a separate generate CRD command but I'm wondering if you wanted to make the CRD generation consistent with how we do it in the refactor branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes sense to me... @johnkim76 any concerns around this?

Copy link
Contributor

Choose a reason for hiding this comment

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

that sounds good. I'll make the update to the filename. thank you.

Generated CR filename: <project-name>/deploy/<kind>_cr.yaml

<project-name>/deploy path must already exist
--api-version and --kind are required flags to generate the new operator application.
`,
Run: crdFunc,
}
crdCmd.Flags().StringVar(&apiVersion, "api-version", "", "Kubernetes apiVersion and has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)")
crdCmd.MarkFlagRequired("api-version")
crdCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService)")
crdCmd.MarkFlagRequired("kind")
return crdCmd
}

func crdFunc(cmd *cobra.Command, args []string) {
if len(args) != 0 {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("crd command doesn't accept any arguments."))
}
verifyCrdFlags()
verifyCrdDeployPath()

fmt.Fprintln(os.Stdout, "Generating custom resource definition (CRD) file")

// generate CRD file
wd, _ := os.Getwd()
Copy link
Contributor

Choose a reason for hiding this comment

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

Check return error.

if err := generator.RenderDeployCrdFiles(filepath.Join(wd, deployCrdDir), apiVersion, kind); err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate CRD and CR files: (%v)", err))
}
}

func verifyCrdFlags() {
if len(apiVersion) == 0 {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--api-version must not have empty value"))
}
if len(kind) == 0 {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must not have empty value"))
}
kindFirstLetter := string(kind[0])
if kindFirstLetter != strings.ToUpper(kindFirstLetter) {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must start with an uppercase letter"))
}
if strings.Count(apiVersion, "/") != 1 {
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))
}
}

// verifyCrdDeployPath checks if the path <project-name>/deploy sub-directory is exists, and that is rooted under $GOPATH
func verifyCrdDeployPath() {
// check if $GOPATH env exists
Copy link
Member

Choose a reason for hiding this comment

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

While re-looking at the code, I would like to change one more thing about the generation as well

I think an ansible operator new, should not have to have Gopath set up. I think this would force users to have that, and I think we should avoid this.

@johnkim76 I wonder if we should just check that the current working dir has a deploy directory instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

ok.. I'll remove the check for the Gopath

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch Shawn. I agree we shouldn't depend on Go being configured.

gp := os.Getenv(goDir)
if len(gp) == 0 {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("$GOPATH env not set"))
}
wd, err := os.Getwd()
if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to determine the full path of the current directory: %v", err))
}
// check if this project's repository path is rooted under $GOPATH
if !strings.HasPrefix(wd, gp) {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("project's repository path (%v) is not rooted under GOPATH (%v)", wd, gp))
}
// check if the deploy sub-directory exist
_, err = os.Stat(filepath.Join(wd, deployCrdDir))
if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("the path (./%v) does not exist. run this command in your project directory", deployCrdDir))
}
}
36 changes: 25 additions & 11 deletions commands/operator-sdk/cmd/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,29 @@ generates a skeletal app-operator application in $GOPATH/src/github.com/example.
newCmd.MarkFlagRequired("api-version")
newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService)")
newCmd.MarkFlagRequired("kind")
newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (e.g \"ansible\")")
newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository")
newCmd.Flags().BoolVar(&generatePlaybook, "generate-playbook", false, "Generate a playbook skeleton. (Only used for --type ansible)")

return newCmd
}

var (
apiVersion string
kind string
projectName string
skipGit bool
apiVersion string
kind string
operatorType string
projectName string
skipGit bool
generatePlaybook bool
)

const (
gopath = "GOPATH"
src = "src"
dep = "dep"
ensureCmd = "ensure"
gopath = "GOPATH"
src = "src"
dep = "dep"
ensureCmd = "ensure"
goOperatorType = "go"
ansibleOperatorType = "ansible"
)

func newFunc(cmd *cobra.Command, args []string) {
Expand All @@ -79,13 +85,15 @@ func newFunc(cmd *cobra.Command, args []string) {
parse(args)
mustBeNewProject()
verifyFlags()
g := generator.NewGenerator(apiVersion, kind, projectName, repoPath())
g := generator.NewGenerator(apiVersion, kind, operatorType, projectName, repoPath(), generatePlaybook)
err := g.Render()
if err != nil {
cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create project %v: %v", projectName, err))
}
pullDep()
generate.K8sCodegen(projectName)
if operatorType == goOperatorType {
pullDep()
generate.K8sCodegen(projectName)
}
initGit()
}

Expand Down Expand Up @@ -136,6 +144,12 @@ func verifyFlags() {
if len(kind) == 0 {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must not have empty value"))
}
if operatorType != goOperatorType && operatorType != ansibleOperatorType {
dymurray marked this conversation as resolved.
Show resolved Hide resolved
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--type can only be `go` or `ansible`"))
}
if operatorType != ansibleOperatorType && generatePlaybook {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--generate-playbook can only be used with --type `ansible`"))
}
kindFirstLetter := string(kind[0])
if kindFirstLetter != strings.ToUpper(kindFirstLetter) {
cmdError.ExitWithError(cmdError.ExitBadArgs, errors.New("--kind must start with an uppercase letter"))
Expand Down
Loading