github.com/git-ogawa/go-dbyml@v1.2.1/dbyml/build.go (about) 1 package dbyml 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "regexp" 8 "strings" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/network" 14 dockerStrSlice "github.com/docker/docker/api/types/strslice" 15 "github.com/docker/docker/client" 16 "github.com/docker/docker/pkg/jsonmessage" 17 "github.com/moby/moby/api/types/strslice" 18 "github.com/moby/moby/pkg/stdcopy" 19 "github.com/moby/term" 20 specs "github.com/opencontainers/image-spec/specs-go/v1" 21 ) 22 23 // The container image used in buildkitd container 24 const buildkitImageName = "moby/buildkit:v0.10.3" 25 26 // BuildkitInfo defines setting on build with buildkit. 27 type BuildkitInfo struct { 28 Enabled bool `yaml:"enabled"` 29 Output map[string]interface{} `yaml:"output"` 30 Cache map[string]interface{} `yaml:"cache"` 31 Platform []string `yaml:"platform"` 32 Remove bool `yaml:"remove"` 33 } 34 35 // NewBuildkitInfo makes BuildkitInfo object with default values. 36 func NewBuildkitInfo() *BuildkitInfo { 37 build := new(BuildkitInfo) 38 build.Enabled = false 39 build.Output = map[string]interface{}{} 40 build.Cache = map[string]interface{}{} 41 build.Remove = true 42 return build 43 } 44 45 // ParseOptions parses options related to buildkit and sets buildctl args. 46 func (buildkit *BuildkitInfo) ParseOptions(imageInfo ImageInfo) []string { 47 var opts []string 48 var cmd string 49 50 // Output 51 cmd = fmt.Sprintf("type=%s,name=%s,push=true", buildkit.Output["type"], buildkit.Output["name"]) 52 if buildkit.Output["insecure"] == true { 53 cmd = fmt.Sprintf("%s,registry.insecure=true", cmd) 54 } 55 opts = append(opts, "--output", cmd) 56 57 // Cache 58 if len(buildkit.Cache) != 0 { 59 exportCache := buildkit.Cache["export"].(map[string]string)["type"] 60 if exportCache == "inline" { 61 cmd = "type=inline" 62 } else if exportCache == "registry" { 63 cmd = fmt.Sprintf("type=registry,ref=%s", buildkit.Cache["export"].(map[string]string)["value"]) 64 } 65 opts = append(opts, "--export-cache", cmd) 66 67 importCache := buildkit.Cache["import"].(map[string]string)["type"] 68 if importCache == "registry" { 69 cmd = fmt.Sprintf( 70 "type=registry,ref=%s", 71 buildkit.Cache["import"].(map[string]string)["value"], 72 ) 73 } 74 opts = append(opts, "--import-cache", cmd) 75 } 76 77 // Platform 78 if len(buildkit.Platform) != 0 { 79 cmd = fmt.Sprintf("platform=%s", strings.Join(buildkit.Platform, ",")) 80 opts = append(opts, "--opt", cmd) 81 } 82 83 // Other imageInfo options 84 if len(imageInfo.Labels) != 0 { 85 for k, v := range imageInfo.Labels { 86 cmd = fmt.Sprintf("label:%s=%s", k, v) 87 opts = append(opts, "--opt", cmd) 88 } 89 } 90 91 if len(imageInfo.BuildArgs) != 0 { 92 for k, v := range imageInfo.BuildArgs { 93 cmd = fmt.Sprintf("build-arg:%s=%s", k, *v) 94 opts = append(opts, "--opt", cmd) 95 } 96 } 97 98 return opts 99 } 100 101 // Builder describes a container information on buildkit 102 type Builder struct { 103 Name string // The name of builder container 104 Image BuildkitImage // The image of builder container 105 ID string // The container ID 106 Config *container.Config // Container config 107 HostConfig *container.HostConfig // Container host config 108 Context string // The build context in builder 109 DockerfilePath string // The path to Dockerfile in builder 110 Cmd []string // The command executed in the builder 111 Client *client.Client // Docker client for connecting to builder 112 } 113 114 // NewBuilder creates a builder object with the default values. 115 func NewBuilder() (builder *Builder) { 116 builder = new(Builder) 117 builder.Name = "dbyml-buildkit-builder" 118 builder.Image = BuildkitImage{buildkitImageName} 119 builder.Context = "/tmp" 120 builder.DockerfilePath = "/tmp" 121 builder.Cmd = []string{ 122 "buildctl", 123 "build", 124 "--frontend", 125 "dockerfile.v0", 126 "--local", 127 fmt.Sprintf("context=%s", builder.Context), 128 "--local", 129 fmt.Sprintf("dockerfile=%s", builder.DockerfilePath), 130 } 131 builder.Config = &container.Config{ 132 Image: builder.Image.Name, 133 Entrypoint: dockerStrSlice.StrSlice( 134 []string{"buildkitd", "--config", "/etc/buildkitd.toml"}, 135 ), 136 } 137 builder.HostConfig = &container.HostConfig{ 138 NetworkMode: "host", 139 Privileged: true, 140 } 141 builder.Client, _ = client.NewClientWithOpts(client.FromEnv) 142 return builder 143 } 144 145 // AddCmd adds arguments passed to buildctl. 146 func (builder *Builder) AddCmd(cmd ...string) { 147 builder.Cmd = append(builder.Cmd, cmd...) 148 } 149 150 // Setup creates a builder container and copy setting toml into the builder. 151 func (builder *Builder) Setup(config *RegistryInfo) error { 152 err := builder.Create() 153 if err != nil { 154 return err 155 } 156 157 path, err := MakeBuildkitToml(config) 158 if err != nil { 159 return err 160 } 161 162 if err = builder.CopyFiles(path, "/etc"); err != nil { 163 return err 164 } 165 166 if err = os.Remove(path); err != nil { 167 return err 168 } 169 170 return nil 171 } 172 173 // Exists checks if a builder container exists. 174 func (builder *Builder) Exists() bool { 175 json, err := builder.Inspect() 176 if err != nil { 177 panic(err) 178 } 179 if json.ContainerJSONBase != nil { 180 return true 181 } 182 return false 183 } 184 185 // SetContainerID sets container ID of a builder. 186 func (builder *Builder) SetContainerID() error { 187 json, err := builder.Inspect() 188 if err != nil { 189 return err 190 } 191 builder.ID = json.ContainerJSONBase.ID 192 return nil 193 } 194 195 // Inspect gets a builder information. 196 func (builder *Builder) Inspect() (types.ContainerJSON, error) { 197 var json types.ContainerJSON 198 ret, err := builder.Client.ContainerList( 199 context.Background(), 200 types.ContainerListOptions{All: true}, 201 ) 202 if err != nil { 203 return json, err 204 } 205 206 target := "/" + builder.Name 207 if len(ret) > 0 { 208 for _, container := range ret { 209 for _, name := range container.Names { 210 if name == target { 211 return builder.Client.ContainerInspect(context.Background(), container.ID) 212 } 213 } 214 } 215 } 216 return json, nil 217 } 218 219 // Create creates a builder container. 220 func (builder *Builder) Create() error { 221 body, err := builder.Client.ContainerCreate( 222 context.Background(), 223 builder.Config, 224 builder.HostConfig, 225 &network.NetworkingConfig{}, 226 &specs.Platform{}, 227 builder.Name, 228 ) 229 if err != nil { 230 return err 231 } 232 builder.ID = body.ID 233 return nil 234 } 235 236 // Start starts a builder container. 237 func (builder *Builder) Start() error { 238 return builder.Client.ContainerStart( 239 context.Background(), 240 builder.ID, 241 types.ContainerStartOptions{}, 242 ) 243 } 244 245 // Stop stops a builder container. 246 func (builder *Builder) Stop() error { 247 timeout := time.Second * 60 248 return builder.Client.ContainerStop(context.Background(), builder.ID, &timeout) 249 } 250 251 // Remove removes a builder container. 252 func (builder *Builder) Remove() error { 253 return builder.Client.ContainerRemove( 254 context.Background(), 255 builder.ID, 256 types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: false, Force: true}, 257 ) 258 } 259 260 // CopyFiles copies directory in client to builder container. 261 // If the directory contains some other directories, copy them recursively. 262 func (builder *Builder) CopyFiles(path string, dst string) error { 263 opts := types.CopyToContainerOptions{ 264 AllowOverwriteDirWithFile: true, 265 CopyUIDGID: false, 266 } 267 return builder.Client.CopyToContainer( 268 context.Background(), 269 builder.ID, 270 dst, 271 GetBuildkitContext(path), 272 opts, 273 ) 274 } 275 276 // Build builds a image in a builder. 277 func (builder *Builder) Build(debug bool) error { 278 if debug { 279 fmt.Println("The following command will be run in buildkit container.") 280 re := regexp.MustCompile(`\s{1}-{2}`) 281 cmd := strings.Join(builder.Cmd, " ") 282 cmd = re.ReplaceAllString(cmd, "\n\t--") 283 fmt.Println(cmd) 284 } 285 return builder.Exec(builder.Cmd) 286 } 287 288 // Exec runs a command in buildkit container. 289 func (builder *Builder) Exec(cmd []string) error { 290 execConfig := types.ExecConfig{ 291 Privileged: true, 292 AttachStdin: true, 293 AttachStdout: true, 294 AttachStderr: true, 295 Tty: true, 296 Detach: false, 297 Cmd: strslice.StrSlice(cmd), 298 } 299 300 // Create a new exec configuration to run an exec process. 301 res, err := builder.Client.ContainerExecCreate(context.Background(), builder.Name, execConfig) 302 if err != nil { 303 return err 304 } 305 306 // Run the exec process and attach it. 307 hijackRes, _ := builder.Client.ContainerExecAttach( 308 context.Background(), 309 res.ID, 310 types.ExecStartCheck{}, 311 ) 312 defer func() error { 313 return hijackRes.Conn.Close() 314 }() 315 _, err = stdcopy.StdCopy(os.Stdout, os.Stderr, hijackRes.Reader) 316 if err != nil { 317 return err 318 } 319 return nil 320 } 321 322 // BuildkitImage defines a docker image used in a builder container. 323 type BuildkitImage struct { 324 Name string 325 } 326 327 // Exists checks if the image exists on host. 328 func (buildkit *BuildkitImage) Exists() bool { 329 cli, _ := client.NewClientWithOpts(client.FromEnv) 330 imgs, err := cli.ImageList(context.Background(), types.ImageListOptions{}) 331 if err != nil { 332 panic(err) 333 } 334 for _, img := range imgs { 335 if contains(img.RepoTags, buildkit.Name) { 336 return true 337 } 338 } 339 return false 340 } 341 342 // Pull pulls a buildkit image from official dockerhub. 343 func (buildkit *BuildkitImage) Pull() error { 344 cli, _ := client.NewClientWithOpts(client.FromEnv) 345 ret, err := cli.ImagePull(context.Background(), buildkit.Name, types.ImagePullOptions{}) 346 if err != nil { 347 return err 348 } 349 350 termFd, isTerm := term.GetFdInfo(os.Stderr) 351 return jsonmessage.DisplayJSONMessagesStream(ret, os.Stderr, termFd, isTerm, nil) 352 } 353 354 func contains(s []string, tag string) bool { 355 for _, v := range s { 356 if tag == v { 357 return true 358 } 359 } 360 return false 361 }