15370: Merge branch 'main' into 15370-loopback-dispatchcloud
[arvados.git] / cmd / arvados-package / build.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "context"
10         "fmt"
11         "io"
12         "io/ioutil"
13         "os"
14         "os/exec"
15         "os/user"
16         "path/filepath"
17         "strings"
18
19         "git.arvados.org/arvados.git/sdk/go/ctxlog"
20         "github.com/docker/docker/api/types"
21         "github.com/docker/docker/client"
22 )
23
24 func build(ctx context.Context, opts opts, stdin io.Reader, stdout, stderr io.Writer) error {
25         if opts.PackageVersion == "" {
26                 var buf bytes.Buffer
27                 cmd := exec.CommandContext(ctx, "bash", "./build/version-at-commit.sh", "HEAD")
28                 cmd.Stdout = &buf
29                 cmd.Stderr = stderr
30                 cmd.Dir = opts.SourceDir
31                 err := cmd.Run()
32                 if err != nil {
33                         return fmt.Errorf("%v: %w", cmd.Args, err)
34                 }
35                 opts.PackageVersion = strings.TrimSpace(buf.String())
36                 ctxlog.FromContext(ctx).Infof("version not specified; using %s", opts.PackageVersion)
37         }
38
39         if opts.PackageChown == "" {
40                 whoami, err := user.Current()
41                 if err != nil {
42                         return fmt.Errorf("user.Current: %w", err)
43                 }
44                 opts.PackageChown = whoami.Uid + ":" + whoami.Gid
45         }
46
47         // Build in a tempdir, then move to the desired destination
48         // dir. Otherwise, errors might cause us to leave a mess:
49         // truncated files, files owned by root, etc.
50         _, prog := filepath.Split(os.Args[0])
51         tmpdir, err := ioutil.TempDir(opts.PackageDir, prog+".")
52         if err != nil {
53                 return err
54         }
55         defer os.RemoveAll(tmpdir)
56         if abs, err := filepath.Abs(tmpdir); err != nil {
57                 return fmt.Errorf("error getting absolute path of tmpdir %s: %w", tmpdir, err)
58         } else {
59                 tmpdir = abs
60         }
61
62         selfbin, err := os.Readlink("/proc/self/exe")
63         if err != nil {
64                 return fmt.Errorf("readlink /proc/self/exe: %w", err)
65         }
66         buildImageName := "arvados-package-build-" + opts.TargetOS
67         packageFilename := "arvados-server-easy_" + opts.PackageVersion + "_amd64.deb"
68
69         if ok, err := dockerImageExists(ctx, buildImageName); err != nil {
70                 return err
71         } else if !ok || opts.RebuildImage {
72                 buildCtrName := strings.Replace(buildImageName, ":", "-", -1)
73                 err = dockerRm(ctx, buildCtrName)
74                 if err != nil {
75                         return err
76                 }
77
78                 defer dockerRm(ctx, buildCtrName)
79                 cmd := exec.CommandContext(ctx, "docker", "run",
80                         "--name", buildCtrName,
81                         "--tmpfs", "/tmp:exec,mode=01777",
82                         "-v", selfbin+":/arvados-package:ro",
83                         "-v", opts.SourceDir+":/arvados:ro",
84                         opts.TargetOS,
85                         "/arvados-package", "_install",
86                         "-eatmydata",
87                         "-type", "package",
88                         "-source", "/arvados",
89                         "-package-version", opts.PackageVersion,
90                 )
91                 cmd.Stdout = stdout
92                 cmd.Stderr = stderr
93                 err = cmd.Run()
94                 if err != nil {
95                         return fmt.Errorf("%v: %w", cmd.Args, err)
96                 }
97
98                 cmd = exec.CommandContext(ctx, "docker", "commit", buildCtrName, buildImageName)
99                 cmd.Stdout = stdout
100                 cmd.Stderr = stderr
101                 err = cmd.Run()
102                 if err != nil {
103                         return fmt.Errorf("docker commit: %w", err)
104                 }
105
106                 ctxlog.FromContext(ctx).Infof("created docker image %s", buildImageName)
107         }
108
109         cmd := exec.CommandContext(ctx, "docker", "run",
110                 "--rm",
111                 "--tmpfs", "/tmp:exec,mode=01777",
112                 "-v", tmpdir+":/pkg",
113                 "-v", selfbin+":/arvados-package:ro",
114                 "-v", opts.SourceDir+":/arvados:ro",
115                 buildImageName,
116                 "eatmydata", "/arvados-package", "_fpm",
117                 "-source", "/arvados",
118                 "-package-version", opts.PackageVersion,
119                 "-package-dir", "/pkg",
120                 "-package-chown", opts.PackageChown,
121                 "-package-maintainer", opts.Maintainer,
122                 "-package-vendor", opts.Vendor,
123         )
124         cmd.Stdout = stdout
125         cmd.Stderr = stderr
126         err = cmd.Run()
127         if err != nil {
128                 return fmt.Errorf("%v: %w", cmd.Args, err)
129         }
130
131         err = os.Rename(tmpdir+"/"+packageFilename, opts.PackageDir+"/"+packageFilename)
132         if err != nil {
133                 return err
134         }
135
136         return nil
137 }
138
139 func dockerRm(ctx context.Context, name string) error {
140         cli, err := client.NewEnvClient()
141         if err != nil {
142                 return err
143         }
144         ctrs, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true, Limit: -1})
145         if err != nil {
146                 return err
147         }
148         for _, ctr := range ctrs {
149                 for _, ctrname := range ctr.Names {
150                         if ctrname == "/"+name {
151                                 err = cli.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{})
152                                 if err != nil {
153                                         return fmt.Errorf("error removing container %s: %w", ctr.ID, err)
154                                 }
155                                 break
156                         }
157                 }
158         }
159         return nil
160 }