1// Copyright 2021 Google Inc. 2// 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// This executable builds the Docker images based off the Skia executables in the 7// gcr.io/skia-public/skia-release image. It then issues a PubSub notification to have those apps 8// tagged and deployed by docker_pushes_watcher. 9// See //docker_pushes_watcher/README.md in the infra repo for more. 10package main 11 12import ( 13 "context" 14 "flag" 15 "fmt" 16 "os" 17 "path" 18 "path/filepath" 19 20 "cloud.google.com/go/pubsub" 21 "google.golang.org/api/option" 22 23 "go.skia.org/infra/go/auth" 24 infra_common "go.skia.org/infra/go/common" 25 docker_pubsub "go.skia.org/infra/go/docker/build/pubsub" 26 sk_exec "go.skia.org/infra/go/exec" 27 "go.skia.org/infra/task_driver/go/lib/auth_steps" 28 "go.skia.org/infra/task_driver/go/lib/bazel" 29 "go.skia.org/infra/task_driver/go/lib/checkout" 30 "go.skia.org/infra/task_driver/go/lib/docker" 31 "go.skia.org/infra/task_driver/go/lib/golang" 32 "go.skia.org/infra/task_driver/go/lib/os_steps" 33 "go.skia.org/infra/task_driver/go/td" 34 "go.skia.org/infra/task_scheduler/go/types" 35 "go.skia.org/skia/infra/bots/task_drivers/common" 36) 37 38var ( 39 // Required properties for this task. 40 projectId = flag.String("project_id", "", "ID of the Google Cloud project.") 41 taskId = flag.String("task_id", "", "ID of this task.") 42 taskName = flag.String("task_name", "", "Name of the task.") 43 workdir = flag.String("workdir", ".", "Working directory") 44 infraRevision = flag.String("infra_revision", "origin/main", "Specifies which revision of the infra repo the images should be built off") 45 46 checkoutFlags = checkout.SetupFlags(nil) 47 48 // Optional flags. 49 local = flag.Bool("local", false, "True if running locally (as opposed to on the bots)") 50 output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.") 51) 52 53const ( 54 fiddlerImageName = "fiddler" 55 apiImageName = "api" 56) 57 58func buildPushFiddlerImage(ctx context.Context, dkr *docker.Docker, tag, infraCheckoutDir string, topic *pubsub.Topic) error { 59 // Run skia-release image and extract products out of /tmp/skia/skia. See 60 // https://skia.googlesource.com/skia/+/0e845dc8b05cb2d40d1c880184e33dd76081283a/docker/skia-release/Dockerfile#33 61 productsDir, err := os_steps.TempDir(ctx, "", "") 62 if err != nil { 63 return err 64 } 65 volumes := []string{ 66 fmt.Sprintf("%s:/OUT", productsDir), 67 } 68 skiaCopyCmd := []string{"/bin/sh", "-c", "cd /tmp; tar cvzf skia.tar.gz --directory=/tmp/skia skia; cp /tmp/skia.tar.gz /OUT/"} 69 releaseImg := fmt.Sprintf("gcr.io/skia-public/skia-release:%s", tag) 70 if err := dkr.Run(ctx, releaseImg, skiaCopyCmd, volumes, nil); err != nil { 71 return err 72 } 73 74 err = td.Do(ctx, td.Props("Build "+fiddlerImageName+" image").Infra(), func(ctx context.Context) error { 75 runCmd := &sk_exec.Command{ 76 Name: "make", 77 Args: []string{"release-fiddler-ci"}, 78 InheritEnv: true, 79 Env: []string{ 80 "COPY_FROM_DIR=" + productsDir, 81 "STABLE_DOCKER_TAG=" + tag, 82 }, 83 Dir: filepath.Join(infraCheckoutDir, "fiddlek"), 84 LogStdout: true, 85 LogStderr: true, 86 } 87 _, err := sk_exec.RunCommand(ctx, runCmd) 88 if err != nil { 89 return err 90 } 91 return nil 92 }) 93 if err != nil { 94 return err 95 } 96 if err := docker.PublishToTopic(ctx, "gcr.io/skia-public/"+fiddlerImageName, tag, infra_common.REPO_SKIA, topic); err != nil { 97 return err 98 } 99 100 return cleanupTempFiles(ctx, dkr, releaseImg, volumes) 101} 102 103func cleanupTempFiles(ctx context.Context, dkr *docker.Docker, image string, volumes []string) error { 104 // Remove all temporary files from the host machine. Swarming gets upset if there are root-owned 105 // files it cannot clean up. 106 cleanupCmd := []string{"/bin/sh", "-c", "rm -rf /OUT/*"} 107 return dkr.Run(ctx, image, cleanupCmd, volumes, nil) 108} 109 110func buildPushApiImage(ctx context.Context, dkr *docker.Docker, tag, checkoutDir, infraCheckoutDir string, topic *pubsub.Topic) error { 111 tempDir, err := os_steps.TempDir(ctx, "", "") 112 if err != nil { 113 return err 114 } 115 // Change perms of the directory for doxygen to be able to write to it. 116 if err := os.Chmod(tempDir, 0777); err != nil { 117 return err 118 } 119 // Run Doxygen pointing to the location of the checkout and the out dir. 120 volumes := []string{ 121 fmt.Sprintf("%s:/OUT", tempDir), 122 fmt.Sprintf("%s:/CHECKOUT", checkoutDir), 123 } 124 env := []string{ 125 "OUTPUT_DIRECTORY=/OUT", 126 } 127 doxygenCmd := []string{"/bin/sh", "-c", "cd /CHECKOUT/tools/doxygen && doxygen ProdDoxyfile"} 128 doxygenImg := "gcr.io/skia-public/doxygen:testing-slim" 129 // Make sure we have the latest doxygen image. 130 if err := dkr.Pull(ctx, doxygenImg); err != nil { 131 return err 132 } 133 if err := dkr.Run(ctx, doxygenImg, doxygenCmd, volumes, env); err != nil { 134 return err 135 } 136 137 err = td.Do(ctx, td.Props("Build "+apiImageName+" image").Infra(), func(ctx context.Context) error { 138 runCmd := &sk_exec.Command{ 139 Name: "make", 140 Args: []string{"release-api-ci"}, 141 InheritEnv: true, 142 Env: []string{ 143 "COPY_FROM_DIR=" + filepath.Join(tempDir, "html"), 144 "STABLE_DOCKER_TAG=" + tag, 145 }, 146 Dir: filepath.Join(infraCheckoutDir, "api"), 147 LogStdout: true, 148 LogStderr: true, 149 } 150 _, err := sk_exec.RunCommand(ctx, runCmd) 151 if err != nil { 152 return err 153 } 154 return nil 155 }) 156 if err != nil { 157 return err 158 } 159 if err := docker.PublishToTopic(ctx, "gcr.io/skia-public/"+apiImageName, tag, infra_common.REPO_SKIA, topic); err != nil { 160 return err 161 } 162 163 return cleanupTempFiles(ctx, dkr, doxygenImg, volumes) 164} 165 166func main() { 167 bazelFlags := common.MakeBazelFlags(common.MakeBazelFlagsOpts{}) 168 169 // Setup. 170 ctx := td.StartRun(projectId, taskId, taskName, output, local) 171 defer td.EndRun(ctx) 172 173 bazelFlags.Validate(ctx) 174 175 if *infraRevision == "" { 176 td.Fatalf(ctx, "Must specify --infra_revision") 177 } 178 179 rs, err := checkout.GetRepoState(checkoutFlags) 180 if err != nil { 181 td.Fatal(ctx, err) 182 } 183 wd, err := os_steps.Abs(ctx, *workdir) 184 if err != nil { 185 td.Fatal(ctx, err) 186 } 187 // Check out the Skia repo code. 188 co, err := checkout.EnsureGitCheckout(ctx, path.Join(wd, "repo"), rs) 189 if err != nil { 190 td.Fatal(ctx, err) 191 } 192 skiaCheckoutDir := co.Dir() 193 194 // Checkout out the Skia infra repo at the specified commit. 195 infraRS := types.RepoState{ 196 Repo: infra_common.REPO_SKIA_INFRA, 197 Revision: *infraRevision, 198 } 199 infraCheckoutDir := filepath.Join("infra_repo") 200 if _, err := checkout.EnsureGitCheckout(ctx, infraCheckoutDir, infraRS); err != nil { 201 td.Fatal(ctx, err) 202 } 203 204 // Setup go. 205 ctx = golang.WithEnv(ctx, wd) 206 207 // Ensure that the bazel cache is setup. 208 opts := bazel.BazelOptions{ 209 CachePath: *bazelFlags.CacheDir, 210 } 211 if err := bazel.EnsureBazelRCFile(ctx, opts); err != nil { 212 td.Fatal(ctx, err) 213 } 214 215 // Create token source with scope for cloud registry (storage) and pubsub. 216 ts, err := auth_steps.Init(ctx, *local, auth.ScopeUserinfoEmail, auth.ScopeFullControl, pubsub.ScopePubSub) 217 if err != nil { 218 td.Fatal(ctx, err) 219 } 220 221 // Create pubsub client. 222 client, err := pubsub.NewClient(ctx, docker_pubsub.TOPIC_PROJECT_ID, option.WithTokenSource(ts)) 223 if err != nil { 224 td.Fatal(ctx, err) 225 } 226 topic := client.Topic(docker_pubsub.TOPIC) 227 228 // Figure out which tag to use for docker build and push. 229 tag := rs.Revision 230 if rs.Issue != "" && rs.Patchset != "" { 231 tag = fmt.Sprintf("%s_%s", rs.Issue, rs.Patchset) 232 } 233 234 // Instantiate docker. 235 dkr, err := docker.New(ctx, ts) 236 if err != nil { 237 td.Fatal(ctx, err) 238 } 239 240 // Build and push all apps of interest below. 241 if err := buildPushApiImage(ctx, dkr, tag, skiaCheckoutDir, infraCheckoutDir, topic); err != nil { 242 td.Fatal(ctx, err) 243 } 244 if err := buildPushFiddlerImage(ctx, dkr, tag, infraCheckoutDir, topic); err != nil { 245 td.Fatal(ctx, err) 246 } 247 248 if !*local { 249 if err := common.BazelCleanIfLowDiskSpace(ctx, *bazelFlags.CacheDir, skiaCheckoutDir, "bazelisk"); err != nil { 250 td.Fatal(ctx, err) 251 } 252 } 253} 254