Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ All notable changes to `src-cli` are documented in this file.

### Added

- `src campaign [apply|preview]` now accept a `-vv` option to display even more verbose output, most notably every command being spawned during execution. [#402](https://github.com/sourcegraph/src-cli/pull/402)

### Changed

- `src campaign [apply|preview]` now show the current execution progress in numbers next to the progress bar. [#396](https://github.com/sourcegraph/src-cli/pull/396)
Expand Down
6 changes: 6 additions & 0 deletions cmd/src/campaigns_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ Examples:
return &usageError{errors.New("additional arguments not allowed")}
}

// -vv implies -v.
if flags.superVerbose {
*verbose = true
}

out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})

ctx, cancel := contextCancelOnInterrupt(context.Background())
Expand All @@ -70,6 +75,7 @@ Examples:
svc := campaigns.NewService(&campaigns.ServiceOpts{
AllowUnsupported: flags.allowUnsupported,
Client: cfg.apiClient(flags.api, flagSet.Output()),
OnCommand: campaignsOnCommand(out, flags.superVerbose),
})

if err := svc.DetermineFeatureFlags(ctx); err != nil {
Expand Down
13 changes: 13 additions & 0 deletions cmd/src/campaigns_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type campaignsApplyFlags struct {
keepLogs bool
namespace string
parallelism int
superVerbose bool
timeout time.Duration
cleanArchives bool
skipErrors bool
Expand Down Expand Up @@ -102,6 +103,8 @@ func newCampaignsApplyFlags(flagSet *flag.FlagSet, cacheDir, tempDir string) *ca

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

flagSet.BoolVar(&caf.superVerbose, "vv", false, "print more verbose output")

return caf
}

Expand Down Expand Up @@ -463,3 +466,13 @@ func contextCancelOnInterrupt(parent context.Context) (context.Context, func())
ctxCancel()
}
}

func campaignsOnCommand(out *output.Output, superVerbose bool) campaigns.OnCommand {
if superVerbose {
return func(cmd *exec.Cmd) {
out.VerboseLine(output.Line("🚀", output.Fg256Color(184), cmd.String()))
}
}

return nil
}
6 changes: 6 additions & 0 deletions cmd/src/campaigns_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Examples:
return &usageError{errors.New("additional arguments not allowed")}
}

// -vv implies -v.
if flags.superVerbose {
*verbose = true
}

out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})

ctx, cancel := contextCancelOnInterrupt(context.Background())
Expand All @@ -45,6 +50,7 @@ Examples:
svc := campaigns.NewService(&campaigns.ServiceOpts{
AllowUnsupported: flags.allowUnsupported,
Client: cfg.apiClient(flags.api, flagSet.Output()),
OnCommand: campaignsOnCommand(out, flags.superVerbose),
})

