1 package actions
2
3 import (
4 "context"
5 "errors"
6 "flag"
7 "fmt"
8 "path/filepath"
9 "strings"
10
11 "github.com/peterbourgon/ff/v3"
12
13 "edge-infra.dev/pkg/f8n/devinfra/gcp/job/storage"
14 "edge-infra.dev/pkg/lib/build/bazel"
15 "edge-infra.dev/pkg/lib/cli/rags"
16 "edge-infra.dev/pkg/lib/cli/sink"
17 "edge-infra.dev/pkg/lib/fog"
18 )
19
20 var (
21
22 bucket string
23 dry bool
24 )
25
26
27 type actions struct {
28 jobID string
29 workflow string
30 runID string
31 attempt string
32 repo string
33 }
34
35 func actionsFlags(a *actions) []*rags.Rag {
36 rs := rags.New("actions", flag.ExitOnError)
37 storage.BindStorageBucketFlag(rs, &bucket)
38
39 rs.StringVar(&a.jobID, "github-job", "", "[github actions] job_id")
40 rs.StringVar(&a.workflow, "github-workflow", "", "[github actions] workflow name")
41 rs.StringVar(&a.runID, "github-run-id", "", "[github actions] unique run id")
42 rs.StringVar(&a.attempt, "github-run-attempt", "", "[github actions] attempt number")
43 rs.StringVar(&a.repo, "github-repository", "", "[github actions] org/repo slug")
44
45 rs.BoolVar(&dry, "dry-run", false, "print out paths being uploaded but dont upload them", rags.WithShort("d"))
46 return rs.Rags()
47 }
48
49 func New() *sink.Command {
50 actions := &actions{}
51
52 cmd := &sink.Command{
53 Use: "actions [flags] <directories>",
54 Short: "upload artifacts from Github Actions to google cloud storage",
55 Extensions: []sink.Extension{},
56 Flags: actionsFlags(actions),
57 Options: []ff.Option{
58 ff.WithEnvVarNoPrefix(),
59 },
60 Exec: func(ctx context.Context, r sink.Run) error {
61 if err := actions.validate(); err != nil {
62 return err
63 }
64
65
66 actions.repo = strings.Split(actions.repo, "/")[1]
67
68 storagePath := storage.BasePath(
69 actions.repo,
70 actions.workflow,
71 fmt.Sprintf("%s-%s", actions.runID, actions.attempt),
72 actions.jobID,
73 )
74 return upload(ctx, storagePath, r.Args())
75 },
76 }
77 return cmd
78 }
79
80
81 func (a *actions) validate() error {
82 if a.jobID == "" || a.workflow == "" || a.runID == "" || a.repo == "" || a.attempt == "" {
83 return errors.New("not all [github actions] flags were provided, run -help for more info")
84 }
85
86 if !strings.Contains(a.repo, "/") {
87 return errors.New("github-repository not in expected form based on " +
88 "GITHUB_REPOSITORY variable, are you (incorrectly) faking the github actions runtime?")
89 }
90
91 return nil
92 }
93
94 func upload(ctx context.Context, storagePath string, dirs []string) error {
95 log := fog.FromContext(ctx)
96
97
98
99 cwd, err := bazel.ResolveWd()
100 if err != nil {
101 return fmt.Errorf("failed to determine cwd: %w", err)
102 }
103
104 s, err := storage.New(ctx)
105 if err != nil {
106 return fmt.Errorf("failed to create storage client: %w", err)
107 }
108
109 b, err := s.NewBucket(ctx, storage.WithBucket(bucket))
110 if err != nil {
111 return fmt.Errorf("failed to create bucket handler: %w", err)
112 }
113
114 log.Info("uploading job artifacts", "bucket", bucket, "storagePath", storagePath)
115 for _, dir := range dirs {
116
117 if dry {
118 log.Info("[dry run] uploading", "dir", dir)
119 continue
120 }
121
122 log.Info("uploading", "dir", dir)
123 err := b.UploadArtifacts(ctx, storagePath, filepath.Join(cwd, dir))
124 if !errors.Is(err, storage.ErrEmptyDir) && err != nil {
125 return fmt.Errorf("failed to upload artifacts: %w", err)
126 }
127 }
128
129 return nil
130 }
131
View as plain text