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 +}