Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 380eb6c

Browse files
benjaminjbcbandy
andauthored
Github operator build update (#4305)
Update GitHub builds * Adjust chmod for licenses, queries * Adjust license aggregation Issues: [PGO-2695] Co-authored-by: Chris Bandy <bandy.chris@gmail.com>
1 parent b1b6652 commit 380eb6c

File tree

4 files changed

+242
-3
lines changed

4 files changed

+242
-3
lines changed

‎.dockerignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
/.git
44
/bin
55
/hack
6+
!/hack/extract-licenses.go
67
!/hack/tools/queries

‎Dockerfile‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ COPY hack/tools/queries /opt/crunchy/conf
1010
WORKDIR /usr/src/app
1111
COPY . .
1212
ENV GOCACHE=/var/cache/go
13+
14+
# Build the operator and assemble the licenses
1315
RUN --mount=type=cache,target=/var/cache/go go build ./cmd/postgres-operator
16+
RUN go run ./hack/extract-licenses.go licenses postgres-operator
1417

1518
FROM docker.io/library/debian:bookworm
1619

17-
COPY --from=build /licenses /licenses
18-
COPY --from=build /opt/crunchy/conf /opt/crunchy/conf
20+
COPY --from=build --chmod=0444 /usr/src/app/licenses /licenses
21+
COPY --from=build --chmod=0444 /opt/crunchy/conf /opt/crunchy/conf
1922
COPY --from=build /usr/src/app/postgres-operator /usr/local/bin
2023

2124
USER 2

‎hack/extract-licenses.go‎

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Copyright 2024 - 2025 Crunchy Data Solutions, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package main
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"encoding/csv"
11+
"encoding/json"
12+
"errors"
13+
"flag"
14+
"fmt"
15+
"io"
16+
"io/fs"
17+
"os"
18+
"os/exec"
19+
"os/signal"
20+
"path/filepath"
21+
"slices"
22+
"strings"
23+
"syscall"
24+
)
25+
26+
func main() {
27+
flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
28+
flags.Usage = func() {
29+
fmt.Fprintln(flags.Output(), strings.TrimSpace(`
30+
Usage: `+flags.Name()+` {directory} {executables...}
31+
32+
This program downloads and extracts the licenses of Go modules used to build
33+
Go executables.
34+
35+
The first argument is a directory that will receive license files. It will be
36+
created if it does not exist. This program will overwrite existing files but
37+
not delete them. Remaining arguments must be Go executables.
38+
39+
Go modules are downloaded to the Go module cache which can be changed via
40+
the environment: https://go.dev/ref/mod#module-cache`,
41+
))
42+
}
43+
if _ = flags.Parse(os.Args[1:]); flags.NArg() < 2 || slices.ContainsFunc(
44+
os.Args, func(arg string) bool { return arg == "-help" || arg == "--help" },
45+
) {
46+
flags.Usage()
47+
os.Exit(2)
48+
}
49+
50+
ctx, cancel := context.WithCancel(context.Background())
51+
signals := make(chan os.Signal, 1)
52+
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
53+
go func() { <-signals; cancel() }()
54+
55+
// Create the target directory.
56+
if err := os.MkdirAll(flags.Arg(0), 0o755); err != nil {
57+
fmt.Fprintln(os.Stderr, err)
58+
os.Exit(1)
59+
}
60+
61+
// Extract module information from remaining arguments.
62+
modules := identifyModules(ctx, flags.Args()[1:]...)
63+
64+
// Ignore packages from Crunchy Data. Most are not available in any [proxy],
65+
// and we handle their licenses elsewhere.
66+
//
67+
// This is also a quick fix to avoid the [replace] directive in our projects.
68+
// The logic below cannot handle them. Showing xxhash versus a replace:
69+
//
70+
// dep github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
71+
// dep github.com/crunchydata/postgres-operator v0.0.0-00010101000000-000000000000
72+
// => ./postgres-operator (devel)
73+
//
74+
// [proxy]: https://go.dev/ref/mod#module-proxy
75+
// [replace]: https://go.dev/ref/mod#go-mod-file-replace
76+
modules = slices.DeleteFunc(modules, func(s string) bool {
77+
return strings.HasPrefix(s, "github.com/crunchydata/")
78+
})
79+
80+
// Download modules to the Go module cache.
81+
directories := downloadModules(ctx, modules...)
82+
83+
// Gather license files from every module into the target directory.
84+
for module, directory := range directories {
85+
for _, license := range findLicenses(directory) {
86+
relative := module + strings.TrimPrefix(license, directory)
87+
destination := filepath.Join(flags.Arg(0), relative)
88+
89+
var data []byte
90+
err := ctx.Err()
91+
92+
if err == nil {
93+
err = os.MkdirAll(filepath.Dir(destination), 0o755)
94+
}
95+
if err == nil {
96+
data, err = os.ReadFile(license)
97+
}
98+
if err == nil {
99+
// When we copy the licenses in the Dockerfiles, make sure
100+
// to `--chmod` them to an appropriate permissions, e.g., 0o444
101+
err = os.WriteFile(destination, data, 0o600)
102+
}
103+
if err == nil {
104+
fmt.Fprintln(os.Stdout, license, "=>", destination)
105+
}
106+
if err != nil {
107+
fmt.Fprintln(os.Stderr, err)
108+
os.Exit(1)
109+
}
110+
}
111+
}
112+
}
113+
114+
func downloadModules(ctx context.Context, modules ...string) map[string]string {
115+
var stdout bytes.Buffer
116+
117+
// Download modules and read their details into a series of JSON objects.
118+
// - https://go.dev/ref/mod#go-mod-download
119+
//gosec:disable G204 -- Use this environment variable to switch Go versions without touching PATH
120+
cmd := exec.CommandContext(ctx, os.Getenv("GO"), append([]string{"mod", "download", "-json"}, modules...)...)
121+
if cmd.Path == "" {
122+
cmd.Path, cmd.Err = exec.LookPath("go")
123+
}
124+
cmd.Stderr = os.Stderr
125+
cmd.Stdout = &stdout
126+
if err := cmd.Run(); err != nil {
127+
fmt.Fprintln(os.Stderr, err)
128+
os.Exit(cmd.ProcessState.ExitCode())
129+
}
130+
131+
decoder := json.NewDecoder(&stdout)
132+
results := make(map[string]string, len(modules))
133+
134+
// NOTE: The directory in the cache is a normalized spelling of the module path;
135+
// ask Go for the directory; do not try to spell it yourself.
136+
// - https://go.dev/ref/mod#module-cache
137+
// - https://go.dev/ref/mod#module-path
138+
for {
139+
var module struct {
140+
Path string `json:"path,omitempty"`
141+
Version string `json:"version,omitempty"`
142+
Dir string `json:"dir,omitempty"`
143+
}
144+
err := decoder.Decode(&module)
145+
146+
if err == nil {
147+
results[module.Path+"@"+module.Version] = module.Dir
148+
continue
149+
}
150+
if errors.Is(err, io.EOF) {
151+
break
152+
}
153+
154+
fmt.Fprintln(os.Stderr, err)
155+
os.Exit(1)
156+
}
157+
158+
return results
159+
}
160+
161+
func findLicenses(directory string) []string {
162+
var results []string
163+
164+
// Syft maintains a list of license filenames that began as a list maintained by
165+
// Go. We gather a similar list by matching on "copying" and "license" filenames.
166+
// - https://pkg.go.dev/github.com/anchore/syft@v1.3.0/internal/licenses#FileNames
167+
//
168+
// Ignore Go files and anything in the special "testdata" directory.
169+
// - https://go.dev/cmd/go
170+
err := filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error {
171+
if d.IsDir() && d.Name() == "testdata" {
172+
return fs.SkipDir
173+
}
174+
if d.IsDir() || strings.HasSuffix(path, ".go") {
175+
return err
176+
}
177+
178+
lower := strings.ToLower(d.Name())
179+
if strings.Contains(lower, "copying") || strings.Contains(lower, "license") {
180+
results = append(results, path)
181+
}
182+
183+
return err
184+
})
185+
186+
if err != nil {
187+
fmt.Fprintln(os.Stderr, err)
188+
os.Exit(1)
189+
}
190+
191+
return results
192+
}
193+
194+
func identifyModules(ctx context.Context, executables ...string) []string {
195+
var stdout bytes.Buffer
196+
197+
// Use `go version -m` to read the embedded module information as a text table.
198+
// - https://go.dev/ref/mod#go-version-m
199+
//gosec:disable G204 -- Use this environment variable to switch Go versions without touching PATH
200+
cmd := exec.CommandContext(ctx, os.Getenv("GO"), append([]string{"version", "-m"}, executables...)...)
201+
if cmd.Path == "" {
202+
cmd.Path, cmd.Err = exec.LookPath("go")
203+
}
204+
cmd.Stderr = os.Stderr
205+
cmd.Stdout = &stdout
206+
if err := cmd.Run(); err != nil {
207+
fmt.Fprintln(os.Stderr, err)
208+
os.Exit(cmd.ProcessState.ExitCode())
209+
}
210+
211+
// Parse the tab-separated table without checking row lengths.
212+
reader := csv.NewReader(&stdout)
213+
reader.Comma = '\t'
214+
reader.FieldsPerRecord = -1
215+
216+
lines, _ := reader.ReadAll()
217+
result := make([]string, 0, len(lines))
218+
219+
for _, fields := range lines {
220+
if len(fields) > 3 && fields[1] == "dep" {
221+
result = append(result, fields[2]+"@"+fields[3])
222+
}
223+
if len(fields) > 4 && fields[1] == "mod" && fields[4] != "" {
224+
result = append(result, fields[2]+"@"+fields[3])
225+
}
226+
}
227+
228+
// The `go version -m` command returns no information for empty files, and it
229+
// is possible for a Go executable to have no main module and no dependencies.
230+
if len(result) == 0 {
231+
fmt.Fprintf(os.Stderr, "no Go modules in %v\n", executables)
232+
os.Exit(0)
233+
}
234+
235+
return result
236+
}

‎licenses/.gitignore‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /