Skip to content

Commit

Permalink
Merge branch 'volume_home'
Browse files Browse the repository at this point in the history
  • Loading branch information
justone committed Mar 1, 2017
2 parents a40c231 + 87a9316 commit 05340c0
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 72 deletions.
91 changes: 81 additions & 10 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"regexp"
"sort"
Expand All @@ -27,6 +28,7 @@ type UserImage struct {
Name string
EnvCount int
Labels map[string]string
Version int
}

type BaseImage struct {
Expand Down Expand Up @@ -54,6 +56,7 @@ type CreateOpts struct {
Volumes []string
ProjectDir string
ForceBuild bool
VolumeHome bool
Build BuildOpts
}

Expand Down Expand Up @@ -125,6 +128,23 @@ func DestroyEnvironment(dc DockerClient, sc SystemClient, envName string) error
return err
}

volumeName := fmt.Sprintf("%s_%s_%s", CONT_PREFIX, sc.Username(), envName)
logrus.Debugf("removing docker volume (%s), if it exists", volumeName)

vols, err := dc.ListVolumes()
if err != nil {
return err
}

for _, vol := range vols {
if vol.Name == volumeName {
err = dc.RemoveVolume(volumeName)
if err != nil {
return err
}
}
}

return nil
}

Expand Down Expand Up @@ -181,6 +201,10 @@ func RebuildEnvironment(dc DockerClient, sc SystemClient, co CreateOpts, output
}
}

if volumeHome, ok := env.Container.Labels["skeg.io/container/volume_home"]; ok {
co.VolumeHome = (volumeHome == "true")
}

// fmt.Println(co)

