Skip to content

Commit

Permalink
Operator Skeleton Generators (#1222)
Browse files Browse the repository at this point in the history
Co-Authored-By: Andreas Neumann <aneumann@mesosphere.com>
Signed-off-by: Ken Sipe <kensipe@gmail.com>
  • Loading branch information
kensipe and ANeumann82 committed Jan 7, 2020
1 parent 7d84119 commit 60b0f19
Show file tree
Hide file tree
Showing 27 changed files with 1,711 additions and 99 deletions.
12 changes: 6 additions & 6 deletions pkg/apis/kudo/v1beta1/operatorversion_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,26 +141,26 @@ type TaskSpec struct {
type ResourceTaskSpec struct {
// +optional
// +nullable
Resources []string `json:"resources"`
Resources []string `json:"resources,omitempty"`
}

// DummyTaskSpec can succeed or fail on demand and is very useful for testing operators
type DummyTaskSpec struct {
// +optional
WantErr bool `json:"wantErr"`
WantErr bool `json:"wantErr,omitempty"`
// +optional
Fatal bool `json:"fatal"`
Fatal bool `json:"fatal,omitempty"`
// +optional
Done bool `json:"done"`
Done bool `json:"done,omitempty"`
}

// PipeTask specifies a task that generates files and stores them for later usage in subsequent tasks
type PipeTaskSpec struct {
// +optional
Pod string `json:"pod"`
Pod string `json:"pod,omitempty"`
// +optional
// +nullable
Pipe []PipeSpec `json:"pipe"`
Pipe []PipeSpec `json:"pipe,omitempty"`
}

// PipeSpec describes how a file generated by a PipeTask is stored and referenced
Expand Down
32 changes: 32 additions & 0 deletions pkg/kudoctl/cmd/generate/maintainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package generate

import (
"github.com/spf13/afero"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader"
)

// AddMaintainer adds a maintainer to the operator.yaml
func AddMaintainer(fs afero.Fs, path string, m *v1beta1.Maintainer) error {

p, err := reader.ReadDir(fs, path)
if err != nil {
return err
}
o := p.Files.Operator

o.Maintainers = append(o.Maintainers, m)

return writeOperator(fs, path, o)
}

// MaintainerList provides a list of operator maintainers
func MaintainerList(fs afero.Fs, path string) ([]*v1beta1.Maintainer, error) {
p, err := reader.ReadDir(fs, path)
if err != nil {
return nil, err
}

return p.Files.Operator.Maintainers, nil
}
56 changes: 56 additions & 0 deletions pkg/kudoctl/cmd/generate/maintainer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package generate

import (
"flag"
"io/ioutil"
"path/filepath"
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/files"
)

var updateGolden = flag.Bool("update", false, "update .golden files and manifests in /config/crd")

func TestAddMaintainer(t *testing.T) {
goldenFile := "maintainer"
fs := afero.NewMemMapFs()
files.CopyOperatorToFs(fs, "../../packages/testdata/zk", "/opt")
m := v1beta1.Maintainer{
Name: "Cat in the hat",
Email: "c@hat.com",
}

err := AddMaintainer(fs, "/opt/zk", &m)
assert.NoError(t, err)

operator, err := afero.ReadFile(fs, "/opt/zk/operator.yaml")
assert.NoError(t, err)

gp := filepath.Join("testdata", goldenFile+".golden")

if *updateGolden {
t.Logf("updating golden file %s", goldenFile)
if err := ioutil.WriteFile(gp, operator, 0644); err != nil {
t.Fatalf("failed to update golden file: %s", err)
}
}
g, err := ioutil.ReadFile(gp)
if err != nil {
t.Fatalf("failed reading .golden: %s", err)
}

assert.Equal(t, g, operator, "for golden file: %s", gp)
}

func TestListMaintainers(t *testing.T) {
fs := afero.OsFs{}
m, err := MaintainerList(fs, "../../packages/testdata/zk")
assert.NoError(t, err)

assert.Equal(t, 3, len(m))
assert.Equal(t, "Alena Varkockova", m[0].Name)
}
35 changes: 33 additions & 2 deletions pkg/kudoctl/cmd/generate/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"path"
"path/filepath"

"github.com/spf13/afero"
"sigs.k8s.io/yaml"
Expand Down Expand Up @@ -36,7 +37,7 @@ func CanGenerateOperator(fs afero.Fs, dir string, overwrite bool) error {
}

// Operator generates an initial operator folder with a operator.yaml
func Operator(fs afero.Fs, dir string, op packages.OperatorFile, overwrite bool) error {
func Operator(fs afero.Fs, dir string, op *packages.OperatorFile, overwrite bool) error {
err := CanGenerateOperator(fs, dir, overwrite)
if err != nil {
return err
Expand Down Expand Up @@ -90,7 +91,7 @@ func writeParameters(fs afero.Fs, dir string, params packages.ParamsFile) error
return afero.WriteFile(fs, fname, p, 0755)
}

func writeOperator(fs afero.Fs, dir string, op packages.OperatorFile) error {
func writeOperator(fs afero.Fs, dir string, op *packages.OperatorFile) error {
o, err := yaml.Marshal(op)
if err != nil {
return err
Expand All @@ -99,3 +100,33 @@ func writeOperator(fs afero.Fs, dir string, op packages.OperatorFile) error {
fname := path.Join(dir, reader.OperatorFileName)
return afero.WriteFile(fs, fname, o, 0755)
}

// OperatorPath determines the path to use as operator path for generators
// the path is either current "", or a dir with operator.yaml (if 1) else an error
// and is determined based on location of operator.yaml
func OperatorPath(fs afero.Fs) (string, error) {
fname := "operator.yaml"

exists, err := afero.Exists(fs, fname)
if err != nil {
return "", err
}

if exists {
return "", nil
}

pat := path.Join("**", fname)
// one more try
files, err := afero.Glob(fs, pat)
if err != nil {
return "", err
}
if len(files) < 1 {
return "", errors.New("no operator folder discovered")
}
if len(files) > 1 {
return "", errors.New("multiple operator folders discovered")
}
return filepath.Dir(files[0]), nil
}
8 changes: 4 additions & 4 deletions pkg/kudoctl/cmd/generate/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestOperator_Write(t *testing.T) {

fs := afero.NewMemMapFs()

err := Operator(fs, "operator", op1, false)
err := Operator(fs, "operator", &op1, false)
// no error on create
assert.Nil(t, err)

Expand All @@ -59,11 +59,11 @@ func TestOperator_Write(t *testing.T) {
assert.True(t, exists)

// test fail on existing
err = Operator(fs, "operator", op1, false)
err = Operator(fs, "operator", &op1, false)
assert.Errorf(t, err, "folder 'operator' already exists")

// test overwriting with no error
err = Operator(fs, "operator", op1, true)
err = Operator(fs, "operator", &op1, true)
// no error on overwrite
assert.Nil(t, err)

Expand All @@ -76,7 +76,7 @@ func TestOperator_Write(t *testing.T) {
err = writeParameters(fs, "operator", pf)
assert.Nil(t, err)
// test overwriting with no error
err = Operator(fs, "operator", op1, true)
err = Operator(fs, "operator", &op1, true)
// no error on overwrite
assert.Nil(t, err)
parmfile, _ := afero.ReadFile(fs, paramFilename)
Expand Down
35 changes: 35 additions & 0 deletions pkg/kudoctl/cmd/generate/parameter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package generate

import (
"github.com/spf13/afero"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader"
)

// AddParameter writes a parameter to the params.yaml file
func AddParameter(fs afero.Fs, path string, p *v1beta1.Parameter) error {

pf, err := reader.ReadDir(fs, path)
if err != nil {
return err
}

params := pf.Files.Params
params.Parameters = append(params.Parameters, *p)

return writeParameters(fs, path, *params)
}

func ParameterNameList(fs afero.Fs, path string) (paramNames []string, err error) {
pf, err := reader.ReadDir(fs, path)
if err != nil {
return nil, err
}

for _, parameter := range pf.Files.Params.Parameters {
paramNames = append(paramNames, parameter.Name)
}

return paramNames, nil
}
71 changes: 71 additions & 0 deletions pkg/kudoctl/cmd/generate/parameter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package generate

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/files"
)

func TestAddParameter(t *testing.T) {
goldenFile := "parameter"
path := "/opt/zk"
fs := afero.NewMemMapFs()
files.CopyOperatorToFs(fs, "../../packages/testdata/zk", "/opt")

bar := "Bar"
p := v1beta1.Parameter{
Name: "Foo",
Default: &bar,
}

err := AddParameter(fs, path, &p)
assert.NoError(t, err)

params, err := afero.ReadFile(fs, "/opt/zk/params.yaml")
assert.NoError(t, err)

gp := filepath.Join("testdata", goldenFile+".golden")

if *updateGolden {
t.Logf("updating golden file %s", goldenFile)
if err := ioutil.WriteFile(gp, params, 0644); err != nil {
t.Fatalf("failed to update golden file: %s", err)
}
}
golden, err := ioutil.ReadFile(gp)
if err != nil {
t.Fatalf("failed reading .golden: %s", err)
}

assert.Equal(t, golden, params, "for golden file: %s", gp)
}

func TestAddParameter_bad_path(t *testing.T) {
path, _ := os.Getwd()
fs := afero.OsFs{}

bar := "Bar"
p := v1beta1.Parameter{
Name: "Foo",
Default: &bar,
}

err := AddParameter(fs, path, &p)
assert.Error(t, err)
}

func TestListParams(t *testing.T) {
fs := afero.OsFs{}
ps, err := ParameterNameList(fs, "../../packages/testdata/zk")
assert.NoError(t, err)

assert.Equal(t, 2, len(ps))
assert.Equal(t, "memory", ps[0])
}
51 changes: 51 additions & 0 deletions pkg/kudoctl/cmd/generate/plans.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package generate

import (
"sort"

"github.com/spf13/afero"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader"
)

// AddPlan adds a plan to the operator.yaml file
func AddPlan(fs afero.Fs, path string, planName string, plan *v1beta1.Plan) error {

pf, err := reader.ReadDir(fs, path)
if err != nil {
return err
}

o := pf.Files.Operator
plans := o.Plans
plans[planName] = *plan
pf.Files.Operator.Plans = plans

return writeOperator(fs, path, o)
}

// PlanList provides a list of operator plans
func PlanList(fs afero.Fs, path string) (map[string]v1beta1.Plan, error) {
p, err := reader.ReadDir(fs, path)
if err != nil {
return nil, err
}

return p.Files.Operator.Plans, nil
}

// PlanNameList provides a list of operator plan names
func PlanNameList(fs afero.Fs, path string) ([]string, error) {

names := []string{}
p, err := reader.ReadDir(fs, path)
if err != nil {
return nil, err
}
for name := range p.Files.Operator.Plans {
names = append(names, name)
}
sort.Strings(names)
return names, nil
}
Loading

0 comments on commit 60b0f19

Please sign in to comment.