diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 9349b79c876..08b97b67b74 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -82,7 +82,7 @@ func buildFunc(cmd *cobra.Command, args []string) { if err = yaml.Unmarshal(fp, c); err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to unmarshal config file %v: (%v)", configYaml, err)) } - if err = generator.RenderDeployFiles(c, image); err != nil { + if err = generator.RenderOperatorYaml(c, image); err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/operator.yaml: (%v)", err)) } } diff --git a/pkg/generator/deploy_tmpl.go b/pkg/generator/deploy_tmpl.go index 719d63c6ad0..645dc89f1c8 100644 --- a/pkg/generator/deploy_tmpl.go +++ b/pkg/generator/deploy_tmpl.go @@ -76,3 +76,9 @@ roleRef: name: {{.ProjectName}} apiGroup: rbac.authorization.k8s.io ` + +const crYamlTmpl = `apiVersion: "{{.APIVersion}}" +kind: "{{.Kind}}" +metadata: + name: "example" +` diff --git a/pkg/generator/gen_deploy.go b/pkg/generator/gen_deploy.go index 59eb10473fd..b56cd139469 100644 --- a/pkg/generator/gen_deploy.go +++ b/pkg/generator/gen_deploy.go @@ -24,6 +24,7 @@ import ( const ( operatorTmplName = "deploy/operator.yaml" rbacTmplName = "deploy/rbac.yaml" + crTmplName = "deploy/cr.yaml" ) // OperatorYaml contains all the customized data needed to generate deploy/operator.yaml for a new operator @@ -78,3 +79,24 @@ func renderRBACYaml(w io.Writer, projectName string) error { r := RBACYaml{ProjectName: projectName} return t.Execute(w, r) } + +// CRYaml contains all the customized data needed to generate deploy/cr.yaml. +type CRYaml struct { + APIVersion string + Kind string + Name string +} + +func renderCustomResourceYaml(w io.Writer, apiVersion, kind string) error { + t := template.New(crTmplName) + t, err := t.Parse(crYamlTmpl) + if err != nil { + return fmt.Errorf("failed to parse cr yaml template: %v", err) + } + + r := CRYaml{ + APIVersion: apiVersion, + Kind: kind, + } + return t.Execute(w, r) +} diff --git a/pkg/generator/gen_handler.go b/pkg/generator/gen_handler.go index d8e19429b93..cac2739fdcc 100644 --- a/pkg/generator/gen_handler.go +++ b/pkg/generator/gen_handler.go @@ -24,10 +24,15 @@ import ( type Handler struct { // imports OperatorSDKImport string + + RepoPath string + Kind string + APIDirName string + Version string } // renderHandlerFile generates the stub/handler.go file. -func renderHandlerFile(w io.Writer) error { +func renderHandlerFile(w io.Writer, repoPath, kind, apiDirName, version string) error { t := template.New("stub/handler.go") t, err := t.Parse(handlerTmpl) if err != nil { @@ -36,6 +41,10 @@ func renderHandlerFile(w io.Writer) error { h := Handler{ OperatorSDKImport: sdkImport, + RepoPath: repoPath, + Kind: kind, + APIDirName: apiDirName, + Version: version, } return t.Execute(w, h) } diff --git a/pkg/generator/gen_main.go b/pkg/generator/gen_main.go index d88c44ae25f..8cd40f7d9c4 100644 --- a/pkg/generator/gen_main.go +++ b/pkg/generator/gen_main.go @@ -33,10 +33,13 @@ type Main struct { OperatorSDKImport string StubImport string SDKVersionImport string + + APIVersion string + Kind string } -// renderMainFile generates the cmd//main.go file given a repo path ("github.com/coreos/play") -func renderMainFile(w io.Writer, repo string) error { +// renderMainFile generates the cmd//main.go file. +func renderMainFile(w io.Writer, repo, apiVersion, kind string) error { t := template.New("cmd//main.go") t, err := t.Parse(mainTmpl) if err != nil { @@ -47,6 +50,8 @@ func renderMainFile(w io.Writer, repo string) error { OperatorSDKImport: sdkImport, StubImport: filepath.Join(repo, stubDir), SDKVersionImport: versionImport, + APIVersion: apiVersion, + Kind: kind, } return t.Execute(w, m) } diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 1975c60cb77..b38e02cb89e 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -54,6 +54,7 @@ const ( config = "config.yaml" operatorYaml = deployDir + "/operator.yaml" rbacYaml = "rbac.yaml" + crYaml = "cr.yaml" ) type Generator struct { @@ -127,12 +128,12 @@ func (g *Generator) renderCmd() error { if err := os.MkdirAll(cpDir, defaultDirFileMode); err != nil { return err } - return renderCmdFiles(cpDir, g.repoPath) + return renderCmdFiles(cpDir, g.repoPath, g.apiVersion, g.kind) } -func renderCmdFiles(cmdProjectDir, repoPath string) error { +func renderCmdFiles(cmdProjectDir, repoPath, apiVersion, kind string) error { buf := &bytes.Buffer{} - if err := renderMainFile(buf, repoPath); err != nil { + if err := renderMainFile(buf, repoPath, apiVersion, kind); err != nil { return err } return writeFileAndPrint(filepath.Join(cmdProjectDir, main), buf.Bytes(), defaultFileMode) @@ -159,7 +160,7 @@ func (g *Generator) renderDeploy() error { if err := os.MkdirAll(dp, defaultDirFileMode); err != nil { return err } - return renderRBAC(dp, g.projectName) + return renderDeployFiles(dp, g.projectName, g.apiVersion, g.kind) } func renderRBAC(deployDir, projectName string) error { @@ -170,9 +171,24 @@ func renderRBAC(deployDir, projectName string) error { return writeFileAndPrint(filepath.Join(deployDir, rbacYaml), buf.Bytes(), defaultFileMode) } -// RenderDeployFiles generates "deploy/operator.yaml" -// TODO: rethink about when/what to generate when invoking build command. -func RenderDeployFiles(c *Config, image string) error { +func renderDeployFiles(deployDir, projectName, apiVersion, kind string) error { + buf := &bytes.Buffer{} + if err := renderRBACYaml(buf, projectName); err != nil { + return err + } + if err := writeFileAndPrint(filepath.Join(deployDir, rbacYaml), buf.Bytes(), defaultFileMode); err != nil { + return err + } + + buf = &bytes.Buffer{} + if err := renderCustomResourceYaml(buf, apiVersion, kind); err != nil { + return err + } + return writeFileAndPrint(filepath.Join(deployDir, crYaml), buf.Bytes(), defaultFileMode) +} + +// RenderOperatorYaml generates "deploy/operator.yaml" +func RenderOperatorYaml(c *Config, image string) error { buf := &bytes.Buffer{} if err := renderOperatorYaml(buf, c.Kind, c.APIVersion, c.ProjectName, image); err != nil { return err @@ -238,7 +254,8 @@ func renderCodegenFiles(codegenDir, repoPath, apiDirName, version, projectName s func (g *Generator) renderPkg() error { v := version(g.apiVersion) - apiDir := filepath.Join(g.projectName, apisDir, apiDirName(g.apiVersion), v) + adn := apiDirName(g.apiVersion) + apiDir := filepath.Join(g.projectName, apisDir, adn, v) if err := os.MkdirAll(apiDir, defaultDirFileMode); err != nil { return err } @@ -250,7 +267,7 @@ func (g *Generator) renderPkg() error { if err := os.MkdirAll(sDir, defaultDirFileMode); err != nil { return err } - return renderStubFiles(sDir) + return renderStubFiles(sDir, g.repoPath, g.kind, adn, v) } func renderAPIFiles(apiDir, groupName, version, kind string) error { @@ -277,9 +294,9 @@ func renderAPIFiles(apiDir, groupName, version, kind string) error { return writeFileAndPrint(filepath.Join(apiDir, types), buf.Bytes(), defaultFileMode) } -func renderStubFiles(stubDir string) error { +func renderStubFiles(stubDir, repoPath, kind, apiDirName, version string) error { buf := &bytes.Buffer{} - if err := renderHandlerFile(buf); err != nil { + if err := renderHandlerFile(buf, repoPath, kind, apiDirName, version); err != nil { return err } return writeFileAndPrint(filepath.Join(stubDir, handler), buf.Bytes(), defaultFileMode) diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go index e001288d50c..6274fafa17f 100644 --- a/pkg/generator/generator_test.go +++ b/pkg/generator/generator_test.go @@ -19,15 +19,25 @@ import ( "testing" ) +const ( + // test constants for app-operator + appRepoPath = "github.com/example-inc/app-operator" + appKind = "App" + appApiDirName = "app" + appAPIVersion = appGroupName + "/" + appVersion + appVersion = "v1alpha1" + appGroupName = "app.example.com" +) + const mainExp = `package main import ( "context" "runtime" - stub "github.com/coreos/play/pkg/stub" + stub "github.com/example-inc/app-operator/pkg/stub" sdk "github.com/coreos/operator-sdk/pkg/sdk" - sdkVersion "github.com/coreos/operator-sdk/version" + sdkVersion "github.com/coreos/operator-sdk/version" "github.com/sirupsen/logrus" ) @@ -40,15 +50,15 @@ func printVersion() { func main() { printVersion() - sdk.Watch("apps/v1", "Deployment", "default") + sdk.Watch("app.example.com/v1alpha1", "App", "default") sdk.Handle(stub.NewHandler()) - sdk.Run(context.TODO()) + sdk.Run(context.TODO()) } ` func TestGenMain(t *testing.T) { buf := &bytes.Buffer{} - if err := renderMainFile(buf, "github.com/coreos/play"); err != nil { + if err := renderMainFile(buf, appRepoPath, appAPIVersion, appKind); err != nil { t.Error(err) return } @@ -61,10 +71,16 @@ func TestGenMain(t *testing.T) { const handlerExp = `package stub import ( + "github.com/example-inc/app-operator/pkg/apis/app/v1alpha1" + + "github.com/coreos/operator-sdk/pkg/sdk/action" "github.com/coreos/operator-sdk/pkg/sdk/handler" "github.com/coreos/operator-sdk/pkg/sdk/types" "github.com/sirupsen/logrus" - apps_v1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" ) func NewHandler() handler.Handler { @@ -76,18 +92,55 @@ type Handler struct { } func (h *Handler) Handle(ctx types.Context, event types.Event) error { - // Change me switch o := event.Object.(type) { - case *apps_v1.Deployment: - logrus.Printf("Received Deployment: %v", o.Name) + case *v1alpha1.App: + err := action.Create(newbusyBoxPod(o)) + if err != nil && !errors.IsAlreadyExists(err) { + logrus.Errorf("Failed to create busybox pod : %v", err) + return err + } } return nil } + +// newbusyBoxPod demonstrates how to create a busybox pod +func newbusyBoxPod(cr *v1alpha1.App) *v1.Pod { + labels := map[string]string{ + "app": "busy-box", + } + return &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "busy-box", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(cr, schema.GroupVersionKind{ + Group: v1alpha1.SchemeGroupVersion.Group, + Version: v1alpha1.SchemeGroupVersion.Version, + Kind: "App", + }), + }, + Labels: labels, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "busybox", + Image: "busybox", + Command: []string{"sleep", "3600"}, + }, + }, + }, + } +} ` func TestGenHandler(t *testing.T) { buf := &bytes.Buffer{} - if err := renderHandlerFile(buf); err != nil { + if err := renderHandlerFile(buf, appRepoPath, appKind, appApiDirName, appVersion); err != nil { t.Error(err) return } diff --git a/pkg/generator/handler_tmpl.go b/pkg/generator/handler_tmpl.go index 8a22bd3431e..ee528d09698 100644 --- a/pkg/generator/handler_tmpl.go +++ b/pkg/generator/handler_tmpl.go @@ -18,10 +18,16 @@ package generator const handlerTmpl = `package stub import ( + "{{.RepoPath}}/pkg/apis/{{.APIDirName}}/{{.Version}}" + + "{{.OperatorSDKImport}}/action" "{{.OperatorSDKImport}}/handler" "{{.OperatorSDKImport}}/types" "github.com/sirupsen/logrus" - apps_v1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" ) func NewHandler() handler.Handler { @@ -33,11 +39,48 @@ type Handler struct { } func (h *Handler) Handle(ctx types.Context, event types.Event) error { - // Change me switch o := event.Object.(type) { - case *apps_v1.Deployment: - logrus.Printf("Received Deployment: %v", o.Name) + case *{{.Version}}.{{.Kind}}: + err := action.Create(newbusyBoxPod(o)) + if err != nil && !errors.IsAlreadyExists(err) { + logrus.Errorf("Failed to create busybox pod : %v", err) + return err + } } return nil } + +// newbusyBoxPod demonstrates how to create a busybox pod +func newbusyBoxPod(cr *{{.Version}}.{{.Kind}}) *v1.Pod { + labels := map[string]string{ + "app": "busy-box", + } + return &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "busy-box", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(cr, schema.GroupVersionKind{ + Group: {{.Version}}.SchemeGroupVersion.Group, + Version: {{.Version}}.SchemeGroupVersion.Version, + Kind: "{{.Kind}}", + }), + }, + Labels: labels, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "busybox", + Image: "busybox", + Command: []string{"sleep", "3600"}, + }, + }, + }, + } +} ` diff --git a/pkg/generator/main_tmpl.go b/pkg/generator/main_tmpl.go index 053c28cd9c4..5283b35001c 100644 --- a/pkg/generator/main_tmpl.go +++ b/pkg/generator/main_tmpl.go @@ -36,8 +36,8 @@ func printVersion() { func main() { printVersion() - sdk.Watch("apps/v1", "Deployment", "default") + sdk.Watch("{{.APIVersion}}", "{{.Kind}}", "default") sdk.Handle(stub.NewHandler()) - sdk.Run(context.TODO()) + sdk.Run(context.TODO()) } `