Skip to content

Instantly share code, notes, and snippets.

@spagettikod
Last active May 2, 2023 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spagettikod/b10ea9ed06ce4c328f05935f51055a36 to your computer and use it in GitHub Desktop.
Save spagettikod/b10ea9ed06ce4c328f05935f51055a36 to your computer and use it in GitHub Desktop.
Export swarm stack as Docker Compose
#!/bin/bash
list_stacks() {
echo $(docker stack ls --format '{{ .Name }}')
}
# returns a list of identifiers for all services in a stack
list_stack_service_ids () {
echo $(docker service ls --filter label=com.docker.stack.namespace=$1 -q)
}
# Returns a list of unique identifiers for secrets used in a stack
list_stack_secret_ids () {
local service_ids="$(list_stack_service_ids $1)"
local secret_ids=$(docker service inspect --format '{{- range .Spec.TaskTemplate.ContainerSpec.Secrets }}
{{ .SecretID }}
{{- end }}' $service_ids)
# make the list unique
echo $secret_ids | xargs -n1 | sort -u | xargs
}
# Returns a list of unique identifiers for volumes used in a stack
list_stack_volume_names () {
local service_ids="$(list_stack_service_ids $1)"
local config_ids=$(docker service inspect --format '{{- range .Spec.TaskTemplate.ContainerSpec.Mounts }}
{{- if eq .Type "volume" }}
{{ .Source }}
{{- end }}{{- end }}' $service_ids)
# make the list unique
echo $config_ids | xargs -n1 | sort -u | xargs
}
# Returns a list of unique identifiers for configs used in a stack
list_stack_config_ids () {
local service_ids="$(list_stack_service_ids $1)"
local config_ids=$(docker service inspect --format '{{- range .Spec.TaskTemplate.ContainerSpec.Configs }}
{{ .ConfigID }}
{{- end}}' $service_ids)
# make the list unique
echo $config_ids | xargs -n1 | sort -u | xargs
}
# Outputs the Docker compose top-level services element.
export_services () {
local service_ids="$(list_stack_service_ids $1)"
local services="$(docker service inspect --format ' {{ .Spec.Name }}:
image: {{ index (split .Spec.TaskTemplate.ContainerSpec.Image "@") 0 }}
{{- if .Spec.EndpointSpec.Ports }}
ports:
{{- range .Spec.EndpointSpec.Ports }}
- {{ .PublishedPort }}:{{ .TargetPort }}
{{- end }}
{{- end }}
{{- if .Spec.TaskTemplate.ContainerSpec.Mounts }}
volumes:
{{- $stack := index .Spec.Labels "com.docker.stack.namespace" }}
{{- range .Spec.TaskTemplate.ContainerSpec.Mounts }}
{{- $source_without_stack := slice .Source (len (print ($stack) "_")) }}
{{- if eq .Type "volume" }}
- {{ $source_without_stack }}:{{ .Target }}
{{- end }}
{{- end }}
{{- end }}
deploy:
{{- if .Spec.Mode.Replicated }}
mode: replicated
{{- end }}
replicas: {{ .Spec.Mode.Replicated.Replicas }}
{{- $first := 0 }}
{{- range $key, $value := .Spec.Labels }}
{{- if and (ne $key "com.docker.stack.image") (ne $key "com.docker.stack.namespace") }}
{{- if eq $first 0 }}
labels:
{{- $first = 1}}
{{- end }}
- {{ $key }}="{{ $value }}"
{{- end }}
{{- end }}
{{- if .Spec.TaskTemplate.ContainerSpec.Env }}
environment:
{{- range .Spec.TaskTemplate.ContainerSpec.Env }}
- {{ index (split . "=") 0 }}="{{ index (split . "=") 1 }}"
{{- end}}
{{- end}}
{{- if .Spec.TaskTemplate.ContainerSpec.Configs }}
configs:
{{- range .Spec.TaskTemplate.ContainerSpec.Configs }}
- {{ .File.Name }}
{{- end }}
{{- end }}
{{- if .Spec.TaskTemplate.ContainerSpec.Secrets }}
secrets:
{{- range .Spec.TaskTemplate.ContainerSpec.Secrets }}
- {{ .File.Name }}
{{- end }}
{{- end }}
' $service_ids)"
printf "services:\n$services"
}
# Outputs the Docker compose top-level configs element. Configs
# will be created from files in the subfolder "configs".
export_configs () {
local config_ids="$(list_stack_config_ids $1)"
# TODO fix the extra line break between secrets
local configs="$(docker config inspect --format '{{ $name_without_stack := slice .Spec.Name (len (print (index .Spec.Labels "com.docker.stack.namespace") "_")) -}}
{{ print " " }}{{ $name_without_stack -}}:
file: configs/{{ .Spec.Name -}}' $config_ids)"
printf "configs:\n$configs"
}
# Outputs the Docker compose top-level secrets element. Secrets
# will be created from files in the subfolder "secrets".
export_secrets () {
local secret_ids="$(list_stack_secret_ids $1)"
local secrets="$(docker secret inspect --format '{{ $name_without_stack := slice .Spec.Name (len (print (index .Spec.Labels "com.docker.stack.namespace") "_")) -}}
{{ print " " }}{{ $name_without_stack }}:
file: secrets/{{ .Spec.Name }}' $secret_ids)"
printf "secrets:\n$secrets"
}
export_volumes () {
local volume_names="$(list_stack_volume_names $1)"
local volumes="$(docker volume inspect --format '{{ $name_without_stack := slice .Name (len (print (index .Labels "com.docker.stack.namespace") "_")) -}}
{{ print " " }}{{ $name_without_stack }}:
driver_opts:
type: "{{ .Options.type }}"
o: "{{ .Options.o }}"
device: "{{ .Options.device }}"' $volume_names)"
printf "volumes:\n$volumes"
}
export_stack () {
printf "version: \"3.9\"\n"
printf "$(export_services $1)\n\n"
printf "$(export_configs $1)\n\n"
printf "$(export_secrets $1)\n\n"
printf "$(export_volumes $1)\n\n"
}
export_stack $1
exit 0
# STACK_FOLDER=backup
# STACKS=$(list_stacks)
# mkdir -p $STACK_FOLDER
# for i in $STACKS
# do
# file="${STACK_FOLDER}/${i}-docker-compose.yml"
# # printf "$(list_stack_volume_names $1)"
# printf "version: \"3.9\"\n" > $file
# printf "$(export_services $i)\n\n" >> $file
# printf "$(export_configs $i)\n\n" >> $file
# printf "$(export_secrets $i)\n\n" >> $file
# printf "$(export_volumes $i)\n\n" >> $file
# done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment