diff --git a/cmd/k8s-pipeliner/main.go b/cmd/k8s-pipeliner/main.go index 81a98c9..5a2fc60 100644 --- a/cmd/k8s-pipeliner/main.go +++ b/cmd/k8s-pipeliner/main.go @@ -3,17 +3,18 @@ package main import ( "encoding/json" "errors" + "fmt" "os" + "github.com/namely/k8s-pipeliner/pipeline" "github.com/namely/k8s-pipeliner/pipeline/builder" "github.com/namely/k8s-pipeliner/pipeline/config" - "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const ( // Version defines the current version of k8s-pipeliner - Version = "0.0.1" + Version = "0.0.2" ) func main() { @@ -29,10 +30,15 @@ func main() { Usage: "creates a spinnaker pipeline for a given application on multiple k8s clusters", Action: createAction, }, + { + Name: "validate", + Usage: "performs simple validation on a pipeline to ensure it will work with Spinnaker + Kubernetes", + Action: validateAction, + }, } if err := app.Run(os.Args); err != nil { - logrus.WithError(err).Error() + fmt.Println(err.Error()) os.Exit(1) } } @@ -48,10 +54,38 @@ func createAction(ctx *cli.Context) error { return err } - pipeline, err := config.NewPipeline(f) + p, err := config.NewPipeline(f) if err != nil { return err } - return json.NewEncoder(os.Stdout).Encode(builder.New(pipeline)) + return json.NewEncoder(os.Stdout).Encode(builder.New(p)) +} + +func validateAction(ctx *cli.Context) error { + p, err := pipelineConfigHelper(ctx) + if err != nil { + return err + } + + return pipeline.NewValidator(p).Validate() +} + +func pipelineConfigHelper(ctx *cli.Context) (*config.Pipeline, error) { + pipelineFile := ctx.Args().First() + if pipelineFile == "" { + return nil, errors.New("missing parameter: file") + } + + f, err := os.Open(pipelineFile) + if err != nil { + return nil, err + } + + p, err := config.NewPipeline(f) + if err != nil { + return nil, err + } + + return p, nil } diff --git a/pipeline/builder/builder.go b/pipeline/builder/builder.go index a471981..79bba1d 100644 --- a/pipeline/builder/builder.go +++ b/pipeline/builder/builder.go @@ -24,8 +24,9 @@ func New(p *config.Pipeline) *Builder { return &Builder{p} } -// MarshalJSON implements json.Marshaller -func (b *Builder) MarshalJSON() ([]byte, error) { +// Pipeline returns a filled out spinnaker pipeline from the given +// config +func (b *Builder) Pipeline() (*types.SpinnakerPipeline, error) { sp := &types.SpinnakerPipeline{} for _, trigger := range b.pipeline.Triggers { @@ -64,6 +65,16 @@ func (b *Builder) MarshalJSON() ([]byte, error) { sp.Stages = append(sp.Stages, s) } + return sp, nil +} + +// MarshalJSON implements json.Marshaller +func (b *Builder) MarshalJSON() ([]byte, error) { + sp, err := b.Pipeline() + if err != nil { + return nil, err + } + return json.Marshal(sp) } diff --git a/pipeline/validator.go b/pipeline/validator.go new file mode 100644 index 0000000..7feafd1 --- /dev/null +++ b/pipeline/validator.go @@ -0,0 +1,70 @@ +package pipeline + +import ( + "fmt" + + multierror "github.com/hashicorp/go-multierror" + "github.com/namely/k8s-pipeliner/pipeline/builder" + "github.com/namely/k8s-pipeliner/pipeline/builder/types" + "github.com/namely/k8s-pipeliner/pipeline/config" +) + +// Validator validates that a pipeline is valid +type Validator struct { + pipeline *config.Pipeline +} + +// NewValidator initializes a validator object +func NewValidator(p *config.Pipeline) *Validator { + return &Validator{pipeline: p} +} + +// Validate performs some validations on the pipeline configuration +// to see if it passes some simple standards such as "do deploys have resources allocated" +func (v *Validator) Validate() error { + b := builder.New(v.pipeline) + sp, err := b.Pipeline() + if err != nil { + return err + } + + var errs *multierror.Error + for _, stage := range sp.Stages { + switch s := stage.(type) { + case *types.DeployStage: + errs = multierror.Append(validateDeployStage(s)) + } + } + + return errs +} + +func validateDeployStage(s *types.DeployStage) error { + var errs *multierror.Error + + for _, cluster := range s.Clusters { + for _, container := range cluster.Containers { + if container.Requests.CPU == "0" { + err := fmt.Errorf("Stage: %s, Container: %s - Missing CPU on Resource Requests", s.Name, container.Name) + errs = multierror.Append(err) + } + + if container.Requests.Memory == "0" { + err := fmt.Errorf("Stage: %s, Container: %s - Missing Memory on Resource Requests", s.Name, container.Name) + errs = multierror.Append(err) + } + + if container.Limits.CPU == "0" { + err := fmt.Errorf("Stage: %s, Container: %s - Missing CPU on Resource Limits", s.Name, container.Name) + errs = multierror.Append(err) + } + + if container.Limits.Memory == "0" { + err := fmt.Errorf("Stage: %s, Container: %s - Missing Memory on Resource Limits", s.Name, container.Name) + errs = multierror.Append(err) + } + } + } + + return errs +} diff --git a/test-deployment.yml b/test-deployment.yml index ad1226d..6cc8b5e 100644 --- a/test-deployment.yml +++ b/test-deployment.yml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta2 +apiVersion: apps/v1beta2 kind: Deployment metadata: name: example