@@ -2,7 +2,6 @@ package main
22
33import (
44 "bufio"
5- "bytes"
65 "context"
76 "encoding/json"
87 "flag"
@@ -19,43 +18,11 @@ import (
1918 "time"
2019
2120 "github.com/fatih/color"
22- "github.com/hashicorp/go-multierror"
2321 "github.com/mattn/go-isatty"
2422 "github.com/pkg/errors"
25- "github.com/sourcegraph/src-cli/schema"
26- "github.com/xeipuuv/gojsonschema"
23+ "github.com/sourcegraph/src-cli/internal/campaigns"
2724)
2825
29- type Action struct {
30- ScopeQuery string `json:"scopeQuery,omitempty"`
31- Steps []* ActionStep `json:"steps"`
32- }
33-
34- type ActionStep struct {
35- Type string `json:"type"` // "command"
36- Image string `json:"image,omitempty"` // Docker image
37- CacheDirs []string `json:"cacheDirs,omitempty"`
38- Args []string `json:"args,omitempty"`
39-
40- // ImageContentDigest is an internal field that should not be set by users.
41- ImageContentDigest string
42- }
43-
44- type PatchInput struct {
45- Repository string `json:"repository"`
46- BaseRevision string `json:"baseRevision"`
47- BaseRef string `json:"baseRef"`
48- Patch string `json:"patch"`
49- }
50-
51- func userCacheDir () (string , error ) {
52- userCacheDir , err := os .UserCacheDir ()
53- if err != nil {
54- return "" , err
55- }
56- return filepath .Join (userCacheDir , "sourcegraph-src" ), nil
57- }
58-
5926const defaultTimeout = 60 * time .Minute
6027
6128func init () {
@@ -132,7 +99,7 @@ Format of the action JSON files:
13299 fmt .Println (usage )
133100 }
134101
135- cacheDir , _ := userCacheDir ()
102+ cacheDir , _ := campaigns . UserCacheDir ()
136103 if cacheDir != "" {
137104 cacheDir = filepath .Join (cacheDir , "action-exec" )
138105 }
@@ -211,12 +178,12 @@ Format of the action JSON files:
211178 }
212179 }
213180
214- err = validateActionDefinition (actionFile )
181+ err = campaigns . ValidateActionDefinition (actionFile )
215182 if err != nil {
216183 return err
217184 }
218185
219- var action Action
186+ var action campaigns. Action
220187 if err := jsonxUnmarshal (string (actionFile ), & action ); err != nil {
221188 return errors .Wrap (err , "invalid JSON action file" )
222189 }
@@ -238,19 +205,21 @@ Format of the action JSON files:
238205 os .Exit (2 )
239206 }()
240207
241- logger := newActionLogger (* verbose , * keepLogsFlag )
208+ logger := campaigns . NewActionLogger (* verbose , * keepLogsFlag )
242209
243210 // Fetch Docker images etc.
244- err = prepareAction (ctx , action , logger )
211+ err = campaigns . PrepareAction (ctx , action , logger )
245212 if err != nil {
246213 return errors .Wrap (err , "Failed to prepare action" )
247214 }
248215
249- opts := actionExecutorOptions {
250- timeout : * timeoutFlag ,
251- keepLogs : * keepLogsFlag ,
252- clearCache : * clearCacheFlag ,
253- cache : actionExecutionDiskCache {dir : * cacheDirFlag },
216+ opts := campaigns.ExecutorOpts {
217+ Endpoint : cfg .Endpoint ,
218+ AccessToken : cfg .AccessToken ,
219+ Timeout : * timeoutFlag ,
220+ KeepLogs : * keepLogsFlag ,
221+ ClearCache : * clearCacheFlag ,
222+ Cache : campaigns.ExecutionDiskCache {Dir : * cacheDirFlag },
254223 }
255224
256225 // Query repos over which to run action
@@ -264,15 +233,15 @@ Format of the action JSON files:
264233 totalSteps := len (repos ) * len (action .Steps )
265234 logger .Start (totalSteps )
266235
267- executor := newActionExecutor (action , * parallelismFlag , logger , opts )
236+ executor := campaigns . NewExecutor (action , * parallelismFlag , logger , opts )
268237 for _ , repo := range repos {
269- executor .enqueueRepo (repo )
238+ executor .EnqueueRepo (repo )
270239 }
271240
272- go executor .start (ctx )
273- err = executor .wait ()
241+ go executor .Start (ctx )
242+ err = executor .Wait ()
274243
275- patches := executor .allPatches ()
244+ patches := executor .AllPatches ()
276245 if len (patches ) == 0 {
277246 // We call os.Exit because we don't want to return the error
278247 // and have it printed.
@@ -344,115 +313,7 @@ Format of the action JSON files:
344313 })
345314}
346315
347- func formatValidationErrs (es []error ) string {
348- points := make ([]string , len (es ))
349- for i , err := range es {
350- points [i ] = fmt .Sprintf ("- %s" , err )
351- }
352-
353- return fmt .Sprintf (
354- "Validating action definition failed:\n %s\n " ,
355- strings .Join (points , "\n " ))
356- }
357-
358- func validateActionDefinition (def []byte ) error {
359- sl := gojsonschema .NewSchemaLoader ()
360- sc , err := sl .Compile (gojsonschema .NewStringLoader (schema .ActionSchemaJSON ))
361- if err != nil {
362- return errors .Wrapf (err , "failed to compile actions schema" )
363- }
364-
365- normalized , err := jsonxToJSON (string (def ))
366- if err != nil {
367- return err
368- }
369-
370- res , err := sc .Validate (gojsonschema .NewBytesLoader (normalized ))
371- if err != nil {
372- return errors .Wrap (err , "failed to validate config against schema" )
373- }
374-
375- errs := & multierror.Error {ErrorFormat : formatValidationErrs }
376- for _ , err := range res .Errors () {
377- e := err .String ()
378- // Remove `(root): ` from error formatting since these errors are
379- // presented to users.
380- e = strings .TrimPrefix (e , "(root): " )
381- errs = multierror .Append (errs , errors .New (e ))
382- }
383-
384- return errs .ErrorOrNil ()
385- }
386-
387- func prepareAction (ctx context.Context , action Action , logger * actionLogger ) error {
388- // Build any Docker images.
389- for _ , step := range action .Steps {
390- if step .Type == "docker" {
391- // Set digests for Docker images so we don't cache action runs in 2 different images with
392- // the same tag.
393- var err error
394- step .ImageContentDigest , err = getDockerImageContentDigest (ctx , step .Image , logger )
395- if err != nil {
396- return errors .Wrap (err , "Failed to get Docker image content digest" )
397- }
398- }
399- }
400-
401- return nil
402- }
403-
404- // getDockerImageContentDigest gets the content digest for the image. Note that this
405- // is different from the "distribution digest" (which is what you can use to specify
406- // an image to `docker run`, as in `my/image@sha256:xxx`). We need to use the
407- // content digest because the distribution digest is only computed for images that
408- // have been pulled from or pushed to a registry. See
409- // https://windsock.io/explaining-docker-image-ids/ under "A Final Twist" for a good
410- // explanation.
411- func getDockerImageContentDigest (ctx context.Context , image string , logger * actionLogger ) (string , error ) {
412- // TODO!(sqs): is image id the right thing to use here? it is NOT the
413- // digest. but the digest is not calculated for all images (unless they are
414- // pulled/pushed from/to a registry), see
415- // https://github.com/moby/moby/issues/32016.
416- out , err := exec .CommandContext (ctx , "docker" , "image" , "inspect" , "--format" , "{{.Id}}" , "--" , image ).CombinedOutput ()
417- if err != nil {
418- if ! strings .Contains (string (out ), "No such image" ) {
419- return "" , fmt .Errorf ("error inspecting docker image %q: %s" , image , bytes .TrimSpace (out ))
420- }
421- logger .Infof ("Pulling Docker image %q...\n " , image )
422- pullCmd := exec .CommandContext (ctx , "docker" , "image" , "pull" , image )
423- prefix := fmt .Sprintf ("docker image pull %s" , image )
424- pullCmd .Stdout = logger .InfoPipe (prefix )
425- pullCmd .Stderr = logger .ErrorPipe (prefix )
426-
427- err = pullCmd .Start ()
428- if err != nil {
429- return "" , fmt .Errorf ("error pulling docker image %q: %s" , image , err )
430- }
431- err = pullCmd .Wait ()
432- if err != nil {
433- return "" , fmt .Errorf ("error pulling docker image %q: %s" , image , err )
434- }
435- }
436- out , err = exec .CommandContext (ctx , "docker" , "image" , "inspect" , "--format" , "{{.Id}}" , "--" , image ).CombinedOutput ()
437- // This time, the image MUST be present, so the issue must be something else.
438- if err != nil {
439- return "" , fmt .Errorf ("error inspecting docker image %q: %s" , image , bytes .TrimSpace (out ))
440- }
441- id := string (bytes .TrimSpace (out ))
442- if id == "" {
443- return "" , fmt .Errorf ("unexpected empty docker image content ID for %q" , image )
444- }
445- return id , nil
446- }
447-
448- type ActionRepo struct {
449- ID string
450- Name string
451- Rev string
452- BaseRef string
453- }
454-
455- func actionRepos (ctx context.Context , scopeQuery string , includeUnsupported bool , logger * actionLogger ) ([]ActionRepo , error ) {
316+ func actionRepos (ctx context.Context , scopeQuery string , includeUnsupported bool , logger * campaigns.ActionLogger ) ([]campaigns.ActionRepo , error ) {
456317 hasCount , err := regexp .MatchString (`count:\d+` , scopeQuery )
457318 if err != nil {
458319 return nil , err
@@ -563,7 +424,7 @@ fragment repositoryFields on Repository {
563424
564425 skipped := []string {}
565426 unsupported := []string {}
566- reposByID := map [string ]ActionRepo {}
427+ reposByID := map [string ]campaigns. ActionRepo {}
567428 for _ , searchResult := range result .Data .Search .Results .Results {
568429
569430 var repo Repository
@@ -595,7 +456,7 @@ fragment repositoryFields on Repository {
595456 }
596457
597458 if _ , ok := reposByID [repo .ID ]; ! ok {
598- reposByID [repo .ID ] = ActionRepo {
459+ reposByID [repo .ID ] = campaigns. ActionRepo {
599460 ID : repo .ID ,
600461 Name : repo .Name ,
601462 Rev : repo .DefaultBranch .Target .OID ,
@@ -604,7 +465,7 @@ fragment repositoryFields on Repository {
604465 }
605466 }
606467
607- repos := make ([]ActionRepo , 0 , len (reposByID ))
468+ repos := make ([]campaigns. ActionRepo , 0 , len (reposByID ))
608469 for _ , repo := range reposByID {
609470 repos = append (repos , repo )
610471 }
@@ -640,6 +501,8 @@ fragment repositoryFields on Repository {
640501 return repos , nil
641502}
642503
504+ var yellow = color .New (color .FgYellow )
505+
643506func isGitAvailable () bool {
644507 cmd := exec .Command ("git" , "version" )
645508 if err := cmd .Run (); err != nil {
0 commit comments