...

Source file src/edge-infra.dev/cmd/tools/art/actions/actions.go

Documentation: edge-infra.dev/cmd/tools/art/actions

     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  	// storage bucket to upload to
    22  	bucket string
    23  	dry    bool
    24  )
    25  
    26  // info needed for publishing artifacts for jobs ran on github actions
    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  			// translate org/repo value provided by actions into just repo
    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  // TODO: handle this with RequiredFlags once sink/rags supports it
    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  	// resolve cwd correctly regardless of whether or not we are running
    98  	// in the bazel sandbox
    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  		// dont upload results when dry running
   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