38 Commits

Author SHA1 Message Date
e30c7c007f feat: support service.command
Some checks failed
Markdown linter / markdown-lint (push) Successful in 3s
Release / release (push) Successful in 1m24s
Release / sync-to-hub-docker-io (push) Successful in 28s
Golang Tests / unittest (push) Failing after 2m48s
This patch extends dcmerge to support the command attribut of a defined service.
For example:

```yaml
services:
  busybox
    command: [ "/usr/bin/cp", "--recursive", "--force", "/tmp/bar.txt", "/tmp/foo.txt"]
    image: library/busybox:latest
```

The command attribute is interpreted as a whole. This means that individual
arguments are not merged as a comparison, as this would change the meaning of
the command attribute.
2025-05-22 09:49:43 +02:00
973e90986c style(markdown): adapt configuration
All checks were successful
Markdown linter / markdown-lint (push) Successful in 8s
Golang Tests / unittest (push) Successful in 29s
2025-05-13 17:53:33 +02:00
90a6350a02 fix(drone): remove configuration
All checks were successful
Golang Tests / unittest (push) Successful in 22s
Markdown linter / markdown-lint (push) Successful in 10s
2025-05-08 14:51:15 +02:00
89f4f5b0fa chore(deps): update actions/setup-go action to v5.5.0
All checks were successful
Golang Tests / unittest (pull_request) Successful in 2m40s
Markdown linter / markdown-lint (pull_request) Successful in 11s
Markdown linter / markdown-lint (push) Successful in 7s
Golang Tests / unittest (push) Successful in 24s
2025-05-08 04:03:00 +00:00
ee1474a506 chore(deps): update docker.io/library/golang docker tag to v1.24.3
All checks were successful
Markdown linter / markdown-lint (pull_request) Successful in 11s
Golang Tests / unittest (pull_request) Successful in 2m28s
Markdown linter / markdown-lint (push) Successful in 8s
Golang Tests / unittest (push) Successful in 31s
2025-05-06 22:03:33 +00:00
1d33b165eb chore(deps): update goreleaser/goreleaser-action action to v6.3.0
All checks were successful
Golang Tests / unittest (pull_request) Successful in 47s
Markdown linter / markdown-lint (pull_request) Successful in 11s
Golang Tests / unittest (push) Successful in 45s
Markdown linter / markdown-lint (push) Successful in 12s
2025-04-14 19:07:57 +00:00
6fb289da71 docs(README): remove badge of drone.io
All checks were successful
Golang Tests / unittest (push) Successful in 41s
Markdown linter / markdown-lint (push) Successful in 16s
Update Docker Hub Description / update-description-on-hub-docker-io (push) Successful in 16s
2025-04-14 19:51:56 +02:00
ed0b94e4b8 fix(ci): select correct repository on hub.docker.io
Some checks are pending
Golang Tests / unittest (push) Waiting to run
Markdown linter / markdown-lint (push) Waiting to run
Update Docker Hub Description / update-description-on-hub-docker-io (push) Waiting to run
2025-04-14 19:51:23 +02:00
5f06edff04 chore(ci): use '.yaml' extension 2025-04-14 19:51:23 +02:00
5560d13550 feat(ci): sync description on hub.docker.io
Some checks failed
Markdown linter / markdown-lint (push) Waiting to run
Golang Tests / unittest (push) Has been cancelled
Update Docker Hub Description / update-description-on-hub-docker-io (push) Failing after 10s
2025-04-14 19:45:41 +02:00
6387e972e3 chore(ci): add golang tests
All checks were successful
Golang Tests / unittest (push) Successful in 42s
Markdown linter / markdown-lint (push) Successful in 12s
2025-04-14 19:29:09 +02:00
5eec7fc1a5 chore(ci): add markdownlinter
All checks were successful
Markdown linter / markdown-lint (push) Successful in 8s
2025-04-14 19:20:12 +02:00
0688315658 chore(ci): remove suffix
All checks were successful
Release / release (push) Successful in 1m52s
Release / sync-container-image (push) Successful in 48s
2025-04-14 19:17:56 +02:00
63a54e130d chore(ci): use goreleaser
Some checks failed
Release / release (push) Successful in 6m4s
Release / sync-container-image (push) Failing after 33s
2025-04-14 19:07:10 +02:00
ef1c7f25a3 chore(deps): update container images
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-04-01 19:11:19 +00:00
cc9c98b84d chore(renovate): use configuration preset
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-30 22:46:31 +02:00
9f29df1311 chore(deps): update docker.io/plugins/docker docker tag to v20.18.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-26 20:10:46 +00:00
1d8a8d7b76 Merge pull request 'chore(deps): update docker.io/library/golang docker tag to v1.24.1' (#74) from renovate/docker.io-library-golang-1.x into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #74
2025-03-22 17:01:14 +00:00
3e73692f28 chore(deps): update docker.io/library/golang docker tag to v1.24.1
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2025-03-22 15:29:22 +00:00
7a0c89c8b7 chore(renovate): remove obsolete matchBaseBranches
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-22 15:10:00 +00:00
3c56ae6e5e fix(docker-compose): extend YAML marshaler of service.dependsOn
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
The current implementation of the method DependsOnContainer.MarshalYAML()
transform the short syntax into the long syntax. More about booth version
types of depends_on is described here:

- https://docs.docker.com/reference/compose-file/services/#short-syntax-1
- https://docs.docker.com/reference/compose-file/services/#long-syntax-1

Other applications are not compatible with the long syntax. For this reason the
MarshalYAML method has been adapted to take care of the specific syntax.

As documented of the long syntax, `depends_on.<dependency>.condition: service_started`
is the same as `depends_on: [ 'dependency' ]`, the long syntax will be shortened
when no other condition type of a dependency is specified.
2025-03-04 17:50:01 +01:00
ed7622a34f chore(coverage): ignore coverage.txt 2025-03-04 17:50:01 +01:00
967c20c638 fix(cmd): replace deprecated cobra function 2025-03-04 17:50:01 +01:00
c23633a385 chore(deps): update quay.io/skopeo/stable docker tag to v1.18.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-28 17:10:07 +00:00
68db80a05a fix(yaml): set indentation to 2 spaces
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
The replaced module gopkg.in/yaml.v3 enforce an indentation of 4 spaces, when 0
spaces are defined. This is sad, because the old behavior can not be reproduced.

Instead of using 4 spaces for indentation, when `yamlEncoder.SetIndent(0)` is
called, we call `yamlEncoder.SetIndent(2)` to use 2 spaces for indentation,
because it is closer to 0 instead 4 spaces.
2025-02-25 16:35:44 +01:00
6761dbf419 test(docker-compose): add further tests regrading depends_on
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-02-25 16:21:13 +01:00
ab282e5173 feat: support depends_on
All checks were successful
continuous-integration/drone/push Build is passing
This PR supports the extended pattern of `depends_on`. If the short version of
`depends_on` is defined, it will be migrated to the extended version like the
example below:

```yaml
services:
  web:
    depends_on
    - database

services:
  web:
    depends_on:
      database:
        condition: service_started
```

All three types of merging strategies are supported.
2025-02-24 22:44:11 +01:00
003db26fe5 feat: respect individual ports instead of replacing the entire slice of ports
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
The following patch adapts the logic of the merge strategy existing and last
win for service ports.

In the past, the complete object has been replaced based on the merge strategy.
This behavior has been adapted, that each port should now considered
individually per strategy.

Both strategies now focus on the src port of the host system. With a last-win,
the dest port of the container is overwritten with an existing src port.

```diff
  service:
    my-app:
      ports:
- - 0.0.0.0:8080:80
+ - 0.0.0.0:8080:8080
      - 0.0.0.0:8443:8443
```

The situation is different with the existing win strategy. There, the destination
port can no longer be changed once there is a connection with a sourc port.
2025-02-21 13:55:53 +01:00
f8b4fe9af6 fix(dockerCompoe): compare srcIP and srcPort of mergeExistingWinPorts() 2025-02-21 09:51:53 +01:00
2c91222753 chore(renovate): disable go package gopkg.in/yaml.v2
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-20 17:29:39 +01:00
b45f8181aa style(lint): block unused parameter
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-20 17:26:43 +01:00
13301cb367 fix(dockerCompose): add further methods of port
This patch adds the following methods:

ports.ExistsDstIP()
ports.ExistsDstPort()
ports.ExistsProtocol()
ports.ExistsSrcIP()
ports.ExistsSrcPort()
ports.GetDstIP()
ports.GetDstPort()
ports.GetProtocol()
ports.GetSrcIP()
ports.GetSrcPort()
2025-02-20 17:26:43 +01:00
0a68b2d8e7 fix(dockerCompose): add port.ExistProtocol 2025-02-20 17:26:43 +01:00
54af4469a4 chore(deps): update docker.io/plugins/docker docker tag to v20.18.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-18 08:08:41 +00:00
dc15512229 chore(deps): update module github.com/spf13/cobra to v1.9.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-17 02:10:59 +00:00
5fa0991bc2 chore(deps): update docker.io/library/golang docker tag to v1.24.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-15 23:09:33 +00:00
efb5fba9a9 chore(deps): update module github.com/spf13/cobra to v1.9.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-15 20:12:23 +00:00
c8894de28c chore(deps): update docker.io/library/golang docker tag to v1.23.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-05 23:22:50 +00:00
34 changed files with 1816 additions and 1027 deletions

View File

@ -1,727 +0,0 @@
---
kind: pipeline
type: kubernetes
name: linter
clone:
disable: true
platform:
os: linux
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: markdown lint
commands:
- markdownlint *.md
image: git.cryptic.systems/volker.raschek/markdownlint:0.43.0
resources:
limits:
cpu: 150
memory: 150M
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
resources:
limits:
cpu: 150
memory: 150M
when:
status:
- changed
- failure
trigger:
event:
exclude:
- tag
---
kind: pipeline
type: docker
name: unit-test-amd64
clone:
disable: true
platform:
arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: unit-test
commands:
- go test -v ./...
image: docker.io/library/golang:1.23.6
trigger:
event:
exclude:
- tag
---
kind: pipeline
type: docker
name: unit-test-arm64
clone:
disable: true
platform:
arch: arm64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: unit-test
commands:
- go test -v ./...
image: docker.io/library/golang:1.23.6
trigger:
event:
include:
- pull_request
- push
exclude:
- tag
---
kind: pipeline
type: docker
name: dry-run-amd64
clone:
disable: true
depends_on:
- linter
- unit-test-amd64
platform:
os: linux
arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: false
dockerfile: Dockerfile
dry_run: true
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
tags: latest-amd64
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
branch:
exclude:
- master
event:
- pull_request
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: docker
name: dry-run-arm64-v8
clone:
disable: true
depends_on:
- linter
- unit-test-arm64
platform:
os: linux
arch: arm64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: false
dockerfile: Dockerfile
dry_run: true
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
tags: latest-arm64-v8
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
branch:
exclude:
- master
event:
- pull_request
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: docker
name: latest-amd64
clone:
disable: true
depends_on:
- linter
- unit-test-amd64
platform:
os: linux
arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: false
dockerfile: Dockerfile
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
tags: latest-amd64
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
branch:
- master
event:
- cron
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: docker
name: latest-arm64-v8
clone:
disable: true
depends_on:
- linter
- unit-test-arm64
platform:
os: linux
arch: arm64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: false
dockerfile: Dockerfile
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
tags: latest-arm64-v8
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
branch:
- master
event:
- cron
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: kubernetes
name: latest-manifest
clone:
disable: true
depends_on:
- latest-amd64
- latest-arm64-v8
# docker.io/plugins/manifest only for amd64 architectures available
node_selector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build-manifest
image: docker.io/plugins/manifest:1.4.0
settings:
auto_tag: false
ignore_missing: true
spec: manifest.tmpl
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
resources:
limits:
cpu: 150
memory: 150M
when:
status:
- changed
- failure
trigger:
branch:
- master
event:
- cron
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: kubernetes
name: latest-sync
clone:
disable: true
depends_on:
- latest-manifest
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: latest-sync
commands:
- skopeo sync --all --src=docker --src-creds=$SRC_CRED_USERNAME:$SRC_CRED_PASSWORD --dest=docker --dest-creds=$DEST_CRED_USERNAME:$DEST_CRED_PASSWORD git.cryptic.systems/volker.raschek/dcmerge docker.io/volkerraschek
environment:
SRC_CRED_USERNAME:
from_secret: git_cryptic_systems_container_registry_user
SRC_CRED_PASSWORD:
from_secret: git_cryptic_systems_container_registry_password
DEST_CRED_USERNAME:
from_secret: container_image_registry_user
DEST_CRED_PASSWORD:
from_secret: container_image_registry_password
image: quay.io/skopeo/stable:v1.17.0
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
resources:
limits:
cpu: 150
memory: 150M
when:
status:
- changed
- failure
trigger:
branch:
- master
event:
- cron
- push
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: docker
name: tagged-amd64
clone:
disable: true
platform:
os: linux
arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: true
auto_tag_suffix: amd64
dockerfile: Dockerfile
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
build_args:
- VERSION=${DRONE_TAG}
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
event:
- tag
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: docker
name: tagged-arm64-v8
clone:
disable: true
platform:
os: linux
arch: arm64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build
image: docker.io/plugins/docker:20.18.6
settings:
auto_tag: true
auto_tag_suffix: arm64-v8
dockerfile: Dockerfile
force_tag: true
no_cache: true
purge: true
mirror:
from_secret: docker_io_mirror
registry: git.cryptic.systems
repo: git.cryptic.systems/volker.raschek/dcmerge
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
build_args:
- VERSION=${DRONE_TAG}
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
when:
status:
- changed
- failure
trigger:
event:
- tag
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: kubernetes
name: tagged-manifest
clone:
disable: true
depends_on:
- tagged-amd64
- tagged-arm64-v8
# docker.io/plugins/manifest only for amd64 architectures available
node_selector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: build-manifest
image: docker.io/plugins/manifest:1.4.0
settings:
auto_tag: true
ignore_missing: true
spec: manifest.tmpl
username:
from_secret: git_cryptic_systems_container_registry_user
password:
from_secret: git_cryptic_systems_container_registry_password
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
resources:
limits:
cpu: 150
memory: 150M
when:
status:
- changed
- failure
trigger:
event:
- tag
repo:
- volker.raschek/dcmerge
---
kind: pipeline
type: kubernetes
name: tagged-sync
clone:
disable: true
depends_on:
- tagged-manifest
steps:
- name: clone
image: git.cryptic.systems/volker.raschek/git:1.4.0
- name: tagged-sync
commands:
- skopeo sync --all --src=docker --src-creds=$SRC_CRED_USERNAME:$SRC_CRED_PASSWORD --dest=docker --dest-creds=$DEST_CRED_USERNAME:$DEST_CRED_PASSWORD git.cryptic.systems/volker.raschek/dcmerge docker.io/volkerraschek
environment:
SRC_CRED_USERNAME:
from_secret: git_cryptic_systems_container_registry_user
SRC_CRED_PASSWORD:
from_secret: git_cryptic_systems_container_registry_password
DEST_CRED_USERNAME:
from_secret: container_image_registry_user
DEST_CRED_PASSWORD:
from_secret: container_image_registry_password
image: quay.io/skopeo/stable:v1.17.0
- name: email-notification
environment:
SMTP_FROM_ADDRESS:
from_secret: smtp_from_address
SMTP_FROM_NAME:
from_secret: smtp_from_name
SMTP_HOST:
from_secret: smtp_host
SMTP_USERNAME:
from_secret: smtp_username
SMTP_PASSWORD:
from_secret: smtp_password
image: git.cryptic.systems/volker.raschek/drone-email:0.1.5
resources:
limits:
cpu: 150
memory: 150M
when:
status:
- changed
- failure
trigger:
event:
- tag
repo:
- volker.raschek/dcmerge

View File

@ -0,0 +1,21 @@
name: Golang Tests
on:
pull_request:
types: [ "opened", "reopened", "synchronize" ]
push:
branches: [ '**' ]
tags-ignore: [ '**' ]
jobs:
unittest:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/setup-go@v5.5.0
with:
go-version: stable
- env:
GOPROXY: ${{ vars.GOPROXY }}
run: make test/unit

View File

@ -0,0 +1,20 @@
name: Markdown linter
on:
pull_request:
types: [ "opened", "reopened", "synchronize" ]
push:
branches: [ '**' ]
tags-ignore: [ '**' ]
workflow_dispatch: {}
jobs:
markdown-lint:
container:
image: git.cryptic.systems/volker.raschek/markdownlint:0.44.0
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- name: Lint Markdown files
run: markdownlint --config .markdownlint.yaml .

View File

@ -0,0 +1,52 @@
name: Release
on:
push:
tags: [ '**' ]
permissions:
contents: write
jobs:
release:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- uses: docker/setup-qemu-action@v3.6.0
- uses: actions/setup-go@v5.5.0
with:
go-version: stable
- uses: docker/login-action@v3.4.0
with:
registry: git.cryptic.systems
username: ${{ github.repository_owner }}
password: ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }}
- env:
GITEA_TOKEN: ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }}
GONOSUMDB: ${{ vars.GONOSUMDB }}
GOPROXY: ${{ vars.GOPROXY }}
uses: goreleaser/goreleaser-action@v6.3.0
with:
version: "~> v2"
args: release --clean
sync-to-hub-docker-io:
needs:
- release
runs-on: ubuntu-latest
steps:
- name: Copy images to docker.io
run: |
TAG=$(echo ${{ github.ref_name }} | sed 's/v//gm')
apt-get update --yes
apt-get install --yes skopeo
skopeo copy \
--all \
--dest-password ${{ secrets.DOCKER_IO_PASSWORD }} \
--dest-username ${{ secrets.DOCKER_IO_USERNAME }} \
--src-password ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }} \
--src-username volker.raschek \
docker://git.cryptic.systems/volker.raschek/dcmerge:${TAG} \
docker://docker.io/volkerraschek/dcmerge:${TAG}

