diff --git a/cmd/command/pr/pr.go b/cmd/command/pr/pr.go index b8dbaba1..0bb4693b 100644 --- a/cmd/command/pr/pr.go +++ b/cmd/command/pr/pr.go @@ -83,13 +83,30 @@ func (p *Plural) prCommands() []cli.Command { { Name: "test", Action: common.LatestVersion(handleTestPrAutomation), - Usage: "create PR automation", + Usage: "tests a PR automation CRD locally", Flags: []cli.Flag{ cli.StringFlag{ Name: "file", Usage: "the file the PR automation was placed in", Required: true, }, + cli.StringFlag{ + Name: "context", + Usage: "a yaml file containing the context for the PRA, will read from stdin if not present", + Required: false, + }, + }, + }, + { + Name: "contracts", + Action: handlePrContracts, + Usage: "Runs a set of contract tests for your pr automations", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "file", + Usage: "the contract file to run", + Required: true, + }, }, }, } @@ -109,7 +126,7 @@ func handlePrTemplate(c *cli.Context) error { } func handleTestPrAutomation(c *cli.Context) error { - template, err := pr.BuildCRD(c.String("file")) + template, err := pr.BuildCRD(c.String("file"), c.String("context")) if err != nil { return err } @@ -121,6 +138,42 @@ func handleTestPrAutomation(c *cli.Context) error { return pr.Apply(template) } +func handlePrContracts(c *cli.Context) error { + contracts, err := pr.BuildContracts(c.String("file")) + if err != nil { + return err + } + + if contracts.Spec.Templates != nil { + tplCopy := contracts.Spec.Templates + if err := utils.CopyFile(tplCopy.From, tplCopy.To); err != nil { + return err + } + } + + if contracts.Spec.Workdir != "" { + if err := os.Chdir(contracts.Spec.Workdir); err != nil { + return err + } + } + + for _, contract := range contracts.Spec.Automations { + template, err := pr.BuildCRD(contract.File, contract.Context) + if err != nil { + return err + } + if contract.ExternalDir != "" { + template.Spec.Creates.ExternalDir = contract.ExternalDir + } + + if err := pr.Apply(template); err != nil { + return err + } + } + + return nil +} + func (p *Plural) handleCreatePrAutomation(c *cli.Context) error { if err := p.InitConsoleClient(consoleToken, consoleURL); err != nil { return err diff --git a/pkg/pr/crd.go b/pkg/pr/crd.go index d476e708..0917cde1 100644 --- a/pkg/pr/crd.go +++ b/pkg/pr/crd.go @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/yaml" ) -func BuildCRD(path string) (*PrTemplate, error) { +func BuildCRD(path, contextFile string) (*PrTemplate, error) { var prAutomationOk bool pr := &v1alpha1.PrAutomation{} data, err := os.ReadFile(path) @@ -49,7 +49,7 @@ func BuildCRD(path string) (*PrTemplate, error) { Spec: PrTemplateSpec{}, } - err, ctx := configuration(pr) + ctx, err := configuration(pr, contextFile) if err != nil { return nil, err } @@ -62,17 +62,22 @@ func BuildCRD(path string) (*PrTemplate, error) { return prTemplate, nil } -func configuration(pr *v1alpha1.PrAutomation) (error, map[string]interface{}) { +func configuration(pr *v1alpha1.PrAutomation, contextFile string) (map[string]interface{}, error) { ctx := map[string]interface{}{} if len(pr.Spec.Configuration) == 0 { - return nil, ctx + return ctx, nil + } + + if contextFile != "" { + err := utils.YamlFile(contextFile, &ctx) + return ctx, err } path := manifest.ProjectManifestPath() man := manifest.ProjectManifest{} if !utils.Exists(path) { if err := man.Write(path); err != nil { - return fmt.Errorf("error writing manifest: %w", err), ctx + return ctx, fmt.Errorf("error writing manifest: %w", err) } defer os.Remove(path) } @@ -117,10 +122,10 @@ func configuration(pr *v1alpha1.PrAutomation) (error, map[string]interface{}) { utils.Highlight("Lets' fill out the configuration for this PR automation:\n") for _, item := range items { if err := bundle.Configure(ctx, item, &manifest.Context{}, ""); err != nil { - return err, ctx + return ctx, err } } - return nil, ctx + return ctx, nil } func deletes(pr *v1alpha1.PrAutomation) *DeleteSpec { diff --git a/pkg/pr/types.go b/pkg/pr/types.go index 733c3600..dfcb8362 100644 --- a/pkg/pr/types.go +++ b/pkg/pr/types.go @@ -52,6 +52,31 @@ type RegexReplacement struct { Templated bool `json:"templated"` } +type PrContracts struct { + ApiVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Metadata map[string]interface{} `json:"metadata"` + Context map[string]interface{} `json:"context"` + Spec PrContractsSpec `json:"spec"` +} + +type PrContractsSpec struct { + Templates *TemplateCopy `json:"templates"` + Workdir string `json:"workdir,omitempty"` + Automations []AutomationContract `json:"automations"` +} + +type TemplateCopy struct { + From string `json:"from"` + To string `json:"to"` +} + +type AutomationContract struct { + File string `json:"file"` + ExternalDir string `json:"externalDir,omitempty"` + Context string `json:"context"` +} + func Build(path string) (*PrTemplate, error) { pr := &PrTemplate{} data, err := os.ReadFile(path) @@ -65,3 +90,17 @@ func Build(path string) (*PrTemplate, error) { return pr, nil } + +func BuildContracts(path string) (*PrContracts, error) { + pr := &PrContracts{} + data, err := os.ReadFile(path) + if err != nil { + return pr, err + } + + if err := yaml.Unmarshal(data, pr); err != nil { + return pr, err + } + + return pr, err +}