Skip to content

Commit

Permalink
first version that can pull and build images
Browse files Browse the repository at this point in the history
  • Loading branch information
oclaussen committed Mar 14, 2018
0 parents commit 9dfec6c
Show file tree
Hide file tree
Showing 4 changed files with 350 additions and 0 deletions.
116 changes: 116 additions & 0 deletions config/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package config

import (
"errors"
"fmt"
"strconv"
"strings"
)

// TODO: add inline dockerfile steps
type BuildConfig struct {
Context string
Dockerfile string
Args map[string]*string
}

// TODO: clean up, this is copied from libcompose

func (b BuildConfig) MarshalYAML() (interface{}, error) {
m := map[string]interface{}{}
if b.Context != "" {
m["context"] = b.Context
}
if b.Dockerfile != "" {
m["dockerfile"] = b.Dockerfile
}
if len(b.Args) > 0 {
m["args"] = b.Args
}
return m, nil
}

func (b *BuildConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
b.Context = stringType
return nil
}

var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
for mapKey, mapValue := range mapType {
switch mapKey {
case "context":
b.Context = mapValue.(string)
case "dockerfile":
b.Dockerfile = mapValue.(string)
case "args":
args, err := handleBuildArgs(mapValue)
if err != nil {
return err
}
b.Args = args
default:
// Ignore unknown keys
continue
}
}
return nil
}

return errors.New("Failed to unmarshal Build")
}

func handleBuildArgs(value interface{}) (map[string]*string, error) {
var args map[string]*string
switch v := value.(type) {
case map[interface{}]interface{}:
return handleBuildArgMap(v)
case []interface{}:
return handleBuildArgSlice(v)
default:
return args, fmt.Errorf("Failed to unmarshal Build args: %#v", value)
}
}

func handleBuildArgSlice(s []interface{}) (map[string]*string, error) {
var args = map[string]*string{}
for _, arg := range s {
// check if a value is provided
switch v := strings.SplitN(arg.(string), "=", 2); len(v) {
case 1:
// if we have not specified a a value for this build arg, we assign it an ascii null value and query the environment
// later when we build the service
str := "\x00"
args[v[0]] = &str
case 2:
// if we do have a value provided, we use it
args[v[0]] = &v[1]
}
}
return args, nil
}

func handleBuildArgMap(m map[interface{}]interface{}) (map[string]*string, error) {
args := map[string]*string{}
for mapKey, mapValue := range m {
var argValue string
name, ok := mapKey.(string)
if !ok {
return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
switch a := mapValue.(type) {
case string:
argValue = a
case int:
argValue = strconv.Itoa(a)
case int64:
argValue = strconv.Itoa(int(a))
default:
return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", mapValue, mapValue)
}
args[name] = &argValue
}
return args, nil
}
31 changes: 31 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package config

import (
"fmt"
"io/ioutil"

"gopkg.in/yaml.v2"
)

type Config struct {
Filename string `yaml:"-"`
Commands map[string]CommandConfig `yaml:"tasks,omitempty"`
}

// TODO: error handling
// TODO: validation
func Load(filename string) (*Config, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("Could not read file %q", filename)
}

config := Config{}
err = yaml.Unmarshal(bytes, &config)
if err != nil {
return nil, fmt.Errorf("Could not load config from %q: %s", filename, err)
}
config.Filename = filename

return &config, nil
}
112 changes: 112 additions & 0 deletions config/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package config

import (
"errors"
"fmt"
"strconv"

"github.com/docker/docker/api/types/strslice"
)

// TODO: include other stuff (resources/networking)?
// TODO: inline dockerfile
// TODO: powerful entrypoint
// TODO: remove config
type CommandConfig struct {
Build *BuildConfig `yaml:"build,omitempty"`
ContainerName string `yaml:"container_name,omitempty"`
EnvFile Stringorslice `yaml:"env_file,omitempty"`
Environment MaporEqualSlice `yaml:"environment,omitempty"`
Image string `yaml:"image,omitempty"`
User string `yaml:"user,omitempty"`
Volumes *Volumes `yaml:"volumes,omitempty"`
VolumesFrom []string `yaml:"volumes_from,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty"`
}

