├── .editorconfig ├── .gitignore ├── .goxc.json ├── README.md ├── examples ├── docker-compose.yml └── output │ └── .gitkeep └── main.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.go] 12 | indent_style = tab 13 | indent_size = 8 14 | 15 | [*.yml] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | examples/output/*.sh 3 | builds 4 | -------------------------------------------------------------------------------- /.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ArtifactsDest": "builds", 3 | "Tasks": [ 4 | "xc" 5 | ], 6 | "BuildConstraints": "linux, windows, darwin", 7 | "PackageVersion": "1.6.0", 8 | "TaskSettings": { 9 | "xc": { 10 | "GOARM": "7" 11 | } 12 | }, 13 | "ConfigVersion": "0.9" 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | compose2bash 2 | ======== 3 | 4 | Tool that converts docker-compose.yml (former fig.yml) files to bash scripts. 5 | 6 | 7 | ## Download 8 | 9 | [Releases](https://github.com/dockito/compose2bash/releases) 10 | 11 | 12 | ## Usage 13 | 14 | ```bash 15 | compose2bash -yml=examples/docker-compose.yml -output=examples/output -app=myapp 16 | ``` 17 | 18 | ## Example 19 | **docker-compose.yml** 20 | 21 | ```yml 22 | api: 23 | command: npm start 24 | image: docker.mydomain.com/api:latest 25 | ports: 26 | - 3000 27 | environment: 28 | VIRTUAL_PORT: 3000 29 | VIRTUAL_HOST: api.mydomain.com 30 | NODE_ENV: development 31 | MONGO_DATABASE: develop_api 32 | volumes: 33 | - .:/src 34 | privileged: True 35 | ``` 36 | 37 | 38 | **output: myapp-api.1.sh** 39 | ```bash 40 | #!/bin/bash 41 | /usr/bin/docker pull docker.mydomain.com/api:latest 42 | 43 | if /usr/bin/docker ps | grep --quiet myapp-api_1 ; then 44 | /usr/bin/docker rm -f myapp-api_1 45 | fi 46 | 47 | /usr/bin/docker run \ 48 | --restart=always \ 49 | -d \ 50 | --name myapp-api_1 \ 51 | -v .:/src \ 52 | --link myapp-redis_1:redis \ 53 | -e MONGO_DATABASE="develop_api" -e NODE_ENV="development" -e VIRTUAL_HOST="api.mydomain.com" -e VIRTUAL_PORT="3000" \ 54 | -p 3000 \ 55 | docker.mydomain.com/api:latest npm start 56 | ``` 57 | 58 | **output: myapp-api.1.sh** with `-interactive-bash` 59 | ```bash 60 | #!/bin/bash 61 | /usr/bin/docker pull docker.mydomain.com/api:latest 62 | 63 | if /usr/bin/docker ps -a | grep --quiet myapp-api_1 ; then 64 | /usr/bin/docker rm -f myapp-api_1 65 | fi 66 | 67 | 68 | while [ "$#" -gt 0 ]; do case "$1" in 69 | --interactive-bash) interactivebash="true"; shift 1;; 70 | *) shift;; 71 | esac 72 | done 73 | 74 | if [[ $interactivebash == "true" ]]; then 75 | /usr/bin/docker run \ 76 | -ti \ 77 | --name myapp-api_1 \ 78 | -v .:/src \ 79 | --link myapp-redis_1:redis \ 80 | -e MONGO_DATABASE="develop_api" -e NODE_ENV="development" -e VIRTUAL_HOST="api.mydomain.com" -e VIRTUAL_PORT="3000" \ 81 | -p 3000 \ 82 | docker.mydomain.com/api:latest bash 83 | else 84 | /usr/bin/docker run \ 85 | --restart=always \ 86 | -d \ 87 | --name myapp-api_1 \ 88 | -v .:/src \ 89 | --link myapp-redis_1:redis \ 90 | -e MONGO_DATABASE="develop_api" -e NODE_ENV="development" -e VIRTUAL_HOST="api.mydomain.com" -e VIRTUAL_PORT="3000" \ 91 | -p 3000 \ 92 | docker.mydomain.com/api:latest npm start 93 | fi 94 | ``` 95 | 96 | ## Options 97 | 98 | - **-v**: Show the current version 99 | - **-app**: Application name 100 | - **-output**: Output directory (default `.`) 101 | - **-yml**: Compose file path (default `docker-compose.yml`) 102 | - **-docker-host**: Docker host connection 103 | - **-interactive-bash**: Include option to run the generated script with interactive bash. Running the generated script without any argument will executed it normally. But running it with `--interactive-bash` will execute the container with interactive bash. Pretty handful for debug. 104 | 105 | 106 | 107 | ## Build 108 | 109 | Using [goxc](https://github.com/laher/goxc). 110 | 111 | ```bash 112 | goxc 113 | ``` 114 | -------------------------------------------------------------------------------- /examples/docker-compose.yml: -------------------------------------------------------------------------------- 1 | redis: 2 | image: redis:2.8 3 | hostname: core 4 | ports: 5 | - 6379 6 | 7 | api: 8 | command: npm start 9 | image: docker.mydomain.com/api:latest 10 | ports: 11 | - 3000 12 | environment: 13 | VIRTUAL_PORT: 3000 14 | VIRTUAL_HOST: api.mydomain.com 15 | NODE_ENV: development 16 | MONGO_DATABASE: develop_api 17 | volumes: 18 | - .:/src 19 | links: 20 | - 'redis:redis' 21 | env_file: 22 | - .db 23 | - .mq 24 | 25 | app: 26 | image: docker.mydomain.com/app:latest 27 | ports: 28 | - 80 29 | environment: 30 | VIRTUAL_HOST: app.mydomain.com 31 | VIRTUAL_PORT: 80 32 | NODE_ENV: production 33 | API_URL: http://api.mydomain.com 34 | privileged: True 35 | -------------------------------------------------------------------------------- /examples/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockito/compose2bash/fb9311f09130ca3d10feb2214d8b7d1b00bb87eb/examples/output/.gitkeep -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | 14 | "gopkg.in/yaml.v2" 15 | ) 16 | 17 | var ( 18 | appName string 19 | composePath string 20 | outputPath string 21 | dockerHostConn string 22 | interactiveBash bool 23 | ) 24 | 25 | const bashTemplate = `#!/bin/bash 26 | /usr/bin/docker {{.DockerHostConnCmdArg}} pull {{.Service.Image}} 27 | 28 | if /usr/bin/docker {{.DockerHostConnCmdArg}} ps -a | grep --quiet {{.Service.Name}}_1 ; then 29 | /usr/bin/docker {{.DockerHostConnCmdArg}} rm -f {{.Service.Name}}_1 30 | fi 31 | 32 | {{if .InteractiveBash}} 33 | while [ "$#" -gt 0 ]; do case "$1" in 34 | --interactive-bash) interactivebash="true"; shift 1;; 35 | *) shift;; 36 | esac 37 | done 38 | 39 | if [[ $interactivebash == "true" ]]; then 40 | /usr/bin/docker {{.DockerHostConnCmdArg}} run \ 41 | {{if .Service.Privileged}}--privileged=true {{end}} \ 42 | -ti \ 43 | --name {{.Service.Name}}_1 \ 44 | {{if .Service.HostName}}--hostname={{.Service.HostName}} {{end}} \ 45 | {{if .Service.Net}}--net={{.Service.Net}} {{end}} \ 46 | {{range .Service.Volumes}}-v {{.}} {{end}} \ 47 | {{range .Service.Links}}--link {{.}} {{end}} \ 48 | {{range $key, $value := .Service.Environment}}-e {{$key}}="{{$value}}" {{end}} \ 49 | {{range .Service.Ports}}-p {{.}} {{end}} \ 50 | {{range .Service.Env_File}}--env-file {{.}} {{end}} \ 51 | {{.Service.Image}} bash 52 | else 53 | /usr/bin/docker {{.DockerHostConnCmdArg}} run \ 54 | {{if .Service.Privileged}}--privileged=true {{end}} \ 55 | --restart=always \ 56 | -d \ 57 | --name {{.Service.Name}}_1 \ 58 | {{if .Service.HostName}}--hostname={{.Service.HostName}} {{end}} \ 59 | {{if .Service.Net}}--net={{.Service.Net}} {{end}} \ 60 | {{range .Service.Volumes}}-v {{.}} {{end}} \ 61 | {{range .Service.Links}}--link {{.}} {{end}} \ 62 | {{range $key, $value := .Service.Environment}}-e {{$key}}="{{$value}}" {{end}} \ 63 | {{range .Service.Ports}}-p {{.}} {{end}} \ 64 | {{range .Service.Env_File}}--env-file {{.}} {{end}} \ 65 | {{.Service.Image}} {{.Service.Command}} 66 | fi 67 | {{else}} 68 | /usr/bin/docker {{.DockerHostConnCmdArg}} run \ 69 | {{if .Service.Privileged}}--privileged=true {{end}} \ 70 | --restart=always \ 71 | -d \ 72 | --name {{.Service.Name}}_1 \ 73 | {{if .Service.HostName}}--hostname={{.Service.HostName}} {{end}} \ 74 | {{if .Service.Net}}--net={{.Service.Net}} {{end}} \ 75 | {{range .Service.Volumes}}-v {{.}} {{end}} \ 76 | {{range .Service.Links}}--link {{.}} {{end}} \ 77 | {{range $key, $value := .Service.Environment}}-e {{$key}}="{{$value}}" {{end}} \ 78 | {{range .Service.Ports}}-p {{.}} {{end}} \ 79 | {{range .Service.Env_File}}--env-file {{.}} {{end}} \ 80 | {{.Service.Image}} {{.Service.Command}} 81 | {{end}} 82 | ` 83 | 84 | // ScriptDataTemplate contains the whole data configuration used to fill the script 85 | type ScriptDataTemplate struct { 86 | AppName string 87 | DockerHostConnCmdArg string 88 | InteractiveBash bool 89 | Service Service 90 | } 91 | 92 | // Service has the same structure used by docker-compose.yml 93 | type Service struct { 94 | Name string 95 | HostName string 96 | Image string 97 | Net string 98 | Ports []string 99 | Volumes []string 100 | Env_File []string 101 | Links []string 102 | Privileged bool 103 | Command string 104 | Environment map[string]string 105 | } 106 | 107 | // Parses the original Yaml to the Service struct 108 | func loadYaml(filename string) (services map[string]Service, err error) { 109 | data, err := ioutil.ReadFile(filename) 110 | if err == nil { 111 | err = yaml.Unmarshal([]byte(data), &services) 112 | } 113 | return 114 | } 115 | 116 | func setLinksWithAppName(service *Service) { 117 | for i := range service.Links { 118 | links := strings.Split(service.Links[i], ":") 119 | containerName := links[0] 120 | containerAlias := containerName + "_1" 121 | if len(links) > 1 { 122 | containerAlias = links[1] 123 | } 124 | 125 | service.Links[i] = fmt.Sprintf("%s-%s_1:%s", appName, containerName, containerAlias) 126 | } 127 | } 128 | 129 | func removeBlankLinkes(path string) { 130 | re := regexp.MustCompile(`(?m)^\s*\\\n`) 131 | read, err := ioutil.ReadFile(path) 132 | if err != nil { 133 | panic(err) 134 | } 135 | 136 | newContents := re.ReplaceAllString(string(read), "") 137 | 138 | err = ioutil.WriteFile(path, []byte(newContents), 0) 139 | if err != nil { 140 | panic(err) 141 | } 142 | } 143 | 144 | func buildScriptDataTemplate(serviceName string, service Service) ScriptDataTemplate { 145 | // common data template for all services from the same app 146 | data := ScriptDataTemplate{ 147 | AppName: appName, 148 | InteractiveBash: interactiveBash, 149 | } 150 | 151 | if dockerHostConn != "" { 152 | data.DockerHostConnCmdArg = "--host=" + dockerHostConn 153 | } 154 | 155 | // specific data for each service 156 | service.Name = appName + "-" + serviceName 157 | setLinksWithAppName(&service) 158 | data.Service = service 159 | 160 | return data 161 | } 162 | 163 | // Saves the services data into bash scripts 164 | func saveToBash(services map[string]Service) (err error) { 165 | t := template.New("service-bash-template") 166 | t, err = t.Parse(bashTemplate) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | for name, service := range services { 172 | data := buildScriptDataTemplate(name, service) 173 | fileName := path.Join(outputPath, data.Service.Name+".1.sh") 174 | 175 | f, _ := os.Create(fileName) 176 | defer f.Close() 177 | t.Execute(f, data) 178 | 179 | removeBlankLinkes(fileName) 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func main() { 186 | version := flag.Bool("v", false, "show the current version") 187 | flag.StringVar(&appName, "app", "", "application name") 188 | flag.StringVar(&composePath, "yml", "docker-compose.yml", "compose file path") 189 | flag.StringVar(&outputPath, "output", ".", "output directory") 190 | flag.StringVar(&dockerHostConn, "docker-host", "", "docker host connection") 191 | flag.BoolVar(&interactiveBash, "interactive-bash", false, "include option to run the generated script with interactive bash") 192 | 193 | flag.Parse() 194 | 195 | if *version { 196 | fmt.Println("compose2bash version 1.5.0") 197 | return 198 | } 199 | 200 | if appName == "" { 201 | fmt.Println("Missing app argument") 202 | os.Exit(1) 203 | } 204 | 205 | services, err := loadYaml(composePath) 206 | if err != nil { 207 | log.Fatalf("error parsing docker-compose.yml %v", err) 208 | } 209 | 210 | if err = saveToBash(services); err != nil { 211 | log.Fatalf("error saving bash template %v", err) 212 | } 213 | 214 | fmt.Println("Successfully converted Yaml to Bash script.") 215 | } 216 | --------------------------------------------------------------------------------