Skip to content

Instantly share code, notes, and snippets.

@proppy
Last active November 30, 2021 17:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save proppy/0566a2e87a381e2ef4b5 to your computer and use it in GitHub Desktop.
Save proppy/0566a2e87a381e2ef4b5 to your computer and use it in GitHub Desktop.
go2docker: build golang docker image without docker :)
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The go2docker command compiles a go main package and forge a minimal
// docker image from the resulting static binary.
//
// usage: go2docker [-image namespace/basename] go/pkg/path | docker load
package main
import (
"archive/tar"
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"flag"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
)
type Config struct {
Cmd []string `json:"Cmd"`
}
type Image struct {
ID string `json:"id"`
Created time.Time `json:"created"`
DockerVersion string `json:"docker_version"`
Config Config `json:"config"`
Architecture string `json:"architecture"`
OS string `json:"os"`
}
var image = flag.String("image", "", "namespace/name for the repository, default to go2docker/$(basename)")
const (
DockerVersion = "1.4.0"
Arch = "amd64"
OS = "linux"
Version = "1.0"
Namespace = "go2docker"
)
func main() {
flag.Parse()
args := []string{"."}
if flag.NArg() > 0 {
args = flag.Args()
}
fpath, err := filepath.Abs(args[0])
ext := filepath.Ext(fpath)
basename := filepath.Base(fpath[:len(fpath)-len(ext)])
if *image == "" {
if err != nil {
log.Fatalf("failed to get absolute path: %v", err)
}
*image = path.Join(Namespace, basename)
}
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
log.Fatalf("failed to create temp directory: %v", err)
}
aout := filepath.Join(tmpDir, basename)
command := append([]string{"go", "build", "-o", aout, "-a", "-tags", "netgo", "-installsuffix", "netgo"}, args...)
if _, err := exec.Command(command[0], command[1:]...).Output(); err != nil {
log.Fatalf("failed to run command %q: %v", strings.Join(command, " "), err)
}
imageIDBytes := make([]byte, 32)
if _, err := rand.Read(imageIDBytes); err != nil {
log.Fatalf("failed to generate ID: %v")
}
imageID := hex.EncodeToString(imageIDBytes)
repo := map[string]map[string]string{
*image: {
"latest": imageID,
},
}
repoJSON, err := json.Marshal(repo)
if err != nil {
log.Fatalf("failed to serialize repo %#v: %v", repo, err)
}
tw := tar.NewWriter(os.Stdout)
if err := tw.WriteHeader(&tar.Header{
Name: "repositories",
Size: int64(len(repoJSON)),
}); err != nil {
log.Fatalf("failed to write /repository header: %v", err)
}
if _, err := tw.Write(repoJSON); err != nil {
log.Fatalf(" failed to write /repository body: %v", err)
}
if err := tw.WriteHeader(&tar.Header{
Name: imageID + "/VERSION",
Size: int64(len(Version)),
}); err != nil {
log.Fatalf("failed to write /%s/VERSION header: %v", imageID, err)
}
if _, err := tw.Write([]byte(Version)); err != nil {
log.Fatalf(" failed to write /%s/VERSION body: %v", imageID, err)
}
imageJSON, err := json.Marshal(Image{
ID: imageID,
Created: time.Now().UTC(),
DockerVersion: DockerVersion,
Config: Config{
Cmd: []string{"/" + basename},
},
Architecture: Arch,
OS: OS,
})
if err := tw.WriteHeader(&tar.Header{
Name: imageID + "/json",
Size: int64(len(imageJSON)),
}); err != nil {
log.Fatalf("failed to write /%s/json header: %v", imageID, err)
}
if _, err := tw.Write(imageJSON); err != nil {
log.Fatalf("failed to write /%s/json body: %v", imageID, err)
}
var buf bytes.Buffer
ftw := tar.NewWriter(&buf)
file, err := os.Open(aout)
if err != nil {
log.Fatalf("failed to open %q: %v", aout, err)
}
finfo, err := file.Stat()
if err != nil {
log.Fatalf("failed to get file info %q: %v", aout, err)
}
fheader, err := tar.FileInfoHeader(finfo, "")
if err != nil {
log.Fatalf("failed to get file info header %q: %v", aout, err)
}
fheader.Name = basename
if err := ftw.WriteHeader(fheader); err != nil {
log.Fatalf("failed to write /%s header: %v", aout, err)
}
if _, err := io.Copy(ftw, file); err != nil {
log.Fatalf("failed to write /%s body: %v", aout, err)
}
if err := ftw.Close(); err != nil {
log.Fatalf("failed to close layer.tar: %v", err)
}
layerBytes := buf.Bytes()
if err := tw.WriteHeader(&tar.Header{
Name: imageID + "/layer.tar",
Size: int64(len(layerBytes)),
}); err != nil {
log.Fatalf("failed to write /%s/layer.tar header: %v", imageID, err)
}
if _, err := tw.Write(layerBytes); err != nil {
log.Fatalf("failed to write /%s/layer.tar body: %v", imageID, err)
}
if err := tw.Close(); err != nil {
log.Fatalf("failed to close image.tar: %v", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment