From 5a540072afe3531a6f41f05327001bd79186754c Mon Sep 17 00:00:00 2001 From: RouxAntoine Date: Fri, 7 Jan 2022 21:49:56 +0100 Subject: [PATCH] create go program to build arch specific oci image, push it and build generic manifest list layer and push it --- .gitignore | 1 + go.mod | 7 +++++ go.sum | 10 +++++++ image.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++ layers.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++ main.go | 33 ++++++++++++++++++++ registry.go | 15 ++++++++++ runner.go | 38 +++++++++++++++++++++++ 8 files changed, 265 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 image.go create mode 100644 layers.go create mode 100644 main.go create mode 100644 registry.go create mode 100644 runner.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d73946d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +golang-multi-arch-builder \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e5afc17 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module golang-multi-arch-builder + +go 1.17 + +require github.com/sirupsen/logrus v1.8.1 + +require golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..59bd790 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/image.go b/image.go new file mode 100644 index 0000000..61d2d6f --- /dev/null +++ b/image.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" +) + +type Image struct { + name string + tag string +} + +func (image Image) String() string { + imageName := image.name + if image.tag != "" { + imageName = fmt.Sprintf("%s:%s", imageName, image.tag) + } + return imageName +} + +type ManifestImage struct { + Image + layers Layers + buildDir string +} + +func NewManifest(imageName string, imageTag string, buildDir string, platforms []string) ManifestImage { + return ManifestImage{ + Image: Image{ + imageName, + imageTag, + }, + layers: NewLayers(imageName, platforms), + buildDir: buildDir, + } +} + +// CreateLayers build and push each layer image +func (manifest *ManifestImage) CreateLayers(registry Registry, buildOpt string) { + for _, layer := range manifest.layers { + layer.CreateLayer(registry, manifest.buildDir, buildOpt) + } +} + +// CreateManifest produce manifest list of layers +func (manifest *ManifestImage) CreateManifest(registry Registry) { + const insecureRegistryOpt = "--insecure" + + // $(OCI_CLI) manifest create $(OCI_OPT) $(REGISTRY_IP):5000/$(1) --amend docker.registry:5000/registry-ui:linux-armv6 + var amend []string + for _, layer := range manifest.layers { + amend = append(amend, "--amend") + amend = append(amend, fmt.Sprintf("%s/%s", registry.String(), layer.Image.String())) + } + + if stdout, err := runOciCli("docker", "manifest create", insecureRegistryOpt, registry, manifest.Image, amend...); err != nil { + log.Fatal(err) + } else { + log.Debug(stdout) + } + + // $(OCI_CLI) manifest annotate --os linux --arch arm --variant v6 $(REGISTRY_IP):5000/$(1) $(REGISTRY_IP):5000/$(1):linux-armv6 + for _, layer := range manifest.layers { + manifest.annotateManifest(registry, layer) + } + + // $(OCI_CLI) manifest push $(OCI_OPT) $(REGISTRY_IP):5000/$(1) + if stdout, err := runOciCli("docker", "manifest push", insecureRegistryOpt, registry, manifest.Image); err != nil { + log.Fatal(err) + } else { + log.Debug(stdout) + } +} + +func (manifest *ManifestImage) annotateManifest(registry Registry, layer Layer) { + stdout, err := runOciCli("docker", "manifest annotate", + fmt.Sprintf("--os %s --arch %s --variant %s", layer.os, layer.arch, layer.variant), + registry, manifest.Image, + fmt.Sprintf("%s/%s", registry.String(), layer.Image.String()), + ) + if err != nil { + log.Warn("layer %s not annotated", layer.String()) + } else { + log.Debug(stdout) + } +} diff --git a/layers.go b/layers.go new file mode 100644 index 0000000..f1e2580 --- /dev/null +++ b/layers.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "strings" +) + +type Layer struct { + Image + os string + arch string + variant string +} + +func (layer *Layer) String() string { + return fmt.Sprintf("%s : os %s, arch %s, variant %s", layer.Image.String(), layer.os, layer.arch, layer.variant) +} + +func (layer *Layer) CreateLayer(registry Registry, buildDir string, buildOpt string) { + if buildOpt != "" { + buildOpt = fmt.Sprintf("--platform %s/%s/%s %s -t", layer.os, layer.arch, layer.variant, buildOpt) + } else { + buildOpt = fmt.Sprintf("--platform %s/%s/%s -t", layer.os, layer.arch, layer.variant) + } + + // $(OCI_CLI_BUILD) build --platform $(2) -t $(REGISTRY_IP):5000/$(1):$(3) . + if stdout, err := runOciCli("docker", "build", buildOpt, registry, layer.Image, buildDir); err != nil { + log.Fatalf("layer build step failed : %v\n", err) + } else { + log.Debug(stdout) + } + + // $(OCI_CLI) push $(REGISTRY_IP):5000/$(1):$(3) + if stdout, err := runOciCli("docker", "push", "", registry, layer.Image); err != nil { + log.Fatalf("layer push step failed : %v\n", err) + } else { + log.Debug(stdout) + } +} + +type Layers []Layer + +func NewLayers(imageName string, platforms []string) Layers { + var layers Layers + for _, platform := range platforms { + splitPlatform := strings.Split(platform, "/") + + var layer Layer + if len(splitPlatform) < 3 { + layer = Layer{ + Image: Image{ + imageName, + fmt.Sprintf("%s-%s", splitPlatform[0], splitPlatform[1]), + }, + os: splitPlatform[0], + arch: splitPlatform[1], + } + } else { + layer = Layer{ + Image: Image{ + imageName, + fmt.Sprintf("%s-%s%s", splitPlatform[0], splitPlatform[1], splitPlatform[2]), + }, + os: splitPlatform[0], + arch: splitPlatform[1], + variant: splitPlatform[2], + } + } + + layers = append(layers, layer) + } + + return layers +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..0605e0b --- /dev/null +++ b/main.go @@ -0,0 +1,33 @@ +package main + +import ( + log "github.com/sirupsen/logrus" +) + +// init logging parameter +func init() { + log.SetLevel(log.DebugLevel) +} + +func main() { + myRegistry := Registry{ + "docker.registry", + 5000, + } + + var manifests []ManifestImage + + manifests = append(manifests, + NewManifest( + "registry-ui", + "latest", + "../rasp/registry/ui/", + []string{"linux/arm/v6"}, + ), + ) + + for _, manifest := range manifests { + manifest.CreateLayers(myRegistry, "") + manifest.CreateManifest(myRegistry) + } +} diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..680a120 --- /dev/null +++ b/registry.go @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type Registry struct { + hostname string + port int +} + +func (registry Registry) String() string { + if registry.port != 0 { + return fmt.Sprintf("%s:%d", registry.hostname, registry.port) + } + return registry.hostname +} diff --git a/runner.go b/runner.go new file mode 100644 index 0000000..acb668d --- /dev/null +++ b/runner.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "os/exec" + "strings" +) + +// runOciCli execute binary formatted like this `{ociCli} {ociVerb} {ociOpt} {registry:port/image} {args}` +func runOciCli(ociCli string, ociVerb string, ociCliOpt string, registry Registry, image Image, args ...string) (string, error) { + if reg := registry.String(); reg != "" { + args = append([]string{fmt.Sprintf("%s/%s", registry.String(), image.String())}, args...) + } else { + args = append([]string{image.String()}, args...) + } + + if ociCliOpt != "" { + args = append(strings.Split(ociCliOpt, " "), args...) + } + + return runCli(ociCli, ociVerb, args...) +} + +// runCli execute binary formatted like this `{bin} {verb} {args}` +func runCli(binary string, verb string, args ...string) (string, error) { + if verb != "" { + args = append(strings.Split(verb, " "), args...) + } + + log.Debugf(" $ %s %s\n", binary, strings.Join(args, " ")) + out, err := exec.Command(binary, args...).Output() + if err != nil { + log.Warnf("Executing binary with argument : `%s %s %s` failed\n", binary, verb, strings.Join(args, " ")) + return "", err + } + return string(out), nil +}