Compare commits

...

311 Commits

Author SHA1 Message Date
rockstardev
0f6717adad
Docker gen multiarch images
Some checks failed
Release assets / assets (push) Has been cancelled
Build and publish Docker images / Build and publish image (alpine) (push) Has been cancelled
2023-12-13 13:48:34 +09:00
Nicolas Duchon
ccceeb6e81
Merge pull request #567 from nginx-proxy/go-1.20.12
build: bump golang to 1.20.12
2023-12-08 01:04:44 +01:00
Nicolas Duchon
823af3c410
Merge pull request #563 from nginx-proxy/dependabot/go_modules/github.com/docker/docker-24.0.7incompatible
build: bump github.com/docker/docker from 24.0.5+incompatible to 24.0.7+incompatible
2023-12-08 01:04:25 +01:00
Nicolas Duchon
9efaf170a4
build: bump golang to 1.20.12 2023-12-08 01:02:22 +01:00
dependabot[bot]
eeaab89099
build: bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.5+incompatible to 24.0.7+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.5...v24.0.7)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-07 23:55:47 +00:00
Nicolas Duchon
efdccbce42
Merge pull request #566 from nginx-proxy/dependabot/docker/alpine-3.18.5
build: bump alpine from 3.18.3 to 3.18.5
2023-12-08 00:53:56 +01:00
Nicolas Duchon
06068583d8
Merge pull request #562 from nginx-proxy/dependabot/docker/debian-12.2-slim
build: bump debian from 12.1-slim to 12.2-slim
2023-12-08 00:53:39 +01:00
Nicolas Duchon
4162ddf8b6
Merge pull request #559 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.10.0
build: bump github.com/fsouza/go-dockerclient from 1.9.8 to 1.10.0
2023-12-08 00:53:11 +01:00
Nicolas Duchon
1003cd33bf
Merge pull request #556 from nginx-proxy/dependabot/github_actions/docker/setup-qemu-action-3
ci: bump docker/setup-qemu-action from 2 to 3
2023-12-08 00:51:45 +01:00
Nicolas Duchon
b1a035aebc
Merge pull request #555 from nginx-proxy/dependabot/github_actions/docker/build-push-action-5
ci: bump docker/build-push-action from 4 to 5
2023-12-08 00:51:07 +01:00
Nicolas Duchon
e19f5ef686
Merge pull request #554 from nginx-proxy/dependabot/github_actions/docker/login-action-3
ci: bump docker/login-action from 2 to 3
2023-12-08 00:50:52 +01:00
Nicolas Duchon
dddbf367f5
Merge pull request #553 from nginx-proxy/dependabot/github_actions/docker/setup-buildx-action-3
ci: bump docker/setup-buildx-action from 2 to 3
2023-12-08 00:50:18 +01:00
Nicolas Duchon
5cf10e49af
Merge pull request #551 from nginx-proxy/dependabot/github_actions/actions/checkout-4
ci: bump actions/checkout from 3 to 4
2023-12-08 00:49:56 +01:00
dependabot[bot]
487871d852
build: bump alpine from 3.18.3 to 3.18.5
Bumps alpine from 3.18.3 to 3.18.5.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:40:55 +00:00
dependabot[bot]
b405986c4d
build: bump debian from 12.1-slim to 12.2-slim
Bumps debian from 12.1-slim to 12.2-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-11 23:01:39 +00:00
dependabot[bot]
6590efa622
build: bump github.com/fsouza/go-dockerclient from 1.9.8 to 1.10.0
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.8 to 1.10.0.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.8...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-06 22:15:13 +00:00
dependabot[bot]
80dba84c0d
ci: bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 22:51:05 +00:00
dependabot[bot]
6ae05d5c34
ci: bump docker/build-push-action from 4 to 5
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 22:51:01 +00:00
dependabot[bot]
63e89bf2e9
ci: bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 22:50:57 +00:00
dependabot[bot]
02a0c7c7c1
ci: bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 22:50:54 +00:00
dependabot[bot]
bd03a2e57b
ci: bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 23:01:14 +00:00
Nicolas Duchon
9fbe293574
Merge pull request #547 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.8
build: bump github.com/fsouza/go-dockerclient from 1.9.7 to 1.9.8
2023-08-11 08:06:22 +02:00
dependabot[bot]
3d51fca143
build: bump github.com/fsouza/go-dockerclient from 1.9.7 to 1.9.8
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.7 to 1.9.8.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.7...v1.9.8)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 22:34:01 +00:00
Nicolas Duchon
9a2936c5f0
Merge pull request #545 from nginx-proxy/dependabot/docker/alpine-3.18.3
build: bump alpine from 3.18.2 to 3.18.3
2023-08-08 17:47:36 +02:00
dependabot[bot]
e173f35f87
build: bump alpine from 3.18.2 to 3.18.3
Bumps alpine from 3.18.2 to 3.18.3.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 22:18:06 +00:00
Nicolas Duchon
8c58889aea
Merge pull request #544 from nginx-proxy/dependabot/docker/golang-1.20.7-alpine
build: bump golang from 1.20.6-alpine to 1.20.7-alpine
2023-08-03 07:58:16 +02:00
dependabot[bot]
d20cd059ad
build: bump golang from 1.20.6-alpine to 1.20.7-alpine
Bumps golang from 1.20.6-alpine to 1.20.7-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-02 22:10:48 +00:00
Nicolas Duchon
a851d2d4cc
Merge pull request #542 from nginx-proxy/dependabot/docker/debian-12.1-slim
build: bump debian from 12.0-slim to 12.1-slim
2023-07-29 14:42:10 +02:00
dependabot[bot]
f77e344821
build: bump debian from 12.0-slim to 12.1-slim
Bumps debian from 12.0-slim to 12.1-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-28 22:52:27 +00:00
Nicolas Duchon
c75b27ade6
Merge pull request #539 from nginx-proxy/dependabot/docker/golang-1.20.6-alpine
build: bump golang from 1.20.5-alpine to 1.20.6-alpine
2023-07-12 14:37:14 +02:00
Nicolas Duchon
c2895e14bf
Merge pull request #540 from nginx-proxy/cgo
build: disable cgo on Debian image
2023-07-12 13:02:24 +02:00
Nicolas Duchon
52a8f5a762
build: disable cgo on Debian image 2023-07-12 12:59:57 +02:00
Nicolas Duchon
78ace42759
style: gofmt linting 2023-07-12 08:24:22 +02:00
Nicolas Duchon
5a4cad392c
build: fix gofmt file listing in makefile 2023-07-12 08:24:03 +02:00
dependabot[bot]
4ea4e83db2
build: bump golang from 1.20.5-alpine to 1.20.6-alpine
Bumps golang from 1.20.5-alpine to 1.20.6-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 22:15:40 +00:00
Nicolas Duchon
686c492bdf
Merge pull request #538 from testwill/ioutil
chore: remove refs to deprecated io/ioutil
2023-07-11 06:59:06 +02:00
guoguangwu
bf159fe3cd fix: Imports must be ordered lexicographically
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2023-07-11 10:10:19 +08:00
guoguangwu
77efaca970 chore: remove refs to deprecated io/ioutil
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2023-07-10 10:46:33 +08:00
Richard Hansen
4f051215af
Merge pull request #536 from nginx-proxy/dependabot/docker/alpine-3.18.2
build: bump alpine from 3.18.0 to 3.18.2
2023-06-23 17:01:23 -04:00
dependabot[bot]
039aaa23c6 build: bump alpine from 3.18.0 to 3.18.2
Bumps alpine from 3.18.0 to 3.18.2.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-23 17:01:00 -04:00
Richard Hansen
95cd59e4b2
Merge pull request #535 from nginx-proxy/dependabot/docker/debian-12.0-slim
build: bump debian from 11.7-slim to 12.0-slim
2023-06-23 17:00:15 -04:00
dependabot[bot]
c68d66a056
build: bump debian from 11.7-slim to 12.0-slim
Bumps debian from 11.7-slim to 12.0-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 22:57:44 +00:00
Nicolas Duchon
75fa364698
Merge pull request #534 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.3.2
build: bump github.com/BurntSushi/toml from 1.3.1 to 1.3.2
2023-06-09 15:05:50 +02:00
dependabot[bot]
a50b5ae743
build: bump github.com/BurntSushi/toml from 1.3.1 to 1.3.2
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 22:57:53 +00:00
Nicolas Duchon
cf170413ef
Merge pull request #533 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.3.1
build: bump github.com/BurntSushi/toml from 1.3.0 to 1.3.1
2023-06-07 06:57:14 +02:00
Nicolas Duchon
28c101b7dd
Merge pull request #532 from nginx-proxy/dependabot/docker/golang-1.20.5-alpine
build: bump golang from 1.20.4-alpine to 1.20.5-alpine
2023-06-07 06:56:19 +02:00
dependabot[bot]
c0f93bd8a6
build: bump github.com/BurntSushi/toml from 1.3.0 to 1.3.1
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 22:57:48 +00:00
dependabot[bot]
ca83ccfdc7
build: bump golang from 1.20.4-alpine to 1.20.5-alpine
Bumps golang from 1.20.4-alpine to 1.20.5-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 22:57:47 +00:00
Nicolas Duchon
2d91e42e86
Merge pull request #530 from oliv3r/doc/better_env
Emphazise the need for properly setup environment variables
2023-06-05 10:57:56 +02:00
Olliver Schinagl
9748e03f32
Emphazise the need for properly setup environment variables
Some templates rely on environment variables to work properly. This is not really touched on in the documentation. Lets make this a little bit more obvious.
2023-06-05 09:23:21 +02:00
Nicolas Duchon
4deba6c403
Merge pull request #528 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.3.0
build: bump github.com/BurntSushi/toml from 1.2.1 to 1.3.0
2023-05-31 19:35:34 +02:00
dependabot[bot]
d319a75291
build: bump github.com/BurntSushi/toml from 1.2.1 to 1.3.0
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-31 16:36:01 +00:00
Nicolas Duchon
0d40ad8aa2
Merge pull request #529 from nginx-proxy/dependabot/go_modules/github.com/stretchr/testify-1.8.4
build: bump github.com/stretchr/testify from 1.8.3 to 1.8.4
2023-05-31 18:35:18 +02:00
dependabot[bot]
4a6cd8e266
build: bump github.com/stretchr/testify from 1.8.3 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 22:59:08 +00:00
Nicolas Duchon
edc74cc3f6
Merge pull request #527 from nginx-proxy/dependabot/go_modules/github.com/stretchr/testify-1.8.3
build: bump github.com/stretchr/testify from 1.8.2 to 1.8.3
2023-05-22 09:59:24 +02:00
dependabot[bot]
e676789f13
build: bump github.com/stretchr/testify from 1.8.2 to 1.8.3
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 22:57:38 +00:00
Richard Hansen
63a86a13ab
Merge pull request #524 from nginx-proxy/worflow-update
ci: image build and publish workflow update
2023-05-11 04:17:16 -04:00
Nicolas Duchon
b21526ecb7 ci: disable automatic latest tag handling 2023-05-11 04:15:50 -04:00
Nicolas Duchon
90151af39a ci: build Docker image for more plaforms 2023-05-11 04:15:50 -04:00
Nicolas Duchon
9e5eaadd35 ci: enable gha caching on docker/build-push-action 2023-05-11 04:15:50 -04:00
Nicolas Duchon
89cc03621a ci: use step outputs rather than env 2023-05-11 04:15:50 -04:00
Nicolas Duchon
888de149c0 ci: better use build matrix 2023-05-11 04:15:50 -04:00
dependabot[bot]
2434456359 build: bump alpine from 3.17.3 to 3.18.0
Bumps alpine from 3.17.3 to 3.18.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-11 04:05:46 -04:00
Nicolas Duchon
aff96536bd
Merge pull request #523 from nginx-proxy/dependabot/docker/debian-11.7-slim
build: bump debian from 11.6-slim to 11.7-slim
2023-05-04 06:43:53 +02:00
dependabot[bot]
869a091f16
build: bump debian from 11.6-slim to 11.7-slim
Bumps debian from 11.6-slim to 11.7-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 22:57:48 +00:00
Nicolas Duchon
0ee8064c8b
Merge pull request #522 from nginx-proxy/dependabot/docker/golang-1.20.4-alpine
build: bump golang from 1.20.3-alpine to 1.20.4-alpine
2023-05-03 07:37:03 +02:00
dependabot[bot]
7698d70a09
build: bump golang from 1.20.3-alpine to 1.20.4-alpine
Bumps golang from 1.20.3-alpine to 1.20.4-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-02 22:57:44 +00:00
Nicolas Duchon
8123f45dff
Merge pull request #521 from nginx-proxy/workflow-rename
ci: rename build / publish workflow
2023-04-30 16:34:46 +02:00
Nicolas Duchon
c417876ba7
ci: rename build / publish workflow 2023-04-30 15:57:07 +02:00
Nicolas Duchon
9dc7546603
Merge pull request #520 from nginx-proxy/dependabot/github_actions/docker/build-push-action-4
ci: bump docker/build-push-action from 3 to 4
2023-04-30 15:29:28 +02:00
dependabot[bot]
a03f43b4ea
ci: bump docker/build-push-action from 3 to 4
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-30 13:28:24 +00:00
Nicolas Duchon
e543d9593c
ci: only trigger test workflow on push for main branch 2023-04-30 15:25:51 +02:00
Nicolas Duchon
43ee41953e
Merge pull request #519 from nginx-proxy/dependabot-gh-actions
ci: add GitHub Actions to Dependabot config
2023-04-30 15:21:22 +02:00
Nicolas Duchon
3e3833d127
Merge pull request #518 from nginx-proxy/go-1.20
build: go 1.19 -> 1.20
2023-04-30 15:20:25 +02:00
Nicolas Duchon
0e834a9ae3
ci: add GitHub Actions to Dependabot config 2023-04-30 15:20:02 +02:00
Nicolas Duchon
fa9ff8c175
build: go 1.19 -> 1.20 2023-04-30 15:10:45 +02:00
Nicolas Duchon
902b12d786
Merge pull request #516 from nginx-proxy/dependabot/go_modules/github.com/docker/docker-23.0.3incompatible
build: bump github.com/docker/docker from 23.0.2+incompatible to 23.0.3+incompatible
2023-04-18 07:21:49 +02:00
dependabot[bot]
72a838e497
build: bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.2+incompatible to 23.0.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.2...v23.0.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-15 18:16:28 +00:00
Nicolas Duchon
76bab51f5c
Merge pull request #514 from nginx-proxy/dependabot/docker/golang-1.20.3-alpine
build: bump golang from 1.20.2-alpine to 1.20.3-alpine
2023-04-05 05:48:23 +02:00
dependabot[bot]
44c771487f
build: bump golang from 1.20.2-alpine to 1.20.3-alpine
Bumps golang from 1.20.2-alpine to 1.20.3-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 22:12:11 +00:00
Nicolas Duchon
c89f05c59a
Merge pull request #512 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.7
build: bump github.com/fsouza/go-dockerclient from 1.9.6 to 1.9.7
2023-03-31 15:31:49 +02:00
dependabot[bot]
01240ea352
build: bump github.com/fsouza/go-dockerclient from 1.9.6 to 1.9.7
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.6 to 1.9.7.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.6...v1.9.7)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-30 22:57:59 +00:00
Nicolas Duchon
cfffd8b069
Merge pull request #510 from nginx-proxy/dependabot/docker/alpine-3.17.3
build: bump alpine from 3.17.2 to 3.17.3
2023-03-30 01:07:30 +02:00
dependabot[bot]
438faf22f1
build: bump alpine from 3.17.2 to 3.17.3
Bumps alpine from 3.17.2 to 3.17.3.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 22:58:17 +00:00
Nicolas Duchon
f6218a130f
Merge pull request #508 from nginx-proxy/dependabot/go_modules/github.com/opencontainers/runc-1.1.5
build: bump github.com/opencontainers/runc from 1.1.2 to 1.1.5
2023-03-29 20:54:05 +02:00
dependabot[bot]
cce8193edb
build: bump github.com/opencontainers/runc from 1.1.2 to 1.1.5
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.2 to 1.1.5.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/v1.1.5/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.2...v1.1.5)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 14:48:38 +00:00
Richard Hansen
63f8ea7e6a
fix: Don't leak signal notification resources (#504)
Co-authored-by: Nicolas Duchon <nicolas.duchon@gmail.com>
2023-03-21 06:57:11 +01:00
Nicolas Duchon
62cd9899a1
Merge pull request #502 from rhansen/race
tests: fix / prevent race conditions in tests
2023-03-14 13:52:52 +01:00
Nicolas Duchon
a1cd2fa47d
Merge pull request #503 from rhansen/sig
fix: Don't attempt to catch SIGQUIT or SIGKILL
2023-03-14 12:56:05 +01:00
Richard Hansen
ab9ed54a35 tests: Pass -race to go test to detect data races 2023-03-14 03:39:19 -04:00
Richard Hansen
bbdbf8a5f3 fix: Don't attempt to catch SIGQUIT or SIGKILL
SIGKILL can't be caught so there's no point in trying.  SIGQUIT is
intended as a harsh exit for debugging purposes; it should generate a
core dump that accurately depicts the program's state at the time the
SIGQUIT was received.
2023-03-14 03:39:19 -04:00
Richard Hansen
c8cfb0bbad tests: Slow down generator tests to work around race condition
5ms resolution is too fine for reliable tests.  Increase delays by a
factor of 10 to improve the chances that the desired path will win the
race.

Ideally the test would be rewritten to remove the races entirely, but
I'll leave that as a future exercise.
2023-03-14 03:39:19 -04:00
Richard Hansen
40e53d22bd tests: Use atomic integer for counter to avoid data race 2023-03-14 03:39:19 -04:00
Richard Hansen
0df0230939 fix: Update list of watchers serially to avoid a data race 2023-03-14 03:39:19 -04:00
Richard Hansen
aafbf65f1a
Merge pull request #506 from nginx-proxy/dependabot/docker/golang-1.20.2-alpine
build: bump golang from 1.20.1-alpine to 1.20.2-alpine
2023-03-11 12:09:34 -05:00
dependabot[bot]
6e9812aa48
build: bump golang from 1.20.1-alpine to 1.20.2-alpine
Bumps golang from 1.20.1-alpine to 1.20.2-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-11 17:08:12 +00:00
Richard Hansen
132b4db1ea
Merge pull request #507 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.6
build: bump github.com/fsouza/go-dockerclient from 1.9.5 to 1.9.6
2023-03-11 12:06:36 -05:00
dependabot[bot]
64fa76a04b
build: bump github.com/fsouza/go-dockerclient from 1.9.5 to 1.9.6
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.5 to 1.9.6.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.5...v1.9.6)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-09 22:58:15 +00:00
Nicolas Duchon
8f6e9ad02d
Merge pull request #500 from nginx-proxy/dependabot/go_modules/github.com/stretchr/testify-1.8.2
build: bump github.com/stretchr/testify from 1.8.1 to 1.8.2
2023-02-28 08:08:30 +01:00
dependabot[bot]
8411202672
build: bump github.com/stretchr/testify from 1.8.1 to 1.8.2
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-28 07:06:21 +00:00
Nicolas Duchon
d84aa9aa08
Merge pull request #499 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.5
build: bump github.com/fsouza/go-dockerclient from 1.9.4 to 1.9.5
2023-02-28 08:02:28 +01:00
Nicolas Duchon
434d7921fa
Merge pull request #494 from rhansen/deepGet
fix: Delete "invalid value" debug message accidentally left in
2023-02-28 07:58:46 +01:00
dependabot[bot]
c99465987e
build: bump github.com/fsouza/go-dockerclient from 1.9.4 to 1.9.5
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.4...v1.9.5)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-24 22:58:22 +00:00
Richard Hansen
9767cfa2cc fix: Delete "invalid value" debug message accidentally left in
Commit e0d71f8165 added an "invalid
value" debug message that I did not intend to commit.  Remove it.
2023-02-17 01:59:40 -05:00
Richard Hansen
c9c372fd4a tests: Refine tests for sortObjectsByKeysAsc and *Desc
* Ensure that `sortObjectsByKeysAsc` and `sortObjectsByKeysDesc` do
    not mutate the input slice.
  * Be more strict when checking the sorted results.
  * Improve readability and reduce duplication by converting to
    idiomatic table-driven tests.
