contrib: add go2docker

pull/6/head
Johan Euphrosine 2015-01-29 14:28:44 -08:00
parent 7603f88960
commit e7c4772d27
2 changed files with 202 additions and 0 deletions

View File

@ -0,0 +1,35 @@
# go2docker
## Description
`go2docker` is a command line tool to create minimal docker images from
`SCRATCH` for go packages.
It is based on the [Docker Image Specification v1.0.0](https://github.com/docker/docker/blob/master/image/spec/v1.md).
l
## Usage
```
go2docker [-image NAMESPACE/BASENAME] [PACKAGES]
```
### Options
- `image`: namespace/name for the repository, default to go2docker/$(basename)
### Examples
```
$ go get -d github.com/golang/example/hello
$ go2docker -image golang/hello github.com/golang/example/hello | docker load
$ docker images | grep hello
golang/hello latest e96b9f048cdf 2 seconds ago 1.477 MB
$ docker run golang/hello
Hello, Go examples!
```
## TODOs
- [ ] add command line flag for entrypoint
- [ ] add command line flag for exposed port
- [ ] add command line flag for volume
- [ ] go get the package if not present in `$GOPATH`
- [ ] add push command
- [ ] test more complicated package (ex: etcd)
- [ ] fix permission inside the tar

View File

@ -0,0 +1,167 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// 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", "-ldflags", "'-w'"}, 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: map[string]string{
"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: Version,
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)
}
}