if err := svc.DetermineFeatureFlags(ctx); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/campaigns/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func getDockerImageContentDigest(ctx context.Context, image string) (string, err
// digest. but the digest is not calculated for all images (unless they are
// pulled/pushed from/to a registry), see
// https://github.com/moby/moby/issues/32016.
out, err := exec.CommandContext(ctx, "docker", "image", "inspect", "--format", "{{.Id}}", "--", image).CombinedOutput()
out, err := noticeCommand(ctx, exec.CommandContext(ctx, "docker", "image", "inspect", "--format", "{{.Id}}", "--", image)).CombinedOutput()
if err != nil {
if !strings.Contains(string(out), "No such image") {
return "", fmt.Errorf("error inspecting docker image %q: %s", image, bytes.TrimSpace(out))
Expand Down
18 changes: 18 additions & 0 deletions internal/campaigns/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package campaigns

import (
"context"
"os/exec"
)

type OnCommand func(*exec.Cmd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick/not-a-blocker: I think CommandWrapper might be a better name here, since it's not a callback (which I always associate with "on" naming, which makes me think of events), but it does take in the command and execute it.


const onCommandContextKey = "campaigns.onCommand"

func noticeCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd {
if onCommand, ok := ctx.Value(onCommandContextKey).(OnCommand); onCommand != nil && ok {
onCommand(cmd)
}

return cmd
}
8 changes: 4 additions & 4 deletions internal/campaigns/run_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
"GIT_COMMITTER_EMAIL=campaigns@sourcegraph.com",
}
cmd.Dir = volumeDir
out, err := cmd.CombinedOutput()
out, err := noticeCommand(ctx, cmd).CombinedOutput()
if err != nil {
return nil, errors.Wrapf(err, "'git %s' failed: %s", strings.Join(args, " "), out)
}
Expand Down Expand Up @@ -95,7 +95,7 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
if err == nil {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
_ = exec.CommandContext(ctx, "docker", "rm", "-f", "--", string(cid)).Run()
_ = noticeCommand(ctx, exec.CommandContext(ctx, "docker", "rm", "-f", "--", string(cid))).Run()
}
}()

Expand Down Expand Up @@ -213,7 +213,7 @@ func runSteps(ctx context.Context, wc *WorkspaceCreator, repo *graphql.Repositor
logger.Logf("[Step %d] full command: %q", i+1, strings.Join(cmd.Args, " "))

t0 := time.Now()
err = cmd.Run()
err = noticeCommand(ctx, cmd).Run()
elapsed := time.Since(t0).Round(time.Millisecond)
if err != nil {
logger.Logf("[Step %d] took %s; error running Docker container: %+v", i+1, elapsed, err)
Expand Down Expand Up @@ -293,7 +293,7 @@ func probeImageForShell(ctx context.Context, image string) (shell, tempfile stri
cmd.Stdout = stdout
cmd.Stderr = stderr

if runErr := cmd.Run(); runErr != nil {
if runErr := noticeCommand(ctx, cmd).Run(); runErr != nil {
err = multierror.Append(err, errors.Wrapf(runErr, "probing shell %q:\n%s", shell, stderr.String()))
} else {
// Even if there were previous errors, we can now ignore them.
Expand Down
24 changes: 24 additions & 0 deletions internal/campaigns/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ type Service struct {
allowUnsupported bool
client api.Client
features featureFlags
onCommand OnCommand
}

type ServiceOpts struct {
AllowUnsupported bool
Client api.Client
OnCommand OnCommand
}

var (
Expand All @@ -37,6 +39,7 @@ func NewService(opts *ServiceOpts) *Service {
return &Service{
allowUnsupported: opts.AllowUnsupported,
client: opts.Client,
onCommand: opts.OnCommand,
}
}

Expand Down Expand Up @@ -68,6 +71,7 @@ func (svc *Service) getSourcegraphVersion(ctx context.Context) (string, error) {
// instance and then sets flags on the Service itself to use features available
// in that version, e.g. gzip compression.
func (svc *Service) DetermineFeatureFlags(ctx context.Context) error {
ctx = svc.decorateContext(ctx)
version, err := svc.getSourcegraphVersion(ctx)
if err != nil {
return errors.Wrap(err, "failed to query Sourcegraph version to check for available features")
Expand Down Expand Up @@ -95,6 +99,8 @@ mutation ApplyCampaign($campaignSpec: ID!) {
` + graphql.CampaignFieldsFragment

func (svc *Service) ApplyCampaign(ctx context.Context, spec CampaignSpecID) (*graphql.Campaign, error) {
ctx = svc.decorateContext(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker: would it make sense to save the context on the *Service instead of passing it into every method? I don't think we call once instance of *Service multiple times with different contexts, so caching it might make more sense vs. having the risk of forgetting to decorate the context every time.


var result struct {
Campaign *graphql.Campaign `json:"applyCampaign"`
}
Expand Down Expand Up @@ -124,6 +130,8 @@ mutation CreateCampaignSpec(
`

func (svc *Service) CreateCampaignSpec(ctx context.Context, namespace, spec string, ids []ChangesetSpecID) (CampaignSpecID, string, error) {
ctx = svc.decorateContext(ctx)

var result struct {
CreateCampaignSpec struct {
ID string
Expand Down Expand Up @@ -156,6 +164,8 @@ mutation CreateChangesetSpec($spec: String!) {
`

func (svc *Service) CreateChangesetSpec(ctx context.Context, spec *ChangesetSpec) (ChangesetSpecID, error) {
ctx = svc.decorateContext(ctx)

raw, err := json.Marshal(spec)
if err != nil {
return "", errors.Wrap(err, "marshalling changeset spec JSON")
Expand Down Expand Up @@ -204,6 +214,8 @@ func (svc *Service) NewWorkspaceCreator(dir string, cleanArchives bool) *Workspa
}

func (svc *Service) SetDockerImages(ctx context.Context, spec *CampaignSpec, progress func(i int)) error {
ctx = svc.decorateContext(ctx)

for i, step := range spec.Steps {
image, err := getDockerImageContentDigest(ctx, step.Container)
if err != nil {
Expand All @@ -217,6 +229,8 @@ func (svc *Service) SetDockerImages(ctx context.Context, spec *CampaignSpec, pro
}

func (svc *Service) ExecuteCampaignSpec(ctx context.Context, repos []*graphql.Repository, x Executor, spec *CampaignSpec, progress func([]*TaskStatus), skipErrors bool) ([]*ChangesetSpec, error) {
ctx = svc.decorateContext(ctx)

for _, repo := range repos {
x.AddTask(repo, spec.Steps, spec.ChangesetTemplate)
}
Expand Down Expand Up @@ -332,6 +346,8 @@ query GetCurrentUserID {
`

func (svc *Service) ResolveNamespace(ctx context.Context, namespace string) (string, error) {
ctx = svc.decorateContext(ctx)

if namespace == "" {
// if no namespace is provided, default to logged in user as namespace
var resp struct {
Expand Down Expand Up @@ -374,6 +390,8 @@ func (svc *Service) ResolveNamespace(ctx context.Context, namespace string) (str
}

func (svc *Service) ResolveRepositories(ctx context.Context, spec *CampaignSpec) ([]*graphql.Repository, error) {
ctx = svc.decorateContext(ctx)

seen := map[string]*graphql.Repository{}
unsupported := UnsupportedRepoSet{}

Expand Down Expand Up @@ -421,6 +439,8 @@ func (svc *Service) ResolveRepositories(ctx context.Context, spec *CampaignSpec)
}

func (svc *Service) ResolveRepositoriesOn(ctx context.Context, on *OnQueryOrRepository) ([]*graphql.Repository, error) {
ctx = svc.decorateContext(ctx)

if on.RepositoriesMatchingQuery != "" {
return svc.resolveRepositorySearch(ctx, on.RepositoriesMatchingQuery)
} else if on.Repository != "" && on.Branch != "" {
Expand Down Expand Up @@ -599,3 +619,7 @@ func (sr *searchResult) UnmarshalJSON(data []byte) error {
return errors.Errorf("unknown GraphQL type %q", tn.Typename)
}
}

func (svc *Service) decorateContext(ctx context.Context) context.Context {
return context.WithValue(ctx, onCommandContextKey, svc.onCommand)
}
Loading