diff --git a/cmd/src/campaigns_common.go b/cmd/src/campaigns_common.go index 095f9df01b..20dc2fea72 100644 --- a/cmd/src/campaigns_common.go +++ b/cmd/src/campaigns_common.go @@ -193,12 +193,8 @@ func campaignsExecute(ctx context.Context, out *output.Output, svc *campaigns.Se } pending := campaignsCreatePending(out, "Parsing campaign spec") - campaignSpec, rawSpec, err := svc.ParseCampaignSpec(specFile) + campaignSpec, rawSpec, err := campaignsParseSpec(out, svc, specFile) if err != nil { - return "", "", errors.Wrap(err, "parsing campaign spec") - } - - if err := campaignsValidateSpec(out, campaignSpec); err != nil { return "", "", err } campaignsCompletePending(pending, "Parsing campaign spec") @@ -290,6 +286,34 @@ func campaignsExecute(ctx context.Context, out *output.Output, svc *campaigns.Se return id, url, nil } +// campaignsParseSpec parses and validates the given campaign spec. If the spec +// has validation errors, the errors are output in a human readable form and an +// exitCodeError is returned. +func campaignsParseSpec(out *output.Output, svc *campaigns.Service, input io.ReadCloser) (*campaigns.CampaignSpec, string, error) { + spec, raw, err := svc.ParseCampaignSpec(input) + if err != nil { + if merr, ok := err.(*multierror.Error); ok { + block := out.Block(output.Line("\u274c", output.StyleWarning, "Campaign spec failed validation.")) + defer block.Close() + + for i, err := range merr.Errors { + block.Writef("%d. %s", i+1, err) + } + + return nil, "", &exitCodeError{ + error: nil, + exitCode: 2, + } + } else { + // This shouldn't happen; let's just punt and let the normal + // rendering occur. + return nil, "", err + } + } + + return spec, raw, nil +} + // printExecutionError is used to print the possible error returned by // campaignsExecute. func printExecutionError(out *output.Output, err error) { diff --git a/cmd/src/campaigns_repositories.go b/cmd/src/campaigns_repositories.go index 4aec8ddbd8..bded48764a 100644 --- a/cmd/src/campaigns_repositories.go +++ b/cmd/src/campaigns_repositories.go @@ -54,13 +54,9 @@ Examples: return err } - spec, _, err := svc.ParseCampaignSpec(specFile) - if err != nil { - return errors.Wrap(err, "parsing campaign spec") - } - out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose}) - if err := campaignsValidateSpec(out, spec); err != nil { + spec, _, err := campaignsParseSpec(out, svc, specFile) + if err != nil { return err } diff --git a/cmd/src/campaigns_validate.go b/cmd/src/campaigns_validate.go index 716b807d79..617ce1ae93 100644 --- a/cmd/src/campaigns_validate.go +++ b/cmd/src/campaigns_validate.go @@ -4,8 +4,6 @@ import ( "flag" "fmt" - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" "github.com/sourcegraph/src-cli/internal/campaigns" "github.com/sourcegraph/src-cli/internal/output" ) @@ -40,13 +38,8 @@ Examples: svc := campaigns.NewService(&campaigns.ServiceOpts{}) - spec, _, err := svc.ParseCampaignSpec(specFile) - if err != nil { - return errors.Wrap(err, "parsing campaign spec") - } - out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose}) - if err := campaignsValidateSpec(out, spec); err != nil { + if _, _, err := campaignsParseSpec(out, svc, specFile); err != nil { return err } @@ -64,30 +57,3 @@ Examples: }, }) } - -// campaignsValidateSpec validates the given campaign spec. If the spec has -// validation errors, they are output in a human readable form and an -// exitCodeError is returned. -func campaignsValidateSpec(out *output.Output, spec *campaigns.CampaignSpec) error { - if err := spec.Validate(); err != nil { - if merr, ok := err.(*multierror.Error); ok { - block := out.Block(output.Line("\u274c", output.StyleWarning, "Campaign spec failed validation.")) - defer block.Close() - - for i, err := range merr.Errors { - block.Writef("%d. %s", i+1, err) - } - - return &exitCodeError{ - error: nil, - exitCode: 2, - } - } else { - // This shouldn't happen; let's just punt and let the normal - // rendering occur. - return err - } - } - - return nil -} diff --git a/go.mod b/go.mod index 660c17f752..63095d98f8 100644 --- a/go.mod +++ b/go.mod @@ -18,13 +18,12 @@ require ( github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/pkg/errors v0.9.1 - github.com/sourcegraph/campaignutils v0.0.0-20201014140011-721315691938 + github.com/sourcegraph/campaignutils v0.0.0-20201016010611-63eb2bca27ad github.com/sourcegraph/codeintelutils v0.0.0-20200824140252-1db3aed5cf58 github.com/sourcegraph/go-diff v0.6.0 github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 gopkg.in/yaml.v2 v2.3.0 @@ -32,3 +31,6 @@ require ( ) replace github.com/gosuri/uilive v0.0.4 => github.com/mrnugget/uilive v0.0.4-fix-escape + +// See: https://github.com/ghodss/yaml/pull/65 +replace github.com/ghodss/yaml => github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 diff --git a/go.sum b/go.sum index fc00fbdd58..13c30d229a 100644 --- a/go.sum +++ b/go.sum @@ -48,14 +48,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sourcegraph/campaignutils v0.0.0-20201014140011-721315691938 h1:h5gOw7sMlYFSQFNdFPXNHJ4dtD0nlgClmL8SGAzlNM4= -github.com/sourcegraph/campaignutils v0.0.0-20201014140011-721315691938/go.mod h1:5P8k8KlKz78RZJ2EFk9k9ln/j/twj28z/+BvO5hHZJ8= +github.com/sourcegraph/campaignutils v0.0.0-20201016010611-63eb2bca27ad h1:HeSWFpxau4Jqk0s4yEhOdep+KYrJDm0497uhb/hnsgU= +github.com/sourcegraph/campaignutils v0.0.0-20201016010611-63eb2bca27ad/go.mod h1:xm6i78Mk2t4DBLQDqEFc/3x6IPf7yYZCgbNaTQGhJHA= github.com/sourcegraph/codeintelutils v0.0.0-20200824140252-1db3aed5cf58 h1:Ps+U1xoZP+Zoph39YfRB6Q846ghO8pgrAgp0MiObvPs= github.com/sourcegraph/codeintelutils v0.0.0-20200824140252-1db3aed5cf58/go.mod h1:HplI8gRslTrTUUsSYwu28hSOderix7m5dHNca7xBzeo= github.com/sourcegraph/go-diff v0.6.0 h1:WbN9e/jD8ujU+o0vd9IFN5AEwtfB0rn/zM/AANaClqQ= github.com/sourcegraph/go-diff v0.6.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf h1:oAdWFqhStsWiiMP/vkkHiMXqFXzl1XfUNOdxKJbd6bI= github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf/go.mod h1:ppFaPm6kpcHnZGqQTFhUIAQRIEhdQDWP1PCv4/ON354= +github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 h1:z/MpntplPaW6QW95pzcAR/72Z5TWDyDnSo0EOcyij9o= +github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -102,7 +104,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:mub0MmFLOn8XLikZOAhgLD1kXJq8jgftSrrv7m00xFo= jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4= diff --git a/internal/campaigns/campaign_spec.go b/internal/campaigns/campaign_spec.go index 659c058eff..8fbb1d45e8 100644 --- a/internal/campaigns/campaign_spec.go +++ b/internal/campaigns/campaign_spec.go @@ -3,12 +3,9 @@ package campaigns import ( "fmt" - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" "github.com/sourcegraph/campaignutils/overridable" + "github.com/sourcegraph/campaignutils/yaml" "github.com/sourcegraph/src-cli/schema" - "github.com/xeipuuv/gojsonschema" - "gopkg.in/yaml.v2" ) // Some general notes about the struct definitions below. @@ -75,41 +72,13 @@ type Step struct { func ParseCampaignSpec(data []byte) (*CampaignSpec, error) { var spec CampaignSpec - if err := yaml.Unmarshal(data, &spec); err != nil { + if err := yaml.UnmarshalValidate(schema.CampaignSpecJSON, data, &spec); err != nil { return nil, err } return &spec, nil } -var campaignSpecSchema *gojsonschema.Schema - -func (spec *CampaignSpec) Validate() error { - if campaignSpecSchema == nil { - var err error - campaignSpecSchema, err = gojsonschema.NewSchemaLoader().Compile(gojsonschema.NewStringLoader(schema.CampaignSpecJSON)) - if err != nil { - return errors.Wrap(err, "parsing campaign spec schema") - } - } - - result, err := campaignSpecSchema.Validate(gojsonschema.NewGoLoader(spec)) - if err != nil { - return errors.Wrapf(err, "validating campaign spec") - } - if result.Valid() { - return nil - } - - var errs *multierror.Error - for _, verr := range result.Errors() { - // ResultError instances don't actually implement error, so we need to - // wrap them as best we can before adding them to the multierror. - errs = multierror.Append(errs, errors.New(verr.String())) - } - return errs -} - func (on *OnQueryOrRepository) String() string { if on.RepositoriesMatchingQuery != "" { return on.RepositoriesMatchingQuery