logrus.Debugf("Stopping environment")
Expand Down Expand Up @@ -217,7 +241,7 @@ func CreateEnvironment(dc DockerClient, sc SystemClient, co CreateOpts, output *

var imageName string
userImages, err := UserImages(dc, sc, co.Build.Image)
if co.ForceBuild || len(userImages) == 0 {
if co.ForceBuild || len(userImages) == 0 || (len(userImages) > 0 && userImages[0].Version < IMAGE_VERSION) {

// TODO: consider whether this is the best default (new image inherits
// previous image's time zone)
Expand All @@ -230,7 +254,7 @@ func CreateEnvironment(dc DockerClient, sc SystemClient, co CreateOpts, output *
}

logrus.Debugf("Building customized docker image")
imageName, err = BuildImage(dc, sc, co.Build, output)
imageName, err = BuildImage(dc, sc, key, co.Build, output)
if err != nil {
return err
}
Expand All @@ -240,15 +264,43 @@ func CreateEnvironment(dc DockerClient, sc SystemClient, co CreateOpts, output *
}

logrus.Debugf("Preparing local environment directory")
path, err := sc.EnsureEnvironmentDir(co.Name, key)
path, err := sc.EnsureEnvironmentDir(co.Name)
if err != nil {
return err
}

labels := make(map[string]string)

homeDir := fmt.Sprintf("/home/%s", sc.Username())
logrus.Debugf("Creating container")
volumes := co.Volumes
volumes = append(volumes, fmt.Sprintf("%s:%s", path, homeDir))
if co.VolumeHome {
volumeName := fmt.Sprintf("%s_%s_%s", CONT_PREFIX, sc.Username(), co.Name)

// check for existence of volume
vols, err := dc.ListVolumes()
if err != nil {
return err
}

volumeFound := false
for _, vol := range vols {
if vol.Name == volumeName {
volumeFound = true
}
}

if !volumeFound {
dc.CreateVolume(CreateVolumeOpts{Name: volumeName, Labels: map[string]string{"skeg": "true"}})
if err != nil {
return err
}
}
volumes = append(volumes, fmt.Sprintf("%s:%s", volumeName, homeDir))
} else {
volumes = append(volumes, fmt.Sprintf("%s:%s", path, homeDir))
}
labels["skeg.io/container/volume_home"] = fmt.Sprintf("%v", co.VolumeHome)
workdirParts := strings.Split(co.ProjectDir, string(os.PathSeparator))
if len(co.ProjectDir) > 0 {
volumes = append(volumes, fmt.Sprintf("%s:%s/%s", co.ProjectDir, homeDir, workdirParts[len(workdirParts)-1]))
Expand Down Expand Up @@ -276,6 +328,7 @@ func CreateEnvironment(dc DockerClient, sc SystemClient, co CreateOpts, output *
Hostname: co.Name,
Ports: ports,
Volumes: volumes,
Labels: labels,
}
err = dc.CreateContainer(ccont)
if err != nil {
Expand Down Expand Up @@ -387,7 +440,7 @@ func ResolveImage(dc DockerClient, io ImageOpts) (string, error) {
return image, nil
}

func BuildImage(dc DockerClient, sc SystemClient, bo BuildOpts, output *os.File) (string, error) {
func BuildImage(dc DockerClient, sc SystemClient, key SSHKey, bo BuildOpts, output *os.File) (string, error) {
var err error
logrus.Debugf("Figuring out which image to use")
image, err := ResolveImage(dc, bo.Image)
Expand All @@ -413,7 +466,12 @@ func BuildImage(dc DockerClient, sc SystemClient, bo BuildOpts, output *os.File)
RUN (addgroup --gid {{ .Gid }} {{ .Username }} || /bin/true) && \
adduser --uid {{ .Uid }} --gid {{ .Gid }} {{ .Username }} --gecos "" --disabled-password && \
echo "{{ .Username }} ALL=NOPASSWD: ALL" >> /etc/sudoers
echo "{{ .Username }} ALL=NOPASSWD: ALL" >> /etc/sudoers && \
echo "AuthorizedKeysFile /etc/ssh/keys/authorized_keys" >> /etc/ssh/sshd_config
COPY ssh_pub /etc/ssh/keys/authorized_keys
RUN chown -R {{ .Uid }}:{{ .Gid }} /etc/ssh/keys && \
chmod 600 /etc/ssh/keys/authorized_keys
{{ .TzSet }}
Expand All @@ -422,7 +480,8 @@ LABEL skeg.io/image/username={{ .Username }} \
skeg.io/image/uid={{ .Uid }} \
skeg.io/image/base={{ .Image }} \
skeg.io/image/buildtime="{{ .Time }}" \
skeg.io/image/timezone="{{ .Tz }}"
skeg.io/image/timezone="{{ .Tz }}" \
skeg.io/image/version="{{ .Version }}"
`
// TODO: make timezone setting work on other distributions
Expand All @@ -433,9 +492,9 @@ LABEL skeg.io/image/username={{ .Username }} \

dockerfileData := struct {
Username, Image, Time, TzSet, Tz string
Uid, Gid int
Uid, Gid, Version int
}{
bo.Username, image, now.Format(time.UnixDate), tzenv, bo.TimeZone, bo.UID, bo.GID,
bo.Username, image, now.Format(time.UnixDate), tzenv, bo.TimeZone, bo.UID, bo.GID, IMAGE_VERSION,
}

tmpl := template.Must(template.New("dockerfile").Parse(dockerfileTmpl))
Expand All @@ -447,7 +506,13 @@ LABEL skeg.io/image/username={{ .Username }} \
}

imageName := fmt.Sprintf("%s-%s-%s", CONT_PREFIX, bo.Username, now.Format("20060102150405"))
err = dc.BuildImage(imageName, dockerfileBytes.String(), output)

data, err := ioutil.ReadFile(key.publicPath)
if err != nil {
return "", err
}

err = dc.BuildImage(imageName, dockerfileBytes.String(), string(data), output)

if err != nil {
return "", err
Expand Down Expand Up @@ -496,11 +561,17 @@ func UserImages(dc DockerClient, sc SystemClient, io ImageOpts) ([]UserImage, er
for _, dockerImage := range dockerImages {
tags := dockerImage.RepoTags

imageVersion := 0
if ver, ok := dockerImage.Labels["skeg.io/image/version"]; ok {
imageVersion, _ = strconv.Atoi(ver)
}

imageUses, _ := uses[tags[0]]
images = append(images, UserImage{
tags[0],
imageUses,
dockerImage.Labels,
imageVersion,
})
}

Expand Down
34 changes: 21 additions & 13 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (rdc *TestDockerClient) PullImage(fullImage string, output *os.File) error
return nil
}

func (rdc *TestDockerClient) BuildImage(name string, dockerfile string, output io.Writer) error {
func (rdc *TestDockerClient) BuildImage(name string, dockerfile string, key string, output io.Writer) error {
return nil
}

Expand Down Expand Up @@ -139,6 +139,18 @@ func (rdc *TestDockerClient) AddImage(image docker.APIImages) error {
return nil
}

func (rdc *TestDockerClient) ListVolumes() ([]docker.Volume, error) {
return []docker.Volume{}, nil
}

func (rdc *TestDockerClient) CreateVolume(cvo CreateVolumeOpts) error {
return nil
}

func (rdc *TestDockerClient) RemoveVolume(name string) error {
return nil
}

type TestSystemClient struct {
environments []string
sshArgs [][]string
Expand Down Expand Up @@ -168,7 +180,7 @@ func (tsc *TestSystemClient) GID() int {
return 1000
}

func (tsc *TestSystemClient) EnsureEnvironmentDir(envName string, keys SSHKey) (string, error) {
func (tsc *TestSystemClient) EnsureEnvironmentDir(envName string) (string, error) {
if err, ok := tsc.fails.failures["EnsureEnvironmentDir"]; ok {
return envName, err
}
Expand Down Expand Up @@ -228,8 +240,7 @@ func TestEnvironments(t *testing.T) {
},
},
)
key, _ := sc.EnsureSSHKey()
sc.EnsureEnvironmentDir("foo", key)
sc.EnsureEnvironmentDir("foo")

var envs map[string]Environment
var err error
Expand Down Expand Up @@ -400,8 +411,7 @@ func TestEnsureStopped(t *testing.T) {
},
},
)
key, _ := sc.EnsureSSHKey()
sc.EnsureEnvironmentDir("foo", key)
sc.EnsureEnvironmentDir("foo")

var env Environment
var err error
Expand Down Expand Up @@ -448,8 +458,7 @@ func TestEnsureRunning(t *testing.T) {
},
},
)
key, _ := sc.EnsureSSHKey()
sc.EnsureEnvironmentDir("foo", key)
sc.EnsureEnvironmentDir("foo")

var env Environment
var err error
Expand Down Expand Up @@ -477,7 +486,6 @@ func TestConnectEnvironment(t *testing.T) {
assert := assert.New(t)

sc := NewTestSystemClient()
key, _ := sc.EnsureSSHKey()

dc := NewTestDockerClient()
dc.AddContainer(
Expand All @@ -494,7 +502,7 @@ func TestConnectEnvironment(t *testing.T) {
},
},
)
sc.EnsureEnvironmentDir("foo", key)
sc.EnsureEnvironmentDir("foo")
dc.AddContainer(
docker.APIContainers{
ID: "qux",
Expand All @@ -507,7 +515,7 @@ func TestConnectEnvironment(t *testing.T) {
},
},
)
sc.EnsureEnvironmentDir("buz", key)
sc.EnsureEnvironmentDir("buz")
dc.AddContainer(
docker.APIContainers{
ID: "buz",
Expand All @@ -522,8 +530,8 @@ func TestConnectEnvironment(t *testing.T) {
},
},
)
sc.EnsureEnvironmentDir("qux", key)
sc.EnsureEnvironmentDir("oof", key)
sc.EnsureEnvironmentDir("qux")
sc.EnsureEnvironmentDir("oof")

var env Environment
var err error
Expand Down
7 changes: 6 additions & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ func (x *BuildCommand) Execute(args []string) error {
return err
}

image, err := BuildImage(dc, sc, buildCommand.toBuildOpts(sc), os.Stdout)
key, err := sc.EnsureSSHKey()
if err != nil {
return err
}

image, err := BuildImage(dc, sc, key, buildCommand.toBuildOpts(sc), os.Stdout)
if err != nil {
return err
}
Expand Down
14 changes: 14 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
package main

// CONT_PREFIX is the prefix string used for containers, images, and volumes.
const CONT_PREFIX string = "skeg"

// DOCKER_HUB_ORG is the name of the organization on Docker Hub where
// predefined images can be found.
const DOCKER_HUB_ORG string = "skegio"

// ENVS_DIR is the directory in the user's homedir where data is created
const ENVS_DIR string = "skegs"

// IMAGE_VERSION defines image capabilities as defined by the BuildImage
// function. When new capabilities are added to images, this number should be
// incremented to force building a new user image on environment creation.
//
// version 0: user/tz creation
// version 1: ssh key inclusion
const IMAGE_VERSION int = 1
2 changes: 2 additions & 0 deletions create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type CreateCommand struct {
Ports []string `short:"p" long:"port" description:"Ports to expose (similar to docker -p)."`
Volumes []string `long:"volume" description:"Volume to mount (similar to docker -v)."`
ForceBuild bool `long:"force-build" description:"Force building of new user image."`
VolumeHome bool `long:"volume-home" description:"Use docker volume for homedir instead of skeg dir"`
Args struct {
Name string `description:"Name of environment."`
} `positional-args:"yes" required:"yes"`
Expand All @@ -28,6 +29,7 @@ func (ccommand *CreateCommand) toCreateOpts(sc SystemClient, workingDir string)
ProjectDir: projectDir,
Ports: ccommand.Ports,
Volumes: ccommand.Volumes,
VolumeHome: ccommand.VolumeHome,
ForceBuild: ccommand.ForceBuild || ccommand.ForcePull,
Build: BuildOpts{
Image: ImageOpts{
Expand Down
Loading

0 comments on commit 05340c0

Please sign in to comment.