package internal import ( "context" "fmt" "strings" "time" "edge-infra.dev/pkg/f8n/warehouse/lift" "edge-infra.dev/pkg/f8n/warehouse/lift/pack" "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters" "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters/transformers/bazel" "edge-infra.dev/pkg/f8n/warehouse/oci/validate" "edge-infra.dev/pkg/f8n/warehouse/pallet" "edge-infra.dev/pkg/lib/build" "edge-infra.dev/pkg/lib/build/git" "edge-infra.dev/pkg/lib/cli/rags" "edge-infra.dev/pkg/lib/cli/sink" ) type Packer struct { Tags []string tag string // required by pkg/lib/build.Version rc bool version string source string revision string created int64 // filters bazel bazel.Filter // embedded instance of package builder, computed after flags are parsed *pack.Packer Config *lift.Config } func NewPacker(cfg lift.Config) *Packer { return &Packer{Config: &cfg} } func (p *Packer) RegisterFlags(fs *rags.RagSet) { fs.StringVar(&p.tag, "tag", "latest", "comma separated list of tags to apply to built pallets") // BuildInfo flags fs.StringVar(&p.version, "version", "", "valid semver version to apply to pallets") fs.StringVar(&p.source, "source", "", "HTTPS url path to relevant source code") fs.StringVar(&p.revision, "revision", "", "the full commit hash for relevant source code") fs.Int64Var(&p.created, "created", 0, "time the revision was authored, in seconds since the Unix epoch (https://reproducible-builds.org/docs/source-date-epoch/)") fs.BoolVar(&p.rc, "rc", true, "is the build a release candidate") // bazel container pushing integration flags p.bazel.RegisterFlags(fs) } func (p *Packer) BeforeRun(ctx context.Context, r sink.Run) (context.Context, sink.Run, error) { buildInfo, err := p.parseBuildInfo() if err != nil { return ctx, r, err } p.Tags = strings.Split(p.tag, ",") p.Packer, err = pack.New(pack.Context{ Config: p.Config, Filters: []filters.Filter{&p.bazel}, }, buildInfo) return ctx, r, err } func (p *Packer) parseBuildInfo() (pallet.BuildInfo, error) { var ( err error v build.Version ) // Infer BuildInfo from git repo if any component is empty g := git.New() commit := p.revision if commit == "" { commit, err = g.Commit() if err != nil { return pallet.BuildInfo{}, err } } source := p.source if source == "" { remote, err := g.Remote() if err != nil { return pallet.BuildInfo{}, err } // handle ssh url sshPrefix := "git@github.com:" if strings.HasPrefix(remote, sshPrefix) { remote = strings.TrimPrefix(remote, sshPrefix) remote = fmt.Sprintf("https://github.com/%s", remote) } source = fmt.Sprintf("%s/tree/%s", remote, commit) } var date time.Time created := p.created if created <= 0 { date, err = g.Timestamp(commit) if err != nil { return pallet.BuildInfo{}, err } created = date.Unix() } else { date = time.Unix(created, 0).UTC() } v = build.Version{ Commit: commit, ReleaseCandidate: p.rc, Timestamp: created, } version := p.version if version == "" { path, err := g.Path() if err != nil { return pallet.BuildInfo{}, err } semver, err := build.LoadVersion(path) if err != nil { return pallet.BuildInfo{}, err } version = semver } v.SemVer = version if err := v.Validate(); err != nil { return pallet.BuildInfo{}, err } buildInfo := pallet.BuildInfo{ Source: source, Version: v.String(), Created: date.Format(time.RFC3339), Revision: commit, } if err = validate.BuildInfo(buildInfo); err != nil { return pallet.BuildInfo{}, fmt.Errorf("invalid build info: %w", err) } return buildInfo, nil }