2023-02-17 01:59:40 -05:00
Richard Hansen
ebd6bf10ce
Merge pull request #496 from nginx-proxy/dependabot/go_modules/github.com/containerd/containerd-1.6.18
build: bump github.com/containerd/containerd from 1.6.14 to 1.6.18
2023-02-16 14:09:39 -05:00
dependabot[bot]
18202be7eb
build: bump github.com/containerd/containerd from 1.6.14 to 1.6.18
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.6.14 to 1.6.18.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.6.14...v1.6.18)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-16 14:24:46 +00:00
Richard Hansen
953a862570
Merge pull request #495 from nginx-proxy/dependabot/docker/golang-1.20.1-alpine
build: bump golang from 1.20.0-alpine to 1.20.1-alpine
2023-02-16 04:27:30 -05:00
dependabot[bot]
5c1c7b8518
build: bump golang from 1.20.0-alpine to 1.20.1-alpine
Bumps golang from 1.20.0-alpine to 1.20.1-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-15 22:58:27 +00:00
Richard Hansen
ffa6d17c0f
Merge pull request #493 from nginx-proxy/dependabot/docker/alpine-3.17.2
build: bump alpine from 3.17.1 to 3.17.2
2023-02-11 12:04:38 -05:00
dependabot[bot]
ffc996abe9
build: bump alpine from 3.17.1 to 3.17.2
Bumps alpine from 3.17.1 to 3.17.2.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-10 22:58:06 +00:00
Nicolas Duchon
38c3397dd0
Merge pull request #491 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.4
build: bump github.com/fsouza/go-dockerclient from 1.9.3 to 1.9.4
2023-02-09 07:59:20 +01:00
dependabot[bot]
b8a831073b
build: bump github.com/fsouza/go-dockerclient from 1.9.3 to 1.9.4
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.3 to 1.9.4.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.3...v1.9.4)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 22:02:55 +00:00
Richard Hansen
9794ff8120
Merge pull request #490 from nginx-proxy/dependabot/docker/golang-1.20.0-alpine
build: bump golang from 1.19.5-alpine to 1.20.0-alpine
2023-02-03 02:52:54 -05:00
dependabot[bot]
850831f6c6
build: bump golang from 1.19.5-alpine to 1.20.0-alpine
Bumps golang from 1.19.5-alpine to 1.20.0-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 22:02:25 +00:00
Richard Hansen
616392cf73
Merge pull request #488 from rhansen/deepGet
Fix functions that take a field path
2023-01-24 10:41:56 -05:00
Richard Hansen
298e6507b8 chore: Use Value.Elem instead of reflect.Indirect for readability 2023-01-23 20:44:21 -05:00
Richard Hansen
218a63df94 fix: Don't dereference pointers in where, groupBy, sort, etc. 2023-01-23 20:44:21 -05:00
Richard Hansen
ba97c3705d feat: Support indexing slices and arrays in deepGet 2023-01-23 20:44:21 -05:00
Richard Hansen
8648033193 feat: Automatically dereference pointer types in deepGet
This matches the behavior of Go, and makes it possible to use `groupBy` and
friends on a slice of `*RuntimeContainers`.
2023-01-23 20:44:21 -05:00
Richard Hansen
a0318cd598 fix: Only strip the first "." from the path passed to deepGet
This makes it possible to use the empty string as a map key.
2023-01-23 20:44:21 -05:00
Richard Hansen
e0d71f8165 chore: Refactor deepGet
This will make it easier to fix bugs and add new features.
2023-01-23 20:44:21 -05:00
Richard Hansen
c7b991e01d feat: New eval function to evaluate a template
This makes it possible to:
  * modularize a template by using an embedded template (defined with
    Go's built-in `define` action) as a function whose return value is
    the expansion of the template
  * post-process the expansion of a template (e.g., pipe it to
    Sprig's `indent` function)

For additional context, see
<https://github.com/golang/go/issues/54748>.  Sprig is unlikely to add
an equivalent to this function any time soon: the function must be a
closure around the template, meaning either Sprig or Go would have to
change its API.
2023-01-23 20:43:52 -05:00
Nicolas Duchon
3b871c22c1
ci: set Dependabot commit prefixes 2023-01-23 23:26:58 +01:00
Richard Hansen
048735c2ed
Merge pull request #487 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.3
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.2 to 1.9.3
2023-01-21 03:35:45 -05:00
dependabot[bot]
e6dfa951a2
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.2 to 1.9.3
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.2...v1.9.3)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-20 22:03:16 +00:00
Nicolas Duchon
832926cc22
Merge pull request #483 from nginx-proxy/dependabot/docker/golang-1.19.5-alpine
build(deps): bump golang from 1.19.4-alpine to 1.19.5-alpine
2023-01-17 07:54:31 +01:00
Nicolas Duchon
38cf87d0ae
Merge pull request #482 from nginx-proxy/dependabot/docker/alpine-3.17.1
build(deps): bump alpine from 3.17.0 to 3.17.1
2023-01-17 07:49:53 +01:00
Nicolas Duchon
3a2b732ab2
Merge pull request #484 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.2
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.1 to 1.9.2
2023-01-17 07:48:59 +01:00
dependabot[bot]
40c224116a
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.1 to 1.9.2
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.1 to 1.9.2.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.1...v1.9.2)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 22:03:12 +00:00
dependabot[bot]
edef17afd7
build(deps): bump golang from 1.19.4-alpine to 1.19.5-alpine
Bumps golang from 1.19.4-alpine to 1.19.5-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-11 22:04:26 +00:00
dependabot[bot]
90694a658a
build(deps): bump alpine from 3.17.0 to 3.17.1
Bumps alpine from 3.17.0 to 3.17.1.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 22:04:37 +00:00
Nicolas Duchon
999e195d48
Merge pull request #480 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.1
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.0 to 1.9.1
2022-12-30 21:03:58 +01:00
dependabot[bot]
62aa4356ea
build(deps): bump github.com/fsouza/go-dockerclient from 1.9.0 to 1.9.1
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-29 22:03:00 +00:00
Nicolas Duchon
60a297e29c
Merge pull request #478 from nginx-proxy/dependabot/docker/debian-11.6-slim
build(deps): bump debian from 11.5-slim to 11.6-slim
2022-12-23 19:13:21 +01:00
Nicolas Duchon
bd8aeaff03
Merge pull request #471 from nginx-proxy/dependabot/docker/alpine-3.17.0
build(deps): bump alpine from 3.16.3 to 3.17.0
2022-12-23 19:11:59 +01:00
dependabot[bot]
d27e650f32
build(deps): bump debian from 11.5-slim to 11.6-slim
Bumps debian from 11.5-slim to 11.6-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-21 22:03:12 +00:00
dependabot[bot]
a5bfd0cec7
build(deps): bump alpine from 3.15.4 to 3.17.0
Bumps alpine from 3.15.4 to 3.17.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-07 23:01:34 +00:00
Nicolas Duchon
7810d3aef1
Merge pull request #474 from nginx-proxy/dependabot/docker/golang-1.19.4-alpine
build(deps): bump golang from 1.19.3-alpine to 1.19.4-alpine
2022-12-08 00:00:34 +01:00
dependabot[bot]
e0cb315d7e
build(deps): bump golang from 1.19.3-alpine to 1.19.4-alpine
Bumps golang from 1.19.3-alpine to 1.19.4-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 22:03:21 +00:00
Nicolas Duchon
fbd1efaace
ci: use actions/checkout@v3 2022-12-03 19:35:46 +01:00
Nicolas Duchon
591cb15c7d
build: golang 1.18 -> 1.19 2022-12-03 14:57:14 +01:00
Nicolas Duchon
83083062cb
style: linting on CI yaml files 2022-12-03 14:28:56 +01:00
Nicolas Duchon
58a62e23fe
ci: update Actions versions 2022-12-03 14:25:20 +01:00
Nicolas Duchon
eddeb863f0
build: bump alpine from 3.15.4 to 3.16.3 2022-12-01 23:06:42 +01:00
Nicolas Duchon
f3a535109e
build: golang buildstages 1.19.3 to 1.18.8
previous update was due to error with dependabot
2022-12-01 23:04:47 +01:00
Nicolas Duchon
619e24de40
Merge pull request #453 from nginx-proxy/dependabot/docker/golang-1.18.5-alpine
build(deps): bump golang from 1.18.1-alpine to 1.18.5-alpine
2022-12-01 22:51:08 +01:00
dependabot[bot]
f7bcb427b5
build(deps): bump golang from 1.18.1-alpine to 1.18.5-alpine
Bumps golang from 1.18.1-alpine to 1.18.5-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 21:48:33 +00:00
Nicolas Duchon
5a1576a21f
Merge pull request #464 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.9.0
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.10 to 1.9.0
2022-12-01 22:41:51 +01:00
dependabot[bot]
903f6059b0
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.10 to 1.9.0
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.7.10 to 1.9.0.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.7.10...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 21:37:47 +00:00
Nicolas Duchon
7ee55bae90
Merge pull request #472 from nginx-proxy/dependabot/go_modules/github.com/Masterminds/sprig/v3-3.2.3
build(deps): bump github.com/Masterminds/sprig/v3 from 3.2.2 to 3.2.3
2022-12-01 22:37:00 +01:00
dependabot[bot]
ffd0ba0a94
build(deps): bump github.com/Masterminds/sprig/v3 from 3.2.2 to 3.2.3
Bumps [github.com/Masterminds/sprig/v3](https://github.com/Masterminds/sprig) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/Masterminds/sprig/releases)
- [Changelog](https://github.com/Masterminds/sprig/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/sprig/compare/v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/sprig/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 21:35:14 +00:00
Nicolas Duchon
6786ed2447
Merge pull request #460 from nginx-proxy/dependabot/docker/debian-11.5-slim
build(deps): bump debian from 11.3-slim to 11.5-slim
2022-12-01 22:27:59 +01:00
Nicolas Duchon
7d43e65e55
Merge pull request #467 from nginx-proxy/dependabot/go_modules/github.com/stretchr/testify-1.8.1
build(deps): bump github.com/stretchr/testify from 1.7.1 to 1.8.1
2022-12-01 22:27:35 +01:00
Nicolas Duchon
70768351d1
Merge pull request #470 from zlocate/patch-1
Bump release version in README
2022-12-01 22:26:30 +01:00
Nicolas Duchon
e7195037ba
fix: typo 2022-12-01 22:25:53 +01:00
Nicolas Duchon
aa484a9151
Merge pull request #466 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.2.1
build(deps): bump github.com/BurntSushi/toml from 1.1.0 to 1.2.1
2022-12-01 22:22:44 +01:00
Denis
ab3bd6b484
feat: bump version number in README 2022-11-16 03:01:22 +03:00
dependabot[bot]
28279e3d25
build(deps): bump github.com/stretchr/testify from 1.7.1 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 22:18:56 +00:00
dependabot[bot]
7529923995
build(deps): bump github.com/BurntSushi/toml from 1.1.0 to 1.2.1
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.1.0 to 1.2.1.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.1.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 22:18:52 +00:00
dependabot[bot]
8ecf12e2b7
build(deps): bump debian from 11.3-slim to 11.5-slim
Bumps debian from 11.3-slim to 11.5-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-13 22:49:19 +00:00
Nicolas Duchon
cfd2934f0f
Merge pull request #428 from nginx-proxy/dependabot/docker/golang-1.18.1-alpine
build(deps): bump golang from 1.18.0-alpine to 1.18.1-alpine
2022-04-18 15:53:06 +02:00
dependabot[bot]
2af14a3175
build(deps): bump golang from 1.18.0-alpine to 1.18.1-alpine
Bumps golang from 1.18.0-alpine to 1.18.1-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-13 22:21:42 +00:00
Nicolas Duchon
baed6929fd
fix(ci): use correct go version in CI 2022-04-10 10:58:21 +02:00
Nicolas Duchon
640ef2f116
Merge pull request #425 from nginx-proxy/go-1.18
Bump Go version to 1.18
2022-04-10 10:53:15 +02:00
Nicolas Duchon
a98b318d71
build: go 1.17 -> 1.18 2022-04-10 10:47:40 +02:00
Nicolas Duchon
a2643dbd0b
Merge pull request #424 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.1.0
build(deps): bump github.com/BurntSushi/toml from 1.0.0 to 1.1.0
2022-04-10 10:36:51 +02:00
Nicolas Duchon
9dfe648b65
Merge pull request #423 from nginx-proxy/dependabot/docker/alpine-3.15.4
build(deps): bump alpine from 3.15.3 to 3.15.4
2022-04-10 10:34:12 +02:00
dependabot[bot]
bdb9680406
build(deps): bump github.com/BurntSushi/toml from 1.0.0 to 1.1.0
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-05 22:15:52 +00:00
dependabot[bot]
e4edd0172f
build(deps): bump alpine from 3.15.3 to 3.15.4
Bumps alpine from 3.15.3 to 3.15.4.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-05 22:15:46 +00:00
Nicolas Duchon
6c3ef6eb58
Merge pull request #421 from nginx-proxy/dependabot/docker/alpine-3.15.3
build(deps): bump alpine from 3.15.2 to 3.15.3
2022-03-30 07:30:18 +02:00
Nicolas Duchon
0c8ac0b822
Merge pull request #420 from nginx-proxy/dependabot/docker/debian-11.3-slim
build(deps): bump debian from 11.2-slim to 11.3-slim
2022-03-30 07:29:46 +02:00
dependabot[bot]
4e3aa778d8
build(deps): bump alpine from 3.15.2 to 3.15.3
Bumps alpine from 3.15.2 to 3.15.3.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 22:27:58 +00:00
dependabot[bot]
4a9ae2ea69
build(deps): bump debian from 11.2-slim to 11.3-slim
Bumps debian from 11.2-slim to 11.3-slim.

---
updated-dependencies:
- dependency-name: debian
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 22:27:55 +00:00
Nicolas Duchon
5a793cd57a
Merge pull request #418 from rhansen/sprig
Add template functions from sprig
2022-03-28 12:24:09 +02:00
Nicolas Duchon
f969097ae0
build: make Masterminds/sprig a direct dependency 2022-03-28 12:16:37 +02:00
Richard Hansen
22eb874943
feat: add template functions from sprig 2022-03-28 12:16:24 +02:00
Richard Hansen
ed4febbfba tests: Don't panic if template parsing fails 2022-03-27 18:53:35 -04:00
Richard Hansen
f072793bc5 tests: Delete unnecessary template prefix parameter
The template name doesn't need to be globally unique.
2022-03-27 18:53:35 -04:00
Richard Hansen
5a35b9ef87 tests: Parallelize templateTestList.run
Because why not.
2022-03-27 17:08:05 -04:00
Richard Hansen
f784650d02 tests: Run templateTestList entries as subtests
This prevents one failed test case from aborting attempts for the
other test cases, and it makes it easier to iterate on a particular
test case.
2022-03-27 17:06:09 -04:00
Nicolas Duchon
85030ce329
Merge pull request #419 from nginx-proxy/dependabot/docker/alpine-3.15.2
build(deps): bump alpine from 3.15.0 to 3.15.2
2022-03-25 11:02:15 +01:00
Nicolas Duchon
d030c1b4cf
Merge pull request #415 from nginx-proxy/dependabot/go_modules/github.com/stretchr/testify-1.7.1
build(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1
2022-03-25 11:02:02 +01:00
dependabot[bot]
e3f08dcc84
build(deps): bump alpine from 3.15.0 to 3.15.2
Bumps alpine from 3.15.0 to 3.15.2.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 22:21:17 +00:00
dependabot[bot]
70d559acaf
build(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-15 22:18:36 +00:00
Nicolas Duchon
6b86d03572
fix: use actions/setup-go in assets.yml workflow 2022-03-09 16:53:16 +01:00
Nicolas Duchon
56b0f5f57f
Merge pull request #410 from nginx-proxy/build-upload-release-assets
Build and upload GitHub release assets in GitHub Actions
2022-03-09 16:45:28 +01:00
Nicolas Duchon
199ac31d30
ci: build and upload GitHub release assets in GHA 2022-03-09 16:41:51 +01:00
Nicolas Duchon
f44b06a4f9
Merge pull request #409 from nginx-proxy/docker-gen-version-env
Set DOCKER_GEN_VERSION in Docker image env
2022-03-09 16:00:19 +01:00
Nicolas Duchon
8b72f3adb9
build: keep DOCKER_GEN_VERSION in Docker image env 2022-03-09 15:54:04 +01:00
Nicolas Duchon
a84a51dd35
Merge pull request #388 from nginx-proxy/dependabot/docker/alpine-3.15.0
build(deps): bump alpine from 3.13 to 3.15.0
2022-03-09 15:27:46 +01:00
Nicolas Duchon
b664a49652
ci: fetch all history and tags before building
This avoid the output "fatal: No names found, cannot describe anything" caused by `git describe --tags` in the Makefile.
2022-03-09 15:23:25 +01:00
dependabot[bot]
347cf746f2
build(deps): bump alpine from 3.13 to 3.15.0
Bumps alpine from 3.13 to 3.15.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-09 14:14:11 +00:00
Nicolas Duchon
d4698cd1f7
Merge pull request #406 from nginx-proxy/dependabot/docker/golang-1.17.8-alpine
build(deps): bump golang from 1.17.7-alpine to 1.17.8-alpine
2022-03-09 15:13:29 +01:00
Nicolas Duchon
f1e009301c
Merge pull request #407 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.7.10
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.9 to 1.7.10
2022-03-09 15:10:01 +01:00
dependabot[bot]
71fc7193a4
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.9 to 1.7.10
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.7.9 to 1.7.10.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.7.9...v1.7.10)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-09 14:07:12 +00:00
Nicolas Duchon
3e2cb3a785
ci: use specific go version with actions/setup-go 2022-03-09 15:04:17 +01:00
Nicolas Duchon
5816c4b9f6
Merge pull request #408 from nginx-proxy/license-in-image
Include license in Docker images
2022-03-09 14:49:03 +01:00
Nicolas Duchon
0ac48ce704
chore: include license in Docker images 2022-03-09 14:20:22 +01:00
dependabot[bot]
7be3ba2ef7
build(deps): bump golang from 1.17.7-alpine to 1.17.8-alpine
Bumps golang from 1.17.7-alpine to 1.17.8-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 22:20:00 +00:00
Nicolas Duchon
781ee5e0a0
ci: use path context 2022-03-05 17:04:33 +01:00
Nicolas Duchon
9a8b5b3a81
Merge pull request #402 from nginx-proxy/dependabot/docker/golang-1.17.7-alpine
build(deps): bump golang from 1.17.6-alpine to 1.17.7-alpine
2022-03-04 11:50:45 +01:00
Nicolas Duchon
f23c0bc89e
ci: build/push images on Dockerfile.debian update 2022-03-04 11:49:10 +01:00
Nicolas Duchon
e541c2bf2a
build: use debian source image tag w/ minor version 2022-03-04 11:47:11 +01:00
Nicolas Duchon
f2fe024c47
ci: publish Docker images to ghcr.io 2022-03-04 11:33:28 +01:00
Nicolas Duchon
bcbac74118
Merge pull request #403 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.7.9
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.8 to 1.7.9
2022-02-23 15:52:30 +01:00
dependabot[bot]
28a3349518
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.8 to 1.7.9
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.7.8 to 1.7.9.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.7.8...v1.7.9)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-22 22:19:17 +00:00
Nicolas Duchon
42d9b4b49e
Merge pull request #401 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.7.8
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.7 to 1.7.8
2022-02-11 23:45:44 +01:00
dependabot[bot]
63de458373
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.7 to 1.7.8
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.7.7 to 1.7.8.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.7.7...v1.7.8)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-11 22:31:14 +00:00
Nicolas Duchon
f22c9e24cd
Merge pull request #398 from nginx-proxy/dependabot/go_modules/github.com/BurntSushi/toml-1.0.0
build(deps): bump github.com/BurntSushi/toml from 0.4.1 to 1.0.0
2022-02-11 23:30:04 +01:00
dependabot[bot]
dc77f15148
build(deps): bump golang from 1.17.6-alpine to 1.17.7-alpine
Bumps golang from 1.17.6-alpine to 1.17.7-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-11 22:24:08 +00:00
Nicolas Duchon
b429924a8e
ci: replace maintainer label w/ OCI authors label 2022-01-14 15:54:39 +01:00
Nicolas Duchon
cf48957cc0
ci: use git describe for OCI image version label 2022-01-14 15:53:13 +01:00
Nicolas Duchon
8b3f38aca3
ci: upgrade to docker/metadata-action@v3 2022-01-14 15:50:59 +01:00
dependabot[bot]
c62a52b3de
build(deps): bump github.com/BurntSushi/toml from 0.4.1 to 1.0.0
Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 0.4.1 to 1.0.0.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v0.4.1...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-12 22:19:43 +00:00
Nicolas Duchon
315cd4f532
chore: update maintainer(s) 2022-01-11 20:05:29 +01:00
Nicolas Duchon
2ea9026c14
Merge pull request #395 from nginx-proxy/dependabot/go_modules/github.com/fsouza/go-dockerclient-1.7.7
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.6 to 1.7.7
2022-01-08 00:56:03 +01:00
Nicolas Duchon
f1d348750e
Merge pull request #394 from nginx-proxy/dependabot/docker/golang-1.17.6-alpine
build(deps): bump golang from 1.17.5-alpine to 1.17.6-alpine
2022-01-08 00:54:45 +01:00
dependabot[bot]
578e366892
build(deps): bump github.com/fsouza/go-dockerclient from 1.7.6 to 1.7.7
Bumps [github.com/fsouza/go-dockerclient](https://github.com/fsouza/go-dockerclient) from 1.7.6 to 1.7.7.
- [Release notes](https://github.com/fsouza/go-dockerclient/releases)
- [Changelog](https://github.com/fsouza/go-dockerclient/blob/main/container_changes_test.go)
- [Commits](https://github.com/fsouza/go-dockerclient/compare/v1.7.6...v1.7.7)

---
updated-dependencies:
- dependency-name: github.com/fsouza/go-dockerclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-07 22:30:57 +00:00
dependabot[bot]
cf878f56ae
build(deps): bump golang from 1.17.5-alpine to 1.17.6-alpine
Bumps golang from 1.17.5-alpine to 1.17.6-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-07 22:16:14 +00:00
Nicolas Duchon
ea6bfb41de
Merge pull request #393 from nginx-proxy/debian-image
Add Debian based image
2021-12-15 17:08:19 +01:00
Nicolas Duchon
7e6f2c0159
ci: build & push debian based images to Dockerhub 2021-12-15 17:05:00 +01:00
Nicolas Duchon
e6ead3f691
build: minor changes to the Alpine Dockerfile 2021-12-15 17:03:49 +01:00
Nicolas Duchon
47a32e917e
build: add debian based Dockerfile 2021-12-15 17:02:39 +01:00
Nicolas Duchon
7301224594
fix: yaml formatting in dependabot.yml 2021-12-14 23:10:20 +01:00
Nicolas Duchon
c29af3a968
ci: add Go modules ecosystem to Dependabot 2021-12-14 23:07:02 +01:00
Nicolas Duchon
40bcf737fa
Merge pull request #391 from nginx-proxy/go-dockerclient-1.7.6
Bump fzouza/go-dockerclient to 1.7.6
2021-12-14 20:51:07 +01:00
Nicolas Duchon
7e7f170712
build: fsouza/go-dockerclient 1.7.4 -> 1.7.6 2021-12-14 20:44:26 +01:00
Nicolas Duchon
810a4bdc51
Merge pull request #390 from nginx-proxy/dependabot/docker/golang-1.17.5-alpine
build(deps): bump golang from 1.17.2-alpine to 1.17.5-alpine
2021-12-10 13:16:34 +01:00
dependabot[bot]
d958a24979
build(deps): bump golang from 1.17.2-alpine to 1.17.5-alpine
Bumps golang from 1.17.2-alpine to 1.17.5-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-09 22:52:27 +00:00
Nicolas Duchon
e7eeee85db
Merge pull request #382 from nginx-proxy/sort-functions
Sorting template functions
2021-10-26 23:25:41 +02:00
Nicolas Duchon
675f804d59
docs: sorting template functions 2021-10-26 23:18:44 +02:00
Nicolas Duchon
2ef737a45a
tests: sorting template functions 2021-10-26 23:15:49 +02:00
Nicolas Duchon
d50c146dbe
feat: sorting template functions 2021-10-26 23:15:35 +02:00
Nicolas Duchon
1f294d3ab7
Merge pull request #381 from nginx-proxy/restructure-2
Further re-organize the project structure
2021-10-26 23:13:58 +02:00
Nicolas Duchon
025381db4d
tests: add package specific tests 2021-10-26 22:49:26 +02:00
Nicolas Duchon
02cefb1172
Merge pull request #379 from nginx-proxy/dependabot/docker/golang-1.17.2-alpine
build(deps): bump golang from 1.17.1-alpine to 1.17.2-alpine
2021-10-09 19:30:50 +02:00
dependabot[bot]
874b1cd6c9
build(deps): bump golang from 1.17.1-alpine to 1.17.2-alpine
Bumps golang from 1.17.1-alpine to 1.17.2-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-08 22:11:29 +00:00
Nicolas Duchon
b5ecf7657c
Merge pull request #378 from JoelLinn/fix-sighup-crash
Fix exit(2) on SIGHUP before fully initialized.
2021-09-30 19:22:21 +02:00
Joel Linn
da437201d7 Fix exit(2) on SIGHUP before fully initialized.
Fixes https://github.com/jwilder/docker-gen/issues/244
2021-09-22 01:31:04 +02:00
Nicolas Duchon
ec2f6f41fc
refactor: further split code base in packages 2021-09-16 07:51:24 +02:00
Nicolas Duchon
02edf71026
Merge pull request #377 from nginx-proxy/gomod-dep
Update Go modules direct dependencies
2021-09-16 07:36:10 +02:00
Nicolas Duchon
c3c11d4d3b
build: BurntSushi/toml v0.3.1 -> v0.4.1 2021-09-16 07:29:17 +02:00
Nicolas Duchon
fc7c95bbee
build: fsouza/go-dockerclient v1.7.2 -> v1.7.4 2021-09-16 07:27:16 +02:00
Nicolas Duchon
458c7f4bd7
Merge pull request #376 from nginx-proxy/go-1.17
Bump Go version to 1.17
2021-09-16 07:20:43 +02:00
Nicolas Duchon
6697d62c93
build: go 1.16 -> 1.17 2021-09-16 07:14:38 +02:00
Nicolas Duchon
342744e000
Merge pull request #371 from nginx-proxy/tests
Additional tests and incremental enhancements
2021-08-08 17:04:28 +02:00
Nicolas Duchon
e23b149210
Merge pull request #370 from nginx-proxy/toupper-tolower
Add toLower and toUpper template functions
2021-08-08 16:59:56 +02:00
SmalkiGroove
cde65d47f2
feat: add toLower and toUpper template functions 2021-08-08 16:56:36 +02:00
Nicolas Duchon
48c39ac18c
tests: additional tests for context and template 2021-08-08 16:12:12 +02:00
Nicolas Duchon
66b123c821
tests: use testify/assert on context 2021-08-08 16:11:46 +02:00
Nicolas Duchon
331416baf0
tests: replace log.Fatal() with testing.T.Fatal() 2021-08-08 16:10:56 +02:00
Nicolas Duchon
59c838a55c
tests: additional template tests 2021-08-08 13:36:22 +02:00
Nicolas Duchon
4282c41a5b
tests: use assert in template_test.go 2021-08-08 13:35:52 +02:00
Nicolas Duchon
3047b24d8b
Merge pull request #368 from nginx-proxy/dependabot/docker/golang-1.16.7-alpine
build(deps): bump golang from 1.16-alpine to 1.16.7-alpine
2021-08-08 11:43:12 +02:00
Nicolas Duchon
2d078b1154
Merge pull request #362 from nginx-proxy/restructure
Re-organize the project structure, change the name and clean the dependencies.
2021-08-08 11:42:20 +02:00
dependabot[bot]
d9b3c25ed1
build(deps): bump golang from 1.16-alpine to 1.16.7-alpine
Bumps golang from 1.16-alpine to 1.16.7-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-06 22:11:38 +00:00
Nicolas Duchon
24bf161c2c
ci: fix typo in dockerhub.yml 2021-08-06 14:27:37 +02:00
Nicolas Duchon
1ffc239fa6
Merge pull request #367 from nginx-proxy/version
Fix `--version` output on the Docker image
2021-08-06 14:25:46 +02:00
Nicolas Duchon
c9f0f3e078
ci: fix --version output on the Docker image 2021-08-06 14:24:12 +02:00
Nicolas Duchon
aa2a909885
refactor: replace exists() with pathExists()
Both were the exact same function.
2021-08-06 12:54:18 +02:00
Nicolas Duchon
c2595abae9
refactor: move pathExists() to utils.go 2021-08-06 12:52:00 +02:00
Nicolas Duchon
dd1d8cc9d5
chore: update repo url in usage() 2021-08-06 11:51:55 +02:00
Nicolas Duchon
43d66a3f26
style: linting with staticcheck 2021-08-06 11:51:55 +02:00
Nicolas Duchon
79abcabeb9
refactor: clean project structure and dependencies 2021-08-06 11:51:55 +02:00
Nicolas Duchon
41f48ce299
Merge pull request #366 from nginx-proxy/tests
Additional tests and use of github.com/stretchr/testify/assert
2021-08-06 11:47:17 +02:00
Nicolas Duchon
f13650e1b8
tests: additional tests for docker_client.go 2021-08-06 11:21:39 +02:00
Nicolas Duchon
e6d285d3d3
tests: use github.com/stretchr/testify/assert 2021-08-06 11:20:06 +02:00
Nicolas Duchon
3a8a565ae5
tests: add tests for config.go 2021-08-06 11:16:42 +02:00
Nicolas Duchon
de17d67356
ci: enable dependabot for Dockerfile 2021-08-05 23:02:51 +02:00
Nicolas Duchon
f46d4868a8
Merge pull request #348 from Syphdias/docker-ro-docket
Explicitly make docker socket rw in documentation
2021-08-03 20:30:59 +02:00
Nicolas Duchon
da4354f7b3
Merge pull request #356 from nginx-proxy/fix-355
Fix container ID extraction from /proc/self/mountinfo file
2021-08-03 17:33:02 +02:00
Nicolas Duchon
0a3bb6ee9a
fix: extract correct CID from mountinfo files
Instead of extracting the first 64 characters ID found
2021-08-03 15:21:32 +02:00
Nicolas Duchon
b3387b13de
test: check if mountinfo files yeld correct CID 2021-08-03 15:20:23 +02:00
Nicolas Duchon
1d0e791067
Merge pull request #313 from carehart/patch-1
correcting a potentially confusing typo
2021-06-15 22:25:43 +02:00
Nicolas Duchon
75b892d304
Merge pull request #352 from nginx-proxy/permissions
Make sure file generated by docker-gen has the right permission
2021-06-15 21:52:20 +02:00
nicolas.dorier
cd1932e43b
Make sure file generated by docker-gen has the right permissions 2021-06-15 21:46:18 +02:00
Nicolas Duchon
e585ae43f3
docs: use the same badges as the other repos 2021-06-15 19:17:50 +02:00
Nicolas Duchon
f5eec15939
ci: use verbose tests 2021-06-15 19:09:57 +02:00
Nicolas Duchon
4c53a8632d
ci: move tests from Travis CI to GitHub Actions 2021-06-15 19:06:02 +02:00
Nicolas Duchon
ea4d0e8618
build: push to nginxproxy/docker-gen 2021-06-15 15:32:17 +02:00
Nicolas Duchon
50fdc25ac3
chore: switch to nginx-proxy org repos & registry 2021-06-15 15:27:15 +02:00
Nicolas Duchon
001c5237ad
chore: rename master branch to main 2021-06-15 14:49:53 +02:00
Jason Wilder
44012202fa
Merge pull request #343 from AlexanderLieret/master
Lift restriction on the function contains
2021-04-29 11:48:41 -06:00
Jason Wilder
de4d9633b2 Update to 0.7.6 2021-04-23 09:10:20 -06:00
Syphdias
99207854c6
Explicitly make docker socket rw in documentation
Docker does not allow for read-only mounts of sockets at this point.
This can be tested by running:
❯ docker run --rm -it -v /var/run/docker.sock:/tmp/docker.sock:ro \
    docker ls -l /tmp/docker.sock
srw-rw----    1 root     967              0 Apr 13 16:16 /tmp/docker.sock
❯ docker run --rm -it -v /var/run/docker.sock:/tmp/docker.sock:ro \
    docker /usr/local/bin/docker -H unix:///tmp/docker.sock ps |wc -l
17

Even if docker supported read-only sockets, you would not want it to be
read-only if you are using `-notify-sighup` because then the docker socket is
used to communicate to the other container to notify it.

This change in documentation reflects the fact that the socket will be writable.
2021-04-13 21:36:41 +02:00
Jason Wilder
2024a2f152
Merge pull request #347 from buchdag/github-actions-build
Build multi arch images on GitHub Actions and push to DockerHub
2021-04-13 11:26:50 -06:00
Nicolas Duchon
33a2d63f48
ci: build multi arch images on GitHub Actions 2021-04-13 17:56:33 +02:00
Jason Wilder
c830c3f1b8
Merge pull request #345 from buchdag/fix-current-cid
Fix GetCurrentContainerID() and corresponding test
2021-04-12 09:49:52 -06:00
Jason Wilder
37de4272a1
Merge pull request #346 from buchdag/fix-dockerfile
Have the Dockerfile build from the actual surrounding sources rather than downloading a hardcoded version
2021-04-09 18:49:52 -06:00
Nicolas Duchon
aa1675fd66
fix: don't hardcode docker-gen tag in dockerfile
The Dockerfile now builds from the actual surrounding sources
2021-04-10 01:19:35 +02:00
Nicolas Duchon
e44864e634
fix(test): fix GetCurrentContainerID() tests
Perform the tests with real file reads instead
of just testing the matching functions
2021-04-10 00:46:36 +02:00
Nicolas Duchon
c6f3145c22
fix(context): fallback method to get current cid
The hostname method can fail if the host name name has been customised.
The fallback method match the first 64 characters hex string found.
2021-04-10 00:44:15 +02:00
Jason Wilder
e877b8f248 Fix Dockerfile alpine build 2021-04-06 00:25:11 -06:00
Jason Wilder
fc48d7e4bc Remove darwin/i386
Not supported by go
2021-04-06 00:12:04 -06:00
Jason Wilder
612d1597b2 Update to 0.7.5 2021-04-06 00:09:39 -06:00
Alexander Lieret
69adf5b754
Lift restriction on the function contains
This commit lifts the restriction of the contains function to only allow
'map[string]string' as an input map. Both the key and value type of the
map can be of an arbitrary type.
2021-04-05 17:17:39 +02:00
Jason Wilder
83d32ba157
Merge pull request #283 from NickBolles/master
Allow more notify and restart options
2021-04-03 11:26:56 -06:00
Jason Wilder
e51c234e86
Merge pull request #336 from btcpayserver/quwn3e
Fix docker-gen breaking on new docker version (See #335)
2021-04-03 11:16:52 -06:00
Jason Wilder
faaa781228
Merge pull request #327 from KWARC/build-from-scratch
Enable multi-architecture docker image builds
2021-04-03 11:14:53 -06:00
Jason Wilder
664fe0c31f
Merge pull request #341 from KWARC/go-modules
Use Go Modules instead of Glock (fixes #338)
2021-04-03 11:12:52 -06:00
Tom Wiesing
9d81762146
Use Go Modules instead of Glock (fixes #338)
Previously, this project was using glock to manage external go
dependencies. As of go 1.11, go comes with builtin module support. Go
Modules fully supersede glock functionality. As of go 1.16, either an
environment variable or go modules are required for this project to
build.

This commit migrates the repository away from glock, and instead uses go
modules. This gets rid of an unneeded dependency, and also allows the
project to natively compile under go 1.16 (which requires `go.mod` by
default).

For go >= 1.13, compilation will "just work". To build under go 1.11 and
go 1.12 one needs to set an environment variable 'GO111MODULE=on'. Since
go <= 1.14 can be considered end of life, this change will likely not be
impact any existing build scripts.

This commit also updates the Travis Tests with more modern go versions,
so that they can pass.
2021-04-02 11:55:43 +02:00
nicolas.dorier
5fb960aa61
Fix docker-gen breaking on new docker version (See #335) 2021-01-21 21:52:58 +09:00
Tom Wiesing
5c7011b8a4
Bump Dockerfile version to 0.7.4
The previous version of the Dockerfile made use of version 0.7.3. However because of changes in dependencies, this can no longer be compiled from source. To fix this regression, this commit updates the version used to 0.7.4.
2020-07-13 12:19:04 +02:00
Tom Wiesing
16dbe9606f
Enable multi-architecture docker image builds
Previously, the Dockerfile downloaded a 'docker-gen' binary during build
time. This caused a problem as it hard-coded the amd64 architecture for
the image.

This commit updates the Dockerfile to build the docker-gen executable
from source instead of downloading a binary. This updated Dockerfile
uses a multi-stage build [1]. The first stage downloads a source tarball
from github and produces a binary out of it. The second stage
corresponds to the old Dockerfile, with the sole change being that is
fetches the binary from the first stage instead of GitHub.

This has the advantage that the build process no longer assumes a
certain architecture and enables building the image for any architecture
that both the golang and alpine base images support.

[1] https://docs.docker.com/develop/develop-images/multistage-build/
2020-07-13 12:18:47 +02:00
Charlie Arehart
ada59baa35
correcting a potentially confusing typo
Signed-off-by: Charles Arehart <charlie@carehart.org>
2019-09-02 16:09:32 -05:00
Nick Bolles
997abf7fbe
Merge pull request #2 from mlatorre31/fix-restart-formatting
Fix formatting in main.go and config go
2018-08-04 10:23:02 -05:00
mlatorre31
6efeb75e5f Fix formatting... 2018-08-04 15:29:16 +02:00
Nick Bolles
0f58b93e22
Merge pull request #1 from mlatorre31/fix-restart
Fix @NickBolles restart feature building and Alpine bin compilation flag
2018-08-04 07:58:06 -05:00
mlatorre31
139979b3fa Fix @NickBolles restart feature building and Alpine bin compilation flag 2018-08-03 23:34:31 +02:00
Nick Bolles
2ff5971b25 Add -notify-container and notify-signal to enable more workflows and allow calling docker restart on a container 2018-04-13 11:58:18 -05:00
54 changed files with 3440 additions and 2255 deletions

View File

@ -1,4 +1,9 @@
.git
.github
docker-gen
dist
examples
Makefile
README.md
templates
*.gz

25
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,25 @@
version: 2
updates:
# Maintain dependencies for Go modules
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix: "build"
# Maintain dependencies for Docker
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix: "build"
# Maintain GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci"

33
.github/workflows/assets.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Release assets
on:
push:
tags:
- "*.*.*"
jobs:
assets:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-go@v4
with:
go-version: "1.20"
- name: Build release assets
run: make release
- name: Upload release assets
uses: alexellis/upload-assets@0.4.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_paths: '["./docker-gen-*.tar.gz"]'
- name: Cleanup release assets
run: make dist-clean

87
.github/workflows/build-publish.yml vendored Normal file
View File

@ -0,0 +1,87 @@
name: Build and publish Docker images
on:
workflow_dispatch:
push:
tags:
- "*.*.*"
paths:
- ".dockerignore"
- ".github/workflows/build-publish.yml"
- "Dockerfile"
- "go.mod"
- "go.sum"
- "**.go"
jobs:
multiarch-build:
name: Build and publish image
strategy:
matrix:
base: [alpine]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Retrieve version
id: docker-gen_version
run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT"
- name: Get Docker tags
id: docker_meta
uses: docker/metadata-action@v4
with:
images: |
btcpayserver/docker-gen
tags: |
type=semver,pattern={{version}},enable=${{ matrix.base == 'alpine' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'alpine' }}
type=semver,suffix=-debian,pattern={{version}},enable=${{ matrix.base == 'debian' }}
type=semver,suffix=-debian,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'debian' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'alpine' }}
type=raw,value=debian,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'debian' }}
labels: |
org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
org.opencontainers.image.version=${{ steps.docker-gen_version.outputs.VERSION }}
flavor: |
latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push the image
id: docker_build
uses: docker/build-push-action@v5
with:
context: .
build-args: DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
platforms: linux/amd64,linux/arm/v7,linux/arm64
file: Dockerfile.${{ matrix.base }}
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Docker image digest
run: echo ${{ steps.docker_build.outputs.digest }}

38
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Tests
on:
push:
branches:
- main
pull_request:
paths-ignore:
- "LICENSE"
- "**.md"
- "examples/*"
- "templates/*"
jobs:
unit:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v4
with:
go-version: "1.20"
- name: Install dependencies
run: make get-deps
- name: Build docker-gen
run: make docker-gen
- name: Check code formatting
run: make check-gofmt
- name: Run tests
run: go test -race -v ./internal/...

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
docker-gen
!cmd/docker-gen
dist
*.gz

View File

@ -1,9 +0,0 @@
language: go
go:
- "1.9"
- "1.10"
- "1.11"
install:
- make get-deps
script:
- make all check-gofmt test

View File

@ -1,12 +0,0 @@
FROM alpine:latest
LABEL maintainer="Jason Wilder <mail@jasonwilder.com>"
RUN apk -U add openssl
ENV VERSION 0.7.3
ENV DOWNLOAD_URL https://github.com/jwilder/docker-gen/releases/download/$VERSION/docker-gen-alpine-linux-amd64-$VERSION.tar.gz
ENV DOCKER_HOST unix:///tmp/docker.sock
RUN wget -qO- $DOWNLOAD_URL | tar xvz -C /usr/local/bin
ENTRYPOINT ["/usr/local/bin/docker-gen"]

31
Dockerfile.alpine Normal file
View File

@ -0,0 +1,31 @@
ARG DOCKER_GEN_VERSION=main
# Build docker-gen from scratch
FROM golang:1.20.12-alpine as go-builder
ARG DOCKER_GEN_VERSION
WORKDIR /build
# Install the dependencies
COPY . .
RUN go mod download
# Build the docker-gen executable
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen
FROM alpine:3.18.5
ARG DOCKER_GEN_VERSION
ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
DOCKER_HOST=unix:///tmp/docker.sock
# Install packages required by the image
RUN apk add --no-cache --virtual .bin-deps openssl
# Install docker-gen from build stage
COPY --from=go-builder /build/docker-gen /usr/local/bin/docker-gen
# Copy the license
COPY LICENSE /usr/local/share/doc/docker-gen/
ENTRYPOINT ["/usr/local/bin/docker-gen"]

35
Dockerfile.debian Normal file
View File

@ -0,0 +1,35 @@
ARG DOCKER_GEN_VERSION=main
# Build docker-gen from scratch
FROM golang:1.20.12 as go-builder
ARG DOCKER_GEN_VERSION
WORKDIR /build
# Install the dependencies
COPY . .
RUN go mod download
# Build the docker-gen executable
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen
FROM debian:12.2-slim
ARG VERSION
ENV DOCKER_GEN_VERSION=${VERSION} \
DOCKER_HOST=unix:///tmp/docker.sock
# Install packages required by the image
RUN apt-get update \
&& apt-get install -y -q --no-install-recommends openssl \
&& apt-get clean \
&& rm -r /var/lib/apt/lists/*
# Install docker-gen from build stage
COPY --from=go-builder /build/docker-gen /usr/local/bin/docker-gen
# Copy the license
COPY LICENSE /usr/local/share/doc/docker-gen/
ENTRYPOINT ["/usr/local/bin/docker-gen"]

View File

@ -1,6 +0,0 @@
github.com/BurntSushi/toml 056c9bc7be7190eaa7715723883caffa5f8fa3e4
github.com/docker/docker f2afa26235941fd79f40eb1e572e19e4ac2b9bbe
github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
github.com/fsouza/go-dockerclient d2a6d0596004cc01062a2a068540b817f911e6dc
github.com/gorilla/mux d391bea3118c9fc17a88d62c9189bb791255e0ef
golang.org/x/net a04bdaca5b32abe1c069418fb7088ae607de5bd0

View File

@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2014 Jason Wilder
Copyright (c) 2014-2021 Jason Wilder
Copyright (c) 2021-2022 Nicolas Duchon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -17,7 +17,7 @@ dist-clean:
rm -f docker-gen-darwin-*.tar.gz
dist: dist-clean
mkdir -p dist/alpine-linux/amd64 && GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -a -tags netgo -installsuffix netgo -o dist/alpine-linux/amd64/docker-gen ./cmd/docker-gen
mkdir -p dist/alpine-linux/amd64 && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -a -tags netgo -installsuffix netgo -o dist/alpine-linux/amd64/docker-gen ./cmd/docker-gen
mkdir -p dist/alpine-linux/arm64 && GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -a -tags netgo -installsuffix netgo -o dist/alpine-linux/arm64/docker-gen ./cmd/docker-gen
mkdir -p dist/alpine-linux/armhf && GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$(LDFLAGS)" -a -tags netgo -installsuffix netgo -o dist/alpine-linux/armhf/docker-gen ./cmd/docker-gen
mkdir -p dist/linux/amd64 && GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/linux/amd64/docker-gen ./cmd/docker-gen
@ -26,11 +26,10 @@ dist: dist-clean
mkdir -p dist/linux/armel && GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "$(LDFLAGS)" -o dist/linux/armel/docker-gen ./cmd/docker-gen
mkdir -p dist/linux/armhf && GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$(LDFLAGS)" -o dist/linux/armhf/docker-gen ./cmd/docker-gen
mkdir -p dist/darwin/amd64 && GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/darwin/amd64/docker-gen ./cmd/docker-gen
mkdir -p dist/darwin/i386 && GOOS=darwin GOARCH=386 go build -ldflags "$(LDFLAGS)" -o dist/darwin/i386/docker-gen ./cmd/docker-gen
release: dist
glock sync -n < GLOCKFILE
go mod tidy
tar -cvzf docker-gen-alpine-linux-amd64-$(TAG).tar.gz -C dist/alpine-linux/amd64 docker-gen
tar -cvzf docker-gen-alpine-linux-arm64-$(TAG).tar.gz -C dist/alpine-linux/arm64 docker-gen
tar -cvzf docker-gen-alpine-linux-armhf-$(TAG).tar.gz -C dist/alpine-linux/armhf docker-gen
@ -40,18 +39,21 @@ release: dist
tar -cvzf docker-gen-linux-armel-$(TAG).tar.gz -C dist/linux/armel docker-gen
tar -cvzf docker-gen-linux-armhf-$(TAG).tar.gz -C dist/linux/armhf docker-gen
tar -cvzf docker-gen-darwin-amd64-$(TAG).tar.gz -C dist/darwin/amd64 docker-gen
tar -cvzf docker-gen-darwin-i386-$(TAG).tar.gz -C dist/darwin/i386 docker-gen
get-deps:
go get github.com/robfig/glock
glock sync -n < GLOCKFILE
go mod download
check-gofmt:
if [ -n "$(shell gofmt -l .)" ]; then \
if [ -n "$(shell go fmt ./cmd/...)" ]; then \
echo 1>&2 'The following files need to be formatted:'; \
gofmt -l .; \
gofmt -l ./cmd/docker-gen; \
exit 1; \
fi
if [ -n "$(shell go fmt ./internal/...)" ]; then \
echo 1>&2 'The following files need to be formatted:'; \
gofmt -l ./internal; \
exit 1; \
fi
test:
go test
go test -race ./internal/...

View File

@ -1,17 +1,20 @@
docker-gen
=====
![latest 0.7.3](https://img.shields.io/badge/latest-0.7.3-green.svg?style=flat)
[![Build Status](https://travis-ci.org/jwilder/docker-gen.svg?branch=master)](https://travis-ci.org/jwilder/docker-gen)
![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)
[![Tests](https://github.com/nginx-proxy/docker-gen/actions/workflows/tests.yml/badge.svg)](https://github.com/nginx-proxy/docker-gen/actions/workflows/tests.yml)
[![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/docker-gen)](https://github.com/nginx-proxy/docker-gen/releases)
[![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/docker-gen?sort=semver)](https://hub.docker.com/r/nginxproxy/docker-gen "Click to view the image on Docker Hub")
[![Docker stars](https://img.shields.io/docker/stars/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub')
[![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub')
`docker-gen` is a file generator that renders templates using docker container meta-data.
It can be used to generate various kinds of files for:
* **Centralized logging** - [fluentd](https://github.com/jwilder/docker-gen/blob/master/templates/fluentd.conf.tmpl), logstash or other centralized logging tools that tail the containers JSON log file or files within the container.
* **Log Rotation** - [logrotate](https://github.com/jwilder/docker-gen/blob/master/templates/logrotate.tmpl) files to rotate container JSON log files
* **Reverse Proxy Configs** - [nginx](https://github.com/jwilder/docker-gen/blob/master/templates/nginx.tmpl), [haproxy](https://github.com/jwilder/docker-discover), etc. reverse proxy configs to route requests from the host to containers
* **Centralized logging** - [fluentd](https://github.com/nginx-proxy/docker-gen/blob/main/templates/fluentd.conf.tmpl), logstash or other centralized logging tools that tail the containers JSON log file or files within the container.
* **Log Rotation** - [logrotate](https://github.com/nginx-proxy/docker-gen/blob/main/templates/logrotate.tmpl) files to rotate container JSON log files
* **Reverse Proxy Configs** - [nginx](https://github.com/nginx-proxy/docker-gen/blob/main/templates/nginx.tmpl), [haproxy](https://github.com/jwilder/docker-discover), etc. reverse proxy configs to route requests from the host to containers
* **Service Discovery** - Scripts (python, bash, etc..) to register containers within [etcd](https://github.com/jwilder/docker-register), hipache, etc..
===
@ -25,17 +28,17 @@ There are three common ways to run docker-gen:
#### Host Install
Linux/OSX binaries for release [0.7.3](https://github.com/jwilder/docker-gen/releases)
Linux/OSX binaries for release [0.9.0](https://github.com/nginx-proxy/docker-gen/releases)
* [amd64](https://github.com/jwilder/docker-gen/releases/download/0.7.3/docker-gen-linux-amd64-0.7.3.tar.gz)
* [i386](https://github.com/jwilder/docker-gen/releases/download/0.7.3/docker-gen-linux-i386-0.7.3.tar.gz)
* [alpine-linux](https://github.com/jwilder/docker-gen/releases/download/0.7.3/docker-gen-alpine-linux-amd64-0.7.3.tar.gz)
* [amd64](https://github.com/nginx-proxy/docker-gen/releases/download/0.9.0/docker-gen-linux-amd64-0.9.0.tar.gz)
* [i386](https://github.com/nginx-proxy/docker-gen/releases/download/0.9.0/docker-gen-linux-i386-0.9.0.tar.gz)
* [alpine-linux](https://github.com/nginx-proxy/docker-gen/releases/download/0.9.0/docker-gen-alpine-linux-amd64-0.9.0.tar.gz)
Download the version you need, untar, and install to your PATH.
```
$ wget https://github.com/jwilder/docker-gen/releases/download/0.7.3/docker-gen-linux-amd64-0.7.3.tar.gz
$ tar xvzf docker-gen-linux-amd64-0.7.3.tar.gz
$ wget https://github.com/nginx-proxy/docker-gen/releases/download/0.9.0/docker-gen-linux-amd64-0.9.0.tar.gz
$ tar xvzf docker-gen-linux-amd64-0.9.0.tar.gz
$ ./docker-gen
```
@ -43,14 +46,14 @@ $ ./docker-gen
Docker-gen can be bundled inside of a container along-side applications.
[jwilder/nginx-proxy](https://index.docker.io/u/jwilder/nginx-proxy/) trusted build is an example of
[nginx-proxy/nginx-proxy](https://hub.docker.com/r/nginxproxy/nginx-proxy) trusted build is an example of
running docker-gen within a container along-side nginx.
[jwilder/docker-register](https://github.com/jwilder/docker-register) is an example of running
docker-gen within a container to do service registration with etcd.
#### Separate Container Install
It can also be run as two separate containers using the [jwilder/docker-gen](https://index.docker.io/u/jwilder/docker-gen/)
It can also be run as two separate containers using the [nginx-proxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen)
image, together with virtually any other image.
This is how you could run the official [nginx](https://registry.hub.docker.com/_/nginx/) image and
@ -66,11 +69,17 @@ $ docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx
Fetch the template and start the docker-gen container with the shared volume:
```
$ mkdir -p /tmp/templates && cd /tmp/templates
$ curl -o nginx.tmpl https://raw.githubusercontent.com/jwilder/docker-gen/master/templates/nginx.tmpl
$ curl -o nginx.tmpl https://raw.githubusercontent.com/nginx-proxy/docker-gen/main/templates/nginx.tmpl
$ docker run -d --name nginx-gen --volumes-from nginx \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
-v /var/run/docker.sock:/tmp/docker.sock:rw \
-v /tmp/templates:/etc/docker-gen/templates \
-t jwilder/docker-gen -notify-sighup nginx -watch -only-exposed /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
-t nginxproxy/docker-gen -notify-sighup nginx -watch -only-exposed /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
```
Start a container, taking note of any Environment variables a container expects. See the top of a template for details.
```
$ docker run --env VIRTUAL_HOST='example.com' --env VIRTUAL_PORT=80 ...
```
===
@ -95,8 +104,13 @@ Options:
run command after template is regenerated (e.g restart xyz)
-notify-output
log the output(stdout/stderr) of notify command
-notify-container container-ID
container to send a signal to
-notify-signal signal
signal to send to the -notify-container. -1 to call docker restart. Defaults to 1 aka. HUP.
All available signals available on the [dockerclient](https://github.com/fsouza/go-dockerclient/blob/01804dec8a84d0a77e63611f2b62d33e9bb2b64a/signal.go)
-notify-sighup container-ID
send HUP signal to container. Equivalent to 'docker kill -s HUP container-ID'
send HUP signal to container. Equivalent to 'docker kill -s HUP container-ID', or `-notify-container container-ID -notify-signal 1`
-only-exposed
only include containers with exposed ports
-only-published
@ -120,7 +134,7 @@ Options:
Arguments:
template - path to a template to generate
dest - path to a write the template. If not specfied, STDOUT is used
dest - path to write the template. If not specfied, STDOUT is used
Environment Variables:
DOCKER_HOST - default value for -endpoint
@ -143,7 +157,7 @@ An example configuration file, **docker-gen.cfg** can be found in the examples f
Starts a configuration section
dest = "path/to/a/file"
path to a write the template. If not specfied, STDOUT is used
path to write the template. If not specfied, STDOUT is used
notifycmd = "/etc/init.d/foo reload"
run command after template is regenerated (e.g restart xyz)
@ -198,7 +212,7 @@ e75a60548dc9 = 1 # a key can be either container name (nginx) or ID
### Templating
The templates used by docker-gen are written using the Go [text/template](http://golang.org/pkg/text/template/) language. In addition to the [built-in functions](http://golang.org/pkg/text/template/#hdr-Functions) supplied by Go, docker-gen provides a number of additional functions to make it simpler (or possible) to generate your desired output.
The templates used by docker-gen are written using the Go [text/template](http://golang.org/pkg/text/template/) language. In addition to the [built-in functions](http://golang.org/pkg/text/template/#hdr-Functions) supplied by Go, docker-gen uses [sprig](https://masterminds.github.io/sprig/) and some additional functions to make it simpler (or possible) to generate your desired output. Some templates rely on environment variables within the container to make decisions on what to generate from the template.
#### Emit Structure
@ -346,31 +360,34 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
#### Functions
* [Functions from Go](https://pkg.go.dev/text/template#hdr-Functions)
* [Functions from Sprig v3](https://masterminds.github.io/sprig/), except for those that have the same name as one of the following functions.
* *`closest $array $value`*: Returns the longest matching substring in `$array` that matches `$value`
* *`coalesce ...`*: Returns the first non-nil argument.
* *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to `string`.
* *`dict $key $value ...`*: Creates a map from a list of pairs. Each `$key` value must be a `string`, but the `$value` can be any type (or `nil`). Useful for passing more than one value as a pipeline context to subtemplates.
* *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to any type.
* *`dir $path`*: Returns an array of filenames in the specified `$path`.
* *`exists $path`*: Returns `true` if `$path` refers to an existing file or directory. Takes a string.
* *`first $array`*: Returns the first value of an array or nil if the arry is nil or empty.
* *`eval $templateName [$data]`*: Evaluates the named template like Go's built-in `template` action, but instead of writing out the result it returns the result as a string so that it can be post-processed. The `$data` argument may be omitted, which is equivalent to passing `nil`.
* *`groupBy $containers $fieldPath`*: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted.
* *`groupByKeys $containers $fieldPath`*: Returns the same as `groupBy` but only returns the keys of the map.
* *`groupByMulti $containers $fieldPath $sep`*: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings.
* *`groupByLabel $containers $label`*: Returns the same as `groupBy` but grouping by the given label's value.
* *`hasPrefix $prefix $string`*: Returns whether `$prefix` is a prefix of `$string`.
* *`hasSuffix $suffix $string`*: Returns whether `$suffix` is a suffix of `$string`.
* *`intersect $slice1 $slice2`*: Returns the strings that exist in both string slices.
* *`json $value`*: Returns the JSON representation of `$value` as a `string`.
* *`keys $map`*: Returns the keys from `$map`. If `$map` is `nil`, a `nil` is returned. If `$map` is not a `map`, an error will be thrown.
* *`last $array`*: Returns the last value of an array.
* *`parseBool $string`*: parseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. Alias for [`strconv.ParseBool`](http://golang.org/pkg/strconv/#ParseBool)
* *`replace $string $old $new $count`*: Replaces up to `$count` occurences of `$old` with `$new` in `$string`. Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace)
* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`.
* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
* *`splitN $string $sep $count`*: Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN)
* *`sortStringsAsc $strings`: Returns a slice of strings `$strings` sorted in ascending order.
* *`sortStringsDesc $strings`: Returns a slice of strings `$strings` sorted in descending (reverse) order.
* *`sortObjectsByKeysAsc $objects $fieldPath`: Returns the array `$objects`, sorted in ascending order based on the values of a field path expression `$fieldPath`.
* *`sortObjectsByKeysDesc $objects $fieldPath`: Returns the array `$objects`, sorted in descending (reverse) order based on the values of a field path expression `$fieldPath`.
* *`trimPrefix $prefix $string`*: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged.
* *`trimSuffix $suffix $string`*: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged.
* *`trim $string`*: Removes whitespace from both sides of `$string`.
* *`toLower $string`*: Replace capital letters in `$string` to lowercase.
* *`toUpper $string`*: Replace lowercase letters in `$string` to uppercase.
* *`when $condition $trueValue $falseValue`*: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise
* *`where $items $fieldPath $value`*: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items having that value.
* *`whereNot $items $fieldPath $value`*: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items **not** having that value.
@ -392,18 +409,18 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
#### NGINX Reverse Proxy Config
[jwilder/nginx-proxy](https://index.docker.io/u/jwilder/nginx-proxy/) trusted build.
[nginxproxy/nginx-proxy](https://hub.docker.com/r/nginxproxy/nginx-proxy) trusted build.
Start nginx-proxy:
```
$ docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t jwilder/nginx-proxy
$ docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t nginxproxy/nginx-proxy
```
Then start containers with a VIRTUAL_HOST env variable:
Then start containers with a VIRTUAL_HOST (and the VIRTUAL_PORT if more than one port is exposed) env variable:
```
$ docker run -e VIRTUAL_HOST=foo.bar.com -t ...
$ docker run -e VIRTUAL_HOST=foo.bar.com -e VIRTUAL_PORT=80 -t ...
```
If you wanted to run docker-gen directly on the host, you could do it with:
@ -434,8 +451,11 @@ $ docker-gen -notify "/bin/bash /tmp/etcd.sh" -interval 10 templates/etcd.tmpl /
### Development
This project uses [glock](https://github.com/robfig/glock) for managing 3rd party dependencies.
You'll need to install glock into your workspace before hacking on docker-gen.
This project uses [Go Modules](https://golang.org/ref/mod) for managing 3rd party dependencies.
This means that at least `go 1.11` is required.
For `go 1.11` and `go 1.12` it is additionally required to manually enable support by setting `GO111MODULE=on`.
For later versions, this is not required.
```
$ git clone <your fork>

View File

@ -5,38 +5,39 @@ import (
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"sync"
"syscall"
"github.com/BurntSushi/toml"
docker "github.com/fsouza/go-dockerclient"
"github.com/jwilder/docker-gen"
"github.com/nginx-proxy/docker-gen/internal/config"
"github.com/nginx-proxy/docker-gen/internal/generator"
)
type stringslice []string
var (
buildVersion string
version bool
watch bool
wait string
notifyCmd string
notifyOutput bool
notifySigHUPContainerID string
onlyExposed bool
onlyPublished bool
includeStopped bool
configFiles stringslice
configs dockergen.ConfigFile
interval int
keepBlankLines bool
endpoint string
tlsCert string
tlsKey string
tlsCaCert string
tlsVerify bool
tlsCertPath string
wg sync.WaitGroup
buildVersion string
version bool
watch bool
wait string
notifyCmd string
notifyOutput bool
notifyContainerID string
notifyContainerSignal int
onlyExposed bool
onlyPublished bool
includeStopped bool
configFiles stringslice
configs config.ConfigFile
interval int
keepBlankLines bool
endpoint string
tlsCert string
tlsKey string
tlsCaCert string
tlsVerify bool
)
func (strings *stringslice) String() string {
@ -68,7 +69,7 @@ Environment Variables:
DOCKER_CERT_PATH - directory path containing key.pem, cert.pem and ca.pem
DOCKER_TLS_VERIFY - enable client TLS verification
`)
println(`For more information, see https://github.com/jwilder/docker-gen`)
println(`For more information, see https://github.com/nginx-proxy/docker-gen`)
}
func loadConfig(file string) error {
@ -95,8 +96,12 @@ func initFlags() {
flag.BoolVar(&includeStopped, "include-stopped", false, "include stopped containers")
flag.BoolVar(&notifyOutput, "notify-output", false, "log the output(stdout/stderr) of notify command")
flag.StringVar(&notifyCmd, "notify", "", "run command after template is regenerated (e.g `restart xyz`)")
flag.StringVar(&notifySigHUPContainerID, "notify-sighup", "",
flag.StringVar(&notifyContainerID, "notify-sighup", "",
"send HUP signal to container. Equivalent to docker kill -s HUP `container-ID`")
flag.StringVar(&notifyContainerID, "notify-container", "",
"container to send a signal to")
flag.IntVar(&notifyContainerSignal, "notify-signal", int(docker.SIGHUP),
"signal to send to the notify-container. Defaults to SIGHUP")
flag.Var(&configFiles, "config", "config files with template directives. Config files will be merged if this option is specified multiple times.")
flag.IntVar(&interval, "interval", 0, "notify command interval (secs)")
flag.BoolVar(&keepBlankLines, "keep-blank-lines", false, "keep blank lines in the output file")
@ -111,6 +116,10 @@ func initFlags() {
}
func main() {
// SIGHUP is used to trigger generation but go programs call os.Exit(2) at default.
// Ignore the signal until the handler is registered:
signal.Ignore(syscall.SIGHUP)
initFlags()
if version {
@ -131,29 +140,29 @@ func main() {
}
}
} else {
w, err := dockergen.ParseWait(wait)
w, err := config.ParseWait(wait)
if err != nil {
log.Fatalf("Error parsing wait interval: %s\n", err)
}
config := dockergen.Config{
cfg := config.Config{
Template: flag.Arg(0),
Dest: flag.Arg(1),
Watch: watch,
Wait: w,
NotifyCmd: notifyCmd,
NotifyOutput: notifyOutput,
NotifyContainers: make(map[string]docker.Signal),
NotifyContainers: make(map[string]int),
OnlyExposed: onlyExposed,
OnlyPublished: onlyPublished,
IncludeStopped: includeStopped,
Interval: interval,
KeepBlankLines: keepBlankLines,
}
if notifySigHUPContainerID != "" {
config.NotifyContainers[notifySigHUPContainerID] = docker.SIGHUP
if notifyContainerID != "" {
cfg.NotifyContainers[notifyContainerID] = notifyContainerSignal
}
configs = dockergen.ConfigFile{
Config: []dockergen.Config{config}}
configs = config.ConfigFile{
Config: []config.Config{cfg}}
}
all := true
@ -163,7 +172,7 @@ func main() {
}
}
generator, err := dockergen.NewGenerator(dockergen.GeneratorConfig{
generator, err := generator.NewGenerator(generator.GeneratorConfig{
Endpoint: endpoint,
TLSKey: tlsKey,
TLSCert: tlsCert,

View File

@ -1,52 +0,0 @@
package dockergen
import (
"testing"
)
func TestGetCurrentContainerID(t *testing.T) {
currentContainerID := GetCurrentContainerID()
if len(currentContainerID) != 0 && len(currentContainerID) != 64 {
t.Fail()
}
}
func TestGetCurrentContainerID_ECS(t *testing.T) {
cgroup :=
`9:perf_event:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
8:memory:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
7:hugetlb:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
6:freezer:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
5:devices:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
4:cpuset:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
3:cpuacct:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
2:cpu:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f
1:blkio:/ecs/628967a1-46b4-4a8a-84ff-605128f4679e/3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f`
if got, exp := matchECSCurrentContainerID(cgroup), "3c94e08259a6235781bb65f3dec91150c92e9d414ecc410d6245687392d3900f"; got != exp {
t.Fatalf("id mismatch: got %v, exp %v", got, exp)
}
}
func TestGetCurrentContainerID_DockerCE(t *testing.T) {
cgroup :=
`13:name=systemd:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
12:pids:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
11:hugetlb:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
10:net_prio:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
9:perf_event:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
8:net_cls:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
7:freezer:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
6:devices:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
5:memory:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
4:blkio:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
3:cpuacct:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
2:cpu:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb
1:cpuset:/docker-ce/docker/18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb`
if got, exp := matchDockerCurrentContainerID(cgroup), "18862cabc2e0d24142cf93c46ccb6e070c2ea7b996c81c0311ec0309abcbcdfb"; got != exp {
t.Fatalf("id mismatch: got %v, exp %v", got, exp)
}
}

View File

@ -1,196 +0,0 @@
package dockergen
import (
"testing"
)
func TestSplitDockerImageRepository(t *testing.T) {
registry, repository, tag := splitDockerImage("ubuntu")
if registry != "" {
t.Fail()
}
if repository != "ubuntu" {
t.Fail()
}
if tag != "" {
t.Fail()
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "ubuntu" != dockerImage.String() {
t.Fail()
}
}
func TestSplitDockerImageWithRegistry(t *testing.T) {
registry, repository, tag := splitDockerImage("custom.registry/ubuntu")
if registry != "custom.registry" {
t.Fail()
}
if repository != "ubuntu" {
t.Fail()
}
if tag != "" {
t.Fail()
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "custom.registry/ubuntu" != dockerImage.String() {
t.Fail()
}
}
func TestSplitDockerImageWithRegistryAndTag(t *testing.T) {
registry, repository, tag := splitDockerImage("custom.registry/ubuntu:12.04")
if registry != "custom.registry" {
t.Fail()
}
if repository != "ubuntu" {
t.Fail()
}
if tag != "12.04" {
t.Fail()
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "custom.registry/ubuntu:12.04" != dockerImage.String() {
t.Fail()
}
}
func TestSplitDockerImageWithRepositoryAndTag(t *testing.T) {
registry, repository, tag := splitDockerImage("ubuntu:12.04")
if registry != "" {
t.Fail()
}
if repository != "ubuntu" {
t.Fail()
}
if tag != "12.04" {
t.Fail()
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "ubuntu:12.04" != dockerImage.String() {
t.Fail()
}
}
func TestSplitDockerImageWithPrivateRegistryPath(t *testing.T) {
registry, repository, tag := splitDockerImage("localhost:8888/ubuntu/foo:12.04")
if registry != "localhost:8888" {
t.Fail()
}
if repository != "ubuntu/foo" {
t.Fail()
}
if tag != "12.04" {
t.Fail()
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "localhost:8888/ubuntu/foo:12.04" != dockerImage.String() {
t.Fail()
}
}
func TestSplitDockerImageWithLocalRepositoryAndTag(t *testing.T) {
registry, repository, tag := splitDockerImage("localhost:8888/ubuntu:12.04")
if registry != "localhost:8888" {
t.Fatalf("registry does not match: expected %s got %s", "localhost:8888", registry)
}
if repository != "ubuntu" {
t.Fatalf("repository does not match: expected %s got %s", "ubuntu", repository)
}
if tag != "12.04" {
t.Fatalf("tag does not match: expected %s got %s", "12.04", tag)
}
dockerImage := DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
if "localhost:8888/ubuntu:12.04" != dockerImage.String() {
t.Fail()
}
}
func TestParseHostUnix(t *testing.T) {
proto, addr, err := parseHost("unix:///var/run/docker.sock")
if err != nil {
t.Fatalf("%s", err)
}
if proto != "unix" || addr != "/var/run/docker.sock" {
t.Fatal("failed to parse unix:///var/run/docker.sock")
}
}
func TestParseHostUnixDefault(t *testing.T) {
proto, addr, err := parseHost("")
if err != nil {
t.Fatalf("%s", err)
}
if proto != "unix" || addr != "/var/run/docker.sock" {
t.Fatal("failed to parse ''")
}
}
func TestParseHostUnixDefaultNoPath(t *testing.T) {
proto, addr, err := parseHost("unix://")
if err != nil {
t.Fatalf("%s", err)
}
if proto != "unix" || addr != "/var/run/docker.sock" {
t.Fatal("failed to parse unix://")
}
}
func TestParseHostTCP(t *testing.T) {
proto, addr, err := parseHost("tcp://127.0.0.1:4243")
if err != nil {
t.Fatalf("%s", err)
}
if proto != "tcp" || addr != "127.0.0.1:4243" {
t.Fatal("failed to parse tcp://127.0.0.1:4243")
}
}
func TestParseHostTCPDefault(t *testing.T) {
proto, addr, err := parseHost("tcp://:4243")
if err != nil {
t.Fatalf("%s", err)
}
if proto != "tcp" || addr != "127.0.0.1:4243" {
t.Fatal("failed to parse unix:///var/run/docker.sock")
}
}

View File

@ -1,6 +1,6 @@
[Unit]
Description=A file generator that renders templates using Docker Container meta-data.
Documentation=https://github.com/jwilder/docker-gen
Documentation=https://github.com/nginx-proxy/docker-gen
After=network.target docker.socket
Requires=docker.socket

50
go.mod Normal file
View File

@ -0,0 +1,50 @@
module github.com/nginx-proxy/docker-gen
go 1.20
require (
github.com/BurntSushi/toml v1.3.2
github.com/Masterminds/sprig/v3 v3.2.3
github.com/fsouza/go-dockerclient v1.10.0
github.com/stretchr/testify v1.8.4
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/containerd/containerd v1.6.18 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/klauspost/compress v1.11.13 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

198
go.sum Normal file
View File

@ -0,0 +1,198 @@
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns=
github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsouza/go-dockerclient v1.10.0 h1:ppSBsbR60I1DFbV4Ag7LlHlHakHFRNLk9XakATW1yVQ=
github.com/fsouza/go-dockerclient v1.10.0/go.mod h1:+iNzAW78AzClIBTZ6WFjkaMvOgz68GyCJ236b1opLTs=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=

View File

@ -1,11 +1,9 @@
package dockergen
package config
import (
"errors"
"strings"
"time"
"github.com/fsouza/go-dockerclient"
)
type Config struct {
@ -15,7 +13,7 @@ type Config struct {
Wait *Wait
NotifyCmd string
NotifyOutput bool
NotifyContainers map[string]docker.Signal
NotifyContainers map[string]int
OnlyExposed bool
OnlyPublished bool
IncludeStopped bool
@ -75,7 +73,7 @@ func ParseWait(s string) (*Wait, error) {
return nil, err
}
if max < min {
return nil, errors.New("Invalid wait interval: max must be larger than min")
return nil, errors.New("invalid wait interval: max must be larger than min")
}
} else {
max = 4 * min

View File

@ -0,0 +1,61 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilterWatches(t *testing.T) {
testConfigFile := &ConfigFile{
Config: []Config{
{Template: "foo", Watch: true},
{Template: "bar"},
{Template: "baz", Watch: true},
},
}
expected := []Config{
{Template: "foo", Watch: true},
{Template: "baz", Watch: true},
}
configFile := testConfigFile.FilterWatches()
assert.Equal(t, expected, configFile.Config)
}
func TestParseWait(t *testing.T) {
incorrectIntervals := []string{
"500x", // Incorrect min interval
"500s:4x", // Incorrect max interval
"1m:1s", // Min interval larger than max interval
}
for _, intervalString := range incorrectIntervals {
wait, err := ParseWait(intervalString)
assert.Error(t, err)
assert.Nil(t, wait)
}
correctIntervals := map[string]Wait{
"": {0, 0}, // Empty time interval string
"1ms": {1000000, 4000000}, // Correct min interval without max
"1ms:111ms": {1000000, 111000000}, // Correct min:max time interval
}
for intervalString, expectedWait := range correctIntervals {
wait, err := ParseWait(intervalString)
assert.NoError(t, err)
assert.Equal(t, &expectedWait, wait)
}
}
func TestWaitUnmarshalText(t *testing.T) {
// Correct min:max time interval
intervalBytes := []byte("1ms:2ms")
expectedWait := &Wait{1000000, 2000000}
wait := new(Wait)
err := wait.UnmarshalText(intervalBytes)
assert.NoError(t, err)
assert.Equal(t, expectedWait, wait)
}

View File

@ -1,12 +1,14 @@
package dockergen
package context
import (
"bufio"
"fmt"
"os"
"regexp"
"sync"
"github.com/fsouza/go-dockerclient"
docker "github.com/fsouza/go-dockerclient"
"github.com/nginx-proxy/docker-gen/internal/utils"
)
var (
@ -18,7 +20,7 @@ var (
type Context []*RuntimeContainer
func (c *Context) Env() map[string]string {
return splitKeyValueSlice(os.Environ())
return utils.SplitKeyValueSlice(os.Environ())
}
func (c *Context) Docker() Docker {
@ -158,36 +160,78 @@ type Docker struct {
CurrentContainerID string
}
func GetCurrentContainerID() string {
file, err := os.Open("/proc/self/cgroup")
if err != nil {
return ""
// GetCurrentContainerID attempts to extract the current container ID from the provided file paths.
// If no files paths are provided, it will default to /proc/1/cpuset, /proc/self/cgroup and /proc/self/mountinfo.
// It attempts to match the HOSTNAME first then use the fallback method, and returns with the first valid match.
func GetCurrentContainerID(filepaths ...string) (id string) {
if len(filepaths) == 0 {
filepaths = []string{"/proc/1/cpuset", "/proc/self/cgroup", "/proc/self/mountinfo"}
}
reader := bufio.NewReader(file)
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
_, lines, err := bufio.ScanLines([]byte(scanner.Text()), true)
if err == nil {
strLines := string(lines)
if id := matchDockerCurrentContainerID(strLines); id != "" {
return id
} else if id := matchECSCurrentContainerID(strLines); id != "" {
return id
// We try to match a 64 character hex string starting with the hostname first
for _, filepath := range filepaths {
file, err := os.Open(filepath)
if err != nil {
continue
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
_, lines, err := bufio.ScanLines([]byte(scanner.Text()), true)
if err == nil {
strLines := string(lines)
if id = matchContainerIDWithHostname(strLines); len(id) == 64 {
return
}
}
}
}
// If we didn't get any ID that matches the hostname, fall back to matching the first 64 character hex string
for _, filepath := range filepaths {
file, err := os.Open(filepath)
if err != nil {
continue
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
_, lines, err := bufio.ScanLines([]byte(scanner.Text()), true)
if err == nil {
strLines := string(lines)
if id = matchContainerID("([[:alnum:]]{64})", strLines); len(id) == 64 {
return
}
}
}
}
return
}
func matchContainerIDWithHostname(lines string) string {
hostname := os.Getenv("HOSTNAME")
re := regexp.MustCompilePOSIX("^[[:alnum:]]{12}$")
if re.MatchString(hostname) {
regex := fmt.Sprintf("(%s[[:alnum:]]{52})", hostname)
return matchContainerID(regex, lines)
}
return ""
}
func matchDockerCurrentContainerID(lines string) string {
regex := "/docker[/-]([[:alnum:]]{64})(\\.scope)?$"
re := regexp.MustCompilePOSIX(regex)
func matchContainerID(regex, lines string) string {
// Attempt to detect if we're on a line from a /proc/<pid>/mountinfo file and modify the regexp accordingly
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt section 3.5
re := regexp.MustCompilePOSIX("^[0-9]+ [0-9]+ [0-9]+:[0-9]+ /")
if re.MatchString(lines) {
regex = fmt.Sprintf("containers/%v", regex)
}
re = regexp.MustCompilePOSIX(regex)
if re.MatchString(lines) {
submatches := re.FindStringSubmatch(string(lines))
containerID := submatches[1]
@ -196,17 +240,3 @@ func matchDockerCurrentContainerID(lines string) string {
}
return ""
}
func matchECSCurrentContainerID(lines string) string {
regex := "/ecs\\/[^\\/]+\\/(.+)$"
re := regexp.MustCompilePOSIX(regex)
if re.MatchString(string(lines)) {
submatches := re.FindStringSubmatch(string(lines))
containerID := submatches[1]
return containerID
}
return ""
}

View File

@ -0,0 +1,200 @@
package context
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
var (
ids = []string{
"0fa939e22e6938e7517f663de83e79a5087a18b1b997a36e0c933a917cddb295",
"e881f8c51a72db7da515e9d5cab8ed105b869579eb9923fdcf4ee80933160802",
"eede6bd9e72f5d783a4bfb845bd71f310e974cb26987328a5d15704e23a8d6cb",
}
fileKeys = []string{
"cpuset",
"cgroup",
"mountinfo",
}
contents = map[string]string{
"cpuset": fmt.Sprintf("/docker/%v", ids[0]),
"cgroup": fmt.Sprintf(`13:name=systemd:/docker-ce/docker/%[1]v
12:pids:/docker-ce/docker/%[1]v
11:hugetlb:/docker-ce/docker/%[1]v
10:net_prio:/docker-ce/docker/%[1]v
9:perf_event:/docker-ce/docker/%[1]v
8:net_cls:/docker-ce/docker/%[1]v
7:freezer:/docker-ce/docker/%[1]v
6:devices:/docker-ce/docker/%[1]v
5:memory:/docker-ce/docker/%[1]v
4:blkio:/docker-ce/docker/%[1]v
3:cpuacct:/docker-ce/docker/%[1]v
2:cpu:/docker-ce/docker/%[1]v
1:cpuset:/docker-ce/docker/%[1]v`, ids[1]),
"mountinfo": fmt.Sprintf(`705 661 0:96 / / rw,relatime master:192 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CVAK3VWZFQCUGTLHRJHPEKJ4UL:/var/lib/docker/overlay2/l/XMJZ73SKVWVECU7TJCOY62F3H2:/var/lib/docker/overlay2/l/AVNBXO52GHDY3MZU3R4RCSNMCE:/var/lib/docker/overlay2/l/L4IJZ33E6NAMXJ5W3SKJSVX5TS:/var/lib/docker/overlay2/l/JXAUAD5TDJCXA34FGS6NYGUZKT:/var/lib/docker/overlay2/l/TBQDSAFKBSTFMUS3QCFWN5NRLB:/var/lib/docker/overlay2/l/MXIUXRGB7MU4Y4NUNZE2VXTXIN:/var/lib/docker/overlay2/l/HN7E4YWJG7TMG7BXLZTGICTBOA:/var/lib/docker/overlay2/l/65XQPC72Z5VRY4THGASZIQXS57:/var/lib/docker/overlay2/l/BVQKC7LU6D7MOSLBDKFHY7YSO3:/var/lib/docker/overlay2/l/R4GGX3SFPMLXTNM3WKMVOKDTOY:/var/lib/docker/overlay2/l/VHGYTU73JLTRCGX45ZF2VGW4FK,upperdir=/var/lib/docker/overlay2/e1fab975d5ffd51474b11a964c82c3bfda1c0e82aec6845a1f12c8150bf61419/diff,workdir=/var/lib/docker/overlay2/e1fab975d5ffd51474b11a964c82c3bfda1c0e82aec6845a1f12c8150bf61419/work,index=off
706 705 0:105 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
707 705 0:106 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
708 707 0:107 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
709 705 0:108 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro
710 709 0:25 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot
711 707 0:104 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
712 707 0:109 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64
713 705 8:3 /var/lib/docker/containers/%[1]v/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sda3 rw
714 705 8:3 /var/lib/docker/containers/%[1]v/hostname /etc/hostname rw,relatime - ext4 /dev/sda3 rw
715 705 8:3 /var/lib/docker/containers/%[1]v/hosts /etc/hosts rw,relatime - ext4 /dev/sda3 rw
716 705 8:3 /var/lib/docker/volumes/ca8074e1a2eb12edc86c59c5108bb48c31bb7ace4b90beb0da8137a9baa45812/_data /etc/nginx/certs rw,relatime master:1 - ext4 /dev/sda3 rw
717 705 8:3 /var/lib/docker/volumes/2cf8a52c907469a56f6e2cc7d1959d74a4dd04131e7edcd53eaf909db28f770f/_data /etc/nginx/dhparam rw,relatime master:1 - ext4 /dev/sda3 rw
662 707 0:107 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
663 706 0:105 /bus /proc/bus ro,relatime - proc proc rw
664 706 0:105 /fs /proc/fs ro,relatime - proc proc rw
665 706 0:105 /irq /proc/irq ro,relatime - proc proc rw
666 706 0:105 /sys /proc/sys ro,relatime - proc proc rw
667 706 0:105 /sysrq-trigger /proc/sysrq-trigger ro,relatime - proc proc rw
668 706 0:110 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64
669 706 0:106 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
670 706 0:106 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
671 706 0:106 /null /proc/latency_stats rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
672 706 0:106 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
673 706 0:106 /null /proc/sched_debug rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
674 706 0:111 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64
675 709 0:112 / /sys/firmware ro,relatime - tmpfs tmpfs ro,inode64`, ids[2]),
}
)
func TestGetCurrentContainerID(t *testing.T) {
hostname := os.Getenv("HOSTNAME")
defer os.Setenv("HOSTNAME", hostname)
var filepaths []string
// Create temporary files with test content
for _, key := range fileKeys {
file, err := os.CreateTemp("", key)
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if _, err = file.WriteString(contents[key]); err != nil {
t.Fatal(err)
}
filepaths = append(filepaths, file.Name())
}
// Each time the HOSTNAME is set to a short form ID, GetCurrentContainerID() should match and return the corresponding full ID
for _, id := range ids {
os.Setenv("HOSTNAME", id[0:12])
assert.Equal(t, id, GetCurrentContainerID(filepaths...), "id mismatch with default HOSTNAME")
}
// If the Hostname isn't a short form ID, we should match the first valid ID (64 character hex string) instead
os.Setenv("HOSTNAME", "customhostname")
assert.Equal(t, ids[0], GetCurrentContainerID(filepaths...), "id mismatch with custom HOSTNAME")
}
func TestGetCurrentContainerIDMountInfo(t *testing.T) {
// Test specific to cases like https://github.com/nginx-proxy/docker-gen/issues/355
// where only the /proc/<pid>/mountinfo file contains information
hostname := os.Getenv("HOSTNAME")
defer os.Setenv("HOSTNAME", hostname)
os.Setenv("HOSTNAME", "customhostname")
id := ids[2]
content := map[string]string{
"cpuset": "/",
"cgroup": "0::/",
"mountinfo": contents["mountinfo"],
}
var filepaths []string
// Create temporary files with test content
for _, key := range fileKeys {
file, err := os.CreateTemp("", key)
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if _, err = file.WriteString(content[key]); err != nil {
t.Fatal(err)
}
filepaths = append(filepaths, file.Name())
}
// We should match the correct 64 characters long ID in mountinfo, not the first encountered
assert.Equal(t, id, GetCurrentContainerID(filepaths...), "id mismatch on mountinfo")
}
func TestGetCurrentContainerEmpty(t *testing.T) {
assert.Equal(t, "", GetCurrentContainerID())
}
func TestPublishedAddresses(t *testing.T) {
container := &RuntimeContainer{
Addresses: []Address{
{
IP: "172.19.0.1",
HostPort: "80",
},
{
IP: "172.19.0.2",
},
{
IP: "172.19.0.3",
HostPort: "8080",
},
},
}
expected := []Address{
{
IP: "172.19.0.1",
HostPort: "80",
},
{
IP: "172.19.0.3",
HostPort: "8080",
},
}
assert.ElementsMatch(t, expected, container.PublishedAddresses())
}
func TestRuntimeContainerEquals(t *testing.T) {
rc1 := &RuntimeContainer{
ID: "baz",
Image: DockerImage{
Registry: "foo/bar",
},
}
rc2 := &RuntimeContainer{
ID: "baz",
Name: "qux",
Image: DockerImage{
Registry: "foo/bar",
},
}
assert.True(t, rc1.Equals(*rc2))
assert.True(t, rc2.Equals(*rc1))
rc2.Image.Tag = "quux"
assert.False(t, rc1.Equals(*rc2))
assert.False(t, rc2.Equals(*rc1))
}
func TestDockerImageString(t *testing.T) {
image := &DockerImage{Repository: "foo/bar"}
assert.Equal(t, "foo/bar", image.String())
image.Registry = "baz.io"
assert.Equal(t, "baz.io/foo/bar", image.String())
image.Tag = "qux"
assert.Equal(t, "baz.io/foo/bar:qux", image.String())
image.Registry = ""
assert.Equal(t, "foo/bar:qux", image.String())
}

View File

@ -1,4 +1,4 @@
package dockergen
package dockerclient
import (
"errors"
@ -8,14 +8,33 @@ import (
"strings"
docker "github.com/fsouza/go-dockerclient"
"github.com/nginx-proxy/docker-gen/internal/utils"
)
func GetEndpoint(endpoint string) (string, error) {
defaultEndpoint := "unix:///var/run/docker.sock"
if os.Getenv("DOCKER_HOST") != "" {
defaultEndpoint = os.Getenv("DOCKER_HOST")
}
if endpoint != "" {
defaultEndpoint = endpoint
}
_, _, err := parseHost(defaultEndpoint)
if err != nil {
return "", err
}
return defaultEndpoint, nil
}
func NewDockerClient(endpoint string, tlsVerify bool, tlsCert, tlsCaCert, tlsKey string) (*docker.Client, error) {
if strings.HasPrefix(endpoint, "unix:") {
return docker.NewClient(endpoint)
} else if tlsVerify || tlsEnabled(tlsCert, tlsCaCert, tlsKey) {
if tlsVerify {
if e, err := pathExists(tlsCaCert); !e || err != nil {
if e, err := utils.PathExists(tlsCaCert); !e || err != nil {
return nil, errors.New("TLS verification was requested, but CA cert does not exist")
}
}
@ -27,7 +46,7 @@ func NewDockerClient(endpoint string, tlsVerify bool, tlsCert, tlsCaCert, tlsKey
func tlsEnabled(tlsCert, tlsCaCert, tlsKey string) bool {
for _, v := range []string{tlsCert, tlsCaCert, tlsKey} {
if e, err := pathExists(v); e && err == nil {
if e, err := utils.PathExists(v); e && err == nil {
return true
}
}
@ -48,7 +67,7 @@ func parseHost(addr string) (string, string, error) {
addr = strings.TrimSpace(addr)
switch {
case addr == "tcp://":
return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
return "", "", fmt.Errorf("invalid bind address format: %s", addr)
case strings.HasPrefix(addr, "unix://"):
proto = "unix"
addr = strings.TrimPrefix(addr, "unix://")
@ -65,7 +84,7 @@ func parseHost(addr string) (string, string, error) {
addr = "/var/run/docker.sock"
default:
if strings.Contains(addr, "://") {
return "", "", fmt.Errorf("Invalid bind address protocol: %s", addr)
return "", "", fmt.Errorf("invalid bind address protocol: %s", addr)
}
proto = "tcp"
}
@ -73,7 +92,7 @@ func parseHost(addr string) (string, string, error) {
if proto != "unix" && strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
return "", "", fmt.Errorf("invalid bind address format: %s", addr)
}
if hostParts[0] != "" {
host = hostParts[0]
@ -84,11 +103,11 @@ func parseHost(addr string) (string, string, error) {
if p, err := strconv.Atoi(hostParts[1]); err == nil && p != 0 {
port = p
} else {
return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
return "", "", fmt.Errorf("invalid bind address format: %s", addr)
}
} else if proto == "tcp" && !strings.Contains(addr, ":") {
return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
return "", "", fmt.Errorf("invalid bind address format: %s", addr)
} else {
host = addr
}
@ -99,7 +118,7 @@ func parseHost(addr string) (string, string, error) {
return proto, fmt.Sprintf("%s:%d", host, port), nil
}
func splitDockerImage(img string) (string, string, string) {
func SplitDockerImage(img string) (string, string, string) {
index := 0
repository := img
var registry, tag string
@ -118,15 +137,3 @@ func splitDockerImage(img string) (string, string, string) {
return registry, repository, tag
}
// pathExists returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@ -0,0 +1,248 @@
package dockerclient
import (
"fmt"
"os"
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)
func TestDefaultEndpoint(t *testing.T) {
err := os.Unsetenv("DOCKER_HOST")
if err != nil {
t.Fatalf("Unable to unset DOCKER_HOST: %s", err)
}
endpoint, err := GetEndpoint("")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "unix:///var/run/docker.sock" {
t.Fatalf("Expected unix:///var/run/docker.sock, got %s", endpoint)
}
}
func TestDockerHostEndpoint(t *testing.T) {
err := os.Setenv("DOCKER_HOST", "tcp://127.0.0.1:4243")
if err != nil {
t.Fatalf("Unable to set DOCKER_HOST: %s", err)
}
endpoint, err := GetEndpoint("")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "tcp://127.0.0.1:4243" {
t.Fatalf("Expected tcp://127.0.0.1:4243, got %s", endpoint)
}
}
func TestDockerFlagEndpoint(t *testing.T) {
err := os.Setenv("DOCKER_HOST", "tcp://127.0.0.1:4243")
if err != nil {
t.Fatalf("Unable to set DOCKER_HOST: %s", err)
}
// flag value should override DOCKER_HOST and default value
endpoint, err := GetEndpoint("tcp://127.0.0.1:5555")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "tcp://127.0.0.1:5555" {
t.Fatalf("Expected tcp://127.0.0.1:5555, got %s", endpoint)
}
}
func TestUnixBadFormat(t *testing.T) {
endpoint := "unix:/var/run/docker.sock"
_, err := GetEndpoint(endpoint)
if err == nil {
t.Fatal("endpoint should have failed")
}
}
func TestSplitDockerImageRepository(t *testing.T) {
registry, repository, tag := SplitDockerImage("ubuntu")
assert.Equal(t, "", registry)
assert.Equal(t, "ubuntu", repository)
assert.Equal(t, "", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "ubuntu", dockerImage.String())
}
func TestSplitDockerImageWithRegistry(t *testing.T) {
registry, repository, tag := SplitDockerImage("custom.registry/ubuntu")
assert.Equal(t, "custom.registry", registry)
assert.Equal(t, "ubuntu", repository)
assert.Equal(t, "", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "custom.registry/ubuntu", dockerImage.String())
}
func TestSplitDockerImageWithRegistryAndTag(t *testing.T) {
registry, repository, tag := SplitDockerImage("custom.registry/ubuntu:12.04")
assert.Equal(t, "custom.registry", registry)
assert.Equal(t, "ubuntu", repository)
assert.Equal(t, "12.04", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "custom.registry/ubuntu:12.04", dockerImage.String())
}
func TestSplitDockerImageWithRepositoryAndTag(t *testing.T) {
registry, repository, tag := SplitDockerImage("ubuntu:12.04")
assert.Equal(t, "", registry)
assert.Equal(t, "ubuntu", repository)
assert.Equal(t, "12.04", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "ubuntu:12.04", dockerImage.String())
}
func TestSplitDockerImageWithPrivateRegistryPath(t *testing.T) {
registry, repository, tag := SplitDockerImage("localhost:8888/ubuntu/foo:12.04")
assert.Equal(t, "localhost:8888", registry)
assert.Equal(t, "ubuntu/foo", repository)
assert.Equal(t, "12.04", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "localhost:8888/ubuntu/foo:12.04", dockerImage.String())
}
func TestSplitDockerImageWithLocalRepositoryAndTag(t *testing.T) {
registry, repository, tag := SplitDockerImage("localhost:8888/ubuntu:12.04")
assert.Equal(t, "localhost:8888", registry)
assert.Equal(t, "ubuntu", repository)
assert.Equal(t, "12.04", tag)
dockerImage := context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
}
assert.Equal(t, "localhost:8888/ubuntu:12.04", dockerImage.String())
}
func TestParseHostUnix(t *testing.T) {
proto, addr, err := parseHost("unix:///var/run/docker.sock")
assert.NoError(t, err)
assert.Equal(t, "unix", proto, "failed to parse unix:///var/run/docker.sock")
assert.Equal(t, "/var/run/docker.sock", addr, "failed to parse unix:///var/run/docker.sock")
}
func TestParseHostUnixDefault(t *testing.T) {
proto, addr, err := parseHost("")
assert.NoError(t, err)
assert.Equal(t, "unix", proto, "failed to parse ''")
assert.Equal(t, "/var/run/docker.sock", addr, "failed to parse ''")
}
func TestParseHostUnixDefaultNoPath(t *testing.T) {
proto, addr, err := parseHost("unix://")
assert.NoError(t, err)
assert.Equal(t, "unix", proto, "failed to parse unix://")
assert.Equal(t, "/var/run/docker.sock", addr, "failed to parse unix://")
}
func TestParseHostTCP(t *testing.T) {
proto, addr, err := parseHost("tcp://127.0.0.1:4243")
assert.NoError(t, err)
assert.Equal(t, "tcp", proto, "failed to parse tcp://127.0.0.1:4243")
assert.Equal(t, "127.0.0.1:4243", addr, "failed to parse tcp://127.0.0.1:4243")
}
func TestParseHostTCPDefault(t *testing.T) {
proto, addr, err := parseHost("tcp://:4243")
assert.NoError(t, err)
assert.Equal(t, "tcp", proto, "failed to parse tcp://:4243")
assert.Equal(t, "127.0.0.1:4243", addr, "failed to parse tcp://:4243")
}
func TestParseHostSystemd(t *testing.T) {
proto, addr, err := parseHost("fd://")
assert.NoError(t, err)
assert.Equal(t, "fd", proto, "failed to parse fd://")
assert.Equal(t, "fd://", addr, "failed to parse fd://")
}
func assertParseHostError(t *testing.T, address string) {
proto, addr, err := parseHost(address)
message := fmt.Sprintf("should have failed to parse %v", address)
assert.Error(t, err, message)
assert.Equal(t, "", proto, message)
assert.Equal(t, "", addr, message)
}
func TestParseHostTCPNoAddressError(t *testing.T) {
assertParseHostError(t, "tcp://")
}
func TestParseHostTCPIncorrectBindAddressError(t *testing.T) {
incorrectBindAdresses := []string{
"tcp://127.0.0.1:4243:80",
"tcp://127.0.0.1:",
"tcp://127.0.0.1",
}
for _, address := range incorrectBindAdresses {
assertParseHostError(t, address)
}
}
func TestParseHostWrongProtocolError(t *testing.T) {
assertParseHostError(t, "foo://")
}
func TestTlsEnabled(t *testing.T) {
tls := tlsEnabled("foo", "bar", "baz")
assert.False(t, tls)
filepaths := map[string]string{
"cert": "",
"caCert": "",
"key": "",
}
// Create temporary files
for key := range filepaths {
file, err := os.CreateTemp("", key)
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
filepaths[key] = file.Name()
}
tls = tlsEnabled(filepaths["cert"], filepaths["caCert"], filepaths["key"])
assert.True(t, tls)
}

View File

@ -1,4 +1,4 @@
package dockergen
package generator
import (
"fmt"
@ -11,12 +11,17 @@ import (
"syscall"
"time"
"github.com/fsouza/go-dockerclient"
docker "github.com/fsouza/go-dockerclient"
"github.com/nginx-proxy/docker-gen/internal/config"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/nginx-proxy/docker-gen/internal/dockerclient"
"github.com/nginx-proxy/docker-gen/internal/template"
"github.com/nginx-proxy/docker-gen/internal/utils"
)
type generator struct {
Client *docker.Client
Configs ConfigFile
Configs config.ConfigFile
Endpoint string
TLSVerify bool
TLSCert, TLSCaCert, TLSKey string
@ -35,18 +40,18 @@ type GeneratorConfig struct {
TLSVerify bool
All bool
ConfigFile ConfigFile
ConfigFile config.ConfigFile
}
func NewGenerator(gc GeneratorConfig) (*generator, error) {
endpoint, err := GetEndpoint(gc.Endpoint)
endpoint, err := dockerclient.GetEndpoint(gc.Endpoint)
if err != nil {
return nil, fmt.Errorf("Bad endpoint: %s", err)
return nil, fmt.Errorf("bad endpoint: %s", err)
}
client, err := NewDockerClient(endpoint, gc.TLSVerify, gc.TLSCert, gc.TLSCACert, gc.TLSKey)
client, err := dockerclient.NewDockerClient(endpoint, gc.TLSVerify, gc.TLSCert, gc.TLSCACert, gc.TLSKey)
if err != nil {
return nil, fmt.Errorf("Unable to create docker client: %s", err)
return nil, fmt.Errorf("unable to create docker client: %s", err)
}
apiVersion, err := client.Version()
@ -55,7 +60,7 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) {
}
// Grab the docker daemon info once and hold onto it
SetDockerEnv(apiVersion)
context.SetDockerEnv(apiVersion)
return &generator{
Client: client,
@ -98,14 +103,15 @@ func (g *generator) generateFromSignals() {
go func() {
defer g.wg.Done()
sigChan := newSignalChannel()
sigChan, cleanup := newSignalChannel()
defer cleanup()
for {
sig := <-sigChan
log.Printf("Received signal: %s\n", sig)
switch sig {
case syscall.SIGHUP:
g.generateFromContainers()
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
case syscall.SIGTERM, syscall.SIGINT:
// exit when context is done
return
}
@ -120,7 +126,7 @@ func (g *generator) generateFromContainers() {
return
}
for _, config := range g.Configs.Config {
changed := GenerateFile(config, containers)
changed := template.GenerateFile(config, containers)
if !changed {
log.Printf("Contents of %s did not change. Skipping notification '%s'", config.Dest, config.NotifyCmd)
continue
@ -131,19 +137,20 @@ func (g *generator) generateFromContainers() {
}
func (g *generator) generateAtInterval() {
for _, config := range g.Configs.Config {
for _, cfg := range g.Configs.Config {
if config.Interval == 0 {
if cfg.Interval == 0 {
continue
}
log.Printf("Generating every %d seconds", config.Interval)
log.Printf("Generating every %d seconds", cfg.Interval)
g.wg.Add(1)
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
go func(config Config) {
ticker := time.NewTicker(time.Duration(cfg.Interval) * time.Second)
go func(cfg config.Config) {
defer g.wg.Done()
sigChan := newSignalChannel()
sigChan, cleanup := newSignalChannel()
defer cleanup()
for {
select {
case <-ticker.C:
@ -153,19 +160,19 @@ func (g *generator) generateAtInterval() {
continue
}
// ignore changed return value. always run notify command
GenerateFile(config, containers)
g.runNotifyCmd(config)
g.sendSignalToContainer(config)
template.GenerateFile(cfg, containers)
g.runNotifyCmd(cfg)
g.sendSignalToContainer(cfg)
case sig := <-sigChan:
log.Printf("Received signal: %s\n", sig)
switch sig {
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
case syscall.SIGTERM, syscall.SIGINT:
ticker.Stop()
return
}
}
}
}(config)
}(cfg)
}
}
@ -178,54 +185,55 @@ func (g *generator) generateFromEvents() {
client := g.Client
var watchers []chan *docker.APIEvents
for _, config := range configs.Config {
for _, cfg := range configs.Config {
if !config.Watch {
if !cfg.Watch {
continue
}
g.wg.Add(1)
watcher := make(chan *docker.APIEvents, 100)
watchers = append(watchers, watcher)
go func(config Config, watcher chan *docker.APIEvents) {
go func(cfg config.Config) {
defer g.wg.Done()
watchers = append(watchers, watcher)
debouncedChan := newDebounceChannel(watcher, config.Wait)
for _ = range debouncedChan {
debouncedChan := newDebounceChannel(watcher, cfg.Wait)
for range debouncedChan {
containers, err := g.getContainers()
if err != nil {
log.Printf("Error listing containers: %s\n", err)
continue
}
changed := GenerateFile(config, containers)
changed := template.GenerateFile(cfg, containers)
if !changed {
log.Printf("Contents of %s did not change. Skipping notification '%s'", config.Dest, config.NotifyCmd)
log.Printf("Contents of %s did not change. Skipping notification '%s'", cfg.Dest, cfg.NotifyCmd)
continue
}
g.runNotifyCmd(config)
g.sendSignalToContainer(config)
g.runNotifyCmd(cfg)
g.sendSignalToContainer(cfg)
}
}(config, make(chan *docker.APIEvents, 100))
}(cfg)
}
// maintains docker client connection and passes events to watchers
go func() {
// channel will be closed by go-dockerclient
eventChan := make(chan *docker.APIEvents, 100)
sigChan := newSignalChannel()
sigChan, cleanup := newSignalChannel()
defer cleanup()
for {
watching := false
if client == nil {
var err error
endpoint, err := GetEndpoint(g.Endpoint)
endpoint, err := dockerclient.GetEndpoint(g.Endpoint)
if err != nil {
log.Printf("Bad endpoint: %s", err)
time.Sleep(10 * time.Second)
continue
}
client, err = NewDockerClient(endpoint, g.TLSVerify, g.TLSCert, g.TLSCaCert, g.TLSKey)
client, err = dockerclient.NewDockerClient(endpoint, g.TLSVerify, g.TLSCert, g.TLSCaCert, g.TLSKey)
if err != nil {
log.Printf("Unable to connect to docker daemon: %s", err)
time.Sleep(10 * time.Second)
@ -291,7 +299,7 @@ func (g *generator) generateFromEvents() {
case sig := <-sigChan:
log.Printf("Received signal: %s\n", sig)
switch sig {
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
case syscall.SIGTERM, syscall.SIGINT:
// close all watchers and exit
for _, watcher := range watchers {
close(watcher)
@ -304,7 +312,7 @@ func (g *generator) generateFromEvents() {
}()
}
func (g *generator) runNotifyCmd(config Config) {
func (g *generator) runNotifyCmd(config config.Config) {
if config.NotifyCmd == "" {
return
}
@ -324,16 +332,24 @@ func (g *generator) runNotifyCmd(config Config) {
}
}
func (g *generator) sendSignalToContainer(config Config) {
func (g *generator) sendSignalToContainer(config config.Config) {
if len(config.NotifyContainers) < 1 {
return
}
for container, signal := range config.NotifyContainers {
log.Printf("Sending container '%s' signal '%v'", container, signal)
if signal == -1 {
if err := g.Client.RestartContainer(container, 10); err != nil {
log.Printf("Error sending restarting container: %s", err)
}
return
}
killOpts := docker.KillContainerOptions{
ID: container,
Signal: signal,
Signal: docker.Signal(signal),
}
if err := g.Client.KillContainer(killOpts); err != nil {
log.Printf("Error sending signal to container: %s", err)
@ -341,12 +357,12 @@ func (g *generator) sendSignalToContainer(config Config) {
}
}
func (g *generator) getContainers() ([]*RuntimeContainer, error) {
func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
apiInfo, err := g.Client.Info()
if err != nil {
log.Printf("Error retrieving docker server info: %s\n", err)
} else {
SetServerInfo(apiInfo)
context.SetServerInfo(apiInfo)
}
apiContainers, err := g.Client.ListContainers(docker.ListContainersOptions{
@ -357,40 +373,41 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
return nil, err
}
containers := []*RuntimeContainer{}
containers := []*context.RuntimeContainer{}
for _, apiContainer := range apiContainers {
container, err := g.Client.InspectContainer(apiContainer.ID)
opts := docker.InspectContainerOptions{ID: apiContainer.ID}
container, err := g.Client.InspectContainerWithOptions(opts)
if err != nil {
log.Printf("Error inspecting container: %s: %s\n", apiContainer.ID, err)
continue
}
registry, repository, tag := splitDockerImage(container.Config.Image)
runtimeContainer := &RuntimeContainer{
registry, repository, tag := dockerclient.SplitDockerImage(container.Config.Image)
runtimeContainer := &context.RuntimeContainer{
ID: container.ID,
Image: DockerImage{
Image: context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
},
State: State{
State: context.State{
Running: container.State.Running,
},
Name: strings.TrimLeft(container.Name, "/"),
Hostname: container.Config.Hostname,
Gateway: container.NetworkSettings.Gateway,
Addresses: []Address{},
Networks: []Network{},
Addresses: []context.Address{},
Networks: []context.Network{},
Env: make(map[string]string),
Volumes: make(map[string]Volume),
Node: SwarmNode{},
Volumes: make(map[string]context.Volume),
Node: context.SwarmNode{},
Labels: make(map[string]string),
IP: container.NetworkSettings.IPAddress,
IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address,
IP6Global: container.NetworkSettings.GlobalIPv6Address,
}
for k, v := range container.NetworkSettings.Ports {
address := Address{
address := context.Address{
IP: container.NetworkSettings.IPAddress,
IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address,
IP6Global: container.NetworkSettings.GlobalIPv6Address,
@ -406,7 +423,7 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
}
for k, v := range container.NetworkSettings.Networks {
network := Network{
network := context.Network{
IP: v.IPAddress,
Name: k,
Gateway: v.Gateway,
@ -422,7 +439,7 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
network)
}
for k, v := range container.Volumes {
runtimeContainer.Volumes[k] = Volume{
runtimeContainer.Volumes[k] = context.Volume{
Path: k,
HostPath: v,
ReadWrite: container.VolumesRW[k],
@ -431,13 +448,13 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
if container.Node != nil {
runtimeContainer.Node.ID = container.Node.ID
runtimeContainer.Node.Name = container.Node.Name
runtimeContainer.Node.Address = Address{
runtimeContainer.Node.Address = context.Address{
IP: container.Node.IP,
}
}
for _, v := range container.Mounts {
runtimeContainer.Mounts = append(runtimeContainer.Mounts, Mount{
runtimeContainer.Mounts = append(runtimeContainer.Mounts, context.Mount{
Name: v.Name,
Source: v.Source,
Destination: v.Destination,
@ -447,7 +464,7 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
})
}
runtimeContainer.Env = splitKeyValueSlice(container.Config.Env)
runtimeContainer.Env = utils.SplitKeyValueSlice(container.Config.Env)
runtimeContainer.Labels = container.Config.Labels
containers = append(containers, runtimeContainer)
}
@ -455,14 +472,13 @@ func (g *generator) getContainers() ([]*RuntimeContainer, error) {
}
func newSignalChannel() <-chan os.Signal {
func newSignalChannel() (<-chan os.Signal, func()) {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGKILL)
return sig
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
return sig, func() { signal.Stop(sig) }
}
func newDebounceChannel(input chan *docker.APIEvents, wait *Wait) chan *docker.APIEvents {
func newDebounceChannel(input chan *docker.APIEvents, wait *config.Wait) chan *docker.APIEvents {
if wait == nil {
return input
}

View File

@ -1,25 +1,29 @@
package dockergen
package generator
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"os"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/fsouza/go-dockerclient"
docker "github.com/fsouza/go-dockerclient"
dockertest "github.com/fsouza/go-dockerclient/testing"
"github.com/nginx-proxy/docker-gen/internal/config"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/nginx-proxy/docker-gen/internal/dockerclient"
)
func TestGenerateFromEvents(t *testing.T) {
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
containerID := "8dfafdbc3a40"
counter := 0
var counter atomic.Int32
eventsResponse := `
{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067924}
@ -35,7 +39,7 @@ func TestGenerateFromEvents(t *testing.T) {
for rsc.Scan() {
w.Write([]byte(rsc.Text()))
w.(http.Flusher).Flush()
time.Sleep(15 * time.Millisecond)
time.Sleep(150 * time.Millisecond)
}
time.Sleep(500 * time.Millisecond)
}))
@ -49,7 +53,7 @@ func TestGenerateFromEvents(t *testing.T) {
}))
server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
result := []docker.APIContainers{
docker.APIContainers{
{
ID: containerID,
Image: "base:latest",
Command: "/bin/sh",
@ -64,7 +68,7 @@ func TestGenerateFromEvents(t *testing.T) {
json.NewEncoder(w).Encode(result)
}))
server.CustomHandler(fmt.Sprintf("/containers/%s/json", containerID), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter++
counter := counter.Add(1)
container := docker.Container{
Name: "docker-gen-test",
ID: containerID,
@ -87,7 +91,7 @@ func TestGenerateFromEvents(t *testing.T) {
},
Image: "0ff407d5a7d9ed36acdf3e75de8cc127afecc9af234d05486be2981cdc01a38d",
NetworkSettings: &docker.NetworkSettings{
IPAddress: fmt.Sprintf("10.0.0.10"),
IPAddress: "10.0.0.10",
IPPrefixLen: 24,
Gateway: "10.0.0.1",
Bridge: "docker0",
@ -102,13 +106,13 @@ func TestGenerateFromEvents(t *testing.T) {
}))
serverURL := fmt.Sprintf("tcp://%s", strings.TrimRight(strings.TrimPrefix(server.URL(), "http://"), "/"))
client, err := NewDockerClient(serverURL, false, "", "", "")
client, err := dockerclient.NewDockerClient(serverURL, false, "", "", "")
if err != nil {
t.Errorf("Failed to create client: %s", err)
}
client.SkipServerVersionCheck = true
tmplFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-tmpl")
tmplFile, err := os.CreateTemp(os.TempDir(), "docker-gen-tmpl")
if err != nil {
t.Errorf("Failed to create temp file: %v\n", err)
}
@ -116,14 +120,14 @@ func TestGenerateFromEvents(t *testing.T) {
tmplFile.Close()
os.Remove(tmplFile.Name())
}()
err = ioutil.WriteFile(tmplFile.Name(), []byte("{{range $key, $value := .}}{{$value.ID}}.{{$value.Env.COUNTER}}{{end}}"), 0644)
err = os.WriteFile(tmplFile.Name(), []byte("{{range $key, $value := .}}{{$value.ID}}.{{$value.Env.COUNTER}}{{end}}"), 0644)
if err != nil {
t.Errorf("Failed to write to temp file: %v\n", err)
}
var destFiles []*os.File
for i := 0; i < 4; i++ {
destFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-out")
destFile, err := os.CreateTemp(os.TempDir(), "docker-gen-out")
if err != nil {
t.Errorf("Failed to create temp file: %v\n", err)
}
@ -140,35 +144,35 @@ func TestGenerateFromEvents(t *testing.T) {
if err != nil {
t.Errorf("Failed to retrieve docker server version info: %v\n", err)
}
SetDockerEnv(apiVersion) // prevents a panic
context.SetDockerEnv(apiVersion) // prevents a panic
generator := &generator{
Client: client,
Endpoint: serverURL,
Configs: ConfigFile{
[]Config{
Config{
Configs: config.ConfigFile{
Config: []config.Config{
{
Template: tmplFile.Name(),
Dest: destFiles[0].Name(),
Watch: false,
},
Config{
{
Template: tmplFile.Name(),
Dest: destFiles[1].Name(),
Watch: true,
Wait: &Wait{0, 0},
Wait: &config.Wait{Min: 0, Max: 0},
},
Config{
{
Template: tmplFile.Name(),
Dest: destFiles[2].Name(),
Watch: true,
Wait: &Wait{20 * time.Millisecond, 25 * time.Millisecond},
Wait: &config.Wait{Min: 200 * time.Millisecond, Max: 250 * time.Millisecond},
},
Config{
{
Template: tmplFile.Name(),
Dest: destFiles[3].Name(),
Watch: true,
Wait: &Wait{25 * time.Millisecond, 100 * time.Millisecond},
Wait: &config.Wait{Min: 250 * time.Millisecond, Max: 1 * time.Second},
},
},
},
@ -185,12 +189,12 @@ func TestGenerateFromEvents(t *testing.T) {
// The counter is incremented in each output file in the following sequence:
//
// init 0ms 5ms 10ms 15ms 20ms 25ms 30ms 35ms 40ms 45ms 50ms 55ms
// init 150ms 200ms 250ms 300ms 350ms 400ms 450ms 500ms 550ms 600ms 650ms 700ms
// ├──────╫──────┼──────┼──────╫──────┼──────┼──────╫──────┼──────┼──────┼──────┼──────┤
// File0 ├─ 1 ║ ║ ║
// File1 ├─ 1 ╟─ 2 ╟─ 3 ╟─ 5
// File2 ├─ 1 ╟───── max (25ms) ──║───────────> 4 ╟─────── min (20ms) ─────> 6
// File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (25ms) ────────> 7
// File2 ├─ 1 ╟───── max (250ms) ──║───────────> 4 ╟─────── min (200ms) ─────> 6
// File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (250ms) ────────> 7
// ┌───╨───┐ ┌───╨──┐ ┌───╨───┐
// │ start │ │ stop │ │ start │
// └───────┘ └──────┘ └───────┘
@ -198,7 +202,7 @@ func TestGenerateFromEvents(t *testing.T) {
expectedCounters := []int{1, 5, 6, 7}
for i, counter := range expectedCounters {
value, _ = ioutil.ReadFile(destFiles[i].Name())
value, _ = os.ReadFile(destFiles[i].Name())
expected = fmt.Sprintf("%s.%d", containerID, counter)
if string(value) != expected {
t.Errorf("expected: %s. got: %s", expected, value)

View File

@ -0,0 +1,165 @@
package template
import (
"bytes"
"crypto/sha1"
"encoding/json"
"fmt"
"io"
"log"
"os"
"reflect"
"strings"
)
func keys(input interface{}) (interface{}, error) {
if input == nil {
return nil, nil
}
val := reflect.ValueOf(input)
if val.Kind() != reflect.Map {
return nil, fmt.Errorf("cannot call keys on a non-map value: %v", input)
}
vk := val.MapKeys()
k := make([]interface{}, val.Len())
for i := range k {
k[i] = vk[i].Interface()
}
return k, nil
}
func intersect(l1, l2 []string) []string {
m := make(map[string]bool)
m2 := make(map[string]bool)
for _, v := range l2 {
m2[v] = true
}
for _, v := range l1 {
if m2[v] {
m[v] = true
}
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func contains(input interface{}, key interface{}) bool {
if input == nil {
return false
}
val := reflect.ValueOf(input)
if val.Kind() == reflect.Map {
for _, k := range val.MapKeys() {
if k.Interface() == key {
return true
}
}
}
return false
}
func hashSha1(input string) string {
h := sha1.New()
io.WriteString(h, input)
return fmt.Sprintf("%x", h.Sum(nil))
}
func marshalJson(input interface{}) (string, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(input); err != nil {
return "", err
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}
func unmarshalJson(input string) (interface{}, error) {
var v interface{}
if err := json.Unmarshal([]byte(input), &v); err != nil {
return nil, err
}
return v, nil
}
// arrayClosest find the longest matching substring in values
// that matches input
func arrayClosest(values []string, input string) string {
best := ""
for _, v := range values {
if strings.Contains(input, v) && len(v) > len(best) {
best = v
}
}
return best
}
// dirList returns a list of files in the specified path
func dirList(path string) ([]string, error) {
names := []string{}
files, err := os.ReadDir(path)
if err != nil {
log.Printf("Template error: %v", err)
return names, nil
}
for _, f := range files {
names = append(names, f.Name())
}
return names, nil
}
// coalesce returns the first non nil argument
func coalesce(input ...interface{}) interface{} {
for _, v := range input {
if v != nil {
return v
}
}
return nil
}
// coalesceempty returns the first non nil argument or empty
func coalesceempty(input ...interface{}) interface{} {
for _, v := range input {
if v != nil && len(fmt.Sprintf("%v", v)) > 0 {
return v
}
}
return nil
}
// trimPrefix returns a string without the prefix, if present
func trimPrefix(prefix, s string) string {
return strings.TrimPrefix(s, prefix)
}
// trimSuffix returns a string without the suffix, if present
func trimSuffix(suffix, s string) string {
return strings.TrimSuffix(s, suffix)
}
// toLower return the string in lower case
func toLower(s string) string {
return strings.ToLower(s)
}
// toUpper return the string in upper case
func toUpper(s string) string {
return strings.ToUpper(s)
}
// when returns the trueValue when the condition is true and the falseValue otherwise
func when(condition bool, trueValue, falseValue interface{}) interface{} {
if condition {
return trueValue
} else {
return falseValue
}
}

View File

@ -0,0 +1,300 @@
package template
import (
"bytes"
"encoding/json"
"os"
"path"
"reflect"
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)
func TestContainsString(t *testing.T) {
env := map[string]string{
"PORT": "1234",
}
assert.True(t, contains(env, "PORT"))
assert.False(t, contains(env, "MISSING"))
}
func TestContainsInteger(t *testing.T) {
env := map[int]int{
42: 1234,
}
assert.True(t, contains(env, 42))
assert.False(t, contains(env, "WRONG TYPE"))
assert.False(t, contains(env, 24))
}
func TestContainsNilInput(t *testing.T) {
var env interface{} = nil
assert.False(t, contains(env, 0))
assert.False(t, contains(env, ""))
}
func TestKeys(t *testing.T) {
env := map[string]string{
"VIRTUAL_HOST": "demo.local",
}
tests := templateTestList{
{`{{range (keys $)}}{{.}}{{end}}`, env, `VIRTUAL_HOST`},
}
tests.run(t)
}
func TestKeysEmpty(t *testing.T) {
input := map[string]int{}
k, err := keys(input)
if err != nil {
t.Fatalf("Error fetching keys: %v", err)
}
vk := reflect.ValueOf(k)
if vk.Kind() == reflect.Invalid {
t.Fatalf("Got invalid kind for keys: %v", vk)
}
if len(input) != vk.Len() {
t.Fatalf("Incorrect key count; expected %d, got %d", len(input), vk.Len())
}
}
func TestKeysNil(t *testing.T) {
k, err := keys(nil)
if err != nil {
t.Fatalf("Error fetching keys: %v", err)
}
vk := reflect.ValueOf(k)
if vk.Kind() != reflect.Invalid {
t.Fatalf("Got invalid kind for keys: %v", vk)
}
}
func TestIntersect(t *testing.T) {
i := intersect([]string{"foo.fo.com", "bar.com"}, []string{"foo.bar.com"})
assert.Len(t, i, 0, "Expected no match")
i = intersect([]string{"foo.fo.com", "bar.com"}, []string{"bar.com", "foo.com"})
assert.Len(t, i, 1, "Expected exactly one match")
i = intersect([]string{"foo.com"}, []string{"bar.com", "foo.com"})
assert.Len(t, i, 1, "Expected exactly one match")
i = intersect([]string{"foo.fo.com", "foo.com", "bar.com"}, []string{"bar.com", "foo.com"})
assert.Len(t, i, 2, "Expected exactly two matches")
}
func TestSplitN(t *testing.T) {
tests := templateTestList{
{`{{index (splitN . "/" 2) 0}}`, "example.com/path", `example.com`},
{`{{index (splitN . "/" 2) 1}}`, "example.com/path", `path`},
{`{{index (splitN . "/" 2) 1}}`, "example.com/a/longer/path", `a/longer/path`},
{`{{len (splitN . "/" 2)}}`, "example.com", `1`},
}
tests.run(t)
}
func TestTrimPrefix(t *testing.T) {
const prefix = "tcp://"
const str = "tcp://127.0.0.1:2375"
const trimmed = "127.0.0.1:2375"
got := trimPrefix(prefix, str)
if got != trimmed {
t.Fatalf("expected trimPrefix(%s,%s) to be %s, got %s", prefix, str, trimmed, got)
}
}
func TestTrimSuffix(t *testing.T) {
const suffix = ".local"
const str = "myhost.local"
const trimmed = "myhost"
got := trimSuffix(suffix, str)
if got != trimmed {
t.Fatalf("expected trimSuffix(%s,%s) to be %s, got %s", suffix, str, trimmed, got)
}
}
func TestToLower(t *testing.T) {
const str = ".RaNd0m StrinG_"
const lowered = ".rand0m string_"
assert.Equal(t, lowered, toLower(str), "Unexpected value from toLower()")
}
func TestToUpper(t *testing.T) {
const str = ".RaNd0m StrinG_"
const uppered = ".RAND0M STRING_"
assert.Equal(t, uppered, toUpper(str), "Unexpected value from toUpper()")
}
func TestSha1(t *testing.T) {
sum := hashSha1("/path")
if sum != "4f26609ad3f5185faaa9edf1e93aa131e2131352" {
t.Fatal("Incorrect SHA1 sum")
}
}
func TestJson(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
output, err := marshalJson(containers)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBufferString(output)
dec := json.NewDecoder(buf)
if err != nil {
t.Fatal(err)
}
var decoded []*context.RuntimeContainer
if err := dec.Decode(&decoded); err != nil {
t.Fatal(err)
}
if len(decoded) != len(containers) {
t.Fatalf("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded))
}
}
func TestParseJson(t *testing.T) {
tests := templateTestList{
{`{{parseJson .}}`, `null`, `<no value>`},
{`{{parseJson .}}`, `true`, `true`},
{`{{parseJson .}}`, `1`, `1`},
{`{{parseJson .}}`, `0.5`, `0.5`},
{`{{index (parseJson .) "enabled"}}`, `{"enabled":true}`, `true`},
{`{{index (parseJson . | first) "enabled"}}`, `[{"enabled":true}]`, `true`},
}
tests.run(t)
}
func TestQueryEscape(t *testing.T) {
tests := templateTestList{
{`{{queryEscape .}}`, `example.com`, `example.com`},
{`{{queryEscape .}}`, `.example.com`, `.example.com`},
{`{{queryEscape .}}`, `*.example.com`, `%2A.example.com`},
{`{{queryEscape .}}`, `~^example\.com(\..*\.xip\.io)?$`, `~%5Eexample%5C.com%28%5C..%2A%5C.xip%5C.io%29%3F%24`},
}
tests.run(t)
}
func TestArrayClosestExact(t *testing.T) {
if arrayClosest([]string{"foo.bar.com", "bar.com"}, "foo.bar.com") != "foo.bar.com" {
t.Fatal("Expected foo.bar.com")
}
}
func TestArrayClosestSubstring(t *testing.T) {
if arrayClosest([]string{"foo.fo.com", "bar.com"}, "foo.bar.com") != "bar.com" {
t.Fatal("Expected bar.com")
}
}
func TestArrayClosestNoMatch(t *testing.T) {
if arrayClosest([]string{"foo.fo.com", "bip.com"}, "foo.bar.com") != "" {
t.Fatal("Expected ''")
}
}
func TestWhen(t *testing.T) {
context := struct {
BoolValue bool
StringValue string
}{
true,
"foo",
}
tests := templateTestList{
{`{{ print (when .BoolValue "first" "second") }}`, context, `first`},
{`{{ print (when (eq .StringValue "foo") "first" "second") }}`, context, `first`},
{`{{ when (not .BoolValue) "first" "second" | print }}`, context, `second`},
{`{{ when (not (eq .StringValue "foo")) "first" "second" | print }}`, context, `second`},
}
tests.run(t)
}
func TestWhenTrue(t *testing.T) {
if when(true, "first", "second") != "first" {
t.Fatal("Expected first value")
}
}
func TestWhenFalse(t *testing.T) {
if when(false, "first", "second") != "second" {
t.Fatal("Expected second value")
}
}
func TestDirList(t *testing.T) {
dir, err := os.MkdirTemp("", "dirList")
if err != nil {
t.Fatal(err)
}
defer os.Remove(dir)
files := map[string]string{
"aaa": "",
"bbb": "",
"ccc": "",
}
// Create temporary files
for key := range files {
file, err := os.CreateTemp(dir, key)
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
files[key] = file.Name()
}
expected := []string{
path.Base(files["aaa"]),
path.Base(files["bbb"]),
path.Base(files["ccc"]),
}
filesList, _ := dirList(dir)
assert.Equal(t, expected, filesList)
filesList, _ = dirList("/wrong/path")
assert.Equal(t, []string{}, filesList)
}
func TestCoalesce(t *testing.T) {
v := coalesce(nil, "second", "third")
assert.Equal(t, "second", v, "Expected second value")
v = coalesce(nil, nil, nil)
assert.Nil(t, v, "Expected nil value")
}

View File

@ -0,0 +1,88 @@
package template
import (
"fmt"
"strings"
"github.com/nginx-proxy/docker-gen/internal/context"
)
// Generalized groupBy function
func generalizedGroupBy(funcName string, entries interface{}, getValue func(interface{}) (interface{}, error), addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)
if err != nil {
return nil, err
}
groups := make(map[string][]interface{})
for i := 0; i < entriesVal.Len(); i++ {
v := entriesVal.Index(i).Interface()
value, err := getValue(v)
if err != nil {
return nil, err
}
if value != nil {
addEntry(groups, value, v)
}
}
return groups, nil
}
func generalizedGroupByKey(funcName string, entries interface{}, key string, addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
getKey := func(v interface{}) (interface{}, error) {
return deepGet(v, key), nil
}
return generalizedGroupBy(funcName, entries, getKey, addEntry)
}
func groupByMulti(entries interface{}, key, sep string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupByMulti", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
items := strings.Split(value.(string), sep)
for _, item := range items {
if item != "" {
groups[item] = append(groups[item], v)
}
}
})
}
// groupBy groups a generic array or slice by the path property key
func groupBy(entries interface{}, key string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupBy", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}
// groupByKeys is the same as groupBy but only returns a list of keys
func groupByKeys(entries interface{}, key string) ([]string, error) {
keys, err := generalizedGroupByKey("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
if err != nil {
return nil, err
}
ret := []string{}
for k := range keys {
ret = append(ret, k)
}
return ret, nil
}
// groupByLabel is the same as groupBy but over a given label
func groupByLabel(entries interface{}, label string) (map[string][]interface{}, error) {
getLabel := func(v interface{}) (interface{}, error) {
if container, ok := v.(*context.RuntimeContainer); ok {
if value, ok := container.Labels[label]; ok {
return value, nil
}
return nil, nil
}
return nil, fmt.Errorf("must pass an array or slice of *RuntimeContainer to 'groupByLabel'; received %v", v)
}
return generalizedGroupBy("groupByLabel", entries, getLabel, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}

View File

@ -0,0 +1,205 @@
package template
import (
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)
func TestGroupByExistingKey(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
groups, err := groupBy(containers, "Env.VIRTUAL_HOST")
assert.NoError(t, err)
assert.Len(t, groups, 2)
assert.Len(t, groups["demo1.localhost"], 2)
assert.Len(t, groups["demo2.localhost"], 1)
assert.Equal(t, "3", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID)
}
func TestGroupByAfterWhere(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"EXTERNAL": "true",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
"EXTERNAL": "true",
},
ID: "3",
},
}
filtered, _ := where(containers, "Env.EXTERNAL", "true")
groups, err := groupBy(filtered, "Env.VIRTUAL_HOST")
assert.NoError(t, err)
assert.Len(t, groups, 2)
assert.Len(t, groups["demo1.localhost"], 1)
assert.Len(t, groups["demo2.localhost"], 1)
assert.Equal(t, "3", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID)
}
func TestGroupByKeys(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
expected := []string{"demo1.localhost", "demo2.localhost"}
groups, err := groupByKeys(containers, "Env.VIRTUAL_HOST")
assert.NoError(t, err)
assert.ElementsMatch(t, expected, groups)
expected = []string{"1", "2", "3"}
groups, err = groupByKeys(containers, "ID")
assert.NoError(t, err)
assert.ElementsMatch(t, expected, groups)
}
func TestGeneralizedGroupByError(t *testing.T) {
groups, err := groupBy("string", "")
assert.Error(t, err)
assert.Nil(t, groups)
}
func TestGroupByLabel(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "1",
},
{
Labels: map[string]string{
"com.docker.compose.project": "two",
},
ID: "2",
},
{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "3",
},
{
ID: "4",
},
{
Labels: map[string]string{
"com.docker.compose.project": "",
},
ID: "5",
},
}
groups, err := groupByLabel(containers, "com.docker.compose.project")
assert.NoError(t, err)
assert.Len(t, groups, 3)
assert.Len(t, groups["one"], 2)
assert.Len(t, groups[""], 1)
assert.Len(t, groups["two"], 1)
assert.Equal(t, "2", groups["two"][0].(*context.RuntimeContainer).ID)
}
func TestGroupByLabelError(t *testing.T) {
strings := []string{"foo", "bar", "baz"}
groups, err := groupByLabel(strings, "")
assert.Error(t, err)
assert.Nil(t, groups)
}
func TestGroupByMulti(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
groups, _ := groupByMulti(containers, "Env.VIRTUAL_HOST", ",")
if len(groups) != 3 {
t.Fatalf("expected 3 got %d", len(groups))
}
if len(groups["demo1.localhost"]) != 2 {
t.Fatalf("expected 2 got %d", len(groups["demo1.localhost"]))
}
if len(groups["demo2.localhost"]) != 1 {
t.Fatalf("expected 1 got %d", len(groups["demo2.localhost"]))
}
if groups["demo2.localhost"][0].(*context.RuntimeContainer).ID != "3" {
t.Fatalf("expected 2 got %s", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID)
}
if len(groups["demo3.localhost"]) != 1 {
t.Fatalf("expect 1 got %d", len(groups["demo3.localhost"]))
}
if groups["demo3.localhost"][0].(*context.RuntimeContainer).ID != "2" {
t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].(*context.RuntimeContainer).ID)
}
}

View File

@ -0,0 +1,57 @@
package template
import (
"log"
"math"
"reflect"
"strconv"
"strings"
)
func deepGetImpl(v reflect.Value, path []string) interface{} {
if !v.IsValid() {
return nil
}
if len(path) == 0 {
return v.Interface()
}
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
if v.Kind() == reflect.Pointer {
log.Printf("unable to descend into pointer of a pointer\n")
return nil
}
switch v.Kind() {
case reflect.Struct:
return deepGetImpl(v.FieldByName(path[0]), path[1:])
case reflect.Map:
return deepGetImpl(v.MapIndex(reflect.ValueOf(path[0])), path[1:])
case reflect.Slice, reflect.Array:
iu64, err := strconv.ParseUint(path[0], 10, 64)
if err != nil {
log.Printf("non-negative decimal number required for array/slice index, got %#v\n", path[0])
return nil
}
if iu64 > math.MaxInt {
iu64 = math.MaxInt
}
i := int(iu64)
if i >= v.Len() {
log.Printf("index %v out of bounds", i)
return nil
}
return deepGetImpl(v.Index(i), path[1:])
default:
log.Printf("unable to index by %s (value %v, kind %s)\n", path[0], v, v.Kind())
return nil
}
}
func deepGet(item interface{}, path string) interface{} {
var parts []string
if path != "" {
parts = strings.Split(strings.TrimPrefix(path, "."), ".")
}
return deepGetImpl(reflect.ValueOf(item), parts)
}

View File

@ -0,0 +1,95 @@
package template
import (
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)
func TestDeepGetNoPath(t *testing.T) {
item := context.RuntimeContainer{}
value := deepGet(item, "")
if _, ok := value.(context.RuntimeContainer); !ok {
t.Fail()
}
returned := value.(context.RuntimeContainer)
if !returned.Equals(item) {
t.Fail()
}
}
func TestDeepGetSimple(t *testing.T) {
item := context.RuntimeContainer{
ID: "expected",
}
value := deepGet(item, "ID")
assert.IsType(t, "", value)
assert.Equal(t, "expected", value)
}
func TestDeepGetSimpleDotPrefix(t *testing.T) {
item := context.RuntimeContainer{
ID: "expected",
}
value := deepGet(item, ".ID")
assert.IsType(t, "", value)
assert.Equal(t, "expected", value)
}
func TestDeepGetMap(t *testing.T) {
item := context.RuntimeContainer{
Env: map[string]string{
"key": "value",
},
}
value := deepGet(item, "Env.key")
assert.IsType(t, "", value)
assert.Equal(t, "value", value)
}
func TestDeepGet(t *testing.T) {
s := struct{ X string }{"foo"}
sp := &s
for _, tc := range []struct {
desc string
item interface{}
path string
want interface{}
}{
{
"map key empty string",
map[string]map[string]map[string]string{
"": {
"": {
"": "foo",
},
},
},
"...",
"foo",
},
{"struct", s, "X", "foo"},
{"pointer to struct", sp, "X", "foo"},
{"double pointer to struct", &sp, ".X", nil},
{"slice index", []string{"foo", "bar"}, "1", "bar"},
{"slice index out of bounds", []string{}, "0", nil},
{"slice index negative", []string{}, "-1", nil},
{"slice index nonnumber", []string{}, "foo", nil},
{"array index", [2]string{"foo", "bar"}, "1", "bar"},
{"array index out of bounds", [1]string{"foo"}, "1", nil},
{"array index negative", [1]string{"foo"}, "-1", nil},
{"array index nonnumber", [1]string{"foo"}, "foo", nil},
} {
t.Run(tc.desc, func(t *testing.T) {
got := deepGet(tc.item, tc.path)
assert.IsType(t, tc.want, got)
assert.Equal(t, tc.want, got)
})
}
}

90
internal/template/sort.go Normal file
View File

@ -0,0 +1,90 @@
package template
import (
"reflect"
"sort"
)
// sortStrings returns a sorted array of strings in increasing order
func sortStringsAsc(values []string) []string {
sort.Strings(values)
return values
}
// sortStringsDesc returns a sorted array of strings in decreasing order
func sortStringsDesc(values []string) []string {
sort.Sort(sort.Reverse(sort.StringSlice(values)))
return values
}
type sortable interface {
sort.Interface
set(string, interface{}) error
get() []interface{}
}
type sortableData struct {
data []interface{}
}
func (s sortableData) get() []interface{} {
return s.data
}
func (s sortableData) Len() int { return len(s.data) }
func (s sortableData) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
type sortableByKey struct {
sortableData
key string
}
func (s *sortableByKey) set(funcName string, entries interface{}) (err error) {
entriesVal, err := getArrayValues(funcName, entries)
if err != nil {
return
}
s.data = make([]interface{}, entriesVal.Len())
for i := 0; i < entriesVal.Len(); i++ {
s.data[i] = entriesVal.Index(i).Interface()
}
return
}
// method required to implement sort.Interface
func (s sortableByKey) Less(i, j int) bool {
values := map[int]string{i: "", j: ""}
for k := range values {
if v := reflect.ValueOf(deepGet(s.data[k], s.key)); v.Kind() != reflect.Invalid {
values[k] = v.Interface().(string)
}
}
return values[i] < values[j]
}
// Generalized SortBy function
func generalizedSortBy(funcName string, entries interface{}, s sortable, reverse bool) (sorted []interface{}, err error) {
err = s.set(funcName, entries)
if err != nil {
return nil, err
}
if reverse {
sort.Stable(sort.Reverse(s))
} else {
sort.Stable(s)
}
return s.get(), nil
}
// sortObjectsByKeysAsc returns a sorted array of objects, sorted by object's key field in ascending order
func sortObjectsByKeysAsc(objs interface{}, key string) ([]interface{}, error) {
s := &sortableByKey{key: key}
return generalizedSortBy("sortObjsByKeys", objs, s, false)
}
// sortObjectsByKeysDesc returns a sorted array of objects, sorted by object's key field in descending order
func sortObjectsByKeysDesc(objs interface{}, key string) ([]interface{}, error) {
s := &sortableByKey{key: key}
return generalizedSortBy("sortObjsByKey", objs, s, true)
}

View File

@ -0,0 +1,66 @@
package template
import (
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/stretchr/testify/assert"
)
func TestSortStringsAsc(t *testing.T) {
strings := []string{"foo", "bar", "baz", "qux"}
expected := []string{"bar", "baz", "foo", "qux"}
assert.Equal(t, expected, sortStringsAsc(strings))
}
func TestSortStringsDesc(t *testing.T) {
strings := []string{"foo", "bar", "baz", "qux"}
expected := []string{"qux", "foo", "baz", "bar"}
assert.Equal(t, expected, sortStringsDesc(strings))
}
func TestSortObjectsByKeys(t *testing.T) {
o0 := &context.RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "bar.localhost",
},
ID: "9",
}
o1 := &context.RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "foo.localhost",
},
ID: "1",
}
o2 := &context.RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "baz.localhost",
},
ID: "3",
}
o3 := &context.RuntimeContainer{
Env: map[string]string{},
ID: "8",
}
containers := []*context.RuntimeContainer{o0, o1, o2, o3}
for _, tc := range []struct {
desc string
fn func(interface{}, string) ([]interface{}, error)
key string
want []interface{}
}{
{"Asc simple", sortObjectsByKeysAsc, "ID", []interface{}{o1, o2, o3, o0}},
{"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}},
{"Desc simple", sortObjectsByKeysDesc, "ID", []interface{}{o0, o3, o2, o1}},
{"Desc complex", sortObjectsByKeysDesc, "Env.VIRTUAL_HOST", []interface{}{o1, o2, o0, o3}},
} {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.fn(containers, tc.key)
assert.NoError(t, err)
// The function should return a sorted copy of the slice, not modify the original.
assert.Equal(t, []*context.RuntimeContainer{o0, o1, o2, o3}, containers)
assert.Equal(t, tc.want, got)
})
}
}

View File

@ -0,0 +1,254 @@
package template
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
"net/url"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"syscall"
"text/template"
"unicode"
sprig "github.com/Masterminds/sprig/v3"
"github.com/nginx-proxy/docker-gen/internal/config"
"github.com/nginx-proxy/docker-gen/internal/context"
"github.com/nginx-proxy/docker-gen/internal/utils"
)
func read(path string) (string, error) {
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
b, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(b), nil
}
func getArrayValues(funcName string, entries interface{}) (*reflect.Value, error) {
entriesVal := reflect.ValueOf(entries)
kind := entriesVal.Kind()
if kind == reflect.Ptr {
entriesVal = entriesVal.Elem()
kind = entriesVal.Kind()
}
switch kind {
case reflect.Array, reflect.Slice:
break
default:
return nil, fmt.Errorf("must pass an array or slice to '%v'; received %v; kind %v", funcName, entries, kind)
}
return &entriesVal, nil
}
func newTemplate(name string) *template.Template {
tmpl := template.New(name)
// The eval function is defined here because it must be a closure around tmpl.
eval := func(name string, args ...any) (string, error) {
buf := bytes.NewBuffer(nil)
data := any(nil)
if len(args) == 1 {
data = args[0]
} else if len(args) > 1 {
return "", errors.New("too many arguments")
}
if err := tmpl.ExecuteTemplate(buf, name, data); err != nil {
return "", err
}
return buf.String(), nil
}
tmpl.Funcs(sprig.TxtFuncMap()).Funcs(template.FuncMap{
"closest": arrayClosest,
"coalesce": coalesce,
"coalesceempty": coalesceempty,
"contains": contains,
"dir": dirList,
"eval": eval,
"exists": utils.PathExists,
"read": read,
"groupBy": groupBy,
"groupByKeys": groupByKeys,
"groupByMulti": groupByMulti,
"groupByLabel": groupByLabel,
"json": marshalJson,
"intersect": intersect,
"keys": keys,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": unmarshalJson,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"sortStringsAsc": sortStringsAsc,
"sortStringsDesc": sortStringsDesc,
"sortObjectsByKeysAsc": sortObjectsByKeysAsc,
"sortObjectsByKeysDesc": sortObjectsByKeysDesc,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"toLower": toLower,
"toUpper": toUpper,
"when": when,
"where": where,
"whereNot": whereNot,
"whereExist": whereExist,
"whereNotExist": whereNotExist,
"whereAny": whereAny,
"whereAll": whereAll,
"whereLabelExists": whereLabelExists,
"whereLabelDoesNotExist": whereLabelDoesNotExist,
"whereLabelValueMatches": whereLabelValueMatches,
})
return tmpl
}
func isBlank(str string) bool {
for _, r := range str {
if !unicode.IsSpace(r) {
return false
}
}
return true
}
func removeBlankLines(reader io.Reader, writer io.Writer) {
breader := bufio.NewReader(reader)
bwriter := bufio.NewWriter(writer)
for {
line, err := breader.ReadString('\n')
if !isBlank(line) {
bwriter.WriteString(line)
}
if err != nil {
break
}
}
bwriter.Flush()
}
func filterRunning(config config.Config, containers context.Context) context.Context {
if config.IncludeStopped {
return containers
} else {
filteredContainers := context.Context{}
for _, container := range containers {
if container.State.Running {
filteredContainers = append(filteredContainers, container)
}
}
return filteredContainers
}
}
func GenerateFile(config config.Config, containers context.Context) bool {
filteredRunningContainers := filterRunning(config, containers)
filteredContainers := context.Context{}
if config.OnlyPublished {
for _, container := range filteredRunningContainers {
if len(container.PublishedAddresses()) > 0 {
filteredContainers = append(filteredContainers, container)
}
}
} else if config.OnlyExposed {
for _, container := range filteredRunningContainers {
if len(container.Addresses) > 0 {
filteredContainers = append(filteredContainers, container)
}
}
} else {
filteredContainers = filteredRunningContainers
}
contents := executeTemplate(config.Template, filteredContainers)
if !config.KeepBlankLines {
buf := new(bytes.Buffer)
removeBlankLines(bytes.NewReader(contents), buf)
contents = buf.Bytes()
}
if config.Dest != "" {
dest, err := os.CreateTemp(filepath.Dir(config.Dest), "docker-gen")
defer func() {
dest.Close()
os.Remove(dest.Name())
}()
if err != nil {
log.Fatalf("Unable to create temp file: %s\n", err)
}
if n, err := dest.Write(contents); n != len(contents) || err != nil {
log.Fatalf("Failed to write to temp file: wrote %d, exp %d, err=%v", n, len(contents), err)
}
oldContents := []byte{}
if fi, err := os.Stat(config.Dest); err == nil || os.IsNotExist(err) {
if err != nil && os.IsNotExist(err) {
emptyFile, err := os.Create(config.Dest)
if err != nil {
log.Fatalf("Unable to create empty destination file: %s\n", err)
} else {
emptyFile.Close()
fi, _ = os.Stat(config.Dest)
}
}
if err := dest.Chmod(fi.Mode()); err != nil {
log.Fatalf("Unable to chmod temp file: %s\n", err)
}
if err := dest.Chown(int(fi.Sys().(*syscall.Stat_t).Uid), int(fi.Sys().(*syscall.Stat_t).Gid)); err != nil {
log.Fatalf("Unable to chown temp file: %s\n", err)
}
oldContents, err = os.ReadFile(config.Dest)
if err != nil {
log.Fatalf("Unable to compare current file contents: %s: %s\n", config.Dest, err)
}
}
if !bytes.Equal(oldContents, contents) {
err = os.Rename(dest.Name(), config.Dest)
if err != nil {
log.Fatalf("Unable to create dest file %s: %s\n", config.Dest, err)
}
log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers))
return true
}
return false
} else {
os.Stdout.Write(contents)
}
return true
}
func executeTemplate(templatePath string, containers context.Context) []byte {
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
if err != nil {
log.Fatalf("Unable to parse template: %s", err)
}
buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
if err != nil {
log.Fatalf("Template error: %s\n", err)
}
return buf.Bytes()
}

View File

@ -0,0 +1,195 @@
package template
import (
"bytes"
"errors"
"reflect"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
type templateTestList []struct {
tmpl string
context interface{}
expected interface{}
}
func (tests templateTestList) run(t *testing.T) {
for n, test := range tests {
test := test
t.Run(strconv.Itoa(n), func(t *testing.T) {
t.Parallel()
wantErr, _ := test.expected.(error)
want, ok := test.expected.(string)
if !ok && wantErr == nil {
t.Fatalf("test bug: want a string or error for .expected, got %v", test.expected)
}
tmpl, err := newTemplate("testTemplate").Parse(test.tmpl)
if err != nil {
t.Fatalf("Template parse failed: %v", err)
}
var b bytes.Buffer
err = tmpl.ExecuteTemplate(&b, "testTemplate", test.context)
got := b.String()
if err != nil {
if wantErr != nil {
return
}
t.Fatalf("Error executing template: %v", err)
} else if wantErr != nil {
t.Fatalf("Expected error, got %v", got)
}
if want != got {
t.Fatalf("Incorrect output found; want %#v, got %#v", want, got)
}
})
}
}
func TestGetArrayValues(t *testing.T) {
values := []string{"foor", "bar", "baz"}
var expectedType *reflect.Value
arrayValues, err := getArrayValues("testFunc", values)
assert.NoError(t, err)
assert.IsType(t, expectedType, arrayValues)
assert.Equal(t, "bar", arrayValues.Index(1).String())
arrayValues, err = getArrayValues("testFunc", &values)
assert.NoError(t, err)
assert.IsType(t, expectedType, arrayValues)
assert.Equal(t, "baz", arrayValues.Index(2).String())
arrayValues, err = getArrayValues("testFunc", "foo")
assert.Error(t, err)
assert.Nil(t, arrayValues)
}
func TestIsBlank(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"", true},
{" ", true},
{" ", true},
{"\t", true},
{"\t\n\v\f\r\u0085\u00A0", true},
{"a", false},
{" a ", false},
{"a ", false},
{" a", false},
{"日本語", false},
}
for _, i := range tests {
v := isBlank(i.input)
if v != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, v)
}
}
}
func TestRemoveBlankLines(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"", ""},
{"\r\n\r\n", ""},
{"line1\nline2", "line1\nline2"},
{"line1\n\nline2", "line1\nline2"},
{"\n\n\n\nline1\n\nline2", "line1\nline2"},
{"\n\n\n\n\n \n \n \n", ""},
// windows line endings \r\n
{"line1\r\nline2", "line1\r\nline2"},
{"line1\r\n\r\nline2", "line1\r\nline2"},
// keep last new line
{"line1\n", "line1\n"},
{"line1\r\n", "line1\r\n"},
}
for _, i := range tests {
output := new(bytes.Buffer)
removeBlankLines(strings.NewReader(i.input), output)
if output.String() != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, output)
}
}
}
// TestSprig ensures that the migration to sprig to provide certain functions did not break
// compatibility with existing templates.
func TestSprig(t *testing.T) {
for _, tc := range []struct {
desc string
tts templateTestList
}{
{"dict", templateTestList{
{`{{ $d := dict "a" "b" }}{{ if eq (index $d "a") "b" }}ok{{ end }}`, nil, `ok`},
{`{{ $d := dict "a" "b" }}{{ if eq (index $d "x") nil }}ok{{ end }}`, nil, `ok`},
{`{{ $d := dict "a" "b" "c" (dict "d" "e") }}{{ if eq (index $d "c" "d") "e" }}ok{{ end }}`, nil, `ok`},
}},
{"first", templateTestList{
{`{{ if eq (first $) "a"}}ok{{ end }}`, []string{"a", "b"}, `ok`},
{`{{ if eq (first $) "a"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`},
}},
{"hasPrefix", templateTestList{
{`{{ if hasPrefix "tcp://" "tcp://127.0.0.1:2375" }}ok{{ end }}`, nil, `ok`},
{`{{ if not (hasPrefix "udp://" "tcp://127.0.0.1:2375") }}ok{{ end }}`, nil, `ok`},
}},
{"hasSuffix", templateTestList{
{`{{ if hasSuffix ".local" "myhost.local" }}ok{{ end }}`, nil, `ok`},
{`{{ if not (hasSuffix ".example" "myhost.local") }}ok{{ end }}`, nil, `ok`},
}},
{"last", templateTestList{
{`{{ if eq (last $) "b"}}ok{{ end }}`, []string{"a", "b"}, `ok`},
{`{{ if eq (last $) "b"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`},
}},
{"trim", templateTestList{
{`{{ if eq (trim " myhost.local ") "myhost.local" }}ok{{ end }}`, nil, `ok`},
}},
} {
t.Run(tc.desc, func(t *testing.T) {
tc.tts.run(t)
})
}
}
func TestEval(t *testing.T) {
for _, tc := range []struct {
desc string
tts templateTestList
}{
{"undefined", templateTestList{
{`{{eval "missing"}}`, nil, errors.New("")},
{`{{eval "missing" nil}}`, nil, errors.New("")},
{`{{eval "missing" "abc"}}`, nil, errors.New("")},
{`{{eval "missing" "abc" "def"}}`, nil, errors.New("")},
}},
// The purpose of the "ctx" context is to assert that $ and . inside the template is the
// eval argument, not the global context.
{"noArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T"}}`, "ctx", "<no value><no value>"},
}},
{"nilArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" nil}}`, "ctx", "<no value><no value>"},
}},
{"oneArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "arg"}}`, "ctx", "argarg"},
}},
{"moreThanOneArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "a" "b"}}`, "ctx", errors.New("")},
}},
} {
t.Run(tc.desc, func(t *testing.T) {
tc.tts.run(t)
})
}
}

125
internal/template/where.go Normal file
View File

@ -0,0 +1,125 @@
package template
import (
"reflect"
"regexp"
"strings"
"github.com/nginx-proxy/docker-gen/internal/context"
)
// Generalized where function
func generalizedWhere(funcName string, entries interface{}, key string, test func(interface{}) bool) (interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)
if err != nil {
return nil, err
}
selection := make([]interface{}, 0)
for i := 0; i < entriesVal.Len(); i++ {
v := entriesVal.Index(i).Interface()
value := deepGet(v, key)
if test(value) {
selection = append(selection, v)
}
}
return selection, nil
}
// selects entries based on key
func where(entries interface{}, key string, cmp interface{}) (interface{}, error) {
return generalizedWhere("where", entries, key, func(value interface{}) bool {
return reflect.DeepEqual(value, cmp)
})
}
// select entries where a key is not equal to a value
func whereNot(entries interface{}, key string, cmp interface{}) (interface{}, error) {
return generalizedWhere("whereNot", entries, key, func(value interface{}) bool {
return !reflect.DeepEqual(value, cmp)
})
}
// selects entries where a key exists
func whereExist(entries interface{}, key string) (interface{}, error) {
return generalizedWhere("whereExist", entries, key, func(value interface{}) bool {
return value != nil
})
}
// selects entries where a key does not exist
func whereNotExist(entries interface{}, key string) (interface{}, error) {
return generalizedWhere("whereNotExist", entries, key, func(value interface{}) bool {
return value == nil
})
}
// selects entries based on key. Assumes key is delimited and breaks it apart before comparing
func whereAny(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
return generalizedWhere("whereAny", entries, key, func(value interface{}) bool {
if value == nil {
return false
} else {
items := strings.Split(value.(string), sep)
return len(intersect(cmp, items)) > 0
}
})
}
// selects entries based on key. Assumes key is delimited and breaks it apart before comparing
func whereAll(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
req_count := len(cmp)
return generalizedWhere("whereAll", entries, key, func(value interface{}) bool {
if value == nil {
return false
} else {
items := strings.Split(value.(string), sep)
return len(intersect(cmp, items)) == req_count
}
})
}
// generalized whereLabel function
func generalizedWhereLabel(funcName string, containers context.Context, label string, test func(string, bool) bool) (context.Context, error) {
selection := make([]*context.RuntimeContainer, 0)
for i := 0; i < len(containers); i++ {
container := containers[i]
value, ok := container.Labels[label]
if test(value, ok) {
selection = append(selection, container)
}
}
return selection, nil
}
// selects containers that have a particular label
func whereLabelExists(containers context.Context, label string) (context.Context, error) {
return generalizedWhereLabel("whereLabelExists", containers, label, func(_ string, ok bool) bool {
return ok
})
}
// selects containers that have don't have a particular label
func whereLabelDoesNotExist(containers context.Context, label string) (context.Context, error) {
return generalizedWhereLabel("whereLabelDoesNotExist", containers, label, func(_ string, ok bool) bool {
return !ok
})
}
// selects containers with a particular label whose value matches a regular expression
func whereLabelValueMatches(containers context.Context, label, pattern string) (context.Context, error) {
rx, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
return generalizedWhereLabel("whereLabelValueMatches", containers, label, func(value string, ok bool) bool {
return ok && rx.MatchString(value)
})
}

View File

@ -0,0 +1,374 @@
package template
import (
"testing"
"github.com/nginx-proxy/docker-gen/internal/context"
)
func TestWhere(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
Addresses: []context.Address{
{
IP: "172.16.42.1",
Port: "80",
Proto: "tcp",
},
},
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
Addresses: []context.Address{
{
IP: "172.16.42.1",
Port: "9999",
Proto: "tcp",
},
},
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{where . "Env.VIRTUAL_HOST" "demo1.localhost" | len}}`, containers, `1`},
{`{{where . "Env.VIRTUAL_HOST" "demo2.localhost" | len}}`, containers, `2`},
{`{{where . "Env.VIRTUAL_HOST" "demo3.localhost" | len}}`, containers, `1`},
{`{{where . "Env.NOEXIST" "demo3.localhost" | len}}`, containers, `0`},
{`{{where .Addresses "Port" "80" | len}}`, containers[0], `1`},
{`{{where .Addresses "Port" "80" | len}}`, containers[1], `0`},
{
`{{where . "Value" 5 | len}}`,
[]struct {
Value int
}{
{Value: 5},
{Value: 3},
{Value: 5},
},
`2`,
},
}
tests.run(t)
}
func TestWhereNot(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
Addresses: []context.Address{
{
IP: "172.16.42.1",
Port: "80",
Proto: "tcp",
},
},
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
Addresses: []context.Address{
{
IP: "172.16.42.1",
Port: "9999",
Proto: "tcp",
},
},
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereNot . "Env.VIRTUAL_HOST" "demo1.localhost" | len}}`, containers, `3`},
{`{{whereNot . "Env.VIRTUAL_HOST" "demo2.localhost" | len}}`, containers, `2`},
{`{{whereNot . "Env.VIRTUAL_HOST" "demo3.localhost" | len}}`, containers, `3`},
{`{{whereNot . "Env.NOEXIST" "demo3.localhost" | len}}`, containers, `4`},
{`{{whereNot .Addresses "Port" "80" | len}}`, containers[0], `0`},
{`{{whereNot .Addresses "Port" "80" | len}}`, containers[1], `1`},
{
`{{whereNot . "Value" 5 | len}}`,
[]struct {
Value int
}{
{Value: 5},
{Value: 3},
{Value: 5},
},
`1`,
},
}
tests.run(t)
}
func TestWhereExist(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_PROTO": "https",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereExist . "Env.VIRTUAL_HOST" | len}}`, containers, `3`},
{`{{whereExist . "Env.VIRTUAL_PATH" | len}}`, containers, `2`},
{`{{whereExist . "Env.NOT_A_KEY" | len}}`, containers, `0`},
{`{{whereExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `1`},
}
tests.run(t)
}
func TestWhereNotExist(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_PROTO": "https",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereNotExist . "Env.VIRTUAL_HOST" | len}}`, containers, `1`},
{`{{whereNotExist . "Env.VIRTUAL_PATH" | len}}`, containers, `2`},
{`{{whereNotExist . "Env.NOT_A_KEY" | len}}`, containers, `4`},
{`{{whereNotExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `3`},
}
tests.run(t)
}
func TestWhereSomeMatch(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost,demo4.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "bar,demo3.localhost,foo",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "demo1.localhost" ",") | len}}`, containers, `1`},
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "demo2.localhost,lala" ",") | len}}`, containers, `2`},
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "something,demo3.localhost" ",") | len}}`, containers, `1`},
{`{{whereAny . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`},
}
tests.run(t)
}
func TestWhereRequires(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost,demo4.localhost",
},
ID: "2",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "bar,demo3.localhost,foo",
},
ID: "3",
},
{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo1.localhost" ",") | len}}`, containers, `1`},
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo2.localhost,lala" ",") | len}}`, containers, `0`},
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo3.localhost" ",") | len}}`, containers, `1`},
{`{{whereAll . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`},
}
tests.run(t)
}
func TestWhereLabelExists(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
{
Labels: map[string]string{
"com.example.bar": "bar",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelExists . "com.example.foo" | len}}`, containers, `1`},
{`{{whereLabelExists . "com.example.bar" | len}}`, containers, `2`},
{`{{whereLabelExists . "com.example.baz" | len}}`, containers, `0`},
}
tests.run(t)
}
func TestWhereLabelDoesNotExist(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
{
Labels: map[string]string{
"com.example.bar": "bar",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelDoesNotExist . "com.example.foo" | len}}`, containers, `1`},
{`{{whereLabelDoesNotExist . "com.example.bar" | len}}`, containers, `0`},
{`{{whereLabelDoesNotExist . "com.example.baz" | len}}`, containers, `2`},
}
tests.run(t)
}
func TestWhereLabelValueMatches(t *testing.T) {
containers := []*context.RuntimeContainer{
{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
{
Labels: map[string]string{
"com.example.bar": "BAR",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelValueMatches . "com.example.foo" "^foo$" | len}}`, containers, `1`},
{`{{whereLabelValueMatches . "com.example.foo" "\\d+" | len}}`, containers, `0`},
{`{{whereLabelValueMatches . "com.example.bar" "^bar$" | len}}`, containers, `1`},
{`{{whereLabelValueMatches . "com.example.bar" "^(?i)bar$" | len}}`, containers, `2`},
{`{{whereLabelValueMatches . "com.example.bar" ".*" | len}}`, containers, `2`},
{`{{whereLabelValueMatches . "com.example.baz" ".*" | len}}`, containers, `0`},
}
tests.run(t)
}

34
internal/utils/utils.go Normal file
View File

@ -0,0 +1,34 @@
package utils
import (
"os"
"strings"
)
// SplitKeyValueSlice takes a string slice where values are of the form
// KEY, KEY=, KEY=VALUE or KEY=NESTED_KEY=VALUE2, and returns a map[string]string where items
// are split at their first `=`.
func SplitKeyValueSlice(in []string) map[string]string {
env := make(map[string]string)
for _, entry := range in {
parts := strings.SplitN(entry, "=", 2)
if len(parts) != 2 {
parts = append(parts, "")
}
env[parts[0]] = parts[1]
}
return env
}
// PathExists returns whether the given file or directory exists or not
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@ -0,0 +1,44 @@
package utils
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitKeyValueSlice(t *testing.T) {
tests := []struct {
input []string
expected string
}{
{[]string{"K"}, ""},
{[]string{"K="}, ""},
{[]string{"K=V3"}, "V3"},
{[]string{"K=V4=V5"}, "V4=V5"},
}
for _, i := range tests {
v := SplitKeyValueSlice(i.input)
if v["K"] != i.expected {
t.Fatalf("expected K='%s'. got '%s'", i.expected, v["K"])
}
}
}
func TestPathExists(t *testing.T) {
file, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
exists, err := PathExists(file.Name())
assert.NoError(t, err)
assert.True(t, exists)
exists, err = PathExists("/wrong/path")
assert.NoError(t, err)
assert.False(t, exists)
}

View File

@ -1,49 +0,0 @@
package dockergen
import (
"log"
"reflect"
"strings"
)
func stripPrefix(s, prefix string) string {
path := s
for {
if strings.HasPrefix(path, ".") {
path = path[1:]
continue
}
break
}
return path
}
func deepGet(item interface{}, path string) interface{} {
if path == "" {
return item
}
path = stripPrefix(path, ".")
parts := strings.Split(path, ".")
itemValue := reflect.ValueOf(item)
if len(parts) > 0 {
switch itemValue.Kind() {
case reflect.Struct:
fieldValue := itemValue.FieldByName(parts[0])
if fieldValue.IsValid() {
return deepGet(fieldValue.Interface(), strings.Join(parts[1:], "."))
}
case reflect.Map:
mapValue := itemValue.MapIndex(reflect.ValueOf(parts[0]))
if mapValue.IsValid() {
return deepGet(mapValue.Interface(), strings.Join(parts[1:], "."))
}
default:
log.Printf("Can't group by %s (value %v, kind %s)\n", path, itemValue, itemValue.Kind())
}
return nil
}
return itemValue.Interface()
}

View File

@ -1,61 +0,0 @@
package dockergen
import "testing"
func TestDeepGetNoPath(t *testing.T) {
item := RuntimeContainer{}
value := deepGet(item, "")
if _, ok := value.(RuntimeContainer); !ok {
t.Fail()
}
var returned RuntimeContainer
returned = value.(RuntimeContainer)
if !returned.Equals(item) {
t.Fail()
}
}
func TestDeepGetSimple(t *testing.T) {
item := RuntimeContainer{
ID: "expected",
}
value := deepGet(item, "ID")
if _, ok := value.(string); !ok {
t.Errorf("expected: %#v. got: %#v", "expected", value)
}
if value != "expected" {
t.Errorf("expected: %s. got: %s", "expected", value)
}
}
func TestDeepGetSimpleDotPrefix(t *testing.T) {
item := RuntimeContainer{
ID: "expected",
}
value := deepGet(item, "...ID")
if _, ok := value.(string); !ok {
t.Errorf("expected: %#v. got: %#v", "expected", value)
}
if value != "expected" {
t.Errorf("expected: %s. got: %s", "expected", value)
}
}
func TestDeepGetMap(t *testing.T) {
item := RuntimeContainer{
Env: map[string]string{
"key": "value",
},
}
value := deepGet(item, "Env.key")
if _, ok := value.(string); !ok {
t.Errorf("expected: %#v. got: %#v", "value", value)
}
if value != "value" {
t.Errorf("expected: %s. got: %s", "value", value)
}
}

View File

@ -1,560 +0,0 @@
package dockergen
import (
"bytes"
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"syscall"
"text/template"
)
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func getArrayValues(funcName string, entries interface{}) (*reflect.Value, error) {
entriesVal := reflect.ValueOf(entries)
kind := entriesVal.Kind()
if kind == reflect.Ptr {
entriesVal = reflect.Indirect(entriesVal)
kind = entriesVal.Kind()
}
switch kind {
case reflect.Array, reflect.Slice:
break
default:
return nil, fmt.Errorf("Must pass an array or slice to '%v'; received %v; kind %v", funcName, entries, kind)
}
return &entriesVal, nil
}
// Generalized groupBy function
func generalizedGroupBy(funcName string, entries interface{}, getValue func(interface{}) (interface{}, error), addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)
if err != nil {
return nil, err
}
groups := make(map[string][]interface{})
for i := 0; i < entriesVal.Len(); i++ {
v := reflect.Indirect(entriesVal.Index(i)).Interface()
value, err := getValue(v)
if err != nil {
return nil, err
}
if value != nil {
addEntry(groups, value, v)
}
}
return groups, nil
}
func generalizedGroupByKey(funcName string, entries interface{}, key string, addEntry func(map[string][]interface{}, interface{}, interface{})) (map[string][]interface{}, error) {
getKey := func(v interface{}) (interface{}, error) {
return deepGet(v, key), nil
}
return generalizedGroupBy(funcName, entries, getKey, addEntry)
}
func groupByMulti(entries interface{}, key, sep string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupByMulti", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
items := strings.Split(value.(string), sep)
for _, item := range items {
groups[item] = append(groups[item], v)
}
})
}
// groupBy groups a generic array or slice by the path property key
func groupBy(entries interface{}, key string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupBy", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}
// groupByKeys is the same as groupBy but only returns a list of keys
func groupByKeys(entries interface{}, key string) ([]string, error) {
keys, err := generalizedGroupByKey("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
if err != nil {
return nil, err
}
ret := []string{}
for k := range keys {
ret = append(ret, k)
}
return ret, nil
}
// groupByLabel is the same as groupBy but over a given label
func groupByLabel(entries interface{}, label string) (map[string][]interface{}, error) {
getLabel := func(v interface{}) (interface{}, error) {
if container, ok := v.(RuntimeContainer); ok {
if value, ok := container.Labels[label]; ok {
return value, nil
}
return nil, nil
}
return nil, fmt.Errorf("Must pass an array or slice of RuntimeContainer to 'groupByLabel'; received %v", v)
}
return generalizedGroupBy("groupByLabel", entries, getLabel, func(groups map[string][]interface{}, value interface{}, v interface{}) {
groups[value.(string)] = append(groups[value.(string)], v)
})
}
// Generalized where function
func generalizedWhere(funcName string, entries interface{}, key string, test func(interface{}) bool) (interface{}, error) {
entriesVal, err := getArrayValues(funcName, entries)
if err != nil {
return nil, err
}
selection := make([]interface{}, 0)
for i := 0; i < entriesVal.Len(); i++ {
v := reflect.Indirect(entriesVal.Index(i)).Interface()
value := deepGet(v, key)
if test(value) {
selection = append(selection, v)
}
}
return selection, nil
}
// selects entries based on key
func where(entries interface{}, key string, cmp interface{}) (interface{}, error) {
return generalizedWhere("where", entries, key, func(value interface{}) bool {
return reflect.DeepEqual(value, cmp)
})
}
// select entries where a key is not equal to a value
func whereNot(entries interface{}, key string, cmp interface{}) (interface{}, error) {
return generalizedWhere("whereNot", entries, key, func(value interface{}) bool {
return !reflect.DeepEqual(value, cmp)
})
}
// selects entries where a key exists
func whereExist(entries interface{}, key string) (interface{}, error) {
return generalizedWhere("whereExist", entries, key, func(value interface{}) bool {
return value != nil
})
}
// selects entries where a key does not exist
func whereNotExist(entries interface{}, key string) (interface{}, error) {
return generalizedWhere("whereNotExist", entries, key, func(value interface{}) bool {
return value == nil
})
}
// selects entries based on key. Assumes key is delimited and breaks it apart before comparing
func whereAny(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
return generalizedWhere("whereAny", entries, key, func(value interface{}) bool {
if value == nil {
return false
} else {
items := strings.Split(value.(string), sep)
return len(intersect(cmp, items)) > 0
}
})
}
// selects entries based on key. Assumes key is delimited and breaks it apart before comparing
func whereAll(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
req_count := len(cmp)
return generalizedWhere("whereAll", entries, key, func(value interface{}) bool {
if value == nil {
return false
} else {
items := strings.Split(value.(string), sep)
return len(intersect(cmp, items)) == req_count
}
})
}
// generalized whereLabel function
func generalizedWhereLabel(funcName string, containers Context, label string, test func(string, bool) bool) (Context, error) {
selection := make([]*RuntimeContainer, 0)
for i := 0; i < len(containers); i++ {
container := containers[i]
value, ok := container.Labels[label]
if test(value, ok) {
selection = append(selection, container)
}
}
return selection, nil
}
// selects containers that have a particular label
func whereLabelExists(containers Context, label string) (Context, error) {
return generalizedWhereLabel("whereLabelExists", containers, label, func(_ string, ok bool) bool {
return ok
})
}
// selects containers that have don't have a particular label
func whereLabelDoesNotExist(containers Context, label string) (Context, error) {
return generalizedWhereLabel("whereLabelDoesNotExist", containers, label, func(_ string, ok bool) bool {
return !ok
})
}
// selects containers with a particular label whose value matches a regular expression
func whereLabelValueMatches(containers Context, label, pattern string) (Context, error) {
rx, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
return generalizedWhereLabel("whereLabelValueMatches", containers, label, func(value string, ok bool) bool {
return ok && rx.MatchString(value)
})
}
// hasPrefix returns whether a given string is a prefix of another string
func hasPrefix(prefix, s string) bool {
return strings.HasPrefix(s, prefix)
}
// hasSuffix returns whether a given string is a suffix of another string
func hasSuffix(suffix, s string) bool {
return strings.HasSuffix(s, suffix)
}
func keys(input interface{}) (interface{}, error) {
if input == nil {
return nil, nil
}
val := reflect.ValueOf(input)
if val.Kind() != reflect.Map {
return nil, fmt.Errorf("Cannot call keys on a non-map value: %v", input)
}
vk := val.MapKeys()
k := make([]interface{}, val.Len())
for i := range k {
k[i] = vk[i].Interface()
}
return k, nil
}
func intersect(l1, l2 []string) []string {
m := make(map[string]bool)
m2 := make(map[string]bool)
for _, v := range l2 {
m2[v] = true
}
for _, v := range l1 {
if m2[v] {
m[v] = true
}
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func contains(item map[string]string, key string) bool {
if _, ok := item[key]; ok {
return true
}
return false
}
func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
}
func hashSha1(input string) string {
h := sha1.New()
io.WriteString(h, input)
return fmt.Sprintf("%x", h.Sum(nil))
}
func marshalJson(input interface{}) (string, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(input); err != nil {
return "", err
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}
func unmarshalJson(input string) (interface{}, error) {
var v interface{}
if err := json.Unmarshal([]byte(input), &v); err != nil {
return nil, err
}
return v, nil
}
// arrayFirst returns first item in the array or nil if the
// input is nil or empty
func arrayFirst(input interface{}) interface{} {
if input == nil {
return nil
}
arr := reflect.ValueOf(input)
if arr.Len() == 0 {
return nil
}
return arr.Index(0).Interface()
}
// arrayLast returns last item in the array
func arrayLast(input interface{}) interface{} {
arr := reflect.ValueOf(input)
return arr.Index(arr.Len() - 1).Interface()
}
// arrayClosest find the longest matching substring in values
// that matches input
func arrayClosest(values []string, input string) string {
best := ""
for _, v := range values {
if strings.Contains(input, v) && len(v) > len(best) {
best = v
}
}
return best
}
// dirList returns a list of files in the specified path
func dirList(path string) ([]string, error) {
names := []string{}
files, err := ioutil.ReadDir(path)
if err != nil {
log.Printf("Template error: %v", err)
return names, nil
}
for _, f := range files {
names = append(names, f.Name())
}
return names, nil
}
// coalesce returns the first non nil argument
func coalesce(input ...interface{}) interface{} {
for _, v := range input {
if v != nil {
return v
}
}
return nil
}
// trimPrefix returns a string without the prefix, if present
func trimPrefix(prefix, s string) string {
return strings.TrimPrefix(s, prefix)
}
// trimSuffix returns a string without the suffix, if present
func trimSuffix(suffix, s string) string {
return strings.TrimSuffix(s, suffix)
}
// trim returns the string without leading or trailing whitespace
func trim(s string) string {
return strings.TrimSpace(s)
}
// when returns the trueValue when the condition is true and the falseValue otherwise
func when(condition bool, trueValue, falseValue interface{}) interface{} {
if condition {
return trueValue
} else {
return falseValue
}
}
func newTemplate(name string) *template.Template {
tmpl := template.New(name).Funcs(template.FuncMap{
"closest": arrayClosest,
"coalesce": coalesce,
"contains": contains,
"dict": dict,
"dir": dirList,
"exists": exists,
"first": arrayFirst,
"groupBy": groupBy,
"groupByKeys": groupByKeys,
"groupByMulti": groupByMulti,
"groupByLabel": groupByLabel,
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"json": marshalJson,
"intersect": intersect,
"keys": keys,
"last": arrayLast,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": unmarshalJson,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"trim": trim,
"when": when,
"where": where,
"whereNot": whereNot,
"whereExist": whereExist,
"whereNotExist": whereNotExist,
"whereAny": whereAny,
"whereAll": whereAll,
"whereLabelExists": whereLabelExists,
"whereLabelDoesNotExist": whereLabelDoesNotExist,
"whereLabelValueMatches": whereLabelValueMatches,
})
return tmpl
}
func filterRunning(config Config, containers Context) Context {
if config.IncludeStopped {
return containers
} else {
filteredContainers := Context{}
for _, container := range containers {
if container.State.Running {
filteredContainers = append(filteredContainers, container)
}
}
return filteredContainers
}
}
func GenerateFile(config Config, containers Context) bool {
filteredRunningContainers := filterRunning(config, containers)
filteredContainers := Context{}
if config.OnlyPublished {
for _, container := range filteredRunningContainers {
if len(container.PublishedAddresses()) > 0 {
filteredContainers = append(filteredContainers, container)
}
}
} else if config.OnlyExposed {
for _, container := range filteredRunningContainers {
if len(container.Addresses) > 0 {
filteredContainers = append(filteredContainers, container)
}
}
} else {
filteredContainers = filteredRunningContainers
}
contents := executeTemplate(config.Template, filteredContainers)
if !config.KeepBlankLines {
buf := new(bytes.Buffer)
removeBlankLines(bytes.NewReader(contents), buf)
contents = buf.Bytes()
}
if config.Dest != "" {
dest, err := ioutil.TempFile(filepath.Dir(config.Dest), "docker-gen")
defer func() {
dest.Close()
os.Remove(dest.Name())
}()
if err != nil {
log.Fatalf("Unable to create temp file: %s\n", err)
}
if n, err := dest.Write(contents); n != len(contents) || err != nil {
log.Fatalf("Failed to write to temp file: wrote %d, exp %d, err=%v", n, len(contents), err)
}
oldContents := []byte{}
if fi, err := os.Stat(config.Dest); err == nil {
if err := dest.Chmod(fi.Mode()); err != nil {
log.Fatalf("Unable to chmod temp file: %s\n", err)
}
if err := dest.Chown(int(fi.Sys().(*syscall.Stat_t).Uid), int(fi.Sys().(*syscall.Stat_t).Gid)); err != nil {
log.Fatalf("Unable to chown temp file: %s\n", err)
}
oldContents, err = ioutil.ReadFile(config.Dest)
if err != nil {
log.Fatalf("Unable to compare current file contents: %s: %s\n", config.Dest, err)
}
}
if bytes.Compare(oldContents, contents) != 0 {
err = os.Rename(dest.Name(), config.Dest)
if err != nil {
log.Fatalf("Unable to create dest file %s: %s\n", config.Dest, err)
}
log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers))
return true
}
return false
} else {
os.Stdout.Write(contents)
}
return true
}
func executeTemplate(templatePath string, containers Context) []byte {
tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
if err != nil {
log.Fatalf("Unable to parse template: %s", err)
}
buf := new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers)
if err != nil {
log.Fatalf("Template error: %s\n", err)
}
return buf.Bytes()
}

View File

@ -1,865 +0,0 @@
package dockergen
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"testing"
"text/template"
)
type templateTestList []struct {
tmpl string
context interface{}
expected string
}
func (tests templateTestList) run(t *testing.T, prefix string) {
for n, test := range tests {
tmplName := fmt.Sprintf("%s-test-%d", prefix, n)
tmpl := template.Must(newTemplate(tmplName).Parse(test.tmpl))
var b bytes.Buffer
err := tmpl.ExecuteTemplate(&b, tmplName, test.context)
if err != nil {
t.Fatalf("Error executing template: %v (test %s)", err, tmplName)
}
got := b.String()
if test.expected != got {
t.Fatalf("Incorrect output found; expected %s, got %s (test %s)", test.expected, got, tmplName)
}
}
}
func TestContains(t *testing.T) {
env := map[string]string{
"PORT": "1234",
}
if !contains(env, "PORT") {
t.Fail()
}
if contains(env, "MISSING") {
t.Fail()
}
}
func TestKeys(t *testing.T) {
env := map[string]string{
"VIRTUAL_HOST": "demo.local",
}
tests := templateTestList{
{`{{range (keys $)}}{{.}}{{end}}`, env, `VIRTUAL_HOST`},
}
tests.run(t, "keys")
}
func TestKeysEmpty(t *testing.T) {
input := map[string]int{}
k, err := keys(input)
if err != nil {
t.Fatalf("Error fetching keys: %v", err)
}
vk := reflect.ValueOf(k)
if vk.Kind() == reflect.Invalid {
t.Fatalf("Got invalid kind for keys: %v", vk)
}
if len(input) != vk.Len() {
t.Fatalf("Incorrect key count; expected %d, got %d", len(input), vk.Len())
}
}
func TestKeysNil(t *testing.T) {
k, err := keys(nil)
if err != nil {
t.Fatalf("Error fetching keys: %v", err)
}
vk := reflect.ValueOf(k)
if vk.Kind() != reflect.Invalid {
t.Fatalf("Got invalid kind for keys: %v", vk)
}
}
func TestIntersect(t *testing.T) {
if len(intersect([]string{"foo.fo.com", "bar.com"}, []string{"foo.bar.com"})) != 0 {
t.Fatal("Expected no match")
}
if len(intersect([]string{"foo.fo.com", "bar.com"}, []string{"bar.com", "foo.com"})) != 1 {
t.Fatal("Expected only one match")
}
if len(intersect([]string{"foo.com"}, []string{"bar.com", "foo.com"})) != 1 {
t.Fatal("Expected only one match")
}
if len(intersect([]string{"foo.fo.com", "foo.com", "bar.com"}, []string{"bar.com", "foo.com"})) != 2 {
t.Fatal("Expected two matches")
}
}
func TestGroupByExistingKey(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
groups, _ := groupBy(containers, "Env.VIRTUAL_HOST")
if len(groups) != 2 {
t.Fail()
}
if len(groups["demo1.localhost"]) != 2 {
t.Fail()
}
if len(groups["demo2.localhost"]) != 1 {
t.FailNow()
}
if groups["demo2.localhost"][0].(RuntimeContainer).ID != "3" {
t.Fail()
}
}
func TestGroupByAfterWhere(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"EXTERNAL": "true",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
"EXTERNAL": "true",
},
ID: "3",
},
}
filtered, _ := where(containers, "Env.EXTERNAL", "true")
groups, _ := groupBy(filtered, "Env.VIRTUAL_HOST")
if len(groups) != 2 {
t.Fail()
}
if len(groups["demo1.localhost"]) != 1 {
t.Fail()
}
if len(groups["demo2.localhost"]) != 1 {
t.FailNow()
}
if groups["demo2.localhost"][0].(RuntimeContainer).ID != "3" {
t.Fail()
}
}
func TestGroupByLabel(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "1",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "two",
},
ID: "2",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "one",
},
ID: "3",
},
&RuntimeContainer{
ID: "4",
},
&RuntimeContainer{
Labels: map[string]string{
"com.docker.compose.project": "",
},
ID: "5",
},
}
groups, err := groupByLabel(containers, "com.docker.compose.project")
if err != nil {
t.FailNow()
}
if len(groups) != 3 {
t.Fail()
}
if len(groups["one"]) != 2 {
t.Fail()
}
if len(groups[""]) != 1 {
t.Fail()
}
if len(groups["two"]) != 1 {
t.FailNow()
}
if groups["two"][0].(RuntimeContainer).ID != "2" {
t.Fail()
}
}
func TestGroupByMulti(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
groups, _ := groupByMulti(containers, "Env.VIRTUAL_HOST", ",")
if len(groups) != 3 {
t.Fatalf("expected 3 got %d", len(groups))
}
if len(groups["demo1.localhost"]) != 2 {
t.Fatalf("expected 2 got %d", len(groups["demo1.localhost"]))
}
if len(groups["demo2.localhost"]) != 1 {
t.Fatalf("expected 1 got %d", len(groups["demo2.localhost"]))
}
if groups["demo2.localhost"][0].(RuntimeContainer).ID != "3" {
t.Fatalf("expected 2 got %s", groups["demo2.localhost"][0].(RuntimeContainer).ID)
}
if len(groups["demo3.localhost"]) != 1 {
t.Fatalf("expect 1 got %d", len(groups["demo3.localhost"]))
}
if groups["demo3.localhost"][0].(RuntimeContainer).ID != "2" {
t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].(RuntimeContainer).ID)
}
}
func TestWhere(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
Addresses: []Address{
Address{
IP: "172.16.42.1",
Port: "80",
Proto: "tcp",
},
},
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
Addresses: []Address{
Address{
IP: "172.16.42.1",
Port: "9999",
Proto: "tcp",
},
},
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{where . "Env.VIRTUAL_HOST" "demo1.localhost" | len}}`, containers, `1`},
{`{{where . "Env.VIRTUAL_HOST" "demo2.localhost" | len}}`, containers, `2`},
{`{{where . "Env.VIRTUAL_HOST" "demo3.localhost" | len}}`, containers, `1`},
{`{{where . "Env.NOEXIST" "demo3.localhost" | len}}`, containers, `0`},
{`{{where .Addresses "Port" "80" | len}}`, containers[0], `1`},
{`{{where .Addresses "Port" "80" | len}}`, containers[1], `0`},
{
`{{where . "Value" 5 | len}}`,
[]struct {
Value int
}{
{Value: 5},
{Value: 3},
{Value: 5},
},
`2`,
},
}
tests.run(t, "where")
}
func TestWhereNot(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
Addresses: []Address{
Address{
IP: "172.16.42.1",
Port: "80",
Proto: "tcp",
},
},
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
Addresses: []Address{
Address{
IP: "172.16.42.1",
Port: "9999",
Proto: "tcp",
},
},
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereNot . "Env.VIRTUAL_HOST" "demo1.localhost" | len}}`, containers, `3`},
{`{{whereNot . "Env.VIRTUAL_HOST" "demo2.localhost" | len}}`, containers, `2`},
{`{{whereNot . "Env.VIRTUAL_HOST" "demo3.localhost" | len}}`, containers, `3`},
{`{{whereNot . "Env.NOEXIST" "demo3.localhost" | len}}`, containers, `4`},
{`{{whereNot .Addresses "Port" "80" | len}}`, containers[0], `0`},
{`{{whereNot .Addresses "Port" "80" | len}}`, containers[1], `1`},
{
`{{whereNot . "Value" 5 | len}}`,
[]struct {
Value int
}{
{Value: 5},
{Value: 3},
{Value: 5},
},
`1`,
},
}
tests.run(t, "whereNot")
}
func TestWhereExist(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_PROTO": "https",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereExist . "Env.VIRTUAL_HOST" | len}}`, containers, `3`},
{`{{whereExist . "Env.VIRTUAL_PATH" | len}}`, containers, `2`},
{`{{whereExist . "Env.NOT_A_KEY" | len}}`, containers, `0`},
{`{{whereExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `1`},
}
tests.run(t, "whereExist")
}
func TestWhereNotExist(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo3.localhost",
"VIRTUAL_PATH": "/api",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_PROTO": "https",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereNotExist . "Env.VIRTUAL_HOST" | len}}`, containers, `1`},
{`{{whereNotExist . "Env.VIRTUAL_PATH" | len}}`, containers, `2`},
{`{{whereNotExist . "Env.NOT_A_KEY" | len}}`, containers, `4`},
{`{{whereNotExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `3`},
}
tests.run(t, "whereNotExist")
}
func TestWhereSomeMatch(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost,demo4.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "bar,demo3.localhost,foo",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "demo1.localhost" ",") | len}}`, containers, `1`},
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "demo2.localhost,lala" ",") | len}}`, containers, `2`},
{`{{whereAny . "Env.VIRTUAL_HOST" "," (split "something,demo3.localhost" ",") | len}}`, containers, `1`},
{`{{whereAny . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`},
}
tests.run(t, "whereAny")
}
func TestWhereRequires(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost,demo4.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "bar,demo3.localhost,foo",
},
ID: "3",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "4",
},
}
tests := templateTestList{
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo1.localhost" ",") | len}}`, containers, `1`},
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo2.localhost,lala" ",") | len}}`, containers, `0`},
{`{{whereAll . "Env.VIRTUAL_HOST" "," (split "demo3.localhost" ",") | len}}`, containers, `1`},
{`{{whereAll . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`},
}
tests.run(t, "whereAll")
}
func TestWhereLabelExists(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
&RuntimeContainer{
Labels: map[string]string{
"com.example.bar": "bar",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelExists . "com.example.foo" | len}}`, containers, `1`},
{`{{whereLabelExists . "com.example.bar" | len}}`, containers, `2`},
{`{{whereLabelExists . "com.example.baz" | len}}`, containers, `0`},
}
tests.run(t, "whereLabelExists")
}
func TestWhereLabelDoesNotExist(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
&RuntimeContainer{
Labels: map[string]string{
"com.example.bar": "bar",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelDoesNotExist . "com.example.foo" | len}}`, containers, `1`},
{`{{whereLabelDoesNotExist . "com.example.bar" | len}}`, containers, `0`},
{`{{whereLabelDoesNotExist . "com.example.baz" | len}}`, containers, `2`},
}
tests.run(t, "whereLabelDoesNotExist")
}
func TestWhereLabelValueMatches(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Labels: map[string]string{
"com.example.foo": "foo",
"com.example.bar": "bar",
},
ID: "1",
},
&RuntimeContainer{
Labels: map[string]string{
"com.example.bar": "BAR",
},
ID: "2",
},
}
tests := templateTestList{
{`{{whereLabelValueMatches . "com.example.foo" "^foo$" | len}}`, containers, `1`},
{`{{whereLabelValueMatches . "com.example.foo" "\\d+" | len}}`, containers, `0`},
{`{{whereLabelValueMatches . "com.example.bar" "^bar$" | len}}`, containers, `1`},
{`{{whereLabelValueMatches . "com.example.bar" "^(?i)bar$" | len}}`, containers, `2`},
{`{{whereLabelValueMatches . "com.example.bar" ".*" | len}}`, containers, `2`},
{`{{whereLabelValueMatches . "com.example.baz" ".*" | len}}`, containers, `0`},
}
tests.run(t, "whereLabelValueMatches")
}
func TestHasPrefix(t *testing.T) {
const prefix = "tcp://"
const str = "tcp://127.0.0.1:2375"
if !hasPrefix(prefix, str) {
t.Fatalf("expected %s to have prefix %s", str, prefix)
}
}
func TestHasSuffix(t *testing.T) {
const suffix = ".local"
const str = "myhost.local"
if !hasSuffix(suffix, str) {
t.Fatalf("expected %s to have suffix %s", str, suffix)
}
}
func TestSplitN(t *testing.T) {
tests := templateTestList{
{`{{index (splitN . "/" 2) 0}}`, "example.com/path", `example.com`},
{`{{index (splitN . "/" 2) 1}}`, "example.com/path", `path`},
{`{{index (splitN . "/" 2) 1}}`, "example.com/a/longer/path", `a/longer/path`},
{`{{len (splitN . "/" 2)}}`, "example.com", `1`},
}
tests.run(t, "splitN")
}
func TestTrimPrefix(t *testing.T) {
const prefix = "tcp://"
const str = "tcp://127.0.0.1:2375"
const trimmed = "127.0.0.1:2375"
got := trimPrefix(prefix, str)
if got != trimmed {
t.Fatalf("expected trimPrefix(%s,%s) to be %s, got %s", prefix, str, trimmed, got)
}
}
func TestTrimSuffix(t *testing.T) {
const suffix = ".local"
const str = "myhost.local"
const trimmed = "myhost"
got := trimSuffix(suffix, str)
if got != trimmed {
t.Fatalf("expected trimSuffix(%s,%s) to be %s, got %s", suffix, str, trimmed, got)
}
}
func TestTrim(t *testing.T) {
const str = " myhost.local "
const trimmed = "myhost.local"
got := trim(str)
if got != trimmed {
t.Fatalf("expected trim(%s) to be %s, got %s", str, trimmed, got)
}
}
func TestDict(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
d, err := dict("/", containers)
if err != nil {
t.Fatal(err)
}
if d["/"] == nil {
t.Fatalf("did not find containers in dict: %s", d)
}
if d["MISSING"] != nil {
t.Fail()
}
}
func TestSha1(t *testing.T) {
sum := hashSha1("/path")
if sum != "4f26609ad3f5185faaa9edf1e93aa131e2131352" {
t.Fatal("Incorrect SHA1 sum")
}
}
func TestJson(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo1.localhost,demo3.localhost",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_HOST": "demo2.localhost",
},
ID: "3",
},
}
output, err := marshalJson(containers)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBufferString(output)
dec := json.NewDecoder(buf)
if err != nil {
t.Fatal(err)
}
var decoded []*RuntimeContainer
if err := dec.Decode(&decoded); err != nil {
t.Fatal(err)
}
if len(decoded) != len(containers) {
t.Fatalf("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded))
}
}
func TestParseJson(t *testing.T) {
tests := templateTestList{
{`{{parseJson .}}`, `null`, `<no value>`},
{`{{parseJson .}}`, `true`, `true`},
{`{{parseJson .}}`, `1`, `1`},
{`{{parseJson .}}`, `0.5`, `0.5`},
{`{{index (parseJson .) "enabled"}}`, `{"enabled":true}`, `true`},
{`{{index (parseJson . | first) "enabled"}}`, `[{"enabled":true}]`, `true`},
}
tests.run(t, "parseJson")
}
func TestQueryEscape(t *testing.T) {
tests := templateTestList{
{`{{queryEscape .}}`, `example.com`, `example.com`},
{`{{queryEscape .}}`, `.example.com`, `.example.com`},
{`{{queryEscape .}}`, `*.example.com`, `%2A.example.com`},
{`{{queryEscape .}}`, `~^example\.com(\..*\.xip\.io)?$`, `~%5Eexample%5C.com%28%5C..%2A%5C.xip%5C.io%29%3F%24`},
}
tests.run(t, "queryEscape")
}
func TestArrayClosestExact(t *testing.T) {
if arrayClosest([]string{"foo.bar.com", "bar.com"}, "foo.bar.com") != "foo.bar.com" {
t.Fatal("Expected foo.bar.com")
}
}
func TestArrayClosestSubstring(t *testing.T) {
if arrayClosest([]string{"foo.fo.com", "bar.com"}, "foo.bar.com") != "bar.com" {
t.Fatal("Expected bar.com")
}
}
func TestArrayClosestNoMatch(t *testing.T) {
if arrayClosest([]string{"foo.fo.com", "bip.com"}, "foo.bar.com") != "" {
t.Fatal("Expected ''")
}
}
func TestWhen(t *testing.T) {
context := struct {
BoolValue bool
StringValue string
}{
true,
"foo",
}
tests := templateTestList{
{`{{ print (when .BoolValue "first" "second") }}`, context, `first`},
{`{{ print (when (eq .StringValue "foo") "first" "second") }}`, context, `first`},
{`{{ when (not .BoolValue) "first" "second" | print }}`, context, `second`},
{`{{ when (not (eq .StringValue "foo")) "first" "second" | print }}`, context, `second`},
}
tests.run(t, "when")
}
func TestWhenTrue(t *testing.T) {
if when(true, "first", "second") != "first" {
t.Fatal("Expected first value")
}
}
func TestWhenFalse(t *testing.T) {
if when(false, "first", "second") != "second" {
t.Fatal("Expected second value")
}
}

View File

@ -1,3 +1,6 @@
{{/* Simple dnsmasq template generating host entries */}}
{{/* Domains are hard-coded, replace 'docker.comany.com' below */}}
{{$domain := "docker.company.com"}}
{{range $key, $value := .}}
# {{ $value.Name }} ({{$value.ID}} from {{$value.Image.Repository}})

View File

@ -1,3 +1,4 @@
{{/* etcd template to generate registration script */}}
#!/bin/bash

View File

@ -1,3 +1,4 @@
{{/* Generates fluentd configuration entries */}}
## File input
## read docker logs with tag=docker.container

View File

@ -1,3 +1,7 @@
{{/* Generate logrotate snippets for logrotate based on files listed in */}}
{{/* the comma separated environment variable LOG_FILES */}}
{{/* e.g. docker run --env='/var/log/messages,/var/log/lastlog' ... */}}
{{ range $index, $value := $ }}
{{ $logs := $value.Env.LOG_FILES }}
{{ if $logs }}

View File

@ -1,3 +1,8 @@
{{/* default nginx configuration template */}}
{{/* Generate a configuration file based on the containers mandatory */}}
{{/* VIRTUAL_HOST environment variable and the exposed ports. If multiple */}}
{{/* ports are exposed, the first one is used, unless set with VIRTUAL_PORT */}}
server {
listen 80 default_server;
server_name _; # This is just an invalid value which will never trigger on a real hostname.
@ -13,7 +18,7 @@ upstream {{ $host }} {
{{ $addrLen := len $value.Addresses }}
{{ $network := index $value.Networks 0 }}
{{/* If only 1 port exposed, use that */}}
{{ if eq $addrLen 1 }}
{{ with $address := index $value.Addresses 0 }}
@ -62,4 +67,4 @@ server {
proxy_set_header Connection "";
}
}
{{ end }}
{{ end }}

View File

@ -1,71 +0,0 @@
package dockergen
import (
"bufio"
"io"
"os"
"strings"
"unicode"
)
func GetEndpoint(endpoint string) (string, error) {
defaultEndpoint := "unix:///var/run/docker.sock"
if os.Getenv("DOCKER_HOST") != "" {
defaultEndpoint = os.Getenv("DOCKER_HOST")
}
if endpoint != "" {
defaultEndpoint = endpoint
}
_, _, err := parseHost(defaultEndpoint)
if err != nil {
return "", err
}
return defaultEndpoint, nil
}
// splitKeyValueSlice takes a string slice where values are of the form
// KEY, KEY=, KEY=VALUE or KEY=NESTED_KEY=VALUE2, and returns a map[string]string where items
// are split at their first `=`.
func splitKeyValueSlice(in []string) map[string]string {
env := make(map[string]string)
for _, entry := range in {
parts := strings.SplitN(entry, "=", 2)
if len(parts) != 2 {
parts = append(parts, "")
}
env[parts[0]] = parts[1]
}
return env
}
func isBlank(str string) bool {
for _, r := range str {
if !unicode.IsSpace(r) {
return false
}
}
return true
}
func removeBlankLines(reader io.Reader, writer io.Writer) {
breader := bufio.NewReader(reader)
bwriter := bufio.NewWriter(writer)
for {
line, err := breader.ReadString('\n')
if !isBlank(line) {
bwriter.WriteString(line)
}
if err != nil {
break
}
}
bwriter.Flush()
}

View File

@ -1,139 +0,0 @@
package dockergen
import (
"bytes"
"os"
"strings"
"testing"
)
func TestDefaultEndpoint(t *testing.T) {
err := os.Unsetenv("DOCKER_HOST")
if err != nil {
t.Fatalf("Unable to unset DOCKER_HOST: %s", err)
}
endpoint, err := GetEndpoint("")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "unix:///var/run/docker.sock" {
t.Fatalf("Expected unix:///var/run/docker.sock, got %s", endpoint)
}
}
func TestDockerHostEndpoint(t *testing.T) {
err := os.Setenv("DOCKER_HOST", "tcp://127.0.0.1:4243")
if err != nil {
t.Fatalf("Unable to set DOCKER_HOST: %s", err)
}
endpoint, err := GetEndpoint("")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "tcp://127.0.0.1:4243" {
t.Fatalf("Expected tcp://127.0.0.1:4243, got %s", endpoint)
}
}
func TestDockerFlagEndpoint(t *testing.T) {
err := os.Setenv("DOCKER_HOST", "tcp://127.0.0.1:4243")
if err != nil {
t.Fatalf("Unable to set DOCKER_HOST: %s", err)
}
// flag value should override DOCKER_HOST and default value
endpoint, err := GetEndpoint("tcp://127.0.0.1:5555")
if err != nil {
t.Fatalf("%s", err)
}
if endpoint != "tcp://127.0.0.1:5555" {
t.Fatalf("Expected tcp://127.0.0.1:5555, got %s", endpoint)
}
}
func TestUnixBadFormat(t *testing.T) {
endpoint := "unix:/var/run/docker.sock"
_, err := GetEndpoint(endpoint)
if err == nil {
t.Fatal("endpoint should have failed")
}
}
func TestSplitKeyValueSlice(t *testing.T) {
tests := []struct {
input []string
expected string
}{
{[]string{"K"}, ""},
{[]string{"K="}, ""},
{[]string{"K=V3"}, "V3"},
{[]string{"K=V4=V5"}, "V4=V5"},
}
for _, i := range tests {
v := splitKeyValueSlice(i.input)
if v["K"] != i.expected {
t.Fatalf("expected K='%s'. got '%s'", i.expected, v["K"])
}
}
}
func TestIsBlank(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"", true},
{" ", true},
{" ", true},
{"\t", true},
{"\t\n\v\f\r\u0085\u00A0", true},
{"a", false},
{" a ", false},
{"a ", false},
{" a", false},
{"日本語", false},
}
for _, i := range tests {
v := isBlank(i.input)
if v != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, v)
}
}
}
func TestRemoveBlankLines(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"", ""},
{"\r\n\r\n", ""},
{"line1\nline2", "line1\nline2"},
{"line1\n\nline2", "line1\nline2"},
{"\n\n\n\nline1\n\nline2", "line1\nline2"},
{"\n\n\n\n\n \n \n \n", ""},
// windows line endings \r\n
{"line1\r\nline2", "line1\r\nline2"},
{"line1\r\n\r\nline2", "line1\r\nline2"},
// keep last new line
{"line1\n", "line1\n"},
{"line1\r\n", "line1\r\n"},
}
for _, i := range tests {
output := new(bytes.Buffer)
removeBlankLines(strings.NewReader(i.input), output)
if output.String() != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, output)
}
}
}