1 package containers
2
3 import (
4 "fmt"
5 "io"
6 "os"
7 "os/exec"
8 "strings"
9
10 "github.com/google/go-containerregistry/pkg/name"
11
12 "edge-infra.dev/pkg/lib/build/bazel"
13 )
14
15
16
17 const (
18 workloadsRepoFlag = "--workloads_repo"
19 thirdPartyRepoFlag = "--thirdparty_repo"
20 )
21
22
23 const (
24 insecureFlag = "insecure"
25 )
26
27 const (
28
29 permissionErr = "Permission \"artifactregistry.repositories.uploadArtifacts\" denied on resource"
30 garFrag = "docker.pkg.dev"
31 gcrFrag = "gcr.io"
32 )
33
34
35
36 func Push(bzl *bazel.Bazel, targets []string, opts ...Option) ([]name.Digest, error) {
37 options := makeOptions(opts...)
38
39 refs := make([]name.Digest, len(targets))
40 for i, t := range targets {
41
42 cmd := bzl.Run(append([]string{t}, options.args()...)...)
43 cmdOutPipe, err := cmd.StdoutPipe()
44 if err != nil {
45 return nil, fmt.Errorf("containers.Push: failed to connect pipe to push command: %w", err)
46 }
47 cmd.Stderr = cmd.Stdout
48
49
50 tee := io.TeeReader(cmdOutPipe, os.Stderr)
51 if err := cmd.Start(); err != nil {
52 return nil, fmt.Errorf("containers.Push: failed to start push command: %w", err)
53 }
54 out, err := io.ReadAll(tee)
55 if err != nil {
56 return nil, fmt.Errorf("containers.Push: failed to read pipe: %w", err)
57 }
58 if err := cmd.Wait(); err != nil {
59 return nil, fmt.Errorf("containers.Push: failed to execute push command '%s': %w%s: %s",
60 strings.Join(cmd.Args, ""), err, pushHelp(options.registry, err), out,
61 )
62 }
63
64
65 refs[i], err = parseRef(string(out))
66 if err != nil {
67 return nil, fmt.Errorf(
68 "containers.Push: failed to parse pushed reference for %s: %w",
69 t, err,
70 )
71 }
72 }
73
74 return refs, nil
75 }
76
77
78
79 func parseRef(out string) (name.Digest, error) {
80
81
82
83 for _, line := range strings.Split(strings.TrimSuffix(out, "\n"), "\n") {
84 digest, err := name.NewDigest(line)
85 if err == nil {
86 return digest, nil
87 }
88 }
89
90
91
92
93
94
95
96 tokens := strings.Split(strings.TrimSpace(strings.ReplaceAll(out, "\r\n", " ")), " ")
97 return name.NewDigest(tokens[len(tokens)-1])
98 }
99
100
101
102 func QueryPushes(expr ...string) ([]string, error) {
103 pushes, err := bazel.QueryFromFile(fmt.Sprintf(
104 "kind('container_push2', '%s')", strings.Join(expr, " "),
105 ))
106 if err != nil {
107 return nil, fmt.Errorf("containers.QueryPushes: failed to query for container_push targets. error: %w", err)
108 }
109 return pushes, nil
110 }
111
112
113
114
115 func Build(bzl *bazel.Bazel, targets []string, opts ...Option) error {
116 options := makeOptions(opts...)
117
118 if err := bzl.Build(append(targets, options.args()...)...).Run(); err != nil {
119 return fmt.Errorf("containers.Build: bazel build failed. error: %w", err)
120 }
121
122 return nil
123 }
124
125 func pushHelp(registry string, err error) string {
126 switch {
127
128 case !strings.Contains(registry, garFrag) && !strings.Contains(registry, gcrFrag):
129 return ""
130 case strings.Contains(err.Error(), permissionErr):
131 helpText := ""
132 if strings.Contains(err.Error(), permissionErr) {
133 command := "gcloud"
134 args := []string{"auth", "list", "--filter=status:ACTIVE", "--format=value(account)"}
135 out, err := exec.Command(command, args...).CombinedOutput()
136 var acct string
137 if err != nil {
138 acct = fmt.Sprintf("unknown (error executing gcloud auth: %s)", err.Error())
139 } else {
140 acct = strings.TrimSpace(string(out))
141 }
142
143 cmd := fmt.Sprintf("gcloud auth configure-docker %s", registry)
144 helpText = fmt.Sprintf("\n\ncurrent Google Cloud account is: %vn\nif you believe this account should have access, you may need to run:\n $ %s\n", acct, cmd)
145 }
146 return helpText
147 default:
148 return ""
149 }
150 }
151
View as plain text