type Stringorslice strslice.StrSlice

func (s *Stringorslice) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
*s = []string{stringType}
return nil
}

var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
parts, err := toStrings(sliceType)
if err != nil {
return err
}
*s = parts
return nil
}

return errors.New("Failed to unmarshal Stringorslice")
}

type MaporEqualSlice []string

func (s *MaporEqualSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
parts, err := unmarshalToStringOrSepMapParts(unmarshal, "=")
if err != nil {
return err
}
*s = parts
return nil
}

func unmarshalToStringOrSepMapParts(unmarshal func(interface{}) error, key string) ([]string, error) {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
return toStrings(sliceType)
}
var mapType map[interface{}]interface{}
if err := unmarshal(&mapType); err == nil {
return toSepMapParts(mapType, key)
}
return nil, errors.New("Failed to unmarshal MaporSlice")
}

func toSepMapParts(value map[interface{}]interface{}, sep string) ([]string, error) {
if len(value) == 0 {
return nil, nil
}
parts := make([]string, 0, len(value))
for k, v := range value {
if sk, ok := k.(string); ok {
if sv, ok := v.(string); ok {
parts = append(parts, sk+sep+sv)
} else if sv, ok := v.(int); ok {
parts = append(parts, sk+sep+strconv.Itoa(sv))
} else if sv, ok := v.(int64); ok {
parts = append(parts, sk+sep+strconv.FormatInt(sv, 10))
} else if sv, ok := v.(float64); ok {
parts = append(parts, sk+sep+strconv.FormatFloat(sv, 'f', -1, 64))
} else if v == nil {
parts = append(parts, sk)
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v)
}
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", k, k)
}
}
return parts, nil
}

func toStrings(s []interface{}) ([]string, error) {
if len(s) == 0 {
return nil, nil
}
r := make([]string, len(s))
for k, v := range s {
if sv, ok := v.(string); ok {
r[k] = sv
} else {
return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v)
}
}
return r, nil
}
91 changes: 91 additions & 0 deletions config/volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package config

import (
"errors"
"fmt"
"sort"
"strings"
)

type Volumes struct {
Volumes []*Volume
}

// Volume represent a service volume
type Volume struct {
Source string `yaml:"-"`
Destination string `yaml:"-"`
AccessMode string `yaml:"-"`
}

func (v *Volumes) HashString() string {
if v == nil {
return ""
}
result := []string{}
for _, vol := range v.Volumes {
result = append(result, vol.String())
}
sort.Strings(result)
return strings.Join(result, ",")
}

func (v *Volume) String() string {
var paths []string
if v.Source != "" {
paths = []string{v.Source, v.Destination}
} else {
paths = []string{v.Destination}
}
if v.AccessMode != "" {
paths = append(paths, v.AccessMode)
}
return strings.Join(paths, ":")
}

func (v Volumes) MarshalYAML() (interface{}, error) {
vs := []string{}
for _, volume := range v.Volumes {
vs = append(vs, volume.String())
}
return vs, nil
}

func (v *Volumes) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
if err := unmarshal(&sliceType); err == nil {
v.Volumes = []*Volume{}
for _, volume := range sliceType {
name, ok := volume.(string)
if !ok {
return fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name)
}
elts := strings.SplitN(name, ":", 3)
var vol *Volume
switch {
case len(elts) == 1:
vol = &Volume{
Destination: elts[0],
}
case len(elts) == 2:
vol = &Volume{
Source: elts[0],
Destination: elts[1],
}
case len(elts) == 3:
vol = &Volume{
Source: elts[0],
Destination: elts[1],
AccessMode: elts[2],
}
default:
// FIXME
return fmt.Errorf("")
}
v.Volumes = append(v.Volumes, vol)
}
return nil
}

return errors.New("Failed to unmarshal Volumes")
}

0 comments on commit 9dfec6c

Please sign in to comment.