Skip to content

Commit

Permalink
[Compose] move dockerCompose and add images system
Browse files Browse the repository at this point in the history
  • Loading branch information
kariae committed Aug 13, 2018
1 parent fd4f900 commit b1301bf
Show file tree
Hide file tree
Showing 10 changed files with 739 additions and 375 deletions.
46 changes: 27 additions & 19 deletions attributes/Attribute.go → compose/attribute.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package attributes
// Manage docker-compose's configuration attributes.

package compose

import (
"fmt"
Expand All @@ -8,6 +10,7 @@ const servicesCategory = 1
const networksCategory = 2
const volumesCategory = 3

// An Attribute represents a docker-compose's configuration attribute
type Attribute struct {
Name string
InputDescription string
Expand All @@ -18,15 +21,7 @@ type Attribute struct {
PossibleValues []string
}

func (attribute Attribute) DisplayHelp() string {
example := ""
if attribute.Example != "" {
example = fmt.Sprintf("(Example: %s)", attribute.Example)
}

return fmt.Sprintf("%s %s.", attribute.InputDescription, example)
}

// InitServicesAttributes create the docker-compose services attributes Composei accepts
func InitServicesAttributes() []Attribute {
return []Attribute{
{
Expand Down Expand Up @@ -112,6 +107,7 @@ func InitServicesAttributes() []Attribute {
}
}

// InitNetworksAttributes create the docker-compose networks attributes Composei accepts
func InitNetworksAttributes() []Attribute {
return []Attribute{
{
Expand Down Expand Up @@ -141,15 +137,7 @@ func InitNetworksAttributes() []Attribute {
}
}

func (attribute *Attribute) GetDescription() string {
example := ""
if attribute.Example != "" {
example = fmt.Sprintf("(Example: %s)", attribute.Example)
}

return fmt.Sprintf("%s %s.", attribute.InputDescription, example)
}

// InitVolumesAttributes create the docker-compose volumes attributes Composei accepts
func InitVolumesAttributes() []Attribute {
return []Attribute{
{
Expand Down Expand Up @@ -177,3 +165,23 @@ func InitVolumesAttributes() []Attribute {
},
}
}

// Display help message if exists for an attribute
func (attribute Attribute) DisplayHelp() string {
example := ""
if attribute.Example != "" {
example = fmt.Sprintf(" (Example: %s)", attribute.Example)
}

return fmt.Sprintf("%s%s", attribute.InputDescription, example)
}

// GetDescription gets attribute description with example
func (attribute *Attribute) GetDescription() string {
example := ""
if attribute.Example != "" {
example = fmt.Sprintf("(Example: %s)", attribute.Example)
}

return fmt.Sprintf("%s %s.", attribute.InputDescription, example)
}
69 changes: 69 additions & 0 deletions compose/attribute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package compose

import (
"testing"
"fmt"
)

func TestInitServicesAttributes(t *testing.T) {
attributes := InitServicesAttributes()
if len(attributes) < 1 {
t.Fatalf("Expected services attribute length to be > 0")
}
}

func TestInitNetworksAttributes(t *testing.T) {
attributes := InitNetworksAttributes()
if len(attributes) < 1 {
t.Fatalf("Expected networks attributes length to be > 0")
}
}

func TestInitVolumesAttributes(t *testing.T) {
attributes := InitVolumesAttributes()
if len(attributes) < 1 {
t.Fatalf("Expected volumes attributes length to be > 0")
}
}

func TestDisplayHelpWhenDescriptionExists(t *testing.T) {
description := "The image to start the container from. Can either be a repository/tag or a partial image ID"

attribute := Attribute{
InputDescription: description,
}

helpMessage := attribute.DisplayHelp()
expectedResult := fmt.Sprintf("%s", attribute.InputDescription)

if helpMessage != expectedResult {
t.Fatalf("Expected `%s` but got `%s`", expectedResult, helpMessage)
}
}

func TestDisplayHelpWhenExampleExists(t *testing.T) {
description := "The image to start the container from. Can either be a repository/tag or a partial image ID"

attribute := Attribute{
InputDescription: description,
Example: "nginx:alpine",
}

helpMessage := attribute.DisplayHelp()
expectedResult := fmt.Sprintf("%s (Example: %s)", attribute.InputDescription, attribute.Example)

if helpMessage != expectedResult {
t.Fatalf("Expected `%s` but got `%s`", description, helpMessage)
}
}

func TestDisplayHelpWhenDescriptionDoesNotExists(t *testing.T) {
attribute := Attribute{}

helpMessage := attribute.DisplayHelp()
expectedResult := ""

if helpMessage != expectedResult {
t.Fatalf("Expected `` but got `%s`", helpMessage)
}
}
191 changes: 191 additions & 0 deletions compose/dockercompose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Manage docker-compose configuration file.

package compose

import (
"io/ioutil"
"gopkg.in/yaml.v2"
"os"
"path/filepath"
"fmt"
"strings"
"regexp"
"github.com/kariae/composei/reader"
"github.com/kariae/composei/logger"
)

// A DockerCompose represents a docker-compose configuration file
type DockerCompose struct {
Filename string
EnvFilename string
Version string
EnvVarsRegex *regexp.Regexp
Values yaml.MapSlice
}

// New instantiates a new docker-compose configuration file.
func New() *DockerCompose {
self := &DockerCompose{}

self.Filename = "docker-compose.yml"
self.EnvFilename = ".env"
self.Version = "3"
self.EnvVarsRegex = regexp.MustCompile(`\${([^}]+)}`)
self.Values = yaml.MapSlice{}

return self
}

// FileExists check if the docker-compose configuration file exists
// or not in the working directory.
func (dockerCompose *DockerCompose) FileExists() bool {
wd := getWorkingDir()
if _, err := os.Stat(filepath.FromSlash(fmt.Sprintf("%s/%s", wd, dockerCompose.Filename))); os.IsNotExist(err) {
return false
} else {
return true
}
}

// LoadFile loads docker-compose file and unmarshal its content in
// DockerCompose Values attribute.
func (dockerCompose *DockerCompose) LoadFile() (error) {
var err error

source, err := ioutil.ReadFile(dockerCompose.Filename)
if err != nil {
return err
}

err = yaml.Unmarshal([]byte(source), &dockerCompose.Values)
if err != nil {
return err
}

return nil
}

// AddService Add a `service` to DockerCompose
func (dockerCompose *DockerCompose) AddService(r reader.InputReader, service yaml.MapItem) {
dockerCompose.addItemToTopLevel(r, "services", service)
}

// AddService Add a `network` to DockerCompose
func (dockerCompose *DockerCompose) AddNetwork(r reader.InputReader, network yaml.MapItem) {
dockerCompose.addItemToTopLevel(r, "networks", network)
}

// AddService Add a `volume` to DockerCompose
func (dockerCompose *DockerCompose) AddVolume(r reader.InputReader, volume yaml.MapItem) {
dockerCompose.addItemToTopLevel(r, "volumes", volume)
}

// AddService Add an element to a top level DockerCompose value
func (dockerCompose *DockerCompose) addItemToTopLevel(r reader.InputReader, topLevelName string, item yaml.MapItem) {
topLevelIndex := dockerCompose.CreateTopLevel(topLevelName, yaml.MapSlice{})

// Check if item already exists
addItem := true
topLevelItems := dockerCompose.Values[topLevelIndex].Value.(yaml.MapSlice)
if topLevelName == "services" {
for i, existingItem := range topLevelItems {
if existingItem.Key == item.Key {
// Plural -> singular with the trivial way possible xD
switch reader.ReadLine(r, fmt.Sprintf("A `%s` already exists with the same name, would you like to replace it", strings.TrimRight(topLevelName, "s")), []string{reader.YesChoice, reader.NoChoice}, false, "") {
case reader.YesChoice:
topLevelItems = append(topLevelItems[:i], topLevelItems[i+1:]...)
case reader.NoChoice:
addItem = false
}
}
}
}

if addItem {
dockerCompose.Values[topLevelIndex].Value = append(topLevelItems, yaml.MapSlice{item}...)
}
}

// CreateTopLevel Create a top level (services, networks or volumes) value
func (dockerCompose *DockerCompose) CreateTopLevel(topLevelName string, value interface{}) (index int) {
exists, index, content := dockerCompose.topLevelExists(topLevelName)
if !exists {
content = yaml.MapItem{Key:topLevelName, Value:value}
dockerCompose.Values = append(dockerCompose.Values, content)
index = len(dockerCompose.Values) - 1
}

return
}

// topLevelExists checks if a top level value (services, networks or volumes) exists
func (dockerCompose *DockerCompose) topLevelExists(key string) (exists bool, index int, content yaml.MapItem) {
exists = false
index = 0
for i,v := range dockerCompose.Values {
if v.Key == key {
exists = true
index = i
content = dockerCompose.Values[i]
break
}
}

return
}

// Save saves dockerCompose in a YAML configuration file
// If environment variables are set they're saved to the
// appropriate .env file.
func (dockerCompose *DockerCompose) Save(r reader.InputReader, noEnv bool) error {
var err error

d, err := yaml.Marshal(&dockerCompose.Values)

if err == nil {
err = ioutil.WriteFile(dockerCompose.Filename, d, 0644)
}

// Generate env file
if !noEnv {
err = dockerCompose.generateEnvFile(r, string(d))
}

return err
}

// generateEnvFile save environment variables in .env file
func (dockerCompose *DockerCompose) generateEnvFile(r reader.InputReader, composeFileContent string) error {
var err error
envVars := make(map[string]bool)
envFileContent := "# env file generated by Composei\n"

if matches := dockerCompose.EnvVarsRegex.FindAllStringSubmatch(composeFileContent, -1); matches != nil {
fmt.Println("Please enter the environment variables value")

for _, envVarMatch := range matches {
if _, ok := envVars[envVarMatch[1]]; !ok {
envVars[envVarMatch[1]] = true

envVarValue := reader.ReadLine(r, fmt.Sprintf("%s", envVarMatch[1]), []string{}, false, "")
envFileContent += fmt.Sprintf("%s=%s\n", envVarMatch[1], envVarValue)
}
}

err = ioutil.WriteFile(dockerCompose.EnvFilename, []byte(envFileContent), 0644)
}

return err
}

// getWorkingDir Get the current working directory
func getWorkingDir() string {
wd, err := os.Getwd()
if err != nil {
logger.ERROR(err.Error())
}

return wd
}


Loading

0 comments on commit b1301bf

Please sign in to comment.