Skip to content

Commit d29ad54

Browse files
authored
Add -skip-errors flag to src campaign apply|preview (#395)
* Add -skip-errors flag to src campaign apply|preview * Fix formatting of errors * Add notice * Remove notice * Add changelog entry
1 parent 6175b96 commit d29ad54

File tree

5 files changed

+69
-29
lines changed

5 files changed

+69
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ All notable changes to `src-cli` are documented in this file.
1414
### Added
1515

1616
- Campaign steps may now include environment variables from outside of the campaign spec using [array syntax](http://docs.sourcegraph.com/campaigns/references/campaign_spec_yaml_reference#environment-array). [#392](https://github.com/sourcegraph/src-cli/pull/392)
17+
- A new `-skip-errors` flag has been added to `src campaign [apply|preview]` to allow users to continue execution of and upload a campaign spec even if execution failed in some repositories. [#395](https://github.com/sourcegraph/src-cli/pull/395)
1718

1819
### Changed
1920

cmd/src/campaigns_apply.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Examples:
7878

7979
if err := doApply(ctx, out, svc, flags); err != nil {
8080
printExecutionError(out, err)
81+
out.Write("")
8182
return &exitCodeError{nil, 1}
8283
}
8384

cmd/src/campaigns_common.go

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type campaignsApplyFlags struct {
4242
parallelism int
4343
timeout time.Duration
4444
cleanArchives bool
45+
skipErrors bool
4546
}
4647

4748
func newCampaignsApplyFlags(flagSet *flag.FlagSet, cacheDir, tempDir string) *campaignsApplyFlags {
@@ -95,6 +96,10 @@ func newCampaignsApplyFlags(flagSet *flag.FlagSet, cacheDir, tempDir string) *ca
9596
&caf.cleanArchives, "clean-archives", true,
9697
"If true, deletes downloaded repository archives after executing campaign steps.",
9798
)
99+
flagSet.BoolVar(
100+
&caf.skipErrors, "skip-errors", false,
101+
"If true, errors encountered while executing steps in a repository won't stop the execution of the campaign spec but only cause that repository to be skipped.",
102+
)
98103

99104
flagSet.BoolVar(verbose, "v", false, "print verbose output")
100105

@@ -235,11 +240,16 @@ func campaignsExecute(ctx context.Context, out *output.Output, svc *campaigns.Se
235240
}
236241

237242
p := newCampaignProgressPrinter(out, *verbose, opts.Parallelism)
238-
specs, err := svc.ExecuteCampaignSpec(ctx, repos, executor, campaignSpec, p.PrintStatuses)
239-
if err != nil {
243+
specs, err := svc.ExecuteCampaignSpec(ctx, repos, executor, campaignSpec, p.PrintStatuses, flags.skipErrors)
244+
if err != nil && !flags.skipErrors {
240245
return "", "", err
246+
241247
}
242248
p.Complete()
249+
if err != nil && flags.skipErrors {
250+
printExecutionError(out, err)
251+
out.WriteLine(output.Line(output.EmojiWarning, output.StyleWarning, "Skipping errors because -skip-errors was used."))
252+
}
243253

244254
if logFiles := executor.LogFiles(); len(logFiles) > 0 && flags.keepLogs {
245255
func() {
@@ -317,40 +327,54 @@ func campaignsParseSpec(out *output.Output, svc *campaigns.Service, input io.Rea
317327
func printExecutionError(out *output.Output, err error) {
318328
out.Write("")
319329

320-
writeErr := func(block *output.Block, err error) {
321-
if block == nil {
322-
return
323-
}
330+
writeErrs := func(errs []error) {
331+
var block *output.Block
324332

325-
if taskErr, ok := err.(campaigns.TaskExecutionErr); ok {
326-
block.Write(formatTaskExecutionErr(taskErr))
333+
if len(errs) > 1 {
334+
block = out.Block(output.Linef(output.EmojiFailure, output.StyleWarning, "%d errors:", len(errs)))
327335
} else {
328-
block.Write(err.Error())
336+
block = out.Block(output.Line(output.EmojiFailure, output.StyleWarning, "Error:"))
337+
}
338+
339+
for _, e := range errs {
340+
if taskErr, ok := e.(campaigns.TaskExecutionErr); ok {
341+
block.Write(formatTaskExecutionErr(taskErr))
342+
} else {
343+
block.Writef("%s%s", output.StyleBold, e.Error())
344+
}
345+
}
346+
347+
if block != nil {
348+
block.Close()
329349
}
330350
}
331351

332-
var block *output.Block
333-
singleErrHeader := output.Line(output.EmojiFailure, output.StyleWarning, "Error:")
352+
switch err := err.(type) {
353+
case parallel.Errors, *multierror.Error:
354+
writeErrs(flattenErrs(err))
334355

335-
if parErr, ok := err.(parallel.Errors); ok {
336-
if len(parErr) > 1 {
337-
block = out.Block(output.Linef(output.EmojiFailure, output.StyleWarning, "%d errors:", len(parErr)))
338-
} else {
339-
block = out.Block(singleErrHeader)
356+
default:
357+
writeErrs([]error{err})
358+
}
359+
360+
}
361+
362+
func flattenErrs(err error) (result []error) {
363+
switch errs := err.(type) {
364+
case parallel.Errors:
365+
for _, e := range errs {
366+
result = append(result, flattenErrs(e)...)
340367
}
341368

342-
for _, e := range parErr {
343-
writeErr(block, e)
369+
case *multierror.Error:
370+
for _, e := range errs.Errors {
371+
result = append(result, flattenErrs(e)...)
344372
}
345-
} else {
346-
block = out.Block(singleErrHeader)
347-
writeErr(block, err)
373+
default:
374+
result = append(result, errs)
348375
}
349376

350-
if block != nil {
351-
block.Close()
352-
}
353-
out.Write("")
377+
return result
354378
}
355379

356380
func formatTaskExecutionErr(err campaigns.TaskExecutionErr) string {

cmd/src/campaigns_preview.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Examples:
5454
_, url, err := campaignsExecute(ctx, out, svc, flags)
5555
if err != nil {
5656
printExecutionError(out, err)
57+
out.Write("")
5758
return &exitCodeError{nil, 1}
5859
}
5960

internal/campaigns/service.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
"github.com/hashicorp/go-multierror"
1516
"github.com/pkg/errors"
1617
"github.com/sourcegraph/src-cli/internal/api"
1718
"github.com/sourcegraph/src-cli/internal/campaigns/graphql"
@@ -215,7 +216,7 @@ func (svc *Service) SetDockerImages(ctx context.Context, spec *CampaignSpec, pro
215216
return nil
216217
}
217218

218-
func (svc *Service) ExecuteCampaignSpec(ctx context.Context, repos []*graphql.Repository, x Executor, spec *CampaignSpec, progress func([]*TaskStatus)) ([]*ChangesetSpec, error) {
219+
func (svc *Service) ExecuteCampaignSpec(ctx context.Context, repos []*graphql.Repository, x Executor, spec *CampaignSpec, progress func([]*TaskStatus), skipErrors bool) ([]*ChangesetSpec, error) {
219220
statuses := make([]*TaskStatus, 0, len(repos))
220221
for _, repo := range repos {
221222
ts := x.AddTask(repo, spec.Steps, spec.ChangesetTemplate)
@@ -242,21 +243,33 @@ func (svc *Service) ExecuteCampaignSpec(ctx context.Context, repos []*graphql.Re
242243
}()
243244
}
244245

246+
var errs *multierror.Error
247+
245248
x.Start(ctx)
246249
specs, err := x.Wait()
247250
if progress != nil {
248251
progress(statuses)
249252
done <- struct{}{}
250253
}
251254
if err != nil {
252-
return nil, err
255+
if skipErrors {
256+
errs = multierror.Append(errs, err)
257+
} else {
258+
return nil, err
259+
}
253260
}
254261

255262
// Add external changeset specs.
256263
for _, ic := range spec.ImportChangesets {
257264
repo, err := svc.resolveRepositoryName(ctx, ic.Repository)
258265
if err != nil {
259-
return nil, errors.Wrapf(err, "resolving repository name %q", ic.Repository)
266+
wrapped := errors.Wrapf(err, "resolving repository name %q", ic.Repository)
267+
if skipErrors {
268+
errs = multierror.Append(errs, wrapped)
269+
continue
270+
} else {
271+
return nil, wrapped
272+
}
260273
}
261274

262275
for _, id := range ic.ExternalIDs {
@@ -284,7 +297,7 @@ func (svc *Service) ExecuteCampaignSpec(ctx context.Context, repos []*graphql.Re
284297
}
285298
}
286299

287-
return specs, nil
300+
return specs, errs.ErrorOrNil()
288301
}
289302

290303
func (svc *Service) ParseCampaignSpec(in io.Reader) (*CampaignSpec, string, error) {

0 commit comments

Comments
 (0)