1 package internal
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7 "time"
8
9 "edge-infra.dev/pkg/f8n/warehouse/lift"
10 "edge-infra.dev/pkg/f8n/warehouse/lift/pack"
11 "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters"
12 "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters/transformers/bazel"
13 "edge-infra.dev/pkg/f8n/warehouse/oci/validate"
14 "edge-infra.dev/pkg/f8n/warehouse/pallet"
15 "edge-infra.dev/pkg/lib/build"
16 "edge-infra.dev/pkg/lib/build/git"
17 "edge-infra.dev/pkg/lib/cli/rags"
18 "edge-infra.dev/pkg/lib/cli/sink"
19 )
20
21 type Packer struct {
22 Tags []string
23 tag string
24
25
26 rc bool
27 version string
28 source string
29 revision string
30 created int64
31
32
33 bazel bazel.Filter
34
35
36 *pack.Packer
37
38 Config *lift.Config
39 }
40
41 func NewPacker(cfg lift.Config) *Packer {
42 return &Packer{Config: &cfg}
43 }
44
45 func (p *Packer) RegisterFlags(fs *rags.RagSet) {
46 fs.StringVar(&p.tag, "tag", "latest", "comma separated list of tags to apply to built pallets")
47
48
49 fs.StringVar(&p.version, "version", "", "valid semver version to apply to pallets")
50 fs.StringVar(&p.source, "source", "", "HTTPS url path to relevant source code")
51 fs.StringVar(&p.revision, "revision", "", "the full commit hash for relevant source code")
52 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/)")
53 fs.BoolVar(&p.rc, "rc", true, "is the build a release candidate")
54
55
56 p.bazel.RegisterFlags(fs)
57 }
58
59 func (p *Packer) BeforeRun(ctx context.Context, r sink.Run) (context.Context, sink.Run, error) {
60 buildInfo, err := p.parseBuildInfo()
61 if err != nil {
62 return ctx, r, err
63 }
64
65 p.Tags = strings.Split(p.tag, ",")
66
67 p.Packer, err = pack.New(pack.Context{
68 Config: p.Config,
69 Filters: []filters.Filter{&p.bazel},
70 }, buildInfo)
71
72 return ctx, r, err
73 }
74
75 func (p *Packer) parseBuildInfo() (pallet.BuildInfo, error) {
76 var (
77 err error
78 v build.Version
79 )
80
81
82 g := git.New()
83
84 commit := p.revision
85 if commit == "" {
86 commit, err = g.Commit()
87 if err != nil {
88 return pallet.BuildInfo{}, err
89 }
90 }
91
92 source := p.source
93 if source == "" {
94 remote, err := g.Remote()
95 if err != nil {
96 return pallet.BuildInfo{}, err
97 }
98
99
100 sshPrefix := "git@github.com:"
101 if strings.HasPrefix(remote, sshPrefix) {
102 remote = strings.TrimPrefix(remote, sshPrefix)
103 remote = fmt.Sprintf("https://github.com/%s", remote)
104 }
105 source = fmt.Sprintf("%s/tree/%s", remote, commit)
106 }
107
108 var date time.Time
109 created := p.created
110 if created <= 0 {
111 date, err = g.Timestamp(commit)
112 if err != nil {
113 return pallet.BuildInfo{}, err
114 }
115 created = date.Unix()
116 } else {
117 date = time.Unix(created, 0).UTC()
118 }
119
120 v = build.Version{
121 Commit: commit,
122 ReleaseCandidate: p.rc,
123 Timestamp: created,
124 }
125
126 version := p.version
127 if version == "" {
128 path, err := g.Path()
129 if err != nil {
130 return pallet.BuildInfo{}, err
131 }
132 semver, err := build.LoadVersion(path)
133 if err != nil {
134 return pallet.BuildInfo{}, err
135 }
136 version = semver
137 }
138 v.SemVer = version
139
140 if err := v.Validate(); err != nil {
141 return pallet.BuildInfo{}, err
142 }
143
144 buildInfo := pallet.BuildInfo{
145 Source: source,
146 Version: v.String(),
147 Created: date.Format(time.RFC3339),
148 Revision: commit,
149 }
150
151 if err = validate.BuildInfo(buildInfo); err != nil {
152 return pallet.BuildInfo{}, fmt.Errorf("invalid build info: %w", err)
153 }
154
155 return buildInfo, nil
156 }
157
View as plain text