Compare commits
1 Commits
master
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ba39e768 |
@ -1,9 +1,4 @@
|
||||
.git
|
||||
.github
|
||||
docker-gen
|
||||
dist
|
||||
examples
|
||||
Makefile
|
||||
README.md
|
||||
templates
|
||||
*.gz
|
||||
|
||||
25
.github/dependabot.yml
vendored
25
.github/dependabot.yml
vendored
@ -1,25 +0,0 @@
|
||||
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
33
.github/workflows/assets.yml
vendored
@ -1,33 +0,0 @@
|
||||
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
87
.github/workflows/build-publish.yml
vendored
@ -1,87 +0,0 @@
|
||||
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
38
.github/workflows/tests.yml
vendored
@ -1,38 +0,0 @@
|
||||
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
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
docker-gen
|
||||
!cmd/docker-gen
|
||||
dist
|
||||
*.gz
|
||||
|
||||
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@ -0,0 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.9"
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
install:
|
||||
- make get-deps
|
||||
script:
|
||||
- make all check-gofmt test
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
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"]
|
||||
@ -1,31 +0,0 @@
|
||||
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"]
|
||||
@ -1,35 +0,0 @@
|
||||
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"]
|
||||
6
GLOCKFILE
Normal file
6
GLOCKFILE
Normal file
@ -0,0 +1,6 @@
|
||||
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
|
||||
3
LICENSE
3
LICENSE
@ -1,7 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2021 Jason Wilder
|
||||
Copyright (c) 2021-2022 Nicolas Duchon
|
||||
Copyright (c) 2014 Jason Wilder
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
20
Makefile
20
Makefile
@ -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 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/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/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,10 +26,11 @@ 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
|
||||
go mod tidy
|
||||
glock sync -n < GLOCKFILE
|
||||
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
|
||||
@ -39,21 +40,18 @@ 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 mod download
|
||||
go get github.com/robfig/glock
|
||||
glock sync -n < GLOCKFILE
|
||||
|
||||
check-gofmt:
|
||||
if [ -n "$(shell go fmt ./cmd/...)" ]; then \
|
||||
if [ -n "$(shell gofmt -l .)" ]; then \
|
||||
echo 1>&2 'The following files need to be formatted:'; \
|
||||
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; \
|
||||
gofmt -l .; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
test:
|
||||
go test -race ./internal/...
|
||||
go test
|
||||
|
||||
89
README.md
89
README.md
@ -1,20 +1,17 @@
|
||||
docker-gen
|
||||
=====
|
||||
|
||||
|
||||
[](https://github.com/nginx-proxy/docker-gen/actions/workflows/tests.yml)
|
||||
[](https://github.com/nginx-proxy/docker-gen/releases)
|
||||
[](https://hub.docker.com/r/nginxproxy/docker-gen "Click to view the image on Docker Hub")
|
||||
[](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub')
|
||||
[](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub')
|
||||

|
||||
[](https://travis-ci.org/jwilder/docker-gen)
|
||||

|
||||
|
||||
`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/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
|
||||
* **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
|
||||
* **Service Discovery** - Scripts (python, bash, etc..) to register containers within [etcd](https://github.com/jwilder/docker-register), hipache, etc..
|
||||
|
||||
===
|
||||
@ -28,17 +25,17 @@ There are three common ways to run docker-gen:
|
||||
|
||||
#### Host Install
|
||||
|
||||
Linux/OSX binaries for release [0.9.0](https://github.com/nginx-proxy/docker-gen/releases)
|
||||
Linux/OSX binaries for release [0.7.3](https://github.com/jwilder/docker-gen/releases)
|
||||
|
||||
* [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)
|
||||
* [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)
|
||||
|
||||
Download the version you need, untar, and install to your PATH.
|
||||
|
||||
```
|
||||
$ 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
|
||||
$ 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
|
||||
$ ./docker-gen
|
||||
```
|
||||
|
||||
@ -46,14 +43,14 @@ $ ./docker-gen
|
||||
|
||||
Docker-gen can be bundled inside of a container along-side applications.
|
||||
|
||||
[nginx-proxy/nginx-proxy](https://hub.docker.com/r/nginxproxy/nginx-proxy) trusted build is an example of
|
||||
[jwilder/nginx-proxy](https://index.docker.io/u/jwilder/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 [nginx-proxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen)
|
||||
It can also be run as two separate containers using the [jwilder/docker-gen](https://index.docker.io/u/jwilder/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
|
||||
@ -69,17 +66,11 @@ $ 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/nginx-proxy/docker-gen/main/templates/nginx.tmpl
|
||||
$ curl -o nginx.tmpl https://raw.githubusercontent.com/jwilder/docker-gen/master/templates/nginx.tmpl
|
||||
$ docker run -d --name nginx-gen --volumes-from nginx \
|
||||
-v /var/run/docker.sock:/tmp/docker.sock:rw \
|
||||
-v /var/run/docker.sock:/tmp/docker.sock:ro \
|
||||
-v /tmp/templates:/etc/docker-gen/templates \
|
||||
-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 ...
|
||||
-t jwilder/docker-gen -notify-sighup nginx -watch -only-exposed /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||
```
|
||||
|
||||
===
|
||||
@ -104,13 +95,8 @@ 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', or `-notify-container container-ID -notify-signal 1`
|
||||
send HUP signal to container. Equivalent to 'docker kill -s HUP container-ID'
|
||||
-only-exposed
|
||||
only include containers with exposed ports
|
||||
-only-published
|
||||
@ -134,7 +120,7 @@ Options:
|
||||
|
||||
Arguments:
|
||||
template - path to a template to generate
|
||||
dest - path to write the template. If not specfied, STDOUT is used
|
||||
dest - path to a write the template. If not specfied, STDOUT is used
|
||||
|
||||
Environment Variables:
|
||||
DOCKER_HOST - default value for -endpoint
|
||||
@ -157,7 +143,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 write the template. If not specfied, STDOUT is used
|
||||
path to a 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)
|
||||
@ -212,7 +198,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 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.
|
||||
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.
|
||||
|
||||
#### Emit Structure
|
||||
|
||||
@ -360,34 +346,32 @@ 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 any type.
|
||||
* *`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.
|
||||
* *`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.
|
||||
* *`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`.
|
||||
* *`first $array`*: Returns the first value of an array or nil if the arry is nil or empty.
|
||||
* *`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)
|
||||
* *`read $string`*: Read the content of the file located at `$path`
|
||||
* *`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.
|
||||
* *`toLower $string`*: Replace capital letters in `$string` to lowercase.
|
||||
* *`toUpper $string`*: Replace lowercase letters in `$string` to uppercase.
|
||||
* *`trim $string`*: Removes whitespace from both sides of `$string`.
|
||||
* *`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.
|
||||
@ -409,18 +393,18 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
|
||||
|
||||
#### NGINX Reverse Proxy Config
|
||||
|
||||
[nginxproxy/nginx-proxy](https://hub.docker.com/r/nginxproxy/nginx-proxy) trusted build.
|
||||
[jwilder/nginx-proxy](https://index.docker.io/u/jwilder/nginx-proxy/) trusted build.
|
||||
|
||||
Start nginx-proxy:
|
||||
|
||||
```
|
||||
$ docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t nginxproxy/nginx-proxy
|
||||
$ docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t jwilder/nginx-proxy
|
||||
```
|
||||
|
||||
Then start containers with a VIRTUAL_HOST (and the VIRTUAL_PORT if more than one port is exposed) env variable:
|
||||
Then start containers with a VIRTUAL_HOST env variable:
|
||||
|
||||
```
|
||||
$ docker run -e VIRTUAL_HOST=foo.bar.com -e VIRTUAL_PORT=80 -t ...
|
||||
$ docker run -e VIRTUAL_HOST=foo.bar.com -t ...
|
||||
```
|
||||
|
||||
If you wanted to run docker-gen directly on the host, you could do it with:
|
||||
@ -451,11 +435,8 @@ $ docker-gen -notify "/bin/bash /tmp/etcd.sh" -interval 10 templates/etcd.tmpl /
|
||||
|
||||
### Development
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
```
|
||||
$ git clone <your fork>
|
||||
|
||||
@ -5,39 +5,38 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"sync"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"github.com/nginx-proxy/docker-gen/internal/config"
|
||||
"github.com/nginx-proxy/docker-gen/internal/generator"
|
||||
"github.com/jwilder/docker-gen"
|
||||
)
|
||||
|
||||
type stringslice []string
|
||||
|
||||
var (
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
func (strings *stringslice) String() string {
|
||||
@ -69,7 +68,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/nginx-proxy/docker-gen`)
|
||||
println(`For more information, see https://github.com/jwilder/docker-gen`)
|
||||
}
|
||||
|
||||
func loadConfig(file string) error {
|
||||
@ -96,12 +95,8 @@ func initFlags() {
|
||||
flag.BoolVar(&includeStopped, "include-stopped", false, "include stopped containers")
|
||||
flag.BoolVar(¬ifyOutput, "notify-output", false, "log the output(stdout/stderr) of notify command")
|
||||
flag.StringVar(¬ifyCmd, "notify", "", "run command after template is regenerated (e.g `restart xyz`)")
|
||||
flag.StringVar(¬ifyContainerID, "notify-sighup", "",
|
||||
flag.StringVar(¬ifySigHUPContainerID, "notify-sighup", "",
|
||||
"send HUP signal to container. Equivalent to docker kill -s HUP `container-ID`")
|
||||
flag.StringVar(¬ifyContainerID, "notify-container", "",
|
||||
"container to send a signal to")
|
||||
flag.IntVar(¬ifyContainerSignal, "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")
|
||||
@ -116,10 +111,6 @@ 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 {
|
||||
@ -140,29 +131,29 @@ func main() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w, err := config.ParseWait(wait)
|
||||
w, err := dockergen.ParseWait(wait)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing wait interval: %s\n", err)
|
||||
}
|
||||
cfg := config.Config{
|
||||
config := dockergen.Config{
|
||||
Template: flag.Arg(0),
|
||||
Dest: flag.Arg(1),
|
||||
Watch: watch,
|
||||
Wait: w,
|
||||
NotifyCmd: notifyCmd,
|
||||
NotifyOutput: notifyOutput,
|
||||
NotifyContainers: make(map[string]int),
|
||||
NotifyContainers: make(map[string]docker.Signal),
|
||||
OnlyExposed: onlyExposed,
|
||||
OnlyPublished: onlyPublished,
|
||||
IncludeStopped: includeStopped,
|
||||
Interval: interval,
|
||||
KeepBlankLines: keepBlankLines,
|
||||
}
|
||||
if notifyContainerID != "" {
|
||||
cfg.NotifyContainers[notifyContainerID] = notifyContainerSignal
|
||||
if notifySigHUPContainerID != "" {
|
||||
config.NotifyContainers[notifySigHUPContainerID] = docker.SIGHUP
|
||||
}
|
||||
configs = config.ConfigFile{
|
||||
Config: []config.Config{cfg}}
|
||||
configs = dockergen.ConfigFile{
|
||||
Config: []dockergen.Config{config}}
|
||||
}
|
||||
|
||||
all := true
|
||||
@ -172,7 +163,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
generator, err := generator.NewGenerator(generator.GeneratorConfig{
|
||||
generator, err := dockergen.NewGenerator(dockergen.GeneratorConfig{
|
||||
Endpoint: endpoint,
|
||||
TLSKey: tlsKey,
|
||||
TLSCert: tlsCert,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package config
|
||||
package dockergen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -13,7 +15,7 @@ type Config struct {
|
||||
Wait *Wait
|
||||
NotifyCmd string
|
||||
NotifyOutput bool
|
||||
NotifyContainers map[string]int
|
||||
NotifyContainers map[string]docker.Signal
|
||||
OnlyExposed bool
|
||||
OnlyPublished bool
|
||||
IncludeStopped bool
|
||||
@ -73,7 +75,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
|
||||
@ -1,14 +1,12 @@
|
||||
package context
|
||||
package dockergen
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"github.com/nginx-proxy/docker-gen/internal/utils"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -20,7 +18,7 @@ var (
|
||||
type Context []*RuntimeContainer
|
||||
|
||||
func (c *Context) Env() map[string]string {
|
||||
return utils.SplitKeyValueSlice(os.Environ())
|
||||
return splitKeyValueSlice(os.Environ())
|
||||
}
|
||||
|
||||
func (c *Context) Docker() Docker {
|
||||
@ -160,78 +158,36 @@ type Docker struct {
|
||||
CurrentContainerID string
|
||||
}
|
||||
|
||||
// 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"}
|
||||
func GetCurrentContainerID() string {
|
||||
file, err := os.Open("/proc/self/cgroup")
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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)
|
||||
}
|
||||
func matchDockerCurrentContainerID(lines string) string {
|
||||
regex := "/docker[/-]([[:alnum:]]{64})(\\.scope)?$"
|
||||
re := regexp.MustCompilePOSIX(regex)
|
||||
|
||||
re = regexp.MustCompilePOSIX(regex)
|
||||
if re.MatchString(lines) {
|
||||
submatches := re.FindStringSubmatch(string(lines))
|
||||
containerID := submatches[1]
|
||||
@ -240,3 +196,17 @@ func matchContainerID(regex, 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 ""
|
||||
}
|
||||
52
context_test.go
Normal file
52
context_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package dockerclient
|
||||
package dockergen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -8,33 +8,14 @@ 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 := utils.PathExists(tlsCaCert); !e || err != nil {
|
||||
if e, err := pathExists(tlsCaCert); !e || err != nil {
|
||||
return nil, errors.New("TLS verification was requested, but CA cert does not exist")
|
||||
}
|
||||
}
|
||||
@ -46,7 +27,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 := utils.PathExists(v); e && err == nil {
|
||||
if e, err := pathExists(v); e && err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -67,7 +48,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://")
|
||||
@ -84,7 +65,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"
|
||||
}
|
||||
@ -92,7 +73,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]
|
||||
@ -103,11 +84,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
|
||||
}
|
||||
@ -118,7 +99,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
|
||||
@ -137,3 +118,15 @@ 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
|
||||
}
|
||||
196
docker_client_test.go
Normal file
196
docker_client_test.go
Normal file
@ -0,0 +1,196 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=A file generator that renders templates using Docker Container meta-data.
|
||||
Documentation=https://github.com/nginx-proxy/docker-gen
|
||||
Documentation=https://github.com/jwilder/docker-gen
|
||||
After=network.target docker.socket
|
||||
Requires=docker.socket
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package generator
|
||||
package dockergen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -11,17 +11,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type generator struct {
|
||||
Client *docker.Client
|
||||
Configs config.ConfigFile
|
||||
Configs ConfigFile
|
||||
Endpoint string
|
||||
TLSVerify bool
|
||||
TLSCert, TLSCaCert, TLSKey string
|
||||
@ -40,18 +35,18 @@ type GeneratorConfig struct {
|
||||
TLSVerify bool
|
||||
All bool
|
||||
|
||||
ConfigFile config.ConfigFile
|
||||
ConfigFile ConfigFile
|
||||
}
|
||||
|
||||
func NewGenerator(gc GeneratorConfig) (*generator, error) {
|
||||
endpoint, err := dockerclient.GetEndpoint(gc.Endpoint)
|
||||
endpoint, err := GetEndpoint(gc.Endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad endpoint: %s", err)
|
||||
return nil, fmt.Errorf("Bad endpoint: %s", err)
|
||||
}
|
||||
|
||||
client, err := dockerclient.NewDockerClient(endpoint, gc.TLSVerify, gc.TLSCert, gc.TLSCACert, gc.TLSKey)
|
||||
client, err := 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()
|
||||
@ -60,7 +55,7 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) {
|
||||
}
|
||||
|
||||
// Grab the docker daemon info once and hold onto it
|
||||
context.SetDockerEnv(apiVersion)
|
||||
SetDockerEnv(apiVersion)
|
||||
|
||||
return &generator{
|
||||
Client: client,
|
||||
@ -103,15 +98,14 @@ func (g *generator) generateFromSignals() {
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
|
||||
sigChan, cleanup := newSignalChannel()
|
||||
defer cleanup()
|
||||
sigChan := newSignalChannel()
|
||||
for {
|
||||
sig := <-sigChan
|
||||
log.Printf("Received signal: %s\n", sig)
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
g.generateFromContainers()
|
||||
case syscall.SIGTERM, syscall.SIGINT:
|
||||
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
|
||||
// exit when context is done
|
||||
return
|
||||
}
|
||||
@ -126,7 +120,7 @@ func (g *generator) generateFromContainers() {
|
||||
return
|
||||
}
|
||||
for _, config := range g.Configs.Config {
|
||||
changed := template.GenerateFile(config, containers)
|
||||
changed := GenerateFile(config, containers)
|
||||
if !changed {
|
||||
log.Printf("Contents of %s did not change. Skipping notification '%s'", config.Dest, config.NotifyCmd)
|
||||
continue
|
||||
@ -137,20 +131,19 @@ func (g *generator) generateFromContainers() {
|
||||
}
|
||||
|
||||
func (g *generator) generateAtInterval() {
|
||||
for _, cfg := range g.Configs.Config {
|
||||
for _, config := range g.Configs.Config {
|
||||
|
||||
if cfg.Interval == 0 {
|
||||
if config.Interval == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Generating every %d seconds", cfg.Interval)
|
||||
log.Printf("Generating every %d seconds", config.Interval)
|
||||
g.wg.Add(1)
|
||||
ticker := time.NewTicker(time.Duration(cfg.Interval) * time.Second)
|
||||
go func(cfg config.Config) {
|
||||
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
|
||||
go func(config Config) {
|
||||
defer g.wg.Done()
|
||||
|
||||
sigChan, cleanup := newSignalChannel()
|
||||
defer cleanup()
|
||||
sigChan := newSignalChannel()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@ -160,19 +153,19 @@ func (g *generator) generateAtInterval() {
|
||||
continue
|
||||
}
|
||||
// ignore changed return value. always run notify command
|
||||
template.GenerateFile(cfg, containers)
|
||||
g.runNotifyCmd(cfg)
|
||||
g.sendSignalToContainer(cfg)
|
||||
GenerateFile(config, containers)
|
||||
g.runNotifyCmd(config)
|
||||
g.sendSignalToContainer(config)
|
||||
case sig := <-sigChan:
|
||||
log.Printf("Received signal: %s\n", sig)
|
||||
switch sig {
|
||||
case syscall.SIGTERM, syscall.SIGINT:
|
||||
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(cfg)
|
||||
}(config)
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,55 +178,54 @@ func (g *generator) generateFromEvents() {
|
||||
client := g.Client
|
||||
var watchers []chan *docker.APIEvents
|
||||
|
||||
for _, cfg := range configs.Config {
|
||||
for _, config := range configs.Config {
|
||||
|
||||
if !cfg.Watch {
|
||||
if !config.Watch {
|
||||
continue
|
||||
}
|
||||
|
||||
g.wg.Add(1)
|
||||
watcher := make(chan *docker.APIEvents, 100)
|
||||
watchers = append(watchers, watcher)
|
||||
|
||||
go func(cfg config.Config) {
|
||||
go func(config Config, watcher chan *docker.APIEvents) {
|
||||
defer g.wg.Done()
|
||||
debouncedChan := newDebounceChannel(watcher, cfg.Wait)
|
||||
for range debouncedChan {
|
||||
watchers = append(watchers, watcher)
|
||||
|
||||
debouncedChan := newDebounceChannel(watcher, config.Wait)
|
||||
for _ = range debouncedChan {
|
||||
containers, err := g.getContainers()
|
||||
if err != nil {
|
||||
log.Printf("Error listing containers: %s\n", err)
|
||||
continue
|
||||
}
|
||||
changed := template.GenerateFile(cfg, containers)
|
||||
changed := GenerateFile(config, containers)
|
||||
if !changed {
|
||||
log.Printf("Contents of %s did not change. Skipping notification '%s'", cfg.Dest, cfg.NotifyCmd)
|
||||
log.Printf("Contents of %s did not change. Skipping notification '%s'", config.Dest, config.NotifyCmd)
|
||||
continue
|
||||
}
|
||||
g.runNotifyCmd(cfg)
|
||||
g.sendSignalToContainer(cfg)
|
||||
g.runNotifyCmd(config)
|
||||
g.sendSignalToContainer(config)
|
||||
}
|
||||
}(cfg)
|
||||
}(config, make(chan *docker.APIEvents, 100))
|
||||
}
|
||||
|
||||
// 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, cleanup := newSignalChannel()
|
||||
defer cleanup()
|
||||
sigChan := newSignalChannel()
|
||||
|
||||
for {
|
||||
watching := false
|
||||
|
||||
if client == nil {
|
||||
var err error
|
||||
endpoint, err := dockerclient.GetEndpoint(g.Endpoint)
|
||||
endpoint, err := GetEndpoint(g.Endpoint)
|
||||
if err != nil {
|
||||
log.Printf("Bad endpoint: %s", err)
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
client, err = dockerclient.NewDockerClient(endpoint, g.TLSVerify, g.TLSCert, g.TLSCaCert, g.TLSKey)
|
||||
client, err = 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)
|
||||
@ -299,7 +291,7 @@ func (g *generator) generateFromEvents() {
|
||||
case sig := <-sigChan:
|
||||
log.Printf("Received signal: %s\n", sig)
|
||||
switch sig {
|
||||
case syscall.SIGTERM, syscall.SIGINT:
|
||||
case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT:
|
||||
// close all watchers and exit
|
||||
for _, watcher := range watchers {
|
||||
close(watcher)
|
||||
@ -312,7 +304,7 @@ func (g *generator) generateFromEvents() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (g *generator) runNotifyCmd(config config.Config) {
|
||||
func (g *generator) runNotifyCmd(config Config) {
|
||||
if config.NotifyCmd == "" {
|
||||
return
|
||||
}
|
||||
@ -332,24 +324,16 @@ func (g *generator) runNotifyCmd(config config.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) sendSignalToContainer(config config.Config) {
|
||||
func (g *generator) sendSignalToContainer(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: docker.Signal(signal),
|
||||
Signal: signal,
|
||||
}
|
||||
if err := g.Client.KillContainer(killOpts); err != nil {
|
||||
log.Printf("Error sending signal to container: %s", err)
|
||||
@ -357,12 +341,12 @@ func (g *generator) sendSignalToContainer(config config.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
func (g *generator) getContainers() ([]*RuntimeContainer, error) {
|
||||
apiInfo, err := g.Client.Info()
|
||||
if err != nil {
|
||||
log.Printf("Error retrieving docker server info: %s\n", err)
|
||||
} else {
|
||||
context.SetServerInfo(apiInfo)
|
||||
SetServerInfo(apiInfo)
|
||||
}
|
||||
|
||||
apiContainers, err := g.Client.ListContainers(docker.ListContainersOptions{
|
||||
@ -373,41 +357,40 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containers := []*context.RuntimeContainer{}
|
||||
containers := []*RuntimeContainer{}
|
||||
for _, apiContainer := range apiContainers {
|
||||
opts := docker.InspectContainerOptions{ID: apiContainer.ID}
|
||||
container, err := g.Client.InspectContainerWithOptions(opts)
|
||||
container, err := g.Client.InspectContainer(apiContainer.ID)
|
||||
if err != nil {
|
||||
log.Printf("Error inspecting container: %s: %s\n", apiContainer.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
registry, repository, tag := dockerclient.SplitDockerImage(container.Config.Image)
|
||||
runtimeContainer := &context.RuntimeContainer{
|
||||
registry, repository, tag := splitDockerImage(container.Config.Image)
|
||||
runtimeContainer := &RuntimeContainer{
|
||||
ID: container.ID,
|
||||
Image: context.DockerImage{
|
||||
Image: DockerImage{
|
||||
Registry: registry,
|
||||
Repository: repository,
|
||||
Tag: tag,
|
||||
},
|
||||
State: context.State{
|
||||
State: State{
|
||||
Running: container.State.Running,
|
||||
},
|
||||
Name: strings.TrimLeft(container.Name, "/"),
|
||||
Hostname: container.Config.Hostname,
|
||||
Gateway: container.NetworkSettings.Gateway,
|
||||
Addresses: []context.Address{},
|
||||
Networks: []context.Network{},
|
||||
Addresses: []Address{},
|
||||
Networks: []Network{},
|
||||
Env: make(map[string]string),
|
||||
Volumes: make(map[string]context.Volume),
|
||||
Node: context.SwarmNode{},
|
||||
Volumes: make(map[string]Volume),
|
||||
Node: 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 := context.Address{
|
||||
address := Address{
|
||||
IP: container.NetworkSettings.IPAddress,
|
||||
IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address,
|
||||
IP6Global: container.NetworkSettings.GlobalIPv6Address,
|
||||
@ -423,7 +406,7 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
|
||||
}
|
||||
for k, v := range container.NetworkSettings.Networks {
|
||||
network := context.Network{
|
||||
network := Network{
|
||||
IP: v.IPAddress,
|
||||
Name: k,
|
||||
Gateway: v.Gateway,
|
||||
@ -439,7 +422,7 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
network)
|
||||
}
|
||||
for k, v := range container.Volumes {
|
||||
runtimeContainer.Volumes[k] = context.Volume{
|
||||
runtimeContainer.Volumes[k] = Volume{
|
||||
Path: k,
|
||||
HostPath: v,
|
||||
ReadWrite: container.VolumesRW[k],
|
||||
@ -448,13 +431,13 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
if container.Node != nil {
|
||||
runtimeContainer.Node.ID = container.Node.ID
|
||||
runtimeContainer.Node.Name = container.Node.Name
|
||||
runtimeContainer.Node.Address = context.Address{
|
||||
runtimeContainer.Node.Address = Address{
|
||||
IP: container.Node.IP,
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range container.Mounts {
|
||||
runtimeContainer.Mounts = append(runtimeContainer.Mounts, context.Mount{
|
||||
runtimeContainer.Mounts = append(runtimeContainer.Mounts, Mount{
|
||||
Name: v.Name,
|
||||
Source: v.Source,
|
||||
Destination: v.Destination,
|
||||
@ -464,7 +447,7 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
})
|
||||
}
|
||||
|
||||
runtimeContainer.Env = utils.SplitKeyValueSlice(container.Config.Env)
|
||||
runtimeContainer.Env = splitKeyValueSlice(container.Config.Env)
|
||||
runtimeContainer.Labels = container.Config.Labels
|
||||
containers = append(containers, runtimeContainer)
|
||||
}
|
||||
@ -472,13 +455,14 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) {
|
||||
|
||||
}
|
||||
|
||||
func newSignalChannel() (<-chan os.Signal, func()) {
|
||||
func newSignalChannel() <-chan os.Signal {
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||
return sig, func() { signal.Stop(sig) }
|
||||
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGKILL)
|
||||
|
||||
return sig
|
||||
}
|
||||
|
||||
func newDebounceChannel(input chan *docker.APIEvents, wait *config.Wait) chan *docker.APIEvents {
|
||||
func newDebounceChannel(input chan *docker.APIEvents, wait *Wait) chan *docker.APIEvents {
|
||||
if wait == nil {
|
||||
return input
|
||||
}
|
||||
@ -1,29 +1,25 @@
|
||||
package generator
|
||||
package dockergen
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"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(io.Discard)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
containerID := "8dfafdbc3a40"
|
||||
var counter atomic.Int32
|
||||
counter := 0
|
||||
|
||||
eventsResponse := `
|
||||
{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067924}
|
||||
@ -39,7 +35,7 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
for rsc.Scan() {
|
||||
w.Write([]byte(rsc.Text()))
|
||||
w.(http.Flusher).Flush()
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}))
|
||||
@ -53,7 +49,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",
|
||||
@ -68,7 +64,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.Add(1)
|
||||
counter++
|
||||
container := docker.Container{
|
||||
Name: "docker-gen-test",
|
||||
ID: containerID,
|
||||
@ -91,7 +87,7 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
},
|
||||
Image: "0ff407d5a7d9ed36acdf3e75de8cc127afecc9af234d05486be2981cdc01a38d",
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
IPAddress: "10.0.0.10",
|
||||
IPAddress: fmt.Sprintf("10.0.0.10"),
|
||||
IPPrefixLen: 24,
|
||||
Gateway: "10.0.0.1",
|
||||
Bridge: "docker0",
|
||||
@ -106,13 +102,13 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
}))
|
||||
|
||||
serverURL := fmt.Sprintf("tcp://%s", strings.TrimRight(strings.TrimPrefix(server.URL(), "http://"), "/"))
|
||||
client, err := dockerclient.NewDockerClient(serverURL, false, "", "", "")
|
||||
client, err := NewDockerClient(serverURL, false, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create client: %s", err)
|
||||
}
|
||||
client.SkipServerVersionCheck = true
|
||||
|
||||
tmplFile, err := os.CreateTemp(os.TempDir(), "docker-gen-tmpl")
|
||||
tmplFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-tmpl")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create temp file: %v\n", err)
|
||||
}
|
||||
@ -120,14 +116,14 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
tmplFile.Close()
|
||||
os.Remove(tmplFile.Name())
|
||||
}()
|
||||
err = os.WriteFile(tmplFile.Name(), []byte("{{range $key, $value := .}}{{$value.ID}}.{{$value.Env.COUNTER}}{{end}}"), 0644)
|
||||
err = ioutil.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 := os.CreateTemp(os.TempDir(), "docker-gen-out")
|
||||
destFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-out")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create temp file: %v\n", err)
|
||||
}
|
||||
@ -144,35 +140,35 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Failed to retrieve docker server version info: %v\n", err)
|
||||
}
|
||||
context.SetDockerEnv(apiVersion) // prevents a panic
|
||||
SetDockerEnv(apiVersion) // prevents a panic
|
||||
|
||||
generator := &generator{
|
||||
Client: client,
|
||||
Endpoint: serverURL,
|
||||
Configs: config.ConfigFile{
|
||||
Config: []config.Config{
|
||||
{
|
||||
Configs: ConfigFile{
|
||||
[]Config{
|
||||
Config{
|
||||
Template: tmplFile.Name(),
|
||||
Dest: destFiles[0].Name(),
|
||||
Watch: false,
|
||||
},
|
||||
{
|
||||
Config{
|
||||
Template: tmplFile.Name(),
|
||||
Dest: destFiles[1].Name(),
|
||||
Watch: true,
|
||||
Wait: &config.Wait{Min: 0, Max: 0},
|
||||
Wait: &Wait{0, 0},
|
||||
},
|
||||
{
|
||||
Config{
|
||||
Template: tmplFile.Name(),
|
||||
Dest: destFiles[2].Name(),
|
||||
Watch: true,
|
||||
Wait: &config.Wait{Min: 200 * time.Millisecond, Max: 250 * time.Millisecond},
|
||||
Wait: &Wait{20 * time.Millisecond, 25 * time.Millisecond},
|
||||
},
|
||||
{
|
||||
Config{
|
||||
Template: tmplFile.Name(),
|
||||
Dest: destFiles[3].Name(),
|
||||
Watch: true,
|
||||
Wait: &config.Wait{Min: 250 * time.Millisecond, Max: 1 * time.Second},
|
||||
Wait: &Wait{25 * time.Millisecond, 100 * time.Millisecond},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -189,12 +185,12 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
|
||||
// The counter is incremented in each output file in the following sequence:
|
||||
//
|
||||
// init 150ms 200ms 250ms 300ms 350ms 400ms 450ms 500ms 550ms 600ms 650ms 700ms
|
||||
// init 0ms 5ms 10ms 15ms 20ms 25ms 30ms 35ms 40ms 45ms 50ms 55ms
|
||||
// ├──────╫──────┼──────┼──────╫──────┼──────┼──────╫──────┼──────┼──────┼──────┼──────┤
|
||||
// File0 ├─ 1 ║ ║ ║
|
||||
// File1 ├─ 1 ╟─ 2 ╟─ 3 ╟─ 5
|
||||
// File2 ├─ 1 ╟───── max (250ms) ──║───────────> 4 ╟─────── min (200ms) ─────> 6
|
||||
// File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (250ms) ────────> 7
|
||||
// File2 ├─ 1 ╟───── max (25ms) ───║───────────> 4 ╟─────── min (20ms) ──────> 6
|
||||
// File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (25ms) ─────────> 7
|
||||
// ┌───╨───┐ ┌───╨──┐ ┌───╨───┐
|
||||
// │ start │ │ stop │ │ start │
|
||||
// └───────┘ └──────┘ └───────┘
|
||||
@ -202,7 +198,7 @@ func TestGenerateFromEvents(t *testing.T) {
|
||||
expectedCounters := []int{1, 5, 6, 7}
|
||||
|
||||
for i, counter := range expectedCounters {
|
||||
value, _ = os.ReadFile(destFiles[i].Name())
|
||||
value, _ = ioutil.ReadFile(destFiles[i].Name())
|
||||
expected = fmt.Sprintf("%s.%d", containerID, counter)
|
||||
if string(value) != expected {
|
||||
t.Errorf("expected: %s. got: %s", expected, value)
|
||||
50
go.mod
50
go.mod
@ -1,50 +0,0 @@
|
||||
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
198
go.sum
@ -1,198 +0,0 @@
|
||||
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=
|
||||
@ -1,61 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@ -1,200 +0,0 @@
|
||||
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())
|
||||
}
|
||||
@ -1,248 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,300 +0,0 @@
|
||||
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")
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,254 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@ -1,195 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -1,374 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
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)
|
||||
}
|
||||
49
reflect.go
Normal file
49
reflect.go
Normal file
@ -0,0 +1,49 @@
|
||||
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()
|
||||
}
|
||||
61
reflect_test.go
Normal file
61
reflect_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
576
template.go
Normal file
576
template.go
Normal file
@ -0,0 +1,576 @@
|
||||
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 read(path string) (string, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
b, err := ioutil.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 = 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,
|
||||
"read": read,
|
||||
"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()
|
||||
}
|
||||
865
template_test.go
Normal file
865
template_test.go
Normal file
@ -0,0 +1,865 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
{{/* 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}})
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
{{/* etcd template to generate registration script */}}
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
{{/* Generates fluentd configuration entries */}}
|
||||
|
||||
## File input
|
||||
## read docker logs with tag=docker.container
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
{{/* 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 }}
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
{{/* 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.
|
||||
@ -18,7 +13,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 }}
|
||||
@ -67,4 +62,4 @@ server {
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
71
utils.go
Normal file
71
utils.go
Normal file
@ -0,0 +1,71 @@
|
||||
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()
|
||||
}
|
||||
139
utils_test.go
Normal file
139
utils_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user