View File

@ -0,0 +1,19 @@
name: Update Docker Hub Description
on:
push:
branches: [ 'master' ]
paths: [ 'README.md' ]
jobs:
update-description-on-hub-docker-io:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- uses: peter-evans/dockerhub-description@v4.0.2
with:
username: ${{ secrets.DOCKER_IO_USERNAME }}
password: ${{ secrets.DOCKER_IO_PASSWORD }}
repository: volkerraschek/dcmerge
readme-filepath: README.md

3
.gitignore vendored
View File

@ -1 +1,2 @@
dcmerge
dcmerge
coverage.txt

154
.goreleaser.yaml Normal file
View File

@ -0,0 +1,154 @@
project_name: dcmerge
archives:
- formats: [ "tar.xz" ]
files:
- README.md
- LICENSE
before:
hooks:
- go mod tidy
builds:
- main: main.go
binary: >-
{{ .ProjectName }}-
{{- .Version }}-
{{- .Os }}-
{{- if eq .Arch "amd64" }}amd64
{{- else if eq .Arch "amd64_v1" }}amd64
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}-{{ .Arm }}{{ end }}
env:
- CGO_ENABLED=0
- GONOSUMDB={{ .Env.GONOSUMDB }}
- GOPROXY={{ .Env.GOPROXY }}
goos:
- linux
goarch:
- amd64
- arm
- arm64
goarm:
- "6"
- "7"
flags:
- -trimpath
ldflags:
- -s -w -X 'main.version={{ trimprefix .Tag "v" }}'
changelog:
filters:
exclude:
- '^chore'
- '^docs'
- '^test'
- Merge pull request
- Merge branch
- go mod tidy
format: "{{.SHA}}: {{.Message}} (@{{.AuthorUsername}})"
groups:
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: "Bug fixes"
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Others
order: 999
sort: asc
use: git
dockers:
- build_flag_templates:
- --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
- --label=org.opencontainers.image.description={{ .ProjectName }}
- --label=org.opencontainers.image.documentation={{ .ProjectName }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.source={{ .GitURL }}
- --label=org.opencontainers.image.title={{ .ProjectName }}
- --label=org.opencontainers.image.url=https://git.cryptic.systems/volker.raschek/{{ .ProjectName }}
- --label=org.opencontainers.image.version={{ trimprefix .Tag "v" }}
- --platform=linux/amd64
- --pull
dockerfile: Dockerfile
goarch: amd64
goos: linux
image_templates:
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-amd64'
skip_push: false
use: buildx
- build_flag_templates:
- --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
- --label=org.opencontainers.image.description={{ .ProjectName }}
- --label=org.opencontainers.image.documentation={{ .ProjectName }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.source={{ .GitURL }}
- --label=org.opencontainers.image.title={{ .ProjectName }}
- --label=org.opencontainers.image.url=https://git.cryptic.systems/volker.raschek/{{ .ProjectName }}
- --label=org.opencontainers.image.version={{ trimprefix .Tag "v" }}
- --platform=linux/arm/v6
- --pull
dockerfile: Dockerfile
goarch: arm
goarm: "6"
goos: linux
image_templates:
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm-v6'
skip_push: false
use: buildx
- build_flag_templates:
- --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
- --label=org.opencontainers.image.description={{ .ProjectName }}
- --label=org.opencontainers.image.documentation={{ .ProjectName }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.source={{ .GitURL }}
- --label=org.opencontainers.image.title={{ .ProjectName }}
- --label=org.opencontainers.image.url=https://git.cryptic.systems/volker.raschek/{{ .ProjectName }}
- --label=org.opencontainers.image.version={{ trimprefix .Tag "v" }}
- --platform=linux/arm/v7
- --pull
dockerfile: Dockerfile
goarch: arm
goarm: "7"
goos: linux
image_templates:
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm-v7'
skip_push: false
use: buildx
- build_flag_templates:
- --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
- --label=org.opencontainers.image.description={{ .ProjectName }}
- --label=org.opencontainers.image.documentation={{ .ProjectName }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.source={{ .GitURL }}
- --label=org.opencontainers.image.title={{ .ProjectName }}
- --label=org.opencontainers.image.url=https://git.cryptic.systems/volker.raschek/{{ .ProjectName }}
- --label=org.opencontainers.image.version={{ trimprefix .Tag "v" }}
- --platform=linux/arm64
- --pull
dockerfile: Dockerfile
goarch: arm64
goos: linux
image_templates:
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm64'
skip_push: false
use: buildx
docker_manifests:
- name_template: 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}'
image_templates:
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-amd64'
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm-v6'
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm-v7'
- 'git.cryptic.systems/volker.raschek/{{ .ProjectName }}:{{ trimprefix .Tag "v" }}-arm64'
gitea_urls:
api: https://git.cryptic.systems/api/v1
download: https://git.cryptic.systems
version: 2

View File

@ -56,8 +56,6 @@ MD013:
tables: false
# Include headings
headings: true
# Include headings
headers: true
# Strict length checking
strict: false
# Stern length checking
@ -70,11 +68,6 @@ MD022:
# Blank lines below heading
lines_below: 1
# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content
MD024:
# Only check sibling headings
allow_different_nesting: true
# MD025/single-title/single-h1 - Multiple top-level headings in the same document
MD025:
# Heading level

View File

@ -1,20 +1,5 @@
FROM docker.io/library/golang:1.23.4-alpine3.19 AS build
FROM scratch AS build
RUN apk add git make
WORKDIR /workspace
ADD ./ /workspace
RUN make install \
DESTDIR=/cache \
PREFIX=/usr \
VERSION=${VERSION}
FROM docker.io/library/alpine:3.21
COPY --from=build /cache /
WORKDIR /workspace
VOLUME [ "/workspace" ]
COPY dcmerge-* /usr/bin/dcmerge
ENTRYPOINT [ "/usr/bin/dcmerge" ]

View File

@ -26,11 +26,7 @@ DCMERGE_IMAGE_UNQUALIFIED=${DCMERGE_IMAGE_NAMESPACE}/${DCMERGE_IMAGE_NAME}:${DCM
# ==============================================================================
dcmerge:
CGO_ENABLED=0 \
GOPRIVATE=$(shell go env GOPRIVATE) \
GOPROXY=$(shell go env GOPROXY) \
GONOPROXY=$(shell go env GONOPROXY) \
GONOSUMDB=$(shell go env GONOSUMDB) \
GOSUMDB=$(shell go env GOSUMDB) \
go build -ldflags "-X 'main.version=${VERSION}'" -o ${@} main.go
# CLEAN
@ -43,15 +39,21 @@ clean:
# ==============================================================================
PHONY+=test/unit
test/unit:
go test -v -p 1 -coverprofile=coverage.txt -covermode=count -timeout 1200s ./pkg/...
CGO_ENABLED=0 \
GOPROXY=$(shell go env GOPROXY) \
go test -v -p 1 -coverprofile=coverage.txt -covermode=count -timeout 1200s ./pkg/...
PHONY+=test/integration
test/integration:
go test -v -p 1 -count=1 -timeout 1200s ./it/...
CGO_ENABLED=0 \
GOPROXY=$(shell go env GOPROXY) \
go test -v -p 1 -count=1 -timeout 1200s ./it/...
PHONY+=test/coverage
test/coverage: test/unit
go tool cover -html=coverage.txt
CGO_ENABLED=0 \
GOPROXY=$(shell go env GOPROXY) \
go tool cover -html=coverage.txt
# GOLANGCI-LINT
# ==============================================================================

View File

@ -1,6 +1,5 @@
# dcmerge
[![Build Status](https://drone.cryptic.systems/api/badges/volker.raschek/dcmerge/status.svg)](https://drone.cryptic.systems/volker.raschek/dcmerge)
[![Docker Pulls](https://img.shields.io/docker/pulls/volkerraschek/dcmerge)](https://hub.docker.com/r/volkerraschek/dcmerge)
`dcmerge` is a small program to merge docker-compose files from multiple sources. It is available via RPM and docker.

View File

@ -8,7 +8,7 @@ import (
"git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose"
"git.cryptic.systems/volker.raschek/dcmerge/pkg/fetcher"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func Execute(version string) error {
@ -18,7 +18,7 @@ func Execute(version string) error {
Long: "To load completions",
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Args: cobra.MatchAll(cobra.ExactArgs(1)),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
@ -100,10 +100,12 @@ func run(cmd *cobra.Command, args []string) error {
defer f.Close()
yamlEncoder := yaml.NewEncoder(f)
yamlEncoder.SetIndent(2)
return yamlEncoder.Encode(dockerComposeConfig)
default:
yamlEncoder := yaml.NewEncoder(os.Stdout)
yamlEncoder.SetIndent(2)
return yamlEncoder.Encode(dockerComposeConfig)
}

7
go.mod
View File

@ -3,15 +3,14 @@ module git.cryptic.systems/volker.raschek/dcmerge
go 1.20
require (
github.com/spf13/cobra v1.8.1
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
)

12
go.sum
View File

@ -1,4 +1,4 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -6,15 +6,13 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,20 +0,0 @@
image: git.cryptic.systems/volker.raschek/dcmerge:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
- "latest"
{{/if}}
manifests:
-
image: git.cryptic.systems/volker.raschek/dcmerge:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-amd64
platform:
architecture: amd64
os: linux
-
image: git.cryptic.systems/volker.raschek/dcmerge:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-arm64-v8
platform:
architecture: arm64
os: linux
variant: v8

View File

@ -2,7 +2,10 @@ package dockerCompose
import (
"fmt"
"regexp"
"strings"
"gopkg.in/yaml.v3"
)
const (
@ -68,24 +71,36 @@ func (c *Config) ExistsVolume(name string) bool {
func (c *Config) Merge(config *Config) {
for name, network := range config.Networks {
if !c.ExistsNetwork(name) {
if c.Networks == nil {
c.Networks = make(map[string]*Network)
}
c.Networks[name] = network
}
}
for name, secret := range config.Secrets {
if !c.ExistsSecret(name) {
if c.Secrets == nil {
c.Secrets = make(map[string]*Secret)
}
c.Secrets[name] = secret
}
}
for name, service := range config.Services {
if !c.ExistsService(name) {
if c.Services == nil {
c.Services = make(map[string]*Service)
}
c.Services[name] = service
}
}
for name, volume := range config.Volumes {
if !c.ExistsVolume(name) {
if c.Volumes == nil {
c.Volumes = make(map[string]*Volume)
}
c.Volumes[name] = volume
}
}
@ -516,19 +531,20 @@ func NewSecret() *Secret {
}
type Service struct {
CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"`
CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"`
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on,omitempty"`
Deploy *ServiceDeploy `json:"deploy,omitempty" yaml:"deploy,omitempty"`
Environments []string `json:"environment,omitempty" yaml:"environment,omitempty"`
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts,omitempty"`
Image string `json:"image,omitempty" yaml:"image,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
Networks map[string]*ServiceNetwork `json:"networks,omitempty" yaml:"networks,omitempty"`
Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
Secrets []string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
ULimits *ServiceULimits `json:"ulimits,omitempty" yaml:"ulimits,omitempty"`
Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"`
CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"`
DependsOnContainer *DependsOnContainer `json:"depends_on,omitempty" yaml:"depends_on,omitempty"`
Deploy *ServiceDeploy `json:"deploy,omitempty" yaml:"deploy,omitempty"`
Environments []string `json:"environment,omitempty" yaml:"environment,omitempty"`
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts,omitempty"`
Image string `json:"image,omitempty" yaml:"image,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
Networks map[string]*ServiceNetwork `json:"networks,omitempty" yaml:"networks,omitempty"`
Ports []Port `json:"ports,omitempty" yaml:"ports,omitempty"`
Secrets []string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
ULimits *ServiceULimits `json:"ulimits,omitempty" yaml:"ulimits,omitempty"`
Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
}
// ExistsEnvironment returns true if the passed name of environment variable is
@ -556,39 +572,22 @@ func (s *Service) ExistsLabel(name string) bool {
return false
}
// ExistsPort returns true if the port definition is already present.
func (s *Service) ExistsPort(src string, dest string, protocol string) bool {
for _, port := range s.Ports {
s, d, p := splitStringInPort(port)
if s == src && d == dest && p == protocol {
// ExistsPort returns true if the port definition is already present. The port defines a mapping between the host system
// port and the container port. It is also possible to specify the individual ip address of the host system or the
// container. Additionally, the protocol can be specified as suffix.
//
// // Example
// s := new(Service)
// b := s.ExistsPort("80:80")
// b = s.ExistsPort("0.0.0.0:80:80/tcp")
// b = s.ExistsPort("0.0.0.0:80:80/tcp")
// b = s.ExistsPort("192.168.178.10:80:172.25.18.20:80/tcp")
func (s *Service) ExistsPort(port string) bool {
for _, p := range s.Ports {
if string(p) == port {
return true
}
}
return false
}
// ExistsDestinationPort returns true if the destination port is already used.
func (s *Service) ExistsDestinationPort(dest string) bool {
for _, port := range s.Ports {
_, d, _ := splitStringInPort(port)
if d == dest {
return true
}
}
return false
}
// ExistsSourcePort returns true if the source port is already used.
func (s *Service) ExistsSourcePort(src string) bool {
for _, port := range s.Ports {
s, _, _ := splitStringInPort(port)
if s == src {
return true
}
}
return false
}
@ -643,9 +642,10 @@ func (s *Service) Equal(equalable Equalable) bool {
case s == nil && service != nil:
return false
default:
return equalSlice(s.CapabilitiesAdd, service.CapabilitiesAdd) &&
return equalSlice(s.Command, service.Command) &&
equalSlice(s.CapabilitiesAdd, service.CapabilitiesAdd) &&
equalSlice(s.CapabilitiesDrop, service.CapabilitiesDrop) &&
equalSlice(s.DependsOn, service.DependsOn) &&
s.DependsOnContainer.Equal(service.DependsOnContainer) &&
s.Deploy.Equal(service.Deploy) &&
equalSlice(s.Environments, service.Environments) &&
equalSlice(s.ExtraHosts, service.ExtraHosts) &&
@ -675,9 +675,10 @@ func (s *Service) MergeExistingWin(service *Service) {
// fallthrough
default:
s.mergeExistingWinCommand(service.Command)
s.mergeExistingWinCapabilitiesAdd(service.CapabilitiesAdd)
s.mergeExistingWinCapabilitiesDrop(service.CapabilitiesDrop)
s.mergeExistingWinDependsOn(service.DependsOn)
s.mergeExistingWinDependsOnContainer(service.DependsOnContainer)
s.mergeExistingWinDeploy(service.Deploy)
s.mergeExistingWinEnvironments(service.Environments)
s.mergeExistingWinExtraHosts(service.ExtraHosts)
@ -709,9 +710,10 @@ func (s *Service) MergeLastWin(service *Service) {
// fallthrough
default:
s.mergeLastWinCommand(service.Command)
s.mergeLastWinCapabilitiesAdd(service.CapabilitiesAdd)
s.mergeLastWinCapabilitiesDrop(service.CapabilitiesDrop)
s.mergeLastWinDependsOn(service.DependsOn)
s.mergeLastWinDependsOnContainer(service.DependsOnContainer)
s.mergeLastWinDeploy(service.Deploy)
s.mergeLastWinEnvironments(service.Environments)
s.mergeLastWinExtraHosts(service.ExtraHosts)
@ -725,6 +727,13 @@ func (s *Service) MergeLastWin(service *Service) {
}
}
func (s *Service) mergeExistingWinCommand(command []string) {
if len(s.Command) > 0 {
return
}
s.Command = command
}
func (s *Service) mergeExistingWinCapabilitiesAdd(capabilitiesAdd []string) {
for _, capabilityAdd := range capabilitiesAdd {
if !existsInSlice(s.CapabilitiesAdd, capabilityAdd) && len(capabilityAdd) > 0 {
@ -741,10 +750,22 @@ func (s *Service) mergeExistingWinCapabilitiesDrop(capabilitiesDrop []string) {
}
}
func (s *Service) mergeExistingWinDependsOn(dependsOn []string) {
for _, depOn := range dependsOn {
if !existsInSlice(s.DependsOn, depOn) && len(depOn) > 0 {
s.DependsOn = append(s.DependsOn, depOn)
func (s *Service) mergeExistingWinDependsOnContainer(dependsOnContainer *DependsOnContainer) {
switch {
case s.DependsOnContainer != nil && dependsOnContainer == nil:
fallthrough
case s.DependsOnContainer == nil && dependsOnContainer == nil:
return
case s.DependsOnContainer == nil && dependsOnContainer != nil:
s.DependsOnContainer = dependsOnContainer
default:
for name, depOn := range dependsOnContainer.DependsOn {
if !ExistsInMap(s.DependsOnContainer.DependsOn, name) && depOn != nil {
if s.DependsOnContainer.DependsOn == nil {
s.DependsOnContainer.DependsOn = make(map[string]*ServiceDependsOn)
}
s.DependsOnContainer.DependsOn[name] = depOn
}
}
}
}
@ -846,7 +867,7 @@ func (s *Service) mergeExistingWinNetworks(networks map[string]*ServiceNetwork)
}
}
func (s *Service) mergeExistingWinPorts(ports []string) {
func (s *Service) mergeExistingWinPorts(ports []Port) {
switch {
case s.Ports == nil && ports != nil:
s.Ports = ports
@ -855,15 +876,33 @@ func (s *Service) mergeExistingWinPorts(ports []string) {
case s.Ports == nil && ports == nil:
return
default:
for _, port := range ports {
if len(port) <= 0 {
continue
LOOP:
for i := range ports {
if len(ports[i]) <= 0 {
continue LOOP
}
src, dest, protocol := splitStringInPort(port)
if !s.ExistsDestinationPort(dest) {
s.SetPort(src, dest, protocol)
newPort := Port(ports[i])
for j := range s.Ports {
existingPort := Port(s.Ports[j])
switch {
case newPort.existsSrcIP() && existingPort.existsSrcIP() &&
newPort.getSrc() == existingPort.getSrc():
continue LOOP
case !newPort.existsSrcIP() && existingPort.existsSrcIP() &&
newPort.getSrcPort() == existingPort.getSrcPort():
continue LOOP
case newPort.existsSrcIP() && !existingPort.existsSrcIP() &&
newPort.getSrcPort() == existingPort.getSrcPort():
continue LOOP
case !newPort.existsSrcIP() && !existingPort.existsSrcIP() &&
newPort.getSrcPort() == existingPort.getSrcPort():
continue LOOP
}
}
s.Ports = append(s.Ports, ports[i])
}
}
}
@ -907,6 +946,12 @@ func (s *Service) mergeExistingWinVolumes(volumes []string) {
}
}
func (s *Service) mergeLastWinCommand(command []string) {
if len(command) > 0 {
s.Command = command
}
}
func (s *Service) mergeLastWinCapabilitiesAdd(capabilitiesAdd []string) {
for _, capabilityAdd := range capabilitiesAdd {
if len(capabilityAdd) <= 0 {
@ -931,14 +976,20 @@ func (s *Service) mergeLastWinCapabilitiesDrop(capabilitiesDrop []string) {
}
}
func (s *Service) mergeLastWinDependsOn(dependsOn []string) {
for _, dep := range dependsOn {
if len(dep) <= 0 {
continue
}
if !existsInSlice(s.DependsOn, dep) {
s.DependsOn = append(s.DependsOn, dep)
func (s *Service) mergeLastWinDependsOnContainer(dependsOnContainer *DependsOnContainer) {
switch {
case s.DependsOnContainer != nil && dependsOnContainer == nil:
fallthrough
case s.DependsOnContainer == nil && dependsOnContainer == nil:
return
case s.DependsOnContainer == nil && dependsOnContainer != nil:
s.DependsOnContainer = dependsOnContainer
default:
for name, depOn := range dependsOnContainer.DependsOn {
if s.DependsOnContainer.DependsOn == nil {
s.DependsOnContainer.DependsOn = make(map[string]*ServiceDependsOn)
}
s.DependsOnContainer.DependsOn[name] = depOn
}
}
}
@ -1042,7 +1093,7 @@ func (s *Service) mergeLastWinNetworks(networks map[string]*ServiceNetwork) {
}
}
func (s *Service) mergeLastWinPorts(ports []string) {
func (s *Service) mergeLastWinPorts(ports []Port) {
switch {
case s.Ports == nil && ports != nil:
s.Ports = ports
@ -1051,13 +1102,11 @@ func (s *Service) mergeLastWinPorts(ports []string) {
case s.Ports == nil && ports == nil:
return
default:
for _, port := range ports {
if len(port) <= 0 {
for i := range ports {
if len(ports[i]) <= 0 {
continue
}
src, dest, protocol := splitStringInPort(port)
s.SetPort(src, dest, protocol)
s.SetPort(string(ports[i]))
}
}
}
@ -1120,8 +1169,7 @@ func (s *Service) RemoveEnvironment(name string) {
s.Environments = environments
}
// RemoveLabel remove all found labels from the internal slice matching by the
// passed name.
// RemoveLabel remove all found labels from the internal slice matching by the passed name.
func (s *Service) RemoveLabel(name string) {
labels := make([]string, 0)
for _, label := range s.Labels {
@ -1133,25 +1181,47 @@ func (s *Service) RemoveLabel(name string) {
s.Labels = labels
}
// RemovePort remove all found ports from the internal slice matching by the
// passed dest port.
func (s *Service) RemovePort(dest string) {
ports := make([]string, 0)
// RemovePortByDst remove all found ports from the internal slice matching by the passed destination. The destination
// can contains only the destination port, but also the destination ip address.
//
// // Example
// s := new(Service)
// s.RemovePortByDst("8080")
// s.RemovePortByDst("172.25.18.20:8080")
func (s *Service) RemovePortByDst(dest string) {
ports := make([]Port, 0)
for _, port := range s.Ports {
srcPort, destPort, protocol := splitStringInPort(port)
switch {
case destPort == dest && len(protocol) <= 0:
s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s", srcPort, portDelimiter, destPort))
case destPort == dest && len(protocol) > 0:
s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s%s%s", srcPort, portDelimiter, destPort, portProtocolDelimiter, protocol))
case port.getDst() == dest:
continue
default:
ports = append(ports, port)
}
}
s.Ports = ports
}
// RemoveVolume remove all found volumes from the internal slice matching by the
// dest path.
// RemovePortBySrc remove all found ports from the internal slice matching by the passed source. The source can contains
// only the source port, but also the source ip address.
//
// // Example
// s := new(Service)
// s.RemovePortBySrc("8080")
// s.RemovePortBySrc("192.168.178.10:8080")
func (s *Service) RemovePortBySrc(src string) {
ports := make([]Port, 0)
for _, port := range s.Ports {
switch {
case port.getSrc() == src:
continue
default:
ports = append(ports, port)
}
}
s.Ports = ports
}
// RemoveVolume remove all found volumes from the internal slice matching by the dest path.
func (s *Service) RemoveVolume(dest string) {
volumes := make([]string, 0)
for _, volume := range s.Volumes {
@ -1179,14 +1249,16 @@ func (s *Service) SetLabel(name string, value string) {
s.Labels = append(s.Labels, fmt.Sprintf("%s%s%s", name, labelDelimiter, value))
}
// SetPort add or overwrite an existing port.
func (s *Service) SetPort(src string, dest string, protocol string) {
s.RemovePort(dest)
if len(protocol) <= 0 {
s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s", src, volumeDelimiter, dest))
} else {
s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s%s%s", src, portDelimiter, dest, portProtocolDelimiter, protocol))
}
// SetPort add or overwrite an existing source port.
//
// // Example
// s := new(Service)
// s.SetPort("0.0.0.0:443:172.25.18.20:8443/tcp") // Add new port
// s.SetPort("0.0.0.0:443:10.254.611.66:443/tcp") // Overwrite port determined by source port
func (s *Service) SetPort(port string) {
newPort := Port(port)
s.RemovePortBySrc(newPort.getSrc())
s.Ports = append(s.Ports, newPort)
}
// SetVolume add or overwrite an existing volume.
@ -1199,6 +1271,88 @@ func (s *Service) SetVolume(src string, dest string, perm string) {
}
}
const (
ServiceDependsOnConditionServiceCompletedSuccessfully string = "service_completed_successfully"
ServiceDependsOnConditionServiceHealthy string = "service_healthy"
ServiceDependsOnConditionServiceStarted string = "service_started"
)
// DependsOnContainer is a wrapper to handle different YAML type formats of DependsOn.
type DependsOnContainer struct {
Slice []string
DependsOn map[string]*ServiceDependsOn
}
// Equal returns true if the passed equalable is equal
func (sdoc *DependsOnContainer) Equal(equalable Equalable) bool {
serviceDependsOnContainer, ok := equalable.(*DependsOnContainer)
if !ok {
return false
}
switch {
case sdoc == nil && serviceDependsOnContainer == nil:
return true
case sdoc != nil && serviceDependsOnContainer == nil:
fallthrough
case sdoc == nil && serviceDependsOnContainer != nil:
return false
default:
return equalSlice(sdoc.Slice, serviceDependsOnContainer.Slice) &&
EqualStringMap(sdoc.DependsOn, serviceDependsOnContainer.DependsOn)
}
}
// MarshalYAML implements the MarshalYAML interface to customize the behavior when being marshaled into a YAML document.
func (sdoc *DependsOnContainer) MarshalYAML() (interface{}, error) {
var foundAnotherCondition bool = false
var dependencyNames []string
for dependencyName, dependencyDefinition := range sdoc.DependsOn {
if dependencyDefinition.Condition == ServiceDependsOnConditionServiceStarted {
dependencyNames = append(dependencyNames, dependencyName)
continue
}
foundAnotherCondition = true
}
switch {
case foundAnotherCondition:
return sdoc.DependsOn, nil
case !foundAnotherCondition && len(dependencyNames) > 0:
return dependencyNames, nil
default:
return nil, nil
}
}
// UnmarshalYAML implements the UnmarshalYAML interface to customize the behavior when being unmarshaled into a YAML
// document.
func (sdoc *DependsOnContainer) UnmarshalYAML(value *yaml.Node) error {
if sdoc.DependsOn == nil {
sdoc.DependsOn = make(map[string]*ServiceDependsOn)
}
if sdoc.Slice == nil {
sdoc.Slice = make([]string, 0)
}
if err := value.Decode(&sdoc.Slice); err == nil {
for _, s := range sdoc.Slice {
sdoc.DependsOn[s] = &ServiceDependsOn{
Condition: ServiceDependsOnConditionServiceStarted,
}
}
return nil
}
if err := value.Decode(sdoc.DependsOn); err != nil {
return err
}
return nil
}
// NewService returns an empty initialized Service.
func NewService() *Service {
return &Service{
@ -1209,13 +1363,38 @@ func NewService() *Service {
ExtraHosts: make([]string, 0),
Labels: make([]string, 0),
Networks: make(map[string]*ServiceNetwork),
Ports: make([]string, 0),
Ports: make([]Port, 0),
Secrets: make([]string, 0),
ULimits: new(ServiceULimits),
Volumes: make([]string, 0),
}
}
type ServiceDependsOn struct {
Condition string `yaml:"condition,omitempty"`
Restart string `yaml:"restart,omitempty"`
}
// Equal returns true if the passed equalable is equal
func (sdo *ServiceDependsOn) Equal(equalable Equalable) bool {
serviceDependsOn, ok := equalable.(*ServiceDependsOn)
if !ok {
return false
}
switch {
case sdo == nil && serviceDependsOn == nil:
return true
case sdo != nil && serviceDependsOn == nil:
fallthrough
case sdo == nil && serviceDependsOn != nil:
return false
default:
return sdo.Condition == serviceDependsOn.Condition &&
sdo.Restart == serviceDependsOn.Restart
}
}
type ServiceDeploy struct {
Resources *ServiceDeployResources `json:"resources" yaml:"resources"`
}
@ -1897,7 +2076,7 @@ func (v *Volume) MergeLastWin(volume *Volume) {
}
}
func (v *Volume) mergeExistingWinExternal(external bool) {
func (v *Volume) mergeExistingWinExternal(_ bool) {
if v.External {
return
}
@ -1950,17 +2129,35 @@ func splitStringInKeyValue(s, sep string) (string, string) {
return key, value
}
func splitStringInPort(s string) (string, string, string) {
parts := strings.Split(s, portDelimiter)
src := parts[0]
rest := parts[1]
// splitStringInPortMapping parses a string and returns the src, dest port including an optional protocol.
//
// // Example
// s, d, p := splitStringInPortMapping("80:80/tcp")
// // Output: "80" "80" "tcp"
// s, d, p := splitStringInPortMapping("0.0.0.0:80:80/tcp")
// // Output: "0.0.0.0:80" "80" "tcp"
//
// Deprecated: Instead of using the splitStringInPortMapping function, use the method of the type Port{}.
func splitStringInPortMapping(s string) (string, string, string) {
p := Port(s)
parts = strings.Split(rest, portProtocolDelimiter)
if len(parts) == 2 {
return src, parts[0], parts[1]
var src string
switch {
case p.existsSrcIP() && p.existsSrcPort():
src = fmt.Sprintf("%s:%s", p.getSrcIP(), p.getSrcPort())
case !p.existsSrcIP():
src = p.getSrcPort()
}
return src, parts[0], ""
var dst string
switch {
case p.existsDstIP() && p.existsDstPort():
dst = fmt.Sprintf("%s:%s", p.getDstIP(), p.getDstPort())
case !p.existsDstIP():
dst = p.getDstPort()
}
return src, dst, p.getProtocol()
}
func splitStringInVolume(s string) (string, string, string) {
@ -1973,3 +2170,131 @@ func splitStringInVolume(s string) (string, string, string) {
}
return src, dest, ""
}
var (
regExpPort = regexp.MustCompile(`^((?<srcIP>([\d]{1,3}\.){3}[\d]{1,3}):)?(?<srcPort>[\d]{1,5}):((?<dstIP>([\d]{1,3}\.){3}[\d]{1,3}):)?(?<dstPort>[\d]{1,5})(\/(?<protocol>[a-z]*))?$`)
)
type Port string
// existsDstPort returns true, if the port string contains a trailing destination port definition.
func (p Port) existsDstPort() bool {
return len(p.getDstPort()) > 0
}
// existsDstIP returns true, if the port string contains a trailing destination ip definition.
func (p Port) existsDstIP() bool {
return len(p.getDstIP()) > 0
}
// existsProtocol returns true, if the port string contains a protocol definition.
func (p Port) existsProtocol() bool {
return len(p.getProtocol()) > 0
}
// existsSrcIP returns true, if the port string contains a leading src ip definition.
func (p Port) existsSrcIP() bool {
return len(p.getSrcIP()) > 0
}
// existsSrcPort returns true, if the port string contains a leading src port definition.
func (p Port) existsSrcPort() bool {
return len(p.getSrcPort()) > 0
}
// getDst returns the concatenation of the destination ip and port. If the destination ip is empty, only the port will
// be returned.
func (p Port) getDst() string {
switch {
case p.existsDstIP():
return fmt.Sprintf("%s%s%s", p.getDstIP(), portDelimiter, p.getDstPort())
default:
return p.getDstPort()
}
}
// getSrcIP returns the destination ip, if the port string contains a destination ip definition.
func (p Port) getDstIP() string {
matches := regExpPort.FindStringSubmatch(string(p))
i := regExpPort.SubexpIndex("dstIP")
switch {
case len(matches) <= 0:
return ""
case i < 0:
return ""
}
return matches[i]
}
// getSrcPort returns the destination port, if the port string contains an destination port definition.
func (p Port) getDstPort() string {
matches := regExpPort.FindStringSubmatch(string(p))
i := regExpPort.SubexpIndex("dstPort")
switch {
case len(matches) <= 0:
return ""
case i < 0:
return ""
}
return matches[i]
}
// getProtocol returns the protocol, if the port string contains a protocol definition.
func (p Port) getProtocol() string {
matches := regExpPort.FindStringSubmatch(string(p))
i := regExpPort.SubexpIndex("protocol")
switch {
case len(matches) <= 0:
return ""
case i < 0:
return ""
}
return matches[i]
}
// getSrc returns the concatenation of the source ip and port. If the source ip is empty, only the port will be
// returned.
func (p Port) getSrc() string {
switch {
case p.existsSrcIP():
return fmt.Sprintf("%s%s%s", p.getSrcIP(), portDelimiter, p.getSrcPort())
default:
return p.getSrcPort()
}
}
// getSrcIP returns the source ip, if the port string contains an src ip definition.
func (p Port) getSrcIP() string {
matches := regExpPort.FindStringSubmatch(string(p))
i := regExpPort.SubexpIndex("srcIP")
switch {
case len(matches) <= 0:
return ""
case i < 0:
return ""
}
return matches[i]
}
// getSrcPort returns the source port, if the port string contains an src port definition.
func (p Port) getSrcPort() string {
matches := regExpPort.FindStringSubmatch(string(p))
i := regExpPort.SubexpIndex("srcPort")
switch {
case len(matches) <= 0:
return ""
case i < 0:
return ""
}
return matches[i]
}

View File

@ -0,0 +1,418 @@
package dockerCompose
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_splitStringInPortMapping(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedSrc string
expectedDst string
expectedProtocol string
}{
{
s: "53:53",
expectedSrc: "53",
expectedDst: "53",
expectedProtocol: "",
},
{
s: "0.0.0.0:53:53",
expectedSrc: "0.0.0.0:53",
expectedDst: "53",
expectedProtocol: "",
},
{
s: "0.0.0.0:53:10.11.12.13:53",
expectedSrc: "0.0.0.0:53",
expectedDst: "10.11.12.13:53",
expectedProtocol: "",
},
{
s: "0.0.0.0:53:10.11.12.13:53/tcp",
expectedSrc: "0.0.0.0:53",
expectedDst: "10.11.12.13:53",
expectedProtocol: "tcp",
},
}
for i, testCase := range testCases {
actualSrc, actualDst, actualProtocol := splitStringInPortMapping(testCase.s)
require.Equal(testCase.expectedSrc, actualSrc, "TestCase %v", i)
require.Equal(testCase.expectedDst, actualDst, "TestCase %v", i)
require.Equal(testCase.expectedProtocol, actualProtocol, "TestCase %v", i)
}
}
func TestPort_DstIP(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedBool bool
expectedString string
}{
{
s: "",
expectedBool: false,
expectedString: "",
},
{
s: "53:53",
expectedBool: false,
expectedString: "",
},
{
s: "53:53/tcp",
expectedBool: false,
expectedString: "",
},
{
s: "53:53/udp",
expectedBool: false,
expectedString: "",
},
{
s: "0.0.0.0:53",
expectedBool: false,
expectedString: "",
},
{
s: "53:0.0.0.0:53",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "53:0.0.0.0:53/tcp",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "53:0.0.0.0:53/udp",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "10.11.12.13:53",
expectedBool: false,
expectedString: "",
},
{
s: "53:10.11.12.13:53",
expectedBool: true,
expectedString: "10.11.12.13",
},
{
s: "53:10.11.12.13:53/tcp",
expectedBool: true,
expectedString: "10.11.12.13",
},
{
s: "53:10.11.12.13:53/udp",
expectedBool: true,
expectedString: "10.11.12.13",
},
}
for i, testCase := range testCases {
p := Port(testCase.s)
require.Equal(testCase.expectedBool, p.existsDstIP(), "TestCase %v", i)
require.Equal(testCase.expectedString, p.getDstIP(), "TestCase %v", i)
}
}
func TestPort_DstPort(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedBool bool
expectedString string
}{
{
s: "",
expectedBool: false,
expectedString: "",
},
{
s: "53:53",
expectedBool: true,
expectedString: "53",
},
{
s: "53:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:53/udp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:0.0.0.0:53",
expectedBool: true,
expectedString: "53",
},
{
s: "53:0.0.0.0:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:0.0.0.0:53/udp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:10.11.12.13:53",
expectedBool: true,
expectedString: "53",
},
{
s: "53:10.11.12.13:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:10.11.12.13:53/udp",
expectedBool: true,
expectedString: "53",
},
}
for i, testCase := range testCases {
p := Port(testCase.s)
require.Equal(testCase.expectedBool, p.existsDstPort(), "TestCase %v", i)
require.Equal(testCase.expectedString, p.getDstPort(), "TestCase %v", i)
}
}
func TestPort_Protocol(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedBool bool
expectedString string
}{
{
s: "0",
expectedBool: false,
expectedString: "",
},
{
s: "53/tcp",
expectedBool: false,
expectedString: "",
},
{
s: "53/udp",
expectedBool: false,
expectedString: "",
},
{
s: "53:53",
expectedBool: false,
expectedString: "",
},
{
s: "53:53/tcp",
expectedBool: true,
expectedString: "tcp",
},
{
s: "53:53/udp",
expectedBool: true,
expectedString: "udp",
},
{
s: "0.0.0.0:53:53/tcp",
expectedBool: true,
expectedString: "tcp",
},
{
s: "0.0.0.0:53:53/udp",
expectedBool: true,
expectedString: "udp",
},
{
s: "0.0.0.0:53:53/tcp",
expectedBool: true,
expectedString: "tcp",
},
{
s: "0.0.0.0:53:11.12.13.14:53/tcp",
expectedBool: true,
expectedString: "tcp",
},
{
s: "0.0.0.0:53:11.12.13.14:53/udp",
expectedBool: true,
expectedString: "udp",
},
}
for i, testCase := range testCases {
p := Port(testCase.s)
require.Equal(testCase.expectedBool, p.existsProtocol(), "TestCase %v", i)
require.Equal(testCase.expectedString, p.getProtocol(), "TestCase %v", i)
}
}
func TestPort_SrcIP(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedBool bool
expectedString string
}{
{
s: "",
expectedBool: false,
expectedString: "",
},
{
s: "53:53",
expectedBool: false,
expectedString: "",
},
{
s: "53:53/tcp",
expectedBool: false,
expectedString: "",
},
{
s: "53:53/udp",
expectedBool: false,
expectedString: "",
},
{
s: "0.0.0.0:53",
expectedBool: false,
expectedString: "",
},
{
s: "0.0.0.0:53:53",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "0.0.0.0:53:53/tcp",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "0.0.0.0:53:53/udp",
expectedBool: true,
expectedString: "0.0.0.0",
},
{
s: "10.11.12.13:53",
expectedBool: false,
expectedString: "",
},
{
s: "10.11.12.13:53:53",
expectedBool: true,
expectedString: "10.11.12.13",
},
{
s: "10.11.12.13:53:53/tcp",
expectedBool: true,
expectedString: "10.11.12.13",
},
{
s: "10.11.12.13:53:53/udp",
expectedBool: true,
expectedString: "10.11.12.13",
},
}
for i, testCase := range testCases {
p := Port(testCase.s)
require.Equal(testCase.expectedBool, p.existsSrcIP(), "TestCase %v", i)
require.Equal(testCase.expectedString, p.getSrcIP(), "TestCase %v", i)
}
}
func TestPort_SrcPort(t *testing.T) {
require := require.New(t)
testCases := []struct {
s string
expectedBool bool
expectedString string
}{
{
s: "",
expectedBool: false,
expectedString: "",
},
{
s: "53:53",
expectedBool: true,
expectedString: "53",
},
{
s: "53:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "53:53/udp",
expectedBool: true,
expectedString: "53",
},
{
s: "0.0.0.0:53:53",
expectedBool: true,
expectedString: "53",
},
{
s: "0.0.0.0:53:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "0.0.0.0:53:53/udp",
expectedBool: true,
expectedString: "53",
},
{
s: "10.11.12.13:53:53",
expectedBool: true,
expectedString: "53",
},
{
s: "10.11.12.13:53:53/tcp",
expectedBool: true,
expectedString: "53",
},
{
s: "10.11.12.13:53:53/udp",
expectedBool: true,
expectedString: "53",
},
}
for i, testCase := range testCases {
p := Port(testCase.s)
require.Equal(testCase.expectedBool, p.existsSrcPort(), "TestCase %v", i)
require.Equal(testCase.expectedString, p.getSrcPort(), "TestCase %v", i)
}
}

View File

@ -1,12 +1,90 @@
package dockerCompose_test
import (
"bytes"
"embed"
"strings"
"testing"
"git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
//go:embed test/assets/merge
var testAssetsMerge embed.FS
func TestConfig_Merge(t *testing.T) {
require := require.New(t)
testAssetPath := "test/assets/merge"
testAssetMergeDirEntries, err := testAssetsMerge.ReadDir(testAssetPath)
require.NoError(err)
// iterate over testcase directories
for i, mergeDirEntry := range testAssetMergeDirEntries {
if !mergeDirEntry.IsDir() {
continue
}
// iterate over files in testcase directories
testCaseAssetPath := testAssetPath + "/" + mergeDirEntry.Name()
testCaseDirEntries, err := testAssetsMerge.ReadDir(testCaseAssetPath)
require.NoError(err)
expectedDockerComposeConfig := &dockerCompose.Config{}
dockerComposeConfigs := []*dockerCompose.Config{}
for _, testCaseDirEntry := range testCaseDirEntries {
if testCaseDirEntry.IsDir() {
continue
}
dockerComposeConfigFile := testAssetPath + "/" + mergeDirEntry.Name() + "/" + testCaseDirEntry.Name()
b, err := testAssetsMerge.ReadFile(dockerComposeConfigFile)
require.NoError(err)
yamlDecoder := yaml.NewDecoder(bytes.NewReader(b))
switch {
case strings.HasPrefix(testCaseDirEntry.Name(), "expectedResult"):
err = yamlDecoder.Decode(expectedDockerComposeConfig)
require.NoError(err)
case strings.HasSuffix(testCaseDirEntry.Name(), ".yml") || strings.HasSuffix(testCaseDirEntry.Name(), ".yaml"):
dockerComposeConfig := &dockerCompose.Config{}
err = yamlDecoder.Decode(dockerComposeConfig)
require.NoError(err)
dockerComposeConfigs = append(dockerComposeConfigs, dockerComposeConfig)
}
}
actualDockerComposeConfig := &dockerCompose.Config{}
for _, dockerComposeConfig := range dockerComposeConfigs {
actualDockerComposeConfig.Merge(dockerComposeConfig)
}
expectedBytes := make([]byte, 0)
expectedBytesBuffer := bytes.NewBuffer(expectedBytes)
yamlEncoder := yaml.NewEncoder(expectedBytesBuffer)
err = yamlEncoder.Encode(expectedDockerComposeConfig)
require.NoError(err)
err = yamlEncoder.Close()
require.NoError(err)
actualBytes := make([]byte, 0)
actualBytesBuffer := bytes.NewBuffer(actualBytes)
yamlEncoder = yaml.NewEncoder(actualBytesBuffer)
err = yamlEncoder.Encode(actualDockerComposeConfig)
require.NoError(err)
err = yamlEncoder.Close()
require.NoError(err)
require.Equal(expectedBytesBuffer.String(), actualBytesBuffer.String(), "TestCase %v", i)
}
}
func TestNetwork_Equal(t *testing.T) {
require := require.New(t)
@ -223,34 +301,45 @@ func TestService_Equal(t *testing.T) {
},
{
equalableA: &dockerCompose.Service{
CapabilitiesAdd: []string{},
CapabilitiesDrop: []string{},
DependsOn: []string{},
Deploy: nil,
Environments: []string{},
ExtraHosts: []string{},
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []string{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
Command: []string{},
CapabilitiesAdd: []string{},
CapabilitiesDrop: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
Deploy: nil,
Environments: []string{},
ExtraHosts: []string{},
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []dockerCompose.Port{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
},
equalableB: &dockerCompose.Service{
CapabilitiesAdd: []string{},
CapabilitiesDrop: []string{},
DependsOn: []string{},
Deploy: nil,
Environments: []string{},
ExtraHosts: []string{},
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []string{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
Command: []string{},
CapabilitiesAdd: []string{},
CapabilitiesDrop: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
Deploy: nil,
Environments: []string{},
ExtraHosts: []string{},
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []dockerCompose.Port{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
},
expectedResult: true,
},
{
equalableA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
equalableB: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
expectedResult: true,
},
@ -292,19 +381,74 @@ func TestService_Equal(t *testing.T) {
},
{
equalableA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{"app"}},
},
equalableB: &dockerCompose.Service{
DependsOn: []string{},
DependsOnContainer: nil,
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{"app"}},
},
equalableB: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{"app"}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{}},
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{"app"}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{Slice: []string{"app"}},
},
expectedResult: true,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{}},
},
expectedResult: false,
},
{
equalableA: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
equalableB: &dockerCompose.Service{
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
expectedResult: true,
},
@ -418,19 +562,19 @@ func TestService_Equal(t *testing.T) {
},
{
equalableA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
equalableB: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
expectedResult: true,
},
{
equalableA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
equalableB: &dockerCompose.Service{
Ports: []string{"80:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedResult: false,
},
@ -503,6 +647,52 @@ func TestService_MergeExistingWin(t *testing.T) {
expectedService: &dockerCompose.Service{},
},
// Command
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{""},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
// CapabilitiesAdd
{
serviceDeploymentA: &dockerCompose.Service{
@ -598,46 +788,46 @@ func TestService_MergeExistingWin(t *testing.T) {
// DependsOn
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{""},
DependsOnContainer: nil,
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
@ -1108,13 +1298,13 @@ func TestService_MergeExistingWin(t *testing.T) {
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: nil,
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
@ -1122,65 +1312,102 @@ func TestService_MergeExistingWin(t *testing.T) {
Ports: nil,
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:8080"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:6300:6300/tcp"},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
"0.0.0.0:6300:6300/tcp",
},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"15005:15005",
},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
},
@ -1450,6 +1677,52 @@ func TestService_MergeLastWin(t *testing.T) {
expectedService: &dockerCompose.Service{},
},
// Command
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
expectedService: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Command: []string{"/usr/bin/cp", "--recursive", "/tmp/foo.txt", "/tmp/bar.txt"},
},
serviceDeploymentB: &dockerCompose.Service{
Command: []string{""},
},
expectedService: &dockerCompose.Service{
Command: []string{""},
},
},
// CapabilitiesAdd
{
serviceDeploymentA: &dockerCompose.Service{
@ -1545,46 +1818,46 @@ func TestService_MergeLastWin(t *testing.T) {
// DependsOn
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{},
DependsOnContainer: &dockerCompose.DependsOnContainer{},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
serviceDeploymentB: &dockerCompose.Service{
DependsOn: []string{""},
DependsOnContainer: nil,
},
expectedService: &dockerCompose.Service{
DependsOn: []string{"app"},
DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}},
},
},
@ -2016,13 +2289,13 @@ func TestService_MergeLastWin(t *testing.T) {
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: nil,
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
@ -2030,76 +2303,113 @@ func TestService_MergeLastWin(t *testing.T) {
Ports: nil,
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:10080"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:10080"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{""},
Ports: []dockerCompose.Port{""},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:6300:6300/tcp"},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
"0.0.0.0:6300:6300/tcp",
},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:15005",
},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:15005",
"0.0.0.0:18080:8080/tcp",
},
},
},
@ -2372,6 +2682,203 @@ func TestService_MergeLastWin(t *testing.T) {
}
}
func TestService_RemovePortByDst(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
removePortsByDst []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsByDst: []string{
"53",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsByDst: []string{
"172.25.18.20:443",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:443:443/tcp",
},
},
removePortsByDst: []string{
"443",
},
expectedPorts: []dockerCompose.Port{},
},
}
for i, testCase := range testCases {
for _, removePortByDst := range testCase.removePortsByDst {
testCase.s.RemovePortByDst(removePortByDst)
}
require.Equal(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestService_RemovePortBySrc(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
removePortsBySrc []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsBySrc: []string{
"10.11.12.13:53",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsBySrc: []string{
"0.0.0.0:443",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:443:443/tcp",
},
},
removePortsBySrc: []string{
"0.0.0.0:443",
},
expectedPorts: []dockerCompose.Port{},
},
}
for i, testCase := range testCases {
for _, removePortBySrc := range testCase.removePortsBySrc {
testCase.s.RemovePortBySrc(removePortBySrc)
}
require.Equal(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestService_SetPort(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
setPorts []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{},
expectedPorts: []dockerCompose.Port{"8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{"8080:8080"},
expectedPorts: []dockerCompose.Port{"8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{"8080:80"},
expectedPorts: []dockerCompose.Port{"8080:80"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{"0.0.0.0:8080:8080"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{"0.0.0.0:8080:80"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:80"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080", "0.0.0.0:8443:8443"},
},
setPorts: []string{"0.0.0.0:8080:80"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:80", "0.0.0.0:8443:8443"},
},
}
for i, testCase := range testCases {
for _, setPort := range testCase.setPorts {
testCase.s.SetPort(setPort)
}
require.ElementsMatch(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestSecretDeploy_Equal(t *testing.T) {
require := require.New(t)

View File

@ -0,0 +1,6 @@
services:
frontend:
depends_on:
backend:
condition: service_started
image: library/frontend:latest

View File

@ -0,0 +1,3 @@
services:
backend:
image: library/backend:latest

View File

@ -0,0 +1,7 @@
services:
backend:
image: library/backend:latest
frontend:
depends_on:
- backend
image: library/frontend:latest

View File

@ -0,0 +1,5 @@
services:
frontend:
depends_on:
- backend
image: library/frontend:latest

View File

@ -0,0 +1,3 @@
services:
backend:
image: library/backend:latest

View File

@ -0,0 +1,7 @@
services:
backend:
image: library/backend:latest
frontend:
depends_on:
- backend
image: library/frontend:latest

View File

@ -0,0 +1,6 @@
services:
frontend:
depends_on:
backend:
condition: service_completed_successfully
image: library/frontend:latest

View File

@ -0,0 +1,3 @@
services:
backend:
image: library/backend:latest

View File

@ -0,0 +1,8 @@
services:
backend:
image: library/backend:latest
frontend:
depends_on:
backend:
condition: service_completed_successfully
image: library/frontend:latest

View File

@ -0,0 +1,4 @@
services:
frontend:
command: [ "/usr/bin/cp", "--recursive", "--force", "/tmp/foo.txt", "/tmp/bar.txt" ]
image: library/frontend:latest

View File

@ -0,0 +1,4 @@
services:
frontend:
command: [ "/usr/bin/cp", "--recursive", "--force", "/tmp/bar.txt", "/tmp/foo.txt"]
image: library/frontend:latest

View File

@ -0,0 +1,8 @@
services:
backend:
image: library/backend:latest
frontend:
depends_on:
backend:
condition: service_completed_successfully
image: library/frontend:latest

View File

@ -8,7 +8,7 @@ import (
"os"
"git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func Fetch(urls ...string) ([]*dockerCompose.Config, error) {

View File

@ -1,33 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"assignees": [ "volker.raschek" ],
"labels": [ "renovate" ],
"packageRules": [
{
"addLabels": [ "renovate/droneci", "renovate/automerge" ],
"automerge": true,
"matchManagers": "droneci",
"matchUpdateTypes": [ "minor", "patch"]
},
{
"description": "Automatically update patch version of used container images in docker files",
"addLabels": [ "renovate/container-image", "renovate/automerge" ],
"automerge": true,
"matchBaseBranches": [ "master" ],
"matchManagers": [ "dockerfile" ],
"matchUpdateTypes": [ "minor", "patch" ]
},
{
"description": "Automatically update minor and patch versions of go modules",
"addLabels": [ "renovate/gomod", "renovate/automerge" ],
"automerge": true,
"matchManagers": [ "gomod" ],
"matchUpdateTypes": [ "minor", "patch" ]
}
],
"postUpdateOptions": [
"gomodTidy"
],
"rebaseLabel": "renovate/rebase",
"rebaseWhen": "behind-base-branch"
"extends": [
"local>volker.raschek/renovate-config:default#master",
"local>volker.raschek/renovate-config:container#master",
"local>volker.raschek/renovate-config:actions#master",
"local>volker.raschek/renovate-config:golang#master",
"local>volker.raschek/renovate-config:regexp#master"
]
}