mirror of https://github.com/status-im/consul.git
merge branch main
This commit is contained in:
commit
c258930569
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: removed external dependencies for serving UI assets in favor of Go's native embed capabilities
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
checks: add UDP health checks..
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:enhancement
|
||||||
|
api: add the ability to specify a path prefix for when consul is behind a reverse proxy or API gateway
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:enhancement
|
||||||
|
api: `merge-central-config` query parameter support added to some catalog and health endpoints to view a fully resolved service definition (especially when not written into the catalog that way).
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
proxycfg: Fixed a minor bug that would cause configuring a terminating gateway to watch too many service resolvers and waste resources doing filtering.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
deps: Update go-grpc/grpc, resolving connection memory leak
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
serf: upgrade serf to v0.9.8 which fixes a bug that crashes Consul when serf keyrings are listed
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
Fix a bug when configuring an `add_headers` directive named `Host` the header is not set for `v1/internal/ui/metrics-proxy/` endpoint.
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
```release-note:improvement
|
||||||
|
config: introduce `telemetry.retry_failed_connection` in agent configuration to
|
||||||
|
retry on failed connection to any telemetry backend. This prevents the agent from
|
||||||
|
exiting if the given DogStatsD DNS name is unresolvable, for example.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
config: fix backwards compatibility bug where setting the (deprecated) top-level `verify_incoming` option would enable TLS client authentication on the gRPC port
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
fix a bug that caused an error when creating `grpc` or `http2` ingress gateway listeners with multiple services
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
connect: Added a `max_inbound_connections` setting to service-defaults for limiting the number of concurrent inbound connections to each service instance.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
ui: Re-instate '...' icon for row actions
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
agent: Fixed a bug in HTTP handlers where URLs were being decoded twice
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
telemetry: Added a `consul.server.isLeader` metric to track if a server is a leader or not.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
kvs: Fixed a bug where query options were not being applied to KVS.Get RPC operations.
|
||||||
|
```
|
|
@ -0,0 +1,4 @@
|
||||||
|
```release-note:feature
|
||||||
|
agent: Added information about build date alongside other version information for Consul. Extended /agent/self endpoint and `consul version` commands
|
||||||
|
to report this. Agent also reports build date in log on startup.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: upgrade ember-composable-helpers to v5.x
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
ui: Fix incorrect text on certain page empty states
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
dns: Added support for specifying admin partition in node lookups.
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
connect: Update Envoy support matrix to latest patch releases (1.22.2, 1.21.3, 1.20.4, 1.19.5)
|
||||||
|
```
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:enhancement
|
||||||
|
api: `merge-central-config` query parameter support added to `/catalog/node-services/:node-name` API, to view a fully resolved service definition (especially when not written into the catalog that way).
|
||||||
|
```
|
|
@ -4,4 +4,7 @@ export GIT_COMMIT=$(git rev-parse --short HEAD)
|
||||||
export GIT_COMMIT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
|
export GIT_COMMIT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
|
||||||
export GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
export GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||||
export GIT_IMPORT=github.com/hashicorp/consul/version
|
export GIT_IMPORT=github.com/hashicorp/consul/version
|
||||||
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY}"
|
# we're using this for build date because it's stable across platform builds
|
||||||
|
# the env -i and -noprofile are used to ensure we don't try to recursively call this profile when starting bash
|
||||||
|
export GIT_DATE=$(env -i /bin/bash --noprofile -norc ${CIRCLE_WORKING_DIRECTORY}/build-support/scripts/build-date.sh)
|
||||||
|
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.BuildDate=${GIT_DATE}"
|
||||||
|
|
|
@ -28,8 +28,9 @@ references:
|
||||||
# workflows section for go-test-lib jobs.
|
# workflows section for go-test-lib jobs.
|
||||||
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/cimg/go:1.18.1
|
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/cimg/go:1.18.1
|
||||||
ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:14-browsers
|
ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:14-browsers
|
||||||
|
ubuntu: &UBUNTU_CI_IMAGE ubuntu-2004:202201-02
|
||||||
cache:
|
cache:
|
||||||
yarn: &YARN_CACHE_KEY consul-ui-v7-{{ checksum "ui/yarn.lock" }}
|
yarn: &YARN_CACHE_KEY consul-ui-v8-{{ checksum "ui/yarn.lock" }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
install-gotestsum: &install-gotestsum
|
install-gotestsum: &install-gotestsum
|
||||||
|
@ -167,6 +168,14 @@ jobs:
|
||||||
- run: go install github.com/hashicorp/lint-consul-retry@master && lint-consul-retry
|
- run: go install github.com/hashicorp/lint-consul-retry@master && lint-consul-retry
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
|
lint-enums:
|
||||||
|
docker:
|
||||||
|
- image: *GOLANG_IMAGE
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: go install github.com/reillywatson/enumcover/cmd/enumcover@master && enumcover ./...
|
||||||
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
description: "Run golangci-lint"
|
description: "Run golangci-lint"
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -184,9 +193,7 @@ jobs:
|
||||||
- run: go env
|
- run: go env
|
||||||
- run:
|
- run:
|
||||||
name: Install golangci-lint
|
name: Install golangci-lint
|
||||||
command: |
|
command: make lint-tools
|
||||||
download=https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh
|
|
||||||
wget -O- -q $download | sh -x -s -- -d -b /home/circleci/go/bin v1.45.2
|
|
||||||
- run: go mod download
|
- run: go mod download
|
||||||
- run:
|
- run:
|
||||||
name: lint
|
name: lint
|
||||||
|
@ -238,10 +245,13 @@ jobs:
|
||||||
echo "Generated code was not updated correctly"
|
echo "Generated code was not updated correctly"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
- run:
|
||||||
|
name: "Protobuf Lint"
|
||||||
|
command: make proto-lint
|
||||||
|
|
||||||
go-test-arm64:
|
go-test-arm64:
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:202101-01
|
image: *UBUNTU_CI_IMAGE
|
||||||
resource_class: arm.large
|
resource_class: arm.large
|
||||||
parallelism: 4
|
parallelism: 4
|
||||||
environment:
|
environment:
|
||||||
|
@ -253,7 +263,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
sudo rm -rf /usr/local/go
|
sudo rm -rf /usr/local/go
|
||||||
wget https://dl.google.com/go/go${GO_VERSION}.linux-arm64.tar.gz
|
wget https://dl.google.com/go/go${GO_VERSION}.linux-arm64.tar.gz
|
||||||
sudo tar -C /usr/local -xzvf go${GO_VERSION}.linux-arm64.tar.gz
|
sudo tar -C /usr/local -xzvf go${GO_VERSION}.linux-arm64.tar.gz
|
||||||
|
@ -547,17 +557,17 @@ jobs:
|
||||||
# Run integration tests on nomad/v0.8.7
|
# Run integration tests on nomad/v0.8.7
|
||||||
nomad-integration-0_8:
|
nomad-integration-0_8:
|
||||||
docker:
|
docker:
|
||||||
- image: docker.mirror.hashicorp.services/circleci/golang:1.10
|
- image: docker.mirror.hashicorp.services/cimg/go:1.10
|
||||||
environment:
|
environment:
|
||||||
<<: *ENVIRONMENT
|
<<: *ENVIRONMENT
|
||||||
NOMAD_WORKING_DIR: &NOMAD_WORKING_DIR /go/src/github.com/hashicorp/nomad
|
NOMAD_WORKING_DIR: &NOMAD_WORKING_DIR /home/circleci/go/src/github.com/hashicorp/nomad
|
||||||
NOMAD_VERSION: v0.8.7
|
NOMAD_VERSION: v0.8.7
|
||||||
steps: &NOMAD_INTEGRATION_TEST_STEPS
|
steps: &NOMAD_INTEGRATION_TEST_STEPS
|
||||||
- run: git clone https://github.com/hashicorp/nomad.git --branch ${NOMAD_VERSION} ${NOMAD_WORKING_DIR}
|
- run: git clone https://github.com/hashicorp/nomad.git --branch ${NOMAD_VERSION} ${NOMAD_WORKING_DIR}
|
||||||
|
|
||||||
# get consul binary
|
# get consul binary
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: /go/bin
|
at: /home/circleci/go/bin
|
||||||
|
|
||||||
# make dev build of nomad
|
# make dev build of nomad
|
||||||
- run:
|
- run:
|
||||||
|
@ -587,10 +597,10 @@ jobs:
|
||||||
# run integration tests on nomad/main
|
# run integration tests on nomad/main
|
||||||
nomad-integration-main:
|
nomad-integration-main:
|
||||||
docker:
|
docker:
|
||||||
- image: docker.mirror.hashicorp.services/circleci/golang:1.17 # TODO: replace with cimg/go (requires steps update)
|
- image: docker.mirror.hashicorp.services/cimg/go:1.18
|
||||||
environment:
|
environment:
|
||||||
<<: *ENVIRONMENT
|
<<: *ENVIRONMENT
|
||||||
NOMAD_WORKING_DIR: /go/src/github.com/hashicorp/nomad
|
NOMAD_WORKING_DIR: /home/circleci/go/src/github.com/hashicorp/nomad
|
||||||
NOMAD_VERSION: main
|
NOMAD_VERSION: main
|
||||||
steps: *NOMAD_INTEGRATION_TEST_STEPS
|
steps: *NOMAD_INTEGRATION_TEST_STEPS
|
||||||
|
|
||||||
|
@ -662,23 +672,6 @@ jobs:
|
||||||
- packages/consul-ui/dist
|
- packages/consul-ui/dist
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
# build static-assets file
|
|
||||||
build-static-assets:
|
|
||||||
docker:
|
|
||||||
- image: *GOLANG_IMAGE
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- attach_workspace:
|
|
||||||
at: ./pkg
|
|
||||||
- run: mv pkg/packages/consul-ui/dist pkg/web_ui # 'make static-assets' looks for the 'pkg/web_ui' path
|
|
||||||
- run: make tools
|
|
||||||
- run: make static-assets
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: .
|
|
||||||
paths:
|
|
||||||
- ./agent/uiserver/bindata_assetfs.go
|
|
||||||
- run: *notify-slack-failure
|
|
||||||
|
|
||||||
# commits static assets to git
|
# commits static assets to git
|
||||||
publish-static-assets:
|
publish-static-assets:
|
||||||
docker:
|
docker:
|
||||||
|
@ -691,7 +684,12 @@ jobs:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- run:
|
- run:
|
||||||
name: commit agent/uiserver/bindata_assetfs.go if there are UI changes
|
name: move compiled ui files to agent/uiserver
|
||||||
|
command: |
|
||||||
|
rm -rf agent/uiserver/dist
|
||||||
|
mv packages/consul-ui/dist agent/uiserver
|
||||||
|
- run:
|
||||||
|
name: commit agent/uiserver/dist/ if there are UI changes
|
||||||
command: |
|
command: |
|
||||||
# check if there are any changes in ui/
|
# check if there are any changes in ui/
|
||||||
# if there are, we commit the ui static asset file
|
# if there are, we commit the ui static asset file
|
||||||
|
@ -705,8 +703,8 @@ jobs:
|
||||||
git checkout -B ci/main-assetfs-build main
|
git checkout -B ci/main-assetfs-build main
|
||||||
|
|
||||||
short_sha=$(git rev-parse --short HEAD)
|
short_sha=$(git rev-parse --short HEAD)
|
||||||
git add agent/uiserver/bindata_assetfs.go
|
git add agent/uiserver/dist/
|
||||||
git commit -m "auto-updated agent/uiserver/bindata_assetfs.go from commit ${short_sha}"
|
git commit -m "auto-updated agent/uiserver/dist/ from commit ${short_sha}"
|
||||||
git push --force origin ci/main-assetfs-build
|
git push --force origin ci/main-assetfs-build
|
||||||
else
|
else
|
||||||
echo "no UI changes so no static assets to publish"
|
echo "no UI changes so no static assets to publish"
|
||||||
|
@ -803,7 +801,7 @@ jobs:
|
||||||
|
|
||||||
compatibility-integration-test:
|
compatibility-integration-test:
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:202101-01
|
image: *UBUNTU_CI_IMAGE
|
||||||
docker_layer_caching: true
|
docker_layer_caching: true
|
||||||
parallelism: 1
|
parallelism: 1
|
||||||
steps:
|
steps:
|
||||||
|
@ -824,14 +822,19 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Compatibility Integration Tests
|
name: Compatibility Integration Tests
|
||||||
command: |
|
command: |
|
||||||
subtests=$(ls -d test/integration/consul-container/*/ | grep -v libs | xargs -n 1 basename | circleci tests split)
|
|
||||||
echo "Running $(echo $subtests | wc -w) subtests"
|
|
||||||
echo "$subtests"
|
|
||||||
subtests_pipe_sepr=$(echo "$subtests" | xargs | sed 's/ /|/g')
|
|
||||||
mkdir -p /tmp/test-results/
|
mkdir -p /tmp/test-results/
|
||||||
docker run consul:local consul version
|
|
||||||
cd ./test/integration/consul-container
|
cd ./test/integration/consul-container
|
||||||
gotestsum -- -timeout=30m ./$subtests_pipe_sepr --target-version local --latest-version latest
|
docker run --rm consul:local consul version
|
||||||
|
gotestsum \
|
||||||
|
--format=short-verbose \
|
||||||
|
--debug \
|
||||||
|
--rerun-fails=3 \
|
||||||
|
--packages="./..." \
|
||||||
|
-- \
|
||||||
|
-timeout=30m \
|
||||||
|
./... \
|
||||||
|
--target-version local \
|
||||||
|
--latest-version latest
|
||||||
ls -lrt
|
ls -lrt
|
||||||
environment:
|
environment:
|
||||||
# this is needed because of incompatibility between RYUK container and circleci
|
# this is needed because of incompatibility between RYUK container and circleci
|
||||||
|
@ -849,13 +852,13 @@ jobs:
|
||||||
path: *TEST_RESULTS_DIR
|
path: *TEST_RESULTS_DIR
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
envoy-integration-test-1_19_3: &ENVOY_TESTS
|
envoy-integration-test-1_19_5: &ENVOY_TESTS
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:202201-02
|
image: *UBUNTU_CI_IMAGE
|
||||||
parallelism: 4
|
parallelism: 4
|
||||||
resource_class: medium
|
resource_class: medium
|
||||||
environment:
|
environment:
|
||||||
ENVOY_VERSION: "1.19.3"
|
ENVOY_VERSION: "1.19.5"
|
||||||
steps: &ENVOY_INTEGRATION_TEST_STEPS
|
steps: &ENVOY_INTEGRATION_TEST_STEPS
|
||||||
- checkout
|
- checkout
|
||||||
# Get go binary from workspace
|
# Get go binary from workspace
|
||||||
|
@ -888,20 +891,20 @@ jobs:
|
||||||
path: *TEST_RESULTS_DIR
|
path: *TEST_RESULTS_DIR
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
envoy-integration-test-1_20_2:
|
envoy-integration-test-1_20_4:
|
||||||
<<: *ENVOY_TESTS
|
<<: *ENVOY_TESTS
|
||||||
environment:
|
environment:
|
||||||
ENVOY_VERSION: "1.20.2"
|
ENVOY_VERSION: "1.20.4"
|
||||||
|
|
||||||
envoy-integration-test-1_21_1:
|
envoy-integration-test-1_21_3:
|
||||||
<<: *ENVOY_TESTS
|
<<: *ENVOY_TESTS
|
||||||
environment:
|
environment:
|
||||||
ENVOY_VERSION: "1.21.1"
|
ENVOY_VERSION: "1.21.3"
|
||||||
|
|
||||||
envoy-integration-test-1_22_0:
|
envoy-integration-test-1_22_2:
|
||||||
<<: *ENVOY_TESTS
|
<<: *ENVOY_TESTS
|
||||||
environment:
|
environment:
|
||||||
ENVOY_VERSION: "1.22.0"
|
ENVOY_VERSION: "1.22.2"
|
||||||
|
|
||||||
# run integration tests for the connect ca providers
|
# run integration tests for the connect ca providers
|
||||||
test-connect-ca-providers:
|
test-connect-ca-providers:
|
||||||
|
@ -927,19 +930,6 @@ jobs:
|
||||||
path: *TEST_RESULTS_DIR
|
path: *TEST_RESULTS_DIR
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
# only runs on main: checks latest commit to see if the PR associated has a backport/* or docs* label to cherry-pick
|
|
||||||
cherry-picker:
|
|
||||||
docker:
|
|
||||||
- image: docker.mirror.hashicorp.services/alpine:3.12
|
|
||||||
steps:
|
|
||||||
- run: apk add --no-cache --no-progress git bash curl ncurses jq openssh-client
|
|
||||||
- checkout
|
|
||||||
- add_ssh_keys: # needs a key to push cherry-picked commits back to github
|
|
||||||
fingerprints:
|
|
||||||
- "fc:55:84:15:0a:1d:c8:e9:06:d0:e8:9c:7b:a9:b7:31"
|
|
||||||
- run: .circleci/scripts/cherry-picker.sh
|
|
||||||
- run: *notify-slack-failure
|
|
||||||
|
|
||||||
trigger-oss-merge:
|
trigger-oss-merge:
|
||||||
docker:
|
docker:
|
||||||
- image: docker.mirror.hashicorp.services/alpine:3.12
|
- image: docker.mirror.hashicorp.services/alpine:3.12
|
||||||
|
@ -987,6 +977,7 @@ jobs:
|
||||||
LOCAL_COMMIT_SHA="<< pipeline.parameters.commit >>"
|
LOCAL_COMMIT_SHA="<< pipeline.parameters.commit >>"
|
||||||
fi
|
fi
|
||||||
echo "export LOCAL_COMMIT_SHA=${LOCAL_COMMIT_SHA}" >> $BASH_ENV
|
echo "export LOCAL_COMMIT_SHA=${LOCAL_COMMIT_SHA}" >> $BASH_ENV
|
||||||
|
git checkout ${LOCAL_COMMIT_SHA}
|
||||||
|
|
||||||
short_ref=$(git rev-parse --short ${LOCAL_COMMIT_SHA})
|
short_ref=$(git rev-parse --short ${LOCAL_COMMIT_SHA})
|
||||||
echo "export TF_VAR_ami_owners=$LOAD_TEST_AMI_OWNERS" >> $BASH_ENV
|
echo "export TF_VAR_ami_owners=$LOAD_TEST_AMI_OWNERS" >> $BASH_ENV
|
||||||
|
@ -1006,7 +997,8 @@ jobs:
|
||||||
working_directory: .circleci/terraform/load-test
|
working_directory: .circleci/terraform/load-test
|
||||||
name: terraform init
|
name: terraform init
|
||||||
command: |
|
command: |
|
||||||
echo "commit is ${LOCAL_COMMIT_SHA}"
|
short_ref=$(git rev-parse --short HEAD)
|
||||||
|
echo "Testing commit id: $short_ref"
|
||||||
terraform init \
|
terraform init \
|
||||||
-backend-config="bucket=${BUCKET}" \
|
-backend-config="bucket=${BUCKET}" \
|
||||||
-backend-config="key=${LOCAL_COMMIT_SHA}" \
|
-backend-config="key=${LOCAL_COMMIT_SHA}" \
|
||||||
|
@ -1022,7 +1014,7 @@ jobs:
|
||||||
when: always
|
when: always
|
||||||
name: terraform destroy
|
name: terraform destroy
|
||||||
command: |
|
command: |
|
||||||
terraform destroy -auto-approve
|
for i in $(seq 1 5); do terraform destroy -auto-approve && s=0 && break || s=$? && sleep 20; done; (exit $s)
|
||||||
- run: *notify-slack-failure
|
- run: *notify-slack-failure
|
||||||
|
|
||||||
# The noop job is a used as a very fast job in the verify-ci workflow because every workflow
|
# The noop job is a used as a very fast job in the verify-ci workflow because every workflow
|
||||||
|
@ -1038,7 +1030,7 @@ workflows:
|
||||||
# verify-ci is a no-op workflow that must run on every PR. It is used in a
|
# verify-ci is a no-op workflow that must run on every PR. It is used in a
|
||||||
# branch protection rule to detect when CI workflows are not running.
|
# branch protection rule to detect when CI workflows are not running.
|
||||||
verify-ci:
|
verify-ci:
|
||||||
jobs: [ noop ]
|
jobs: [noop]
|
||||||
|
|
||||||
go-tests:
|
go-tests:
|
||||||
unless: << pipeline.parameters.trigger-load-test >>
|
unless: << pipeline.parameters.trigger-load-test >>
|
||||||
|
@ -1051,6 +1043,7 @@ workflows:
|
||||||
- /^docs\/.*/
|
- /^docs\/.*/
|
||||||
- /^ui\/.*/
|
- /^ui\/.*/
|
||||||
- check-generated-protobuf: *filter-ignore-non-go-branches
|
- check-generated-protobuf: *filter-ignore-non-go-branches
|
||||||
|
- lint-enums: *filter-ignore-non-go-branches
|
||||||
- lint-consul-retry: *filter-ignore-non-go-branches
|
- lint-consul-retry: *filter-ignore-non-go-branches
|
||||||
- lint: *filter-ignore-non-go-branches
|
- lint: *filter-ignore-non-go-branches
|
||||||
- lint:
|
- lint:
|
||||||
|
@ -1061,17 +1054,17 @@ workflows:
|
||||||
- go-test-arm64: *filter-ignore-non-go-branches
|
- go-test-arm64: *filter-ignore-non-go-branches
|
||||||
- dev-build: *filter-ignore-non-go-branches
|
- dev-build: *filter-ignore-non-go-branches
|
||||||
- go-test:
|
- go-test:
|
||||||
requires: [ dev-build ]
|
requires: [dev-build]
|
||||||
- go-test-lib:
|
- go-test-lib:
|
||||||
name: "go-test-api go1.17"
|
name: "go-test-api go1.17"
|
||||||
path: api
|
path: api
|
||||||
go-version: "1.17"
|
go-version: "1.17"
|
||||||
requires: [ dev-build ]
|
requires: [dev-build]
|
||||||
- go-test-lib:
|
- go-test-lib:
|
||||||
name: "go-test-api go1.18"
|
name: "go-test-api go1.18"
|
||||||
path: api
|
path: api
|
||||||
go-version: "1.18"
|
go-version: "1.18"
|
||||||
requires: [ dev-build ]
|
requires: [dev-build]
|
||||||
- go-test-lib:
|
- go-test-lib:
|
||||||
name: "go-test-sdk go1.17"
|
name: "go-test-sdk go1.17"
|
||||||
path: sdk
|
path: sdk
|
||||||
|
@ -1084,6 +1077,7 @@ workflows:
|
||||||
<<: *filter-ignore-non-go-branches
|
<<: *filter-ignore-non-go-branches
|
||||||
- go-test-race: *filter-ignore-non-go-branches
|
- go-test-race: *filter-ignore-non-go-branches
|
||||||
- go-test-32bit: *filter-ignore-non-go-branches
|
- go-test-32bit: *filter-ignore-non-go-branches
|
||||||
|
- noop
|
||||||
build-distros:
|
build-distros:
|
||||||
unless: << pipeline.parameters.trigger-load-test >>
|
unless: << pipeline.parameters.trigger-load-test >>
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1102,20 +1096,12 @@ workflows:
|
||||||
- ember-build-prod:
|
- ember-build-prod:
|
||||||
requires:
|
requires:
|
||||||
- frontend-cache
|
- frontend-cache
|
||||||
- build-static-assets:
|
- publish-static-assets:
|
||||||
requires:
|
requires:
|
||||||
- ember-build-prod
|
- ember-build-prod
|
||||||
- publish-static-assets:
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
- /release\/\d+\.\d+\.x$/
|
|
||||||
requires:
|
|
||||||
- build-static-assets
|
|
||||||
- dev-build:
|
- dev-build:
|
||||||
requires:
|
requires:
|
||||||
- build-static-assets
|
- ember-build-prod
|
||||||
- dev-upload-s3:
|
- dev-upload-s3:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
|
@ -1123,6 +1109,7 @@ workflows:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
context: consul-ci
|
context: consul-ci
|
||||||
|
- noop
|
||||||
test-integrations:
|
test-integrations:
|
||||||
unless: << pipeline.parameters.trigger-load-test >>
|
unless: << pipeline.parameters.trigger-load-test >>
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1144,22 +1131,22 @@ workflows:
|
||||||
- nomad-integration-0_8:
|
- nomad-integration-0_8:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
- envoy-integration-test-1_19_3:
|
- envoy-integration-test-1_19_5:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
- envoy-integration-test-1_20_2:
|
- envoy-integration-test-1_20_4:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
- envoy-integration-test-1_21_1:
|
- envoy-integration-test-1_21_3:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
- envoy-integration-test-1_22_0:
|
- envoy-integration-test-1_22_2:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
- compatibility-integration-test:
|
- compatibility-integration-test:
|
||||||
requires:
|
requires:
|
||||||
- dev-build
|
- dev-build
|
||||||
|
- noop
|
||||||
frontend:
|
frontend:
|
||||||
unless: << pipeline.parameters.trigger-load-test >>
|
unless: << pipeline.parameters.trigger-load-test >>
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1192,6 +1179,7 @@ workflows:
|
||||||
- ember-coverage:
|
- ember-coverage:
|
||||||
requires:
|
requires:
|
||||||
- ember-build-ent
|
- ember-build-ent
|
||||||
|
- noop
|
||||||
workflow-automation:
|
workflow-automation:
|
||||||
unless: << pipeline.parameters.trigger-load-test >>
|
unless: << pipeline.parameters.trigger-load-test >>
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1202,13 +1190,7 @@ workflows:
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- /release\/\d+\.\d+\.x$/
|
- /release\/\d+\.\d+\.x$/
|
||||||
- cherry-picker:
|
|
||||||
context: team-consul
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
- /release\/\d+\.\d+\.x$/
|
|
||||||
load-test:
|
load-test:
|
||||||
when: << pipeline.parameters.trigger-load-test >>
|
when: << pipeline.parameters.trigger-load-test >>
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
@ -1,195 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# This script is meant to run on every new commit to main in CircleCI. If the commit comes from a PR, it will
|
|
||||||
# check the PR associated with the commit for labels. If the label matches `docs*` it will be cherry-picked
|
|
||||||
# to stable-website. If the label matches `backport/*`, it will be cherry-picked to the appropriate `release/*`
|
|
||||||
# branch.
|
|
||||||
|
|
||||||
# Requires $CIRCLE_PROJECT_USERNAME, $CIRCLE_PROJECT_REPONAME, and $CIRCLE_SHA1 from CircleCI
|
|
||||||
|
|
||||||
set -o pipefail
|
|
||||||
|
|
||||||
# colorized status prompt
|
|
||||||
function status {
|
|
||||||
tput setaf 4
|
|
||||||
echo "$@"
|
|
||||||
tput sgr0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Returns the latest GitHub "backport/*" label
|
|
||||||
function get_latest_backport_label {
|
|
||||||
local resp
|
|
||||||
local ret
|
|
||||||
local latest_backport_label
|
|
||||||
|
|
||||||
resp=$(curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/labels?per_page=100")
|
|
||||||
ret="$?"
|
|
||||||
if [[ "$ret" -ne 0 ]]; then
|
|
||||||
status "The GitHub API returned $ret which means it was probably rate limited."
|
|
||||||
exit $ret
|
|
||||||
fi
|
|
||||||
|
|
||||||
latest_backport_label=$(echo "$resp" | jq -r '.[] | select(.name | startswith("backport/")) | .name' | sort -rV | head -n1)
|
|
||||||
echo "$latest_backport_label"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# This function will do the cherry-picking of a commit on a branch
|
|
||||||
# Exit 1 if cherry-picking fails
|
|
||||||
function cherry_pick_with_slack_notification {
|
|
||||||
# Arguments:
|
|
||||||
# $1 - branch to cherry-pick to
|
|
||||||
# $2 - commit to cherry-pick
|
|
||||||
# $3 - url to PR of commit
|
|
||||||
#
|
|
||||||
# Return:
|
|
||||||
# 0 for success
|
|
||||||
# 1 for error
|
|
||||||
|
|
||||||
local branch="$1"
|
|
||||||
local commit="$2"
|
|
||||||
local pr_url="$3"
|
|
||||||
|
|
||||||
git checkout "$branch" || exit 1
|
|
||||||
# If git cherry-pick fails or it fails to push, we send a failure notification
|
|
||||||
if ! (git cherry-pick --mainline 1 "$commit" && git push origin "$branch"); then
|
|
||||||
status "🍒❌ Cherry pick of commit ${commit:0:7} from $pr_url onto $branch failed!"
|
|
||||||
|
|
||||||
# send slack notification
|
|
||||||
curl -X POST -H 'Content-type: application/json' \
|
|
||||||
--data \
|
|
||||||
"{ \
|
|
||||||
\"attachments\": [ \
|
|
||||||
{ \
|
|
||||||
\"fallback\": \"Cherry pick failed!\", \
|
|
||||||
\"text\": \"🍒❌ Cherry picking of <$pr_url|${commit:0:7}> to \`$branch\` failed!\n\nBuild Log: ${CIRCLE_BUILD_URL}\", \
|
|
||||||
\"footer\": \"${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}\", \
|
|
||||||
\"ts\": \"$(date +%s)\", \
|
|
||||||
\"color\": \"danger\" \
|
|
||||||
} \
|
|
||||||
] \
|
|
||||||
}" "${CONSUL_SLACK_WEBHOOK_URL}"
|
|
||||||
|
|
||||||
# post PR comment to GitHub
|
|
||||||
github_message=":cherries::x: Cherry pick of commit ${commit} onto \`$branch\` failed! [Build Log]($CIRCLE_BUILD_URL)"
|
|
||||||
pr_id=$(basename ${pr_url})
|
|
||||||
curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
||||||
-X POST \
|
|
||||||
-d "{ \"body\": \"${github_message}\"}" \
|
|
||||||
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_id}/comments"
|
|
||||||
|
|
||||||
# run git status to leave error in CircleCI log
|
|
||||||
git status
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Else we send a success notification
|
|
||||||
else
|
|
||||||
status "🍒✅ Cherry picking of PR commit ${commit:0:7} from ${pr_url} succeeded!"
|
|
||||||
curl -X POST -H 'Content-type: application/json' \
|
|
||||||
--data \
|
|
||||||
"{ \
|
|
||||||
\"attachments\": [ \
|
|
||||||
{ \
|
|
||||||
\"fallback\": \"Cherry pick succeeded!\", \
|
|
||||||
\"text\": \"🍒✅ Cherry picking of <$pr_url|${commit:0:7}> to \`$branch\` succeeded!\", \
|
|
||||||
\"footer\": \"${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}\", \
|
|
||||||
\"ts\": \"$(date +%s)\", \
|
|
||||||
\"color\": \"good\" \
|
|
||||||
} \
|
|
||||||
] \
|
|
||||||
}" "${CONSUL_SLACK_WEBHOOK_URL}"
|
|
||||||
|
|
||||||
# post PR comment to GitHub
|
|
||||||
github_message=":cherries::white_check_mark: Cherry pick of commit ${commit} onto \`$branch\` succeeded!"
|
|
||||||
pr_id=$(basename ${pr_url})
|
|
||||||
curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
||||||
-X POST \
|
|
||||||
-d "{ \"body\": \"${github_message}\"}" \
|
|
||||||
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_id}/comments"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# search for the PR labels applicable to the specified commit
|
|
||||||
resp=$(curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/search/issues?q=repo:${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}+sha:${CIRCLE_SHA1}")
|
|
||||||
ret="$?"
|
|
||||||
if [[ "$ret" -ne 0 ]]; then
|
|
||||||
status "The GitHub API returned $ret which means it was probably rate limited."
|
|
||||||
exit $ret
|
|
||||||
fi
|
|
||||||
|
|
||||||
# get the count from the GitHub API to check if the commit matched a PR
|
|
||||||
count=$(echo "$resp" | jq '.total_count')
|
|
||||||
if [[ "$count" -eq 0 ]]; then
|
|
||||||
status "This commit was not associated with a PR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# save PR number
|
|
||||||
pr_number=$(echo "$resp" | jq '.items[].number')
|
|
||||||
|
|
||||||
# comment on the PR with the build number to make it easy to re-run the job when
|
|
||||||
# cherry-pick labels are added in the future
|
|
||||||
github_message=":cherries: If backport labels were added before merging, cherry-picking will start automatically.\n\nTo retroactively trigger a backport after merging, add backport labels and re-run ${CIRCLE_BUILD_URL}."
|
|
||||||
curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
||||||
-X POST \
|
|
||||||
-d "{ \"body\": \"${github_message}\"}" \
|
|
||||||
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_number}/comments"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# If the API returned a non-zero count, we have found a PR with that commit so we find
|
|
||||||
# the labels from the PR
|
|
||||||
|
|
||||||
# Sorts the labels from a PR via version sort
|
|
||||||
labels=$(echo "$resp" | jq --raw-output '.items[].labels[] | .name' | sort -rV)
|
|
||||||
ret="$?"
|
|
||||||
pr_url=$(echo "$resp" | jq --raw-output '.items[].pull_request.html_url')
|
|
||||||
if [[ "$ret" -ne 0 ]]; then
|
|
||||||
status "jq exited with $ret when trying to find label names. Are there labels applied to the PR ($pr_url)?"
|
|
||||||
# This can be a valid error but usually this means we do not have any labels so it doesn't signal
|
|
||||||
# cherry-picking is possible. Exit 0 for now unless we run into cases where these failures are important.
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Attach label for latest release branch if 'docs-cherrypick' is present. Will noop if already applied.
|
|
||||||
latest_backport_label=$(get_latest_backport_label)
|
|
||||||
status "latest backport label is $latest_backport_label"
|
|
||||||
if echo "$resp" | jq -e '.items[].labels[] | select(.name | contains("docs-cherrypick"))'; then
|
|
||||||
labels=$(curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST -d "{\"labels\":[\"$latest_backport_label\"]}" "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_number}/labels" | jq --raw-output '.[].name' | sort -rV)
|
|
||||||
ret="$?"
|
|
||||||
if [[ "$ret" -ne 0 ]]; then
|
|
||||||
status "Error applying $latest_backport_label to $pr_url"
|
|
||||||
exit $ret
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
git config --local user.email "github-team-consul-core@hashicorp.com"
|
|
||||||
git config --local user.name "hc-github-team-consul-core"
|
|
||||||
|
|
||||||
backport_failures=0
|
|
||||||
# loop through all labels on the PR
|
|
||||||
for label in $labels; do
|
|
||||||
status "checking label: $label"
|
|
||||||
# if the label matches docs-cherrypick, it will attempt to cherry-pick to stable-website
|
|
||||||
if [[ $label =~ docs-cherrypick ]]; then
|
|
||||||
status "backporting to stable-website"
|
|
||||||
branch="stable-website"
|
|
||||||
cherry_pick_with_slack_notification "$branch" "$CIRCLE_SHA1" "$pr_url"
|
|
||||||
backport_failures=$((backport_failures + "$?"))
|
|
||||||
# else if the label matches backport/*, it will attempt to cherry-pick to the release branch
|
|
||||||
elif [[ $label =~ backport/* ]]; then
|
|
||||||
status "backporting to $label"
|
|
||||||
branch="${label/backport/release}.x"
|
|
||||||
cherry_pick_with_slack_notification "$branch" "$CIRCLE_SHA1" "$pr_url"
|
|
||||||
backport_failures=$((backport_failures + "$?"))
|
|
||||||
fi
|
|
||||||
# reset the working directory for the next label
|
|
||||||
git reset --hard
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$backport_failures" -ne 0 ]; then
|
|
||||||
echo "$backport_failures backports failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -10,15 +10,17 @@ provider "aws" {
|
||||||
}
|
}
|
||||||
|
|
||||||
module "load-test" {
|
module "load-test" {
|
||||||
source = "github.com/hashicorp/consul/test/load/terraform"
|
source = "../../../test/load/terraform"
|
||||||
|
|
||||||
vpc_az = ["us-east-2a", "us-east-2b"]
|
vpc_az = ["us-east-2a", "us-east-2b"]
|
||||||
vpc_name = var.vpc_name
|
vpc_name = var.vpc_name
|
||||||
vpc_cidr = "10.0.0.0/16"
|
vpc_cidr = "10.0.0.0/16"
|
||||||
|
vpc_allwed_ssh_cidr = "0.0.0.0/0"
|
||||||
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
|
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
|
||||||
private_subnet_cidrs = ["10.0.3.0/24"]
|
private_subnet_cidrs = ["10.0.3.0/24"]
|
||||||
test_public_ip = true
|
test_public_ip = true
|
||||||
ami_owners = var.ami_owners
|
ami_owners = var.ami_owners
|
||||||
consul_download_url = var.consul_download_url
|
consul_download_url = var.consul_download_url
|
||||||
cluster_name = var.cluster_name
|
cluster_name = var.cluster_name
|
||||||
|
cluster_tag_key = var.cluster_tag_key
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,3 +22,9 @@ variable "cluster_name" {
|
||||||
type = string
|
type = string
|
||||||
default = "consul-example"
|
default = "consul-example"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "cluster_tag_key" {
|
||||||
|
description = "The tag the EC2 Instances will look for to automatically discover each other and form a cluster."
|
||||||
|
type = string
|
||||||
|
default = "consul-ci-load-test"
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,6 @@
|
||||||
/website/content/api-docs/ @hashicorp/consul-docs
|
/website/content/api-docs/ @hashicorp/consul-docs
|
||||||
|
|
||||||
|
|
||||||
|
# release configuration
|
||||||
|
/.release/ @hashicorp/release-engineering @hashicorp/github-consul-core
|
||||||
|
/.github/workflows/build.yml @hashicorp/release-engineering @hashicorp/github-consul-core
|
||||||
|
|
|
@ -156,12 +156,25 @@ When you're ready to submit a pull request:
|
||||||
if your changes aren't finalized but would benefit from in-process feedback.
|
if your changes aren't finalized but would benefit from in-process feedback.
|
||||||
5. If there's any reason Consul users might need to know about this change,
|
5. If there's any reason Consul users might need to know about this change,
|
||||||
[add a changelog entry](../docs/contributing/add-a-changelog-entry.md).
|
[add a changelog entry](../docs/contributing/add-a-changelog-entry.md).
|
||||||
6. After you submit, the Consul maintainers team needs time to carefully review your
|
6. Add labels to your pull request. A table of commonly use labels is below.
|
||||||
|
If you have any questions about which to apply, feel free to call it out in the PR or comments.
|
||||||
|
| Label | When to Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `pr/no-changelog` | This PR does not have an intended changelog entry |
|
||||||
|
| `pr/no-metrics-test` | This PR does not require any testing for metrics |
|
||||||
|
| `backport/stable-website` | This PR contains documentation changes that are ready to be deployed immediately. Changes will also automatically get backported to the latest release branch |
|
||||||
|
| `backport/1.12.x` | Backport the changes in this PR to the targeted release branch. Consult the [Consul Release Notes](https://www.consul.io/docs/release-notes) page to view active releases. |
|
||||||
|
Other labels may automatically be added by the Github Action CI.
|
||||||
|
7. After you submit, the Consul maintainers team needs time to carefully review your
|
||||||
contribution and ensure it is production-ready, considering factors such as: security,
|
contribution and ensure it is production-ready, considering factors such as: security,
|
||||||
backwards-compatibility, potential regressions, etc.
|
backwards-compatibility, potential regressions, etc.
|
||||||
7. After you address Consul maintainer feedback and the PR is approved, a Consul maintainer
|
8. After you address Consul maintainer feedback and the PR is approved, a Consul maintainer
|
||||||
will merge it. Your contribution will be available from the next major release (e.g., 1.x)
|
will merge it. Your contribution will be available from the next major release (e.g., 1.x)
|
||||||
unless explicitly backported to an existing or previous major release by the maintainer.
|
unless explicitly backported to an existing or previous major release by the maintainer.
|
||||||
|
9. Any backport labels will generate an additional PR to the targeted release branch.
|
||||||
|
These will be linked in the original PR.
|
||||||
|
Assuming the tests pass, the PR will be merged automatically.
|
||||||
|
If the tests fail, it is you responsibility to resolve the issues with backports and request another reviewer.
|
||||||
|
|
||||||
#### Checklists
|
#### Checklists
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,3 @@ Please be mindful not to leak any customer or confidential information. HashiCor
|
||||||
* [ ] updated test coverage
|
* [ ] updated test coverage
|
||||||
* [ ] external facing docs updated
|
* [ ] external facing docs updated
|
||||||
* [ ] not a security concern
|
* [ ] not a security concern
|
||||||
* [ ] checklist [folder](./../docs/config) consulted
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ set -uo pipefail
|
||||||
### It is still up to the reviewer to make sure that any tests added are needed and meaningful.
|
### It is still up to the reviewer to make sure that any tests added are needed and meaningful.
|
||||||
|
|
||||||
# search for any "new" or modified metric emissions
|
# search for any "new" or modified metric emissions
|
||||||
metrics_modified=$(git --no-pager diff HEAD origin/main | grep -i "SetGauge\|EmitKey\|IncrCounter\|AddSample\|MeasureSince\|UpdateFilter")
|
metrics_modified=$(git --no-pager diff origin/main...HEAD | grep -i "SetGauge\|EmitKey\|IncrCounter\|AddSample\|MeasureSince\|UpdateFilter")
|
||||||
# search for PR body or title metric references
|
# search for PR body or title metric references
|
||||||
metrics_in_pr_body=$(echo "${PR_BODY-""}" | grep -i "metric")
|
metrics_in_pr_body=$(echo "${PR_BODY-""}" | grep -i "metric")
|
||||||
metrics_in_pr_title=$(echo "${PR_TITLE-""}" | grep -i "metric")
|
metrics_in_pr_title=$(echo "${PR_TITLE-""}" | grep -i "metric")
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# verify_artifact.sh is the top-level script that implements the logic to decide
|
||||||
|
# which individual verification script to invoke. It decides which verification
|
||||||
|
# script to use based on artifact name it is given. By putting the logic in here,
|
||||||
|
# it keeps the workflow file simpler and easier to manage. It also doubles as a means
|
||||||
|
# to run verifications locally when necessary.
|
||||||
|
|
||||||
|
# set this so we can locate and execute the individual verification scripts.
|
||||||
|
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "verify_artifact.sh <artifact_path> <expect_version>"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Arguments:
|
||||||
|
# $1 - artifact path (eg. /artifacts/consul-1.13.0~dev-1.i386.rpm)
|
||||||
|
# $2 - expected version to match against (eg. v1.13.0-dev)
|
||||||
|
function main {
|
||||||
|
local artifact_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
|
||||||
|
if [[ -z "${artifact_path}" ]]; then
|
||||||
|
echo "ERROR: artifact path argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${expect_version}" ]]; then
|
||||||
|
echo "ERROR: expected version argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "${artifact_path}" ]]; then
|
||||||
|
echo "ERROR: ${artifact_path} does not exist"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# match against the various artifact names:
|
||||||
|
# deb packages: consul_${version}-1_${arch}.deb
|
||||||
|
# rpm packages: consul-${version}-1.${arch}.rpm
|
||||||
|
# zip packages: consul_${version}_${os}_${arch}.zip
|
||||||
|
case "${artifact_path}" in
|
||||||
|
*.rpm) verify_rpm "${artifact_path}" "${expect_version}";;
|
||||||
|
*.deb) verify_deb "${artifact_path}" "${expect_version}";;
|
||||||
|
*.zip) verify_zip "${artifact_path}" "${expect_version}";;
|
||||||
|
*)
|
||||||
|
echo "${artifact_path} did not match known patterns"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Arguments:
|
||||||
|
# $1 - path to rpm (eg. consul-1.13.0~dev-1.aarch64.rpm)
|
||||||
|
# $2 - expected version to match against (eg. v1.13.0-dev)
|
||||||
|
function verify_rpm {
|
||||||
|
local artifact_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local docker_image
|
||||||
|
local docker_platform
|
||||||
|
|
||||||
|
case "${artifact_path}" in
|
||||||
|
*.i386.rpm)
|
||||||
|
docker_platform="linux/386"
|
||||||
|
docker_image="i386/centos:7"
|
||||||
|
;;
|
||||||
|
*.x86_64.rpm)
|
||||||
|
docker_platform="linux/amd64"
|
||||||
|
docker_image="amd64/centos:7"
|
||||||
|
;;
|
||||||
|
*.arm.rpm)
|
||||||
|
docker_platform="linux/arm/v7"
|
||||||
|
docker_image="arm32v7/fedora:36"
|
||||||
|
;;
|
||||||
|
*.aarch64.rpm)
|
||||||
|
docker_platform="linux/arm64"
|
||||||
|
docker_image="arm64v8/fedora:36"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${artifact_path} did not match known patterns for rpms"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "executing RPM verification in Docker with these parameters:"
|
||||||
|
echo "PLATFORM=${docker_platform}"
|
||||||
|
echo "IMAGE=${docker_image}"
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
--platform=${docker_platform} \
|
||||||
|
-v $(pwd):/workdir \
|
||||||
|
-v ${SCRIPT_DIR}:/scripts \
|
||||||
|
-w /workdir \
|
||||||
|
${docker_image} \
|
||||||
|
/scripts/verify_rpm.sh \
|
||||||
|
"/workdir/${artifact_path}" \
|
||||||
|
"${expect_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Arguments:
|
||||||
|
# $1 - path to deb (eg. consul_1.13.0~dev-1_arm64.deb)
|
||||||
|
# $2 - expected version to match against (eg. v1.13.0-dev)
|
||||||
|
function verify_deb {
|
||||||
|
local artifact_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local docker_image
|
||||||
|
local docker_platform
|
||||||
|
|
||||||
|
case "${artifact_path}" in
|
||||||
|
*_i386.deb)
|
||||||
|
docker_platform="linux/386"
|
||||||
|
docker_image="i386/debian:bullseye"
|
||||||
|
;;
|
||||||
|
*_amd64.deb)
|
||||||
|
docker_platform="linux/amd64"
|
||||||
|
docker_image="amd64/debian:bullseye"
|
||||||
|
;;
|
||||||
|
*_arm.deb)
|
||||||
|
docker_platform="linux/arm/v7"
|
||||||
|
docker_image="arm32v7/debian:bullseye"
|
||||||
|
;;
|
||||||
|
*_arm64.deb)
|
||||||
|
docker_platform="linux/arm64"
|
||||||
|
docker_image="arm64v8/debian:bullseye"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${artifact_path} did not match known patterns for debs"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "executing DEB verification in Docker with these parameters:"
|
||||||
|
echo "PLATFORM=${docker_platform}"
|
||||||
|
echo "IMAGE=${docker_image}"
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
--platform=${docker_platform} \
|
||||||
|
-v $(pwd):/workdir \
|
||||||
|
-v ${SCRIPT_DIR}:/scripts \
|
||||||
|
-w /workdir \
|
||||||
|
${docker_image} \
|
||||||
|
/scripts/verify_deb.sh \
|
||||||
|
"/workdir/${artifact_path}" \
|
||||||
|
"${expect_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Arguments:
|
||||||
|
# $1 - path to zip (eg. consul_1.13.0-dev_linux_amd64.zip)
|
||||||
|
# $2 - expected version to match against (eg. v1.13.0-dev)
|
||||||
|
function verify_zip {
|
||||||
|
local artifact_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local machine_os=$(uname -s)
|
||||||
|
local machine_arch=$(uname -m)
|
||||||
|
|
||||||
|
unzip "${artifact_path}"
|
||||||
|
|
||||||
|
if [[ ! -e ./consul ]]; then
|
||||||
|
echo "ERROR: ${artifact_path} did not contain a consul binary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${artifact_path}" in
|
||||||
|
|
||||||
|
*_darwin_amd64.zip)
|
||||||
|
if [[ "${machine_os}" = 'Darwin' ]]; then
|
||||||
|
# run the darwin binary if the host is Darwin.
|
||||||
|
${SCRIPT_DIR}/verify_bin.sh ./consul ${expect_version}
|
||||||
|
else
|
||||||
|
echo "cannot run darwin binary on a non-darwin host (${machine_os})"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*_linux_386.zip | *_linux_amd64.zip)
|
||||||
|
if [[ "${machine_os}" = 'Linux' && "${machine_arch}" = "x86_64" ]]; then
|
||||||
|
# run the binary directly on the host when it's x86_64 Linux
|
||||||
|
${SCRIPT_DIR}/verify_bin.sh ./consul ${expect_version}
|
||||||
|
else
|
||||||
|
# otherwise, use Docker/QEMU
|
||||||
|
docker run \
|
||||||
|
--platform=linux/amd64 \
|
||||||
|
-v $(pwd):/workdir \
|
||||||
|
-v ${SCRIPT_DIR}:/scripts \
|
||||||
|
-w /workdir \
|
||||||
|
amd64/debian \
|
||||||
|
/scripts/verify_bin.sh \
|
||||||
|
./consul \
|
||||||
|
"${expect_version}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*_linux_arm.zip)
|
||||||
|
if [[ "${machine_os}" = 'Linux' && "${machine_arch}" = arm* ]]; then
|
||||||
|
# run the binary directly on the host when it's x86_64 Linux
|
||||||
|
${SCRIPT_DIR}/verify_bin.sh ./consul ${expect_version}
|
||||||
|
else
|
||||||
|
# otherwise, use Docker/QEMU
|
||||||
|
docker run \
|
||||||
|
--platform=linux/arm/v7 \
|
||||||
|
-v $(pwd):/workdir \
|
||||||
|
-v ${SCRIPT_DIR}:/scripts \
|
||||||
|
-w /workdir \
|
||||||
|
arm32v7/debian \
|
||||||
|
/scripts/verify_bin.sh \
|
||||||
|
./consul \
|
||||||
|
"${expect_version}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*_linux_arm64.zip)
|
||||||
|
if [[ "${machine_os}" = 'Linux' && "${machine_arch}" = arm* ]]; then
|
||||||
|
# run the binary directly on the host when it's x86_64 Linux
|
||||||
|
${SCRIPT_DIR}/verify_bin.sh ./consul ${expect_version}
|
||||||
|
else
|
||||||
|
# otherwise, use Docker/QEMU
|
||||||
|
docker run \
|
||||||
|
--platform=linux/arm64 \
|
||||||
|
-v $(pwd):/workdir \
|
||||||
|
-v ${SCRIPT_DIR}:/scripts \
|
||||||
|
-w /workdir \
|
||||||
|
arm64v8/debian \
|
||||||
|
/scripts/verify_bin.sh \
|
||||||
|
./consul \
|
||||||
|
"${expect_version}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "${artifact_path} did not match known patterns for zips"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# verify_bin.sh validates the file at the path given and then runs `./consul version` and inspects its output. If its
|
||||||
|
# output doesn't match the version given, the script will exit 1 and report why it failed.
|
||||||
|
# This is meant to be run as part of the build workflow to verify the built .zip meets some basic criteria for validity.
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "./verify_bin.sh <path_to_bin> <expect_version>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local bin_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local got_version
|
||||||
|
|
||||||
|
if [[ -z "${bin_path}" ]]; then
|
||||||
|
echo "ERROR: path to binary argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${expect_version}" ]]; then
|
||||||
|
echo "ERROR: expected version argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "${bin_path}" ]]; then
|
||||||
|
echo "ERROR: package at ${bin_path} does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
got_version="$( awk '{print $2}' <(head -n1 <(${bin_path} version)) )"
|
||||||
|
if [ "${got_version}" != "${expect_version}" ]; then
|
||||||
|
echo "Test FAILED"
|
||||||
|
echo "Got: ${got_version}, Want: ${expect_version}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Test PASSED"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,57 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# verify_deb.sh tries to install the .deb package at the path given before running `consul version`
|
||||||
|
# to inspect its output. If its output doesn't match the version given, the script will exit 1 and
|
||||||
|
# report why it failed. This is meant to be run as part of the build workflow to verify the built
|
||||||
|
# .deb meets some basic criteria for validity.
|
||||||
|
|
||||||
|
# set this so we can locate and execute the verify_bin.sh script for verifying version output
|
||||||
|
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "./verify_deb.sh <path_to_deb> <expect_version>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local deb_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local got_version
|
||||||
|
|
||||||
|
if [[ -z "${deb_path}" ]]; then
|
||||||
|
echo "ERROR: package path argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${expect_version}" ]]; then
|
||||||
|
echo "ERROR: expected version argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# expand globs for path names, if this fails, the script will exit
|
||||||
|
deb_path=$(echo ${deb_path})
|
||||||
|
|
||||||
|
if [[ ! -e "${deb_path}" ]]; then
|
||||||
|
echo "ERROR: package at ${deb_path} does not exist."
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# we have to install the 'arm' architecture in order to install the 'arm'
|
||||||
|
# package, otherwise we will git a 'package architecture does not match system' error
|
||||||
|
if [[ ${deb_path} = *_arm.deb ]]; then
|
||||||
|
dpkg --add-architecture arm
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt -y update
|
||||||
|
apt -y install openssl
|
||||||
|
dpkg -i ${deb_path}
|
||||||
|
|
||||||
|
# use the script that should be located next to this one for verifying the output
|
||||||
|
exec "${SCRIPT_DIR}/verify_bin.sh" $(which consul) "${expect_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# verify_docker.sh invokes the given Docker image to run `consul version` and inspect its output.
|
||||||
|
# If its output doesn't match the version given, the script will exit 1 and report why it failed.
|
||||||
|
# This is meant to be run as part of the build workflow to verify the built image meets some basic
|
||||||
|
# criteria for validity.
|
||||||
|
#
|
||||||
|
# Because this is meant to be run as the `smoke_test` for the docker-build workflow, the script expects
|
||||||
|
# the image name parameter to be provided by the `IMAGE_NAME` environment variable, rather than a
|
||||||
|
# positional argument.
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "IMAGE_NAME=<image uri> ./verify_docker.sh <expect_version>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local image_name="${IMAGE_NAME:-}"
|
||||||
|
local expect_version="${1:-}"
|
||||||
|
local got_version
|
||||||
|
|
||||||
|
if [[ -z "${image_name}" ]]; then
|
||||||
|
echo "ERROR: IMAGE_NAME is not set"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${expect_version}" ]]; then
|
||||||
|
echo "ERROR: expected version argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
got_version="$( awk '{print $2}' <(head -n1 <(docker run "${image_name}" version)) )"
|
||||||
|
if [ "${got_version}" != "${expect_version}" ]; then
|
||||||
|
echo "Test FAILED"
|
||||||
|
echo "Got: ${got_version}, Want: ${expect_version}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Test PASSED"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# verify_rpm.sh tries to install the .rpm package at the path given before running `consul version`
|
||||||
|
# to inspect its output. If its output doesn't match the version given, the script will exit 1 and
|
||||||
|
# report why it failed. This is meant to be run as part of the build workflow to verify the built
|
||||||
|
# .rpm meets some basic criteria for validity.
|
||||||
|
|
||||||
|
# set this so we can locate and execute the verify_bin.sh script for verifying version output
|
||||||
|
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "./verify_rpm.sh <path_to_rpm> <expect_version>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local rpm_path="${1:-}"
|
||||||
|
local expect_version="${2:-}"
|
||||||
|
local got_version
|
||||||
|
|
||||||
|
if [[ -z "${rpm_path}" ]]; then
|
||||||
|
echo "ERROR: package path argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${expect_version}" ]]; then
|
||||||
|
echo "ERROR: expected version argument is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# expand globs for path names, if this fails, the script will exit
|
||||||
|
rpm_path=$(echo ${rpm_path})
|
||||||
|
|
||||||
|
if [[ ! -e "${rpm_path}" ]]; then
|
||||||
|
echo "ERROR: package at ${rpm_path} does not exist."
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
yum -y clean all
|
||||||
|
yum -y update
|
||||||
|
yum -y install which openssl
|
||||||
|
rpm --ignorearch -i ${rpm_path}
|
||||||
|
|
||||||
|
# use the script that should be located next to this one for verifying the output
|
||||||
|
exec "${SCRIPT_DIR}/verify_bin.sh" $(which consul) "${expect_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -8,6 +8,9 @@ on:
|
||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
- labeled
|
- labeled
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'release/*.*.x'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backport:
|
backport:
|
||||||
|
@ -16,17 +19,34 @@ jobs:
|
||||||
container: hashicorpdev/backport-assistant:0.2.3
|
container: hashicorpdev/backport-assistant:0.2.3
|
||||||
steps:
|
steps:
|
||||||
- name: Run Backport Assistant for stable-website
|
- name: Run Backport Assistant for stable-website
|
||||||
# Update this to auto-merge when we have confidence in the process working and kill Circle
|
|
||||||
run: |
|
run: |
|
||||||
backport-assistant backport -merge-method=rebase
|
backport-assistant backport -merge-method=squash -automerge
|
||||||
env:
|
env:
|
||||||
BACKPORT_LABEL_REGEXP: "type/docs-(?P<target>cherrypick)"
|
BACKPORT_LABEL_REGEXP: "type/docs-(?P<target>cherrypick)"
|
||||||
BACKPORT_TARGET_TEMPLATE: "stable-website"
|
BACKPORT_TARGET_TEMPLATE: "stable-website"
|
||||||
GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
- name: Run Backport Assistant for release branches
|
- name: Backport changes to latest release branch
|
||||||
# Update this to auto-merge when we have confidence in the process working and kill Circle
|
|
||||||
run: |
|
run: |
|
||||||
backport-assistant backport -merge-method=rebase
|
# Use standard token here
|
||||||
|
resp=$(curl -f -s -H 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' "https://api.github.com/repos/$GITHUB_REPOSITORY/labels?per_page=100")
|
||||||
|
ret="$?"
|
||||||
|
if [[ "$ret" -ne 0 ]]; then
|
||||||
|
echo "The GitHub API returned $ret"
|
||||||
|
exit $ret
|
||||||
|
fi
|
||||||
|
# get the latest backport label excluding any website labels, ex: `backport/0.3.x` and not `backport/website`
|
||||||
|
latest_backport_label=$(echo "$resp" | jq -r '.[] | select(.name | (startswith("backport/") and (contains("website") | not))) | .name' | sort -rV | head -n1)
|
||||||
|
echo "Latest backport label: $latest_backport_label"
|
||||||
|
# set BACKPORT_TARGET_TEMPLATE for backport-assistant
|
||||||
|
# trims backport/ from the beginning with parameter substitution
|
||||||
|
export BACKPORT_TARGET_TEMPLATE="release/${latest_backport_label#backport/}.x"
|
||||||
|
backport-assistant backport -merge-method=squash -automerge
|
||||||
|
env:
|
||||||
|
BACKPORT_LABEL_REGEXP: "type/docs-(?P<target>cherrypick)"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
|
- name: Run Backport Assistant for release branches
|
||||||
|
run: |
|
||||||
|
backport-assistant backport -merge-method=squash -automerge
|
||||||
env:
|
env:
|
||||||
BACKPORT_LABEL_REGEXP: "backport/(?P<target>\\d+\\.\\d+)"
|
BACKPORT_LABEL_REGEXP: "backport/(?P<target>\\d+\\.\\d+)"
|
||||||
BACKPORT_TARGET_TEMPLATE: "release/{{.target}}.x"
|
BACKPORT_TARGET_TEMPLATE: "release/{{.target}}.x"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# This workflow sends a reminder comment to PRs that have labels starting with
|
# This workflow sends a reminder comment to PRs that have labels starting with
|
||||||
# `backport/` to check that the backport has run successfully.
|
# `backport/` to check that the backport has run successfully.
|
||||||
|
|
||||||
|
name: Backport Assistant Reminder
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [ labeled ]
|
types: [ labeled ]
|
||||||
|
@ -17,7 +19,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
run: |
|
run: |
|
||||||
github_message="After merging, confirm that you see messages like: 🍒✅ Cherry pick of commit ... onto ... succeeded!"
|
github_message="After merging, confirm that you see linked PRs AND check them for CI errors."
|
||||||
curl -s -H "Authorization: token ${{ secrets.PR_COMMENT_TOKEN }}" \
|
curl -s -H "Authorization: token ${{ secrets.PR_COMMENT_TOKEN }}" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-d "{ \"body\": \"${github_message}\"}" \
|
-d "{ \"body\": \"${github_message}\"}" \
|
||||||
|
|
|
@ -15,6 +15,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
product-version: ${{ steps.get-product-version.outputs.product-version }}
|
product-version: ${{ steps.get-product-version.outputs.product-version }}
|
||||||
|
product-date: ${{ steps.get-product-version.outputs.product-date }}
|
||||||
pre-version: ${{ steps.get-product-version.outputs.pre-version }}
|
pre-version: ${{ steps.get-product-version.outputs.pre-version }}
|
||||||
pkg-version: ${{ steps.get-product-version.outputs.pkg-version }}
|
pkg-version: ${{ steps.get-product-version.outputs.pkg-version }}
|
||||||
shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }}
|
shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }}
|
||||||
|
@ -24,6 +25,7 @@ jobs:
|
||||||
id: get-product-version
|
id: get-product-version
|
||||||
run: |
|
run: |
|
||||||
CONSUL_VERSION=$(build-support/scripts/version.sh -r)
|
CONSUL_VERSION=$(build-support/scripts/version.sh -r)
|
||||||
|
CONSUL_DATE=$(build-support/scripts/build-date.sh)
|
||||||
## TODO: This assumes `make version` outputs 1.1.1+ent-prerel
|
## TODO: This assumes `make version` outputs 1.1.1+ent-prerel
|
||||||
IFS="+" read VERSION _other <<< "$CONSUL_VERSION"
|
IFS="+" read VERSION _other <<< "$CONSUL_VERSION"
|
||||||
IFS="-" read _other PREREL_VERSION <<< "$CONSUL_VERSION"
|
IFS="-" read _other PREREL_VERSION <<< "$CONSUL_VERSION"
|
||||||
|
@ -32,12 +34,15 @@ jobs:
|
||||||
## [version]{-prerelease}+ent before then, we'll need to add
|
## [version]{-prerelease}+ent before then, we'll need to add
|
||||||
## logic to handle presense/absence of the prerelease
|
## logic to handle presense/absence of the prerelease
|
||||||
echo "::set-output name=product-version::${CONSUL_VERSION}"
|
echo "::set-output name=product-version::${CONSUL_VERSION}"
|
||||||
|
echo "::set-output name=product-date::${CONSUL_DATE}"
|
||||||
echo "::set-output name=pre-version::${PREREL_VERSION}"
|
echo "::set-output name=pre-version::${PREREL_VERSION}"
|
||||||
echo "::set-output name=pkg-version::${VERSION}"
|
echo "::set-output name=pkg-version::${VERSION}"
|
||||||
|
|
||||||
- name: Set shared -ldflags
|
- name: Set shared -ldflags
|
||||||
id: shared-ldflags
|
id: shared-ldflags
|
||||||
run: echo "::set-output name=shared-ldflags::-X github.com/hashicorp/consul/version.GitCommit=${GITHUB_SHA::8} -X github.com/hashicorp/consul/version.GitDescribe=${{ steps.get-product-version.outputs.product-version }}"
|
run: |
|
||||||
|
T="github.com/hashicorp/consul/version"
|
||||||
|
echo "::set-output name=shared-ldflags::-X ${T}.GitCommit=${GITHUB_SHA::8} -X ${T}.GitDescribe=${{ steps.get-product-version.outputs.product-version }} -X ${T}.BuildDate=${{ steps.get-product-version.outputs.product-date }}"
|
||||||
|
|
||||||
generate-metadata-file:
|
generate-metadata-file:
|
||||||
needs: get-product-version
|
needs: get-product-version
|
||||||
|
@ -85,11 +90,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Install project dependencies
|
|
||||||
run: |
|
|
||||||
go install github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs@38087fe
|
|
||||||
go install github.com/hashicorp/go-bindata/go-bindata@bf7910a
|
|
||||||
|
|
||||||
- name: Setup with node and yarn
|
- name: Setup with node and yarn
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
|
@ -100,18 +100,16 @@ jobs:
|
||||||
- name: Build UI
|
- name: Build UI
|
||||||
run: |
|
run: |
|
||||||
CONSUL_VERSION=${{ needs.get-product-version.outputs.product-version }}
|
CONSUL_VERSION=${{ needs.get-product-version.outputs.product-version }}
|
||||||
|
CONSUL_DATE=${{ needs.get-product-version.outputs.product-date }}
|
||||||
CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE}
|
CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE}
|
||||||
CONSUL_COPYRIGHT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
|
CONSUL_COPYRIGHT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
|
||||||
echo "consul_version is ${CONSUL_VERSION}"
|
echo "consul_version is ${CONSUL_VERSION}"
|
||||||
|
echo "consul_date is ${CONSUL_DATE}"
|
||||||
echo "consul binary type is ${CONSUL_BINARY_TYPE}"
|
echo "consul binary type is ${CONSUL_BINARY_TYPE}"
|
||||||
echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}"
|
echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}"
|
||||||
cd ui && make && cd ..
|
cd ui && make && cd ..
|
||||||
mkdir pkg
|
rm -rf agent/uiserver/dist
|
||||||
mv ui/packages/consul-ui/dist pkg/web_ui
|
mv ui/packages/consul-ui/dist agent/uiserver/
|
||||||
|
|
||||||
- name: Build static-assets
|
|
||||||
run: make static-assets
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
|
@ -185,11 +183,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Install project dependencies
|
|
||||||
run: |
|
|
||||||
go install github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs@38087fe
|
|
||||||
go install github.com/hashicorp/go-bindata/go-bindata@bf7910a
|
|
||||||
|
|
||||||
- name: Setup with node and yarn
|
- name: Setup with node and yarn
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
|
@ -206,11 +199,8 @@ jobs:
|
||||||
echo "consul binary type is ${CONSUL_BINARY_TYPE}"
|
echo "consul binary type is ${CONSUL_BINARY_TYPE}"
|
||||||
echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}"
|
echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}"
|
||||||
cd ui && make && cd ..
|
cd ui && make && cd ..
|
||||||
mkdir pkg
|
rm -rf agent/uiserver/dist
|
||||||
mv ui/packages/consul-ui/dist pkg/web_ui
|
mv ui/packages/consul-ui/dist agent/uiserver/
|
||||||
|
|
||||||
- name: Build static-assets
|
|
||||||
run: make static-assets
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
|
@ -235,7 +225,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["arm", "arm64", "386", "amd64"]
|
arch: ["386", "amd64", "arm", "arm64"]
|
||||||
env:
|
env:
|
||||||
repo: ${{github.event.repository.name}}
|
repo: ${{github.event.repository.name}}
|
||||||
version: ${{needs.get-product-version.outputs.product-version}}
|
version: ${{needs.get-product-version.outputs.product-version}}
|
||||||
|
@ -251,3 +241,159 @@ jobs:
|
||||||
tags: |
|
tags: |
|
||||||
docker.io/hashicorp/${{env.repo}}:${{env.version}}
|
docker.io/hashicorp/${{env.repo}}:${{env.version}}
|
||||||
public.ecr.aws/hashicorp/${{env.repo}}:${{env.version}}
|
public.ecr.aws/hashicorp/${{env.repo}}:${{env.version}}
|
||||||
|
dev_tags: |
|
||||||
|
docker.io/hashicorppreview/${{ env.repo }}:${{ env.version }}
|
||||||
|
docker.io/hashicorppreview/${{ env.repo }}:${{ env.version }}-${{ github.sha }}
|
||||||
|
smoke_test: .github/scripts/verify_docker.sh v${{ env.version }}
|
||||||
|
|
||||||
|
build-docker-redhat:
|
||||||
|
name: Docker Build UBI Image for RedHat
|
||||||
|
needs:
|
||||||
|
- get-product-version
|
||||||
|
- build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
repo: ${{github.event.repository.name}}
|
||||||
|
version: ${{needs.get-product-version.outputs.product-version}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: hashicorp/actions-docker-build@v1
|
||||||
|
with:
|
||||||
|
version: ${{env.version}}
|
||||||
|
target: ubi
|
||||||
|
arch: amd64
|
||||||
|
redhat_tag: scan.connect.redhat.com/ospid-60f9fdbec3a80eac643abedf/${{env.repo}}:${{env.version}}-ubi
|
||||||
|
smoke_test: .github/scripts/verify_docker.sh v${{ env.version }}
|
||||||
|
|
||||||
|
verify-linux:
|
||||||
|
needs:
|
||||||
|
- get-product-version
|
||||||
|
- build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: ["386", "amd64", "arm", "arm64"]
|
||||||
|
fail-fast: true
|
||||||
|
env:
|
||||||
|
version: ${{ needs.get-product-version.outputs.product-version }}
|
||||||
|
zip_name: consul_${{ needs.get-product-version.outputs.product-version }}_linux_${{ matrix.arch }}.zip
|
||||||
|
|
||||||
|
name: Verify ${{ matrix.arch }} linux binary
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Download ${{ matrix.arch }} zip
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.zip_name }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
if: ${{ matrix.arch == 'arm' || matrix.arch == 'arm64' }}
|
||||||
|
with:
|
||||||
|
# this should be a comma-separated string as opposed to an array
|
||||||
|
platforms: arm,arm64
|
||||||
|
|
||||||
|
- name: Run verification for ${{ matrix.arch }} binary
|
||||||
|
run: .github/scripts/verify_artifact.sh ${{ env.zip_name }} v${{ env.version }}
|
||||||
|
|
||||||
|
verify-darwin:
|
||||||
|
needs:
|
||||||
|
- get-product-version
|
||||||
|
- build-darwin
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
env:
|
||||||
|
version: ${{needs.get-product-version.outputs.product-version}}
|
||||||
|
zip_name: consul_${{ needs.get-product-version.outputs.product-version }}_darwin_amd64.zip
|
||||||
|
|
||||||
|
name: Verify amd64 darwin binary
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Download amd64 darwin zip
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.zip_name }}
|
||||||
|
|
||||||
|
- name: Unzip amd64 darwin zip
|
||||||
|
run: unzip ${{ env.zip_name }}
|
||||||
|
|
||||||
|
- name: Run verification for amd64 darwin binary
|
||||||
|
run: .github/scripts/verify_bin.sh ./consul v${{ env.version }}
|
||||||
|
|
||||||
|
verify-linux-packages-deb:
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
- get-product-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: ["i386", "amd64", "arm", "arm64"]
|
||||||
|
# fail-fast: true
|
||||||
|
env:
|
||||||
|
version: ${{ needs.get-product-version.outputs.product-version }}
|
||||||
|
|
||||||
|
name: Verify ${{ matrix.arch }} debian package
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set package version
|
||||||
|
run: |
|
||||||
|
echo "pkg_version=$(echo ${{ needs.get-product-version.outputs.product-version }} | sed 's/\-/~/g')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set package name
|
||||||
|
run: |
|
||||||
|
echo "pkg_name=consul_${{ env.pkg_version }}-1_${{ matrix.arch }}.deb" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download workflow artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.pkg_name }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Verify ${{ matrix.arch }} debian package
|
||||||
|
run: ./.github/scripts/verify_artifact.sh ${{ env.pkg_name }} v${{ env.version }}
|
||||||
|
|
||||||
|
verify-linux-packages-rpm:
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
- get-product-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: ["i386", "x86_64", "arm", "aarch64"]
|
||||||
|
# fail-fast: true
|
||||||
|
env:
|
||||||
|
version: ${{ needs.get-product-version.outputs.product-version }}
|
||||||
|
|
||||||
|
name: Verify ${{ matrix.arch }} rpm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set package version
|
||||||
|
run: |
|
||||||
|
echo "pkg_version=$(echo ${{ needs.get-product-version.outputs.product-version }} | sed 's/\-/~/g')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set package name
|
||||||
|
run: |
|
||||||
|
echo "pkg_name=consul-${{ env.pkg_version }}-1.${{ matrix.arch }}.rpm" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download workflow artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.pkg_name }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Verify ${{ matrix.arch }} rpm
|
||||||
|
run: ./.github/scripts/verify_artifact.sh ${{ env.pkg_name }} v${{ env.version }}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# This workflow checks that there is either a 'pr/no-changelog' label applied to a PR
|
# This workflow checks that there is either a 'pr/no-changelog' label applied to a PR
|
||||||
# or there is a .changelog/<pr number>.txt file associated with a PR for a changelog entry
|
# or there is a .changelog/<pr number>.txt file associated with a PR for a changelog entry
|
||||||
|
|
||||||
|
name: Changelog Checker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, labeled]
|
types: [opened, synchronize, labeled]
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# This workflow detects if there is a diff in the `agent/uiserver/dist` directory
|
||||||
|
# which is used by Consul to serve its embedded UI.
|
||||||
|
# `agent/uiserver/dist` should not be manually updated.
|
||||||
|
|
||||||
|
name: Embedded Asset Checker
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, labeled, unlabeled, reopened]
|
||||||
|
# Runs on PRs to main and all release branches
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- release/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dist-check:
|
||||||
|
if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/update-ui-assets') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0 # by default the checkout action doesn't checkout all branches
|
||||||
|
- name: Check for agent/uiserver/dist dir change in diff
|
||||||
|
run: |
|
||||||
|
dist_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/${{ github.event.pull_request.base.ref }}")" -- agent/uiserver/dist)
|
||||||
|
if [[ -z "${dist_files}" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found diffs in dir agent/uiserver/dist"
|
||||||
|
github_message="This PR has diffs in \`agent/uiserver/dist\`. If the changes are intentional, add the label \`pr/update-ui-assets\`. Otherwise, revert changes to \`agent/uiserver/dist\`."
|
||||||
|
curl -s -H "Authorization: token ${{ secrets.PR_COMMENT_TOKEN }}" \
|
||||||
|
-X POST \
|
||||||
|
-d "{ \"body\": \"${github_message}\"}" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${{ github.event.pull_request.number }}/comments"
|
||||||
|
exit 1
|
|
@ -1,8 +1,11 @@
|
||||||
|
name: Load Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger-load-test:
|
trigger-load-test:
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
name: Nightly 1.10.x Test
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
env:
|
||||||
|
EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition
|
||||||
|
BRANCH: "release/1.10.x"
|
||||||
|
BRANCH_NAME: "release-1.10.x" # Used for naming artifacts
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend-test-workspace-node:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Workspace Tests
|
||||||
|
id: workspace-test
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make test-workspace
|
||||||
|
|
||||||
|
- name: Node Tests
|
||||||
|
id: node-test
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-node
|
||||||
|
|
||||||
|
frontend-build-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Ember Build OSS
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload OSS Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-oss]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Download OSS Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test OSS
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-build-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Ember Build ENT
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload ENT Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test ENT
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-test-coverage-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Run ENT Code Coverage
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-coverage-ci
|
||||||
|
|
||||||
|
slack-failure-notification:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-test-oss, frontend-test-ent]
|
||||||
|
if: ${{ failure() }}
|
||||||
|
steps:
|
||||||
|
- name: Slack Notification
|
||||||
|
id: slack
|
||||||
|
uses: slackapi/slack-github-action@v1.19
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"message": "One or more nightly UI tests have failed on branch ${{ env.BRANCH }} for Consul. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_UI_SLACK_WEBHOOK }}
|
|
@ -1,47 +1,21 @@
|
||||||
name: nightly-release-ui-test
|
name: Nightly Test 1.11.x
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 4 * * *'
|
- cron: '0 4 * * *'
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partion
|
EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition
|
||||||
|
BRANCH: "release/1.11.x"
|
||||||
|
BRANCH_NAME: "release-1.11.x" # Used for naming artifacts
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
frontend-cache:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 14
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: ./ui/yarn.lock
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
id: install
|
|
||||||
working-directory: ./ui
|
|
||||||
run: make deps
|
|
||||||
|
|
||||||
frontend-test-workspace-node:
|
frontend-test-workspace-node:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-cache]
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -67,18 +41,13 @@ jobs:
|
||||||
|
|
||||||
frontend-build-oss:
|
frontend-build-oss:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-cache]
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
|
||||||
env:
|
env:
|
||||||
JOBS: 2
|
JOBS: 2
|
||||||
CONSUL_NSPACES_ENABLED: 0
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -100,16 +69,16 @@ jobs:
|
||||||
- name: Upload OSS Frontend
|
- name: Upload OSS Frontend
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: frontend-oss-${{ matrix.branch }}
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
path: .ui/packages/consul-ui/dist
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
frontend-test-oss:
|
frontend-test-oss:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-build-oss]
|
needs: [frontend-build-oss]
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
partition: [ 1, 2, 3, 4 ]
|
||||||
partion: [ 1, 2, 3, 4 ]
|
|
||||||
env:
|
env:
|
||||||
CONSUL_NSPACES_ENABLED: 0
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
@ -117,8 +86,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -133,32 +101,25 @@ jobs:
|
||||||
run: make deps
|
run: make deps
|
||||||
|
|
||||||
- name: Download OSS Frontend
|
- name: Download OSS Frontend
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: frontend-oss-${{ matrix.branch }}
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
path: .ui/packages/consul-ui/dist
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
- name: Ember Test OSS
|
- name: Ember Test OSS
|
||||||
id: cache
|
id: cache
|
||||||
working-directory: ./ui/packages/consul-ui
|
working-directory: ./ui/packages/consul-ui
|
||||||
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
# TODO: add test-reporter: https://github.com/marketplace/actions/test-reporter
|
|
||||||
|
|
||||||
frontend-build-ent:
|
frontend-build-ent:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-cache]
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
|
||||||
env:
|
env:
|
||||||
JOBS: 2
|
JOBS: 2
|
||||||
CONSUL_NSPACES_ENABLED: 1
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -180,16 +141,16 @@ jobs:
|
||||||
- name: Upload ENT Frontend
|
- name: Upload ENT Frontend
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: frontend-ent-${{ matrix.branch }}
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
path: .ui/packages/consul-ui/dist
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
frontend-test-ent:
|
frontend-test-ent:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-build-ent]
|
needs: [frontend-build-ent]
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
partition: [ 1, 2, 3, 4 ]
|
||||||
partion: [ 1, 2, 3, 4 ]
|
|
||||||
env:
|
env:
|
||||||
CONSUL_NSPACES_ENABLED: 1
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
@ -197,8 +158,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -213,29 +173,23 @@ jobs:
|
||||||
run: make deps
|
run: make deps
|
||||||
|
|
||||||
- name: Download ENT Frontend
|
- name: Download ENT Frontend
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: frontend-ent-${{ matrix.branch }}
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
path: .ui/packages/consul-ui/dist
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
- name: Ember Test ENT
|
- name: Ember Test ENT
|
||||||
id: cache
|
id: cache
|
||||||
working-directory: ./ui/packages/consul-ui
|
working-directory: ./ui/packages/consul-ui
|
||||||
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
# TODO: add test-reporter: https://github.com/marketplace/actions/test-reporter
|
|
||||||
|
|
||||||
frontend-test-coverage-ent:
|
frontend-test-coverage-ent:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [frontend-build-ent]
|
needs: [frontend-build-ent]
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
branch: [ release/1.10.x, release/1.11.x, release/1.12.x ]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ env.BRANCH }}
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
|
|
||||||
# Not necessary to use yarn, but enables caching
|
# Not necessary to use yarn, but enables caching
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -249,6 +203,12 @@ jobs:
|
||||||
working-directory: ./ui
|
working-directory: ./ui
|
||||||
run: make deps
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
- name: Run ENT Code Coverage
|
- name: Run ENT Code Coverage
|
||||||
working-directory: ./ui/packages/consul-ui
|
working-directory: ./ui/packages/consul-ui
|
||||||
run: make test-coverage-ci
|
run: make test-coverage-ci
|
||||||
|
@ -260,8 +220,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
id: slack
|
id: slack
|
||||||
uses: slackapi/slack-github-action@main
|
uses: slackapi/slack-github-action@v1.19
|
||||||
with:
|
with:
|
||||||
payload: "{\"message\":\"One or more nightly UI test have failed on a release branch for Consul. [Link to Failed Action](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}). \"}"
|
payload: |
|
||||||
|
{
|
||||||
|
"message": "One or more nightly UI tests have failed on branch ${{ env.BRANCH }} for Consul. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_CORE_SLACK_WEBHOOK }}
|
SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_UI_SLACK_WEBHOOK }}
|
|
@ -0,0 +1,230 @@
|
||||||
|
name: Nightly Test 1.12.x
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
env:
|
||||||
|
EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition
|
||||||
|
BRANCH: "release/1.12.x"
|
||||||
|
BRANCH_NAME: "release-1.12.x" # Used for naming artifacts
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend-test-workspace-node:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Workspace Tests
|
||||||
|
id: workspace-test
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make test-workspace
|
||||||
|
|
||||||
|
- name: Node Tests
|
||||||
|
id: node-test
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-node
|
||||||
|
|
||||||
|
frontend-build-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Ember Build OSS
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload OSS Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-oss]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download OSS Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test OSS
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-build-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Ember Build ENT
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload ENT Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test ENT
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-test-coverage-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Run ENT Code Coverage
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-coverage-ci
|
||||||
|
|
||||||
|
slack-failure-notification:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-test-oss, frontend-test-ent]
|
||||||
|
if: ${{ failure() }}
|
||||||
|
steps:
|
||||||
|
- name: Slack Notification
|
||||||
|
id: slack
|
||||||
|
uses: slackapi/slack-github-action@v1.19
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"message": "One or more nightly UI tests have failed on branch ${{ env.BRANCH }} for Consul. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_UI_SLACK_WEBHOOK }}
|
|
@ -0,0 +1,230 @@
|
||||||
|
name: Nightly Test Main
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
env:
|
||||||
|
EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition
|
||||||
|
BRANCH: "main"
|
||||||
|
BRANCH_NAME: "main" # Used for naming artifacts
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
frontend-test-workspace-node:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Workspace Tests
|
||||||
|
id: workspace-test
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make test-workspace
|
||||||
|
|
||||||
|
- name: Node Tests
|
||||||
|
id: node-test
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-node
|
||||||
|
|
||||||
|
frontend-build-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Ember Build OSS
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload OSS Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-oss:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-oss]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 0
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download OSS Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-oss-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test OSS
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-build-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
JOBS: 2
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Ember Build ENT
|
||||||
|
id: build-oss
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make build-ci
|
||||||
|
|
||||||
|
- name: Upload ENT Frontend
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
frontend-test-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
partition: [ 1, 2, 3, 4 ]
|
||||||
|
env:
|
||||||
|
CONSUL_NSPACES_ENABLED: 1
|
||||||
|
EMBER_TEST_REPORT: test-results/report-oss.xml #outputs test report for CircleCI test summary
|
||||||
|
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Ember Test ENT
|
||||||
|
id: cache
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: node_modules/.bin/ember exam --split=$EMBER_PARTITION_TOTAL --partition=${{ matrix.partition }} --path dist --silent -r xunit
|
||||||
|
|
||||||
|
frontend-test-coverage-ent:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-build-ent]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH }}
|
||||||
|
|
||||||
|
# Not necessary to use yarn, but enables caching
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./ui/yarn.lock
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
id: install
|
||||||
|
working-directory: ./ui
|
||||||
|
run: make deps
|
||||||
|
|
||||||
|
- name: Download ENT Frontend
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: frontend-ent-${{ env.BRANCH_NAME }}
|
||||||
|
path: ./ui/packages/consul-ui/dist
|
||||||
|
|
||||||
|
- name: Run ENT Code Coverage
|
||||||
|
working-directory: ./ui/packages/consul-ui
|
||||||
|
run: make test-coverage-ci
|
||||||
|
|
||||||
|
slack-failure-notification:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [frontend-test-oss, frontend-test-ent]
|
||||||
|
if: ${{ failure() }}
|
||||||
|
steps:
|
||||||
|
- name: Slack Notification
|
||||||
|
id: slack
|
||||||
|
uses: slackapi/slack-github-action@v1.19
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"message": "One or more nightly UI tests have failed on branch ${{ env.BRANCH }} for Consul. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_UI_SLACK_WEBHOOK }}
|
|
@ -0,0 +1,26 @@
|
||||||
|
name: Trigger OSS to Enterprise Merge
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'release/*.*.x'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger-oss-merge:
|
||||||
|
# run this only on merge events in OSS repo
|
||||||
|
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/consul' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger Merge
|
||||||
|
env:
|
||||||
|
GIT_REF: ${{ github.ref_name }}
|
||||||
|
GIT_SHA: ${{ github.sha }}
|
||||||
|
GH_PAT: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
|
||||||
|
GIT_ACTOR: ${{ github.actor }}
|
||||||
|
run: |
|
||||||
|
curl -H "Authorization: token $GH_PAT" \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-d "{\"event_type\": \"oss-merge\", \"client_payload\": {\"git-ref\": \"${GIT_REF}\", \"git-sha\": \"${GIT_SHA}\", \"git-actor\": \"${GIT_ACTOR}\" }}" \
|
||||||
|
"https://api.github.com/repos/hashicorp/consul-enterprise/dispatches"
|
|
@ -1,6 +1,6 @@
|
||||||
name: "Pull Request Labeler"
|
name: "Pull Request Labeler"
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
@ -8,7 +8,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
metrics_test_check:
|
metrics_test_check:
|
||||||
if: "!contains(github.event.pull_request.labels.*.name, 'pr/no-metrics-test')"
|
if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-metrics-test') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
# in the PR and if they need to be cherry-picked to the stable-website branch, the
|
# in the PR and if they need to be cherry-picked to the stable-website branch, the
|
||||||
# 'type/docs-cherrypick' label needs to be applied.
|
# 'type/docs-cherrypick' label needs to be applied.
|
||||||
|
|
||||||
|
name: Website Checker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
|
@ -26,6 +26,15 @@ issues:
|
||||||
- linters: [staticcheck]
|
- linters: [staticcheck]
|
||||||
text: 'SA1019: Package github.com/golang/protobuf/proto is deprecated'
|
text: 'SA1019: Package github.com/golang/protobuf/proto is deprecated'
|
||||||
|
|
||||||
|
- linters: [staticcheck]
|
||||||
|
text: 'SA1019: ptypes.MarshalAny is deprecated'
|
||||||
|
|
||||||
|
- linters: [staticcheck]
|
||||||
|
text: 'SA1019: ptypes.UnmarshalAny is deprecated'
|
||||||
|
|
||||||
|
- linters: [staticcheck]
|
||||||
|
text: 'SA1019: package github.com/golang/protobuf/ptypes is deprecated'
|
||||||
|
|
||||||
# An argument that always receives the same value is often not a problem.
|
# An argument that always receives the same value is often not a problem.
|
||||||
- linters: [unparam]
|
- linters: [unparam]
|
||||||
text: 'always receives'
|
text: 'always receives'
|
||||||
|
|
|
@ -164,6 +164,20 @@ event "verify" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event "promote-dev-docker" {
|
||||||
|
depends = ["verify"]
|
||||||
|
action "promote-dev-docker" {
|
||||||
|
organization = "hashicorp"
|
||||||
|
repository = "crt-workflows-common"
|
||||||
|
workflow = "promote-dev-docker"
|
||||||
|
depends = ["verify"]
|
||||||
|
}
|
||||||
|
|
||||||
|
notification {
|
||||||
|
on = "fail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
## These are promotion and post-publish events
|
## These are promotion and post-publish events
|
||||||
## they should be added to the end of the file after the verify event stanza.
|
## they should be added to the end of the file after the verify event stanza.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/dumb-init /bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Note above that we run dumb-init as PID 1 in order to reap zombie processes
|
||||||
|
# as well as forward signals to all processes in its session. Normally, sh
|
||||||
|
# wouldn't do either of these functions so we'd leak zombies as well as do
|
||||||
|
# unclean termination of all our sub-processes.
|
||||||
|
# As of docker 1.13, using docker run --init achieves the same outcome.
|
||||||
|
|
||||||
|
# You can set CONSUL_BIND_INTERFACE to the name of the interface you'd like to
|
||||||
|
# bind to and this will look up the IP and pass the proper -bind= option along
|
||||||
|
# to Consul.
|
||||||
|
CONSUL_BIND=
|
||||||
|
if [ -n "$CONSUL_BIND_INTERFACE" ]; then
|
||||||
|
CONSUL_BIND_ADDRESS=$(ip -o -4 addr list $CONSUL_BIND_INTERFACE | head -n1 | awk '{print $4}' | cut -d/ -f1)
|
||||||
|
if [ -z "$CONSUL_BIND_ADDRESS" ]; then
|
||||||
|
echo "Could not find IP for interface '$CONSUL_BIND_INTERFACE', exiting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONSUL_BIND="-bind=$CONSUL_BIND_ADDRESS"
|
||||||
|
echo "==> Found address '$CONSUL_BIND_ADDRESS' for interface '$CONSUL_BIND_INTERFACE', setting bind option..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# You can set CONSUL_CLIENT_INTERFACE to the name of the interface you'd like to
|
||||||
|
# bind client intefaces (HTTP, DNS, and RPC) to and this will look up the IP and
|
||||||
|
# pass the proper -client= option along to Consul.
|
||||||
|
CONSUL_CLIENT=
|
||||||
|
if [ -n "$CONSUL_CLIENT_INTERFACE" ]; then
|
||||||
|
CONSUL_CLIENT_ADDRESS=$(ip -o -4 addr list $CONSUL_CLIENT_INTERFACE | head -n1 | awk '{print $4}' | cut -d/ -f1)
|
||||||
|
if [ -z "$CONSUL_CLIENT_ADDRESS" ]; then
|
||||||
|
echo "Could not find IP for interface '$CONSUL_CLIENT_INTERFACE', exiting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONSUL_CLIENT="-client=$CONSUL_CLIENT_ADDRESS"
|
||||||
|
echo "==> Found address '$CONSUL_CLIENT_ADDRESS' for interface '$CONSUL_CLIENT_INTERFACE', setting client option..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CONSUL_DATA_DIR is exposed as a volume for possible persistent storage. The
|
||||||
|
# CONSUL_CONFIG_DIR isn't exposed as a volume but you can compose additional
|
||||||
|
# config files in there if you use this image as a base, or use CONSUL_LOCAL_CONFIG
|
||||||
|
# below.
|
||||||
|
CONSUL_DATA_DIR=/consul/data
|
||||||
|
CONSUL_CONFIG_DIR=/consul/config
|
||||||
|
|
||||||
|
# You can also set the CONSUL_LOCAL_CONFIG environemnt variable to pass some
|
||||||
|
# Consul configuration JSON without having to bind any volumes.
|
||||||
|
if [ -n "$CONSUL_LOCAL_CONFIG" ]; then
|
||||||
|
echo "$CONSUL_LOCAL_CONFIG" > "$CONSUL_CONFIG_DIR/local.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the user is trying to run Consul directly with some arguments, then
|
||||||
|
# pass them to Consul.
|
||||||
|
if [ "${1:0:1}" = '-' ]; then
|
||||||
|
set -- consul "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Look for Consul subcommands.
|
||||||
|
if [ "$1" = 'agent' ]; then
|
||||||
|
shift
|
||||||
|
set -- consul agent \
|
||||||
|
-data-dir="$CONSUL_DATA_DIR" \
|
||||||
|
-config-dir="$CONSUL_CONFIG_DIR" \
|
||||||
|
$CONSUL_BIND \
|
||||||
|
$CONSUL_CLIENT \
|
||||||
|
"$@"
|
||||||
|
elif [ "$1" = 'version' ]; then
|
||||||
|
# This needs a special case because there's no help output.
|
||||||
|
set -- consul "$@"
|
||||||
|
elif consul --help "$1" 2>&1 | grep -q "consul $1"; then
|
||||||
|
# We can't use the return code to check for the existence of a subcommand, so
|
||||||
|
# we have to use grep to look for a pattern in the help output.
|
||||||
|
set -- consul "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NOTE: Unlike in the regular Consul Docker image, we don't have code here
|
||||||
|
# for changing data-dir directory ownership or using su-exec because OpenShift
|
||||||
|
# won't run this container as root and so we can't change data dir ownership,
|
||||||
|
# and there's no need to use su-exec.
|
||||||
|
|
||||||
|
exec "$@"
|
100
CHANGELOG.md
100
CHANGELOG.md
|
@ -1,7 +1,45 @@
|
||||||
|
## 1.12.2 (June 3, 2022)
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* kvs: Fixed a bug where query options were not being applied to KVS.Get RPC operations. [[GH-13344](https://github.com/hashicorp/consul/issues/13344)]
|
||||||
|
|
||||||
|
## 1.12.1 (May 25, 2022)
|
||||||
|
|
||||||
|
FEATURES:
|
||||||
|
|
||||||
|
* xds: Add the ability to invoke AWS Lambdas through sidecar proxies. [[GH-12956](https://github.com/hashicorp/consul/issues/12956)]
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* config: introduce `telemetry.retry_failed_connection` in agent configuration to
|
||||||
|
retry on failed connection to any telemetry backend. This prevents the agent from
|
||||||
|
exiting if the given DogStatsD DNS name is unresolvable, for example. [[GH-13091](https://github.com/hashicorp/consul/issues/13091)]
|
||||||
|
* sentinel: **(Enterprise Only)** Sentinel now uses SHA256 to generate policy ids
|
||||||
|
* xds: Envoy now inserts x-forwarded-client-cert for incoming proxy connections [[GH-12878](https://github.com/hashicorp/consul/issues/12878)]
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* Fix a bug when configuring an `add_headers` directive named `Host` the header is not set for `v1/internal/ui/metrics-proxy/` endpoint. [[GH-13071](https://github.com/hashicorp/consul/issues/13071)]
|
||||||
|
* api: Fix a bug that causes partition to be ignored when creating a namespace [[GH-12845](https://github.com/hashicorp/consul/issues/12845)]
|
||||||
|
* api: agent/self now returns version with +ent suffix for Enterprise Consul [[GH-12961](https://github.com/hashicorp/consul/issues/12961)]
|
||||||
|
* areas: **(Enterprise Only)** Fixes a bug when using Yamux pool ( for servers version 1.7.3 and later), the entire pool was locked while connecting to a remote location, which could potentially take a long time. [[GH-1368](https://github.com/hashicorp/consul/issues/1368)]
|
||||||
|
* ca: fix a bug that caused a non blocking leaf cert query after a blocking leaf cert query to block [[GH-12820](https://github.com/hashicorp/consul/issues/12820)]
|
||||||
|
* config: fix backwards compatibility bug where setting the (deprecated) top-level `verify_incoming` option would enable TLS client authentication on the gRPC port [[GH-13118](https://github.com/hashicorp/consul/issues/13118)]
|
||||||
|
* health: ensure /v1/health/service/:service endpoint returns the most recent results when a filter is used with streaming #12640 [[GH-12640](https://github.com/hashicorp/consul/issues/12640)]
|
||||||
|
* snapshot-agent: **(Enterprise only)** Fix a bug where providing the ACL token to the snapshot agent via a CLI or ENV variable without a license configured results in an error during license auto-retrieval.
|
||||||
|
* ui: Re-instate '...' icon for row actions [[GH-13183](https://github.com/hashicorp/consul/issues/13183)]
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
|
||||||
|
* ci: change action to pull v1 instead of main [[GH-12846](https://github.com/hashicorp/consul/issues/12846)]
|
||||||
|
|
||||||
## 1.12.0 (April 20, 2022)
|
## 1.12.0 (April 20, 2022)
|
||||||
|
|
||||||
BREAKING CHANGES:
|
BREAKING CHANGES:
|
||||||
|
|
||||||
|
* connect: Removes support for Envoy 1.17.4 [[GH-12777](https://github.com/hashicorp/consul/issues/12777)]
|
||||||
|
* connect: Removes support for Envoy 1.18.6 [[GH-12805](https://github.com/hashicorp/consul/issues/12805)]
|
||||||
* sdk: several changes to the testutil configuration structs (removed `ACLMasterToken`, renamed `Master` to `InitialManagement`, and `AgentMaster` to `AgentRecovery`) [[GH-11827](https://github.com/hashicorp/consul/issues/11827)]
|
* sdk: several changes to the testutil configuration structs (removed `ACLMasterToken`, renamed `Master` to `InitialManagement`, and `AgentMaster` to `AgentRecovery`) [[GH-11827](https://github.com/hashicorp/consul/issues/11827)]
|
||||||
* telemetry: the disable_compat_1.9 option now defaults to true. 1.9 style `consul.http...` metrics can still be enabled by setting `disable_compat_1.9 = false`. However, we will remove these metrics in 1.13. [[GH-12675](https://github.com/hashicorp/consul/issues/12675)]
|
* telemetry: the disable_compat_1.9 option now defaults to true. 1.9 style `consul.http...` metrics can still be enabled by setting `disable_compat_1.9 = false`. However, we will remove these metrics in 1.13. [[GH-12675](https://github.com/hashicorp/consul/issues/12675)]
|
||||||
|
|
||||||
|
@ -78,6 +116,24 @@ NOTES:
|
||||||
* Forked net/rpc to add middleware support: https://github.com/hashicorp/consul-net-rpc/ . [[GH-12311](https://github.com/hashicorp/consul/issues/12311)]
|
* Forked net/rpc to add middleware support: https://github.com/hashicorp/consul-net-rpc/ . [[GH-12311](https://github.com/hashicorp/consul/issues/12311)]
|
||||||
* dependency: Upgrade to use Go 1.18.1 [[GH-12808](https://github.com/hashicorp/consul/issues/12808)]
|
* dependency: Upgrade to use Go 1.18.1 [[GH-12808](https://github.com/hashicorp/consul/issues/12808)]
|
||||||
|
|
||||||
|
## 1.11.6 (May 25, 2022)
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* sentinel: **(Enterprise Only)** Sentinel now uses SHA256 to generate policy ids
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* Fix a bug when configuring an `add_headers` directive named `Host` the header is not set for `v1/internal/ui/metrics-proxy/` endpoint. [[GH-13071](https://github.com/hashicorp/consul/issues/13071)]
|
||||||
|
* areas: **(Enterprise Only)** Fixes a bug when using Yamux pool ( for servers version 1.7.3 and later), the entire pool was locked while connecting to a remote location, which could potentially take a long time. [[GH-1368](https://github.com/hashicorp/consul/issues/1368)]
|
||||||
|
* ca: fix a bug that caused a non blocking leaf cert query after a blocking leaf cert query to block [[GH-12820](https://github.com/hashicorp/consul/issues/12820)]
|
||||||
|
* health: ensure /v1/health/service/:service endpoint returns the most recent results when a filter is used with streaming #12640 [[GH-12640](https://github.com/hashicorp/consul/issues/12640)]
|
||||||
|
* snapshot-agent: **(Enterprise only)** Fix a bug where providing the ACL token to the snapshot agent via a CLI or ENV variable without a license configured results in an error during license auto-retrieval.
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
|
||||||
|
* ci: change action to pull v1 instead of main [[GH-12846](https://github.com/hashicorp/consul/issues/12846)]
|
||||||
|
|
||||||
## 1.11.5 (April 13, 2022)
|
## 1.11.5 (April 13, 2022)
|
||||||
|
|
||||||
SECURITY:
|
SECURITY:
|
||||||
|
@ -342,6 +398,50 @@ NOTES:
|
||||||
|
|
||||||
* Renamed the `agent_master` field to `agent_recovery` in the `acl-tokens.json` file in which tokens are persisted on-disk (when `acl.enable_token_persistence` is enabled) [[GH-11744](https://github.com/hashicorp/consul/issues/11744)]
|
* Renamed the `agent_master` field to `agent_recovery` in the `acl-tokens.json` file in which tokens are persisted on-disk (when `acl.enable_token_persistence` is enabled) [[GH-11744](https://github.com/hashicorp/consul/issues/11744)]
|
||||||
|
|
||||||
|
## 1.10.11 (May 25, 2022)
|
||||||
|
|
||||||
|
SECURITY:
|
||||||
|
|
||||||
|
* agent: Use SHA256 instead of MD5 to generate persistence file names.
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* sentinel: **(Enterprise Only)** Sentinel now uses SHA256 to generate policy ids
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* Fix a bug when configuring an `add_headers` directive named `Host` the header is not set for `v1/internal/ui/metrics-proxy/` endpoint. [[GH-13071](https://github.com/hashicorp/consul/issues/13071)]
|
||||||
|
* areas: **(Enterprise Only)** Fixes a bug when using Yamux pool ( for servers version 1.7.3 and later), the entire pool was locked while connecting to a remote location, which could potentially take a long time. [[GH-1368](https://github.com/hashicorp/consul/issues/1368)]
|
||||||
|
* ca: fix a bug that caused a non blocking leaf cert query after a locking leaf cert query to block [[GH-12820](https://github.com/hashicorp/consul/issues/12820)]
|
||||||
|
* health: ensure /v1/health/service/:service endpoint returns the most recent results when a filter is used with streaming #12640 [[GH-12640](https://github.com/hashicorp/consul/issues/12640)]
|
||||||
|
* snapshot-agent: **(Enterprise only)** Fix a bug where providing the ACL token to the snapshot agent via a CLI or ENV variable without a license configured results in an error during license auto-retrieval.
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
|
||||||
|
* ci: change action to pull v1 instead of main [[GH-12846](https://github.com/hashicorp/consul/issues/12846)]
|
||||||
|
|
||||||
|
## 1.10.10 (April 13, 2022)
|
||||||
|
|
||||||
|
SECURITY:
|
||||||
|
|
||||||
|
* agent: Added a new check field, `disable_redirects`, that allows for disabling the following of redirects for HTTP checks. The intention is to default this to true in a future release so that redirects must explicitly be enabled. [[GH-12685](https://github.com/hashicorp/consul/issues/12685)]
|
||||||
|
* connect: Properly set SNI when configured for services behind a terminating gateway. [[GH-12672](https://github.com/hashicorp/consul/issues/12672)]
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* xds: ensure that all connect timeout configs can apply equally to tproxy direct dial connections [[GH-12711](https://github.com/hashicorp/consul/issues/12711)]
|
||||||
|
|
||||||
|
DEPRECATIONS:
|
||||||
|
|
||||||
|
* tls: With the upgrade to Go 1.17, the ordering of `tls_cipher_suites` will no longer be honored, and `tls_prefer_server_cipher_suites` is now ignored. [[GH-12766](https://github.com/hashicorp/consul/issues/12766)]
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* connect/ca: cancel old Vault renewal on CA configuration. Provide a 1 - 6 second backoff on repeated token renewal requests to prevent overwhelming Vault. [[GH-12607](https://github.com/hashicorp/consul/issues/12607)]
|
||||||
|
* raft: upgrade to v1.3.6 which fixes a bug where a read replica node could attempt bootstrapping raft and prevent other nodes from bootstrapping at all [[GH-12496](https://github.com/hashicorp/consul/issues/12496)]
|
||||||
|
* replication: Fixed a bug which could prevent ACL replication from continuing successfully after a leader election. [[GH-12565](https://github.com/hashicorp/consul/issues/12565)]
|
||||||
|
* server: fix spurious blocking query suppression for discovery chains [[GH-12512](https://github.com/hashicorp/consul/issues/12512)]
|
||||||
|
|
||||||
## 1.10.9 (February 28, 2022)
|
## 1.10.9 (February 28, 2022)
|
||||||
|
|
||||||
SECURITY:
|
SECURITY:
|
||||||
|
|
93
Dockerfile
93
Dockerfile
|
@ -192,3 +192,96 @@ ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
# everything in RAM, exposes a web UI and HTTP endpoints, and bootstraps itself.
|
# everything in RAM, exposes a web UI and HTTP endpoints, and bootstraps itself.
|
||||||
# Don't use this configuration for production.
|
# Don't use this configuration for production.
|
||||||
CMD ["agent", "-dev", "-client", "0.0.0.0"]
|
CMD ["agent", "-dev", "-client", "0.0.0.0"]
|
||||||
|
|
||||||
|
|
||||||
|
# Red Hat UBI-based image
|
||||||
|
# This target is used to build a Consul image for use on OpenShift.
|
||||||
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 as ubi
|
||||||
|
|
||||||
|
ARG PRODUCT_NAME
|
||||||
|
ARG PRODUCT_VERSION
|
||||||
|
ARG PRODUCT_REVISION
|
||||||
|
ARG BIN_NAME
|
||||||
|
|
||||||
|
# PRODUCT_NAME and PRODUCT_VERSION are the name of the software on releases.hashicorp.com
|
||||||
|
# and the version to download. Example: PRODUCT_NAME=consul PRODUCT_VERSION=1.2.3.
|
||||||
|
ENV BIN_NAME=$BIN_NAME
|
||||||
|
ENV PRODUCT_VERSION=$PRODUCT_VERSION
|
||||||
|
|
||||||
|
ARG PRODUCT_NAME=$BIN_NAME
|
||||||
|
|
||||||
|
# TARGETOS and TARGETARCH are set automatically when --platform is provided.
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="Consul Team <consul@hashicorp.com>" \
|
||||||
|
org.opencontainers.image.url="https://www.consul.io/" \
|
||||||
|
org.opencontainers.image.documentation="https://www.consul.io/docs" \
|
||||||
|
org.opencontainers.image.source="https://github.com/hashicorp/consul" \
|
||||||
|
org.opencontainers.image.version=$VERSION \
|
||||||
|
org.opencontainers.image.vendor="HashiCorp" \
|
||||||
|
org.opencontainers.image.title="consul" \
|
||||||
|
org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration."
|
||||||
|
|
||||||
|
# Copy license for Red Hat certification.
|
||||||
|
COPY LICENSE /licenses/mozilla.txt
|
||||||
|
|
||||||
|
# Set up certificates and base tools.
|
||||||
|
# dumb-init is downloaded directly from GitHub because there's no RPM package.
|
||||||
|
# Its shasum is hardcoded. If you upgrade the dumb-init verion you'll need to
|
||||||
|
# also update the shasum.
|
||||||
|
RUN set -eux && \
|
||||||
|
microdnf install -y ca-certificates curl gnupg libcap openssl iputils jq iptables wget unzip tar && \
|
||||||
|
wget -O /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 && \
|
||||||
|
echo 'e874b55f3279ca41415d290c512a7ba9d08f98041b28ae7c2acb19a545f1c4df /usr/bin/dumb-init' > dumb-init-shasum && \
|
||||||
|
sha256sum --check dumb-init-shasum && \
|
||||||
|
chmod +x /usr/bin/dumb-init
|
||||||
|
|
||||||
|
# Create a non-root user to run the software. On OpenShift, this
|
||||||
|
# will not matter since the container is run as a random user and group
|
||||||
|
# but this is kept for consistency with our other images.
|
||||||
|
RUN groupadd $BIN_NAME && \
|
||||||
|
adduser --uid 100 --system -g $BIN_NAME $BIN_NAME
|
||||||
|
COPY dist/$TARGETOS/$TARGETARCH/$BIN_NAME /bin/
|
||||||
|
|
||||||
|
# The /consul/data dir is used by Consul to store state. The agent will be started
|
||||||
|
# with /consul/config as the configuration directory so you can add additional
|
||||||
|
# config files in that location.
|
||||||
|
# In addition, change the group of the /consul directory to 0 since OpenShift
|
||||||
|
# will always execute the container with group 0.
|
||||||
|
RUN mkdir -p /consul/data && \
|
||||||
|
mkdir -p /consul/config && \
|
||||||
|
chown -R consul /consul && \
|
||||||
|
chgrp -R 0 /consul && chmod -R g+rwX /consul
|
||||||
|
|
||||||
|
# set up nsswitch.conf for Go's "netgo" implementation which is used by Consul,
|
||||||
|
# otherwise DNS supercedes the container's hosts file, which we don't want.
|
||||||
|
RUN test -e /etc/nsswitch.conf || echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||||
|
|
||||||
|
# Expose the consul data directory as a volume since there's mutable state in there.
|
||||||
|
VOLUME /consul/data
|
||||||
|
|
||||||
|
# Server RPC is used for communication between Consul clients and servers for internal
|
||||||
|
# request forwarding.
|
||||||
|
EXPOSE 8300
|
||||||
|
|
||||||
|
# Serf LAN and WAN (WAN is used only by Consul servers) are used for gossip between
|
||||||
|
# Consul agents. LAN is within the datacenter and WAN is between just the Consul
|
||||||
|
# servers in all datacenters.
|
||||||
|
EXPOSE 8301 8301/udp 8302 8302/udp
|
||||||
|
|
||||||
|
# HTTP and DNS (both TCP and UDP) are the primary interfaces that applications
|
||||||
|
# use to interact with Consul.
|
||||||
|
EXPOSE 8500 8600 8600/udp
|
||||||
|
|
||||||
|
COPY .release/docker/docker-entrypoint-ubi.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
# OpenShift by default will run containers with a random user, however their
|
||||||
|
# scanner requires that containers set a non-root user.
|
||||||
|
USER 100
|
||||||
|
|
||||||
|
# By default you'll get an insecure single-node development server that stores
|
||||||
|
# everything in RAM, exposes a web UI and HTTP endpoints, and bootstraps itself.
|
||||||
|
# Don't use this configuration for production.
|
||||||
|
CMD ["agent", "-dev", "-client", "0.0.0.0"]
|
115
GNUmakefile
115
GNUmakefile
|
@ -2,24 +2,16 @@
|
||||||
# https://www.consul.io/docs/install#compiling-from-source
|
# https://www.consul.io/docs/install#compiling-from-source
|
||||||
|
|
||||||
SHELL = bash
|
SHELL = bash
|
||||||
GOTOOLS = \
|
|
||||||
github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs@master \
|
|
||||||
github.com/hashicorp/go-bindata/go-bindata@master \
|
|
||||||
github.com/vektra/mockery/v2@latest \
|
|
||||||
github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2 \
|
|
||||||
github.com/hashicorp/lint-consul-retry@master
|
|
||||||
|
|
||||||
PROTOC_VERSION=3.15.8
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# MOG_VERSION can be either a valid string for "go install <module>@<version>"
|
# These version variables can either be a valid string for "go install <module>@<version>"
|
||||||
# or the string @DEV to imply use whatever is currently installed locally.
|
# or the string @DEV to imply use what is currently installed locally.
|
||||||
###
|
###
|
||||||
|
GOLANGCI_LINT_VERSION='v1.46.2'
|
||||||
|
MOCKERY_VERSION='v2.12.2'
|
||||||
|
BUF_VERSION='v1.4.0'
|
||||||
|
PROTOC_GEN_GO_GRPC_VERSION="v1.2.0"
|
||||||
MOG_VERSION='v0.3.0'
|
MOG_VERSION='v0.3.0'
|
||||||
###
|
|
||||||
# PROTOC_GO_INJECT_TAG_VERSION can be either a valid string for "go install <module>@<version>"
|
|
||||||
# or the string @DEV to imply use whatever is currently installed locally.
|
|
||||||
###
|
|
||||||
PROTOC_GO_INJECT_TAG_VERSION='v1.3.0'
|
PROTOC_GO_INJECT_TAG_VERSION='v1.3.0'
|
||||||
|
|
||||||
GOTAGS ?=
|
GOTAGS ?=
|
||||||
|
@ -28,13 +20,14 @@ MAIN_GOPATH=$(shell go env GOPATH | cut -d: -f1)
|
||||||
|
|
||||||
export PATH := $(PWD)/bin:$(GOPATH)/bin:$(PATH)
|
export PATH := $(PWD)/bin:$(GOPATH)/bin:$(PATH)
|
||||||
|
|
||||||
ASSETFS_PATH?=agent/uiserver/bindata_assetfs.go
|
|
||||||
# Get the git commit
|
# Get the git commit
|
||||||
GIT_COMMIT?=$(shell git rev-parse --short HEAD)
|
GIT_COMMIT?=$(shell git rev-parse --short HEAD)
|
||||||
GIT_COMMIT_YEAR?=$(shell git show -s --format=%cd --date=format:%Y HEAD)
|
GIT_COMMIT_YEAR?=$(shell git show -s --format=%cd --date=format:%Y HEAD)
|
||||||
GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||||
GIT_IMPORT=github.com/hashicorp/consul/version
|
GIT_IMPORT=github.com/hashicorp/consul/version
|
||||||
GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY)
|
DATE_FORMAT="%Y-%m-%dT%H:%M:%SZ" # it's tricky to do an RFC3339 format in a cross platform way, so we hardcode UTC
|
||||||
|
GIT_DATE=$(shell $(CURDIR)/build-support/scripts/build-date.sh) # we're using this for build date because it's stable across platform builds
|
||||||
|
GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY) -X $(GIT_IMPORT).BuildDate=$(GIT_DATE)
|
||||||
|
|
||||||
ifeq ($(FORCE_REBUILD),1)
|
ifeq ($(FORCE_REBUILD),1)
|
||||||
NOCACHE=--no-cache
|
NOCACHE=--no-cache
|
||||||
|
@ -273,34 +266,36 @@ other-consul:
|
||||||
exit 1 ; \
|
exit 1 ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
lint:
|
lint: lint-tools
|
||||||
@echo "--> Running go golangci-lint"
|
@echo "--> Running golangci-lint"
|
||||||
@golangci-lint run --build-tags '$(GOTAGS)' && \
|
@golangci-lint run --build-tags '$(GOTAGS)' && \
|
||||||
(cd api && golangci-lint run --build-tags '$(GOTAGS)') && \
|
(cd api && golangci-lint run --build-tags '$(GOTAGS)') && \
|
||||||
(cd sdk && golangci-lint run --build-tags '$(GOTAGS)')
|
(cd sdk && golangci-lint run --build-tags '$(GOTAGS)')
|
||||||
|
@echo "--> Running lint-consul-retry"
|
||||||
|
@lint-consul-retry
|
||||||
|
@echo "--> Running enumcover"
|
||||||
|
@enumcover ./...
|
||||||
|
|
||||||
# If you've run "make ui" manually then this will get called for you. This is
|
# Build the static web ui inside a Docker container. For local testing only; do not commit these assets.
|
||||||
# also run as part of the release build script when it verifies that there are no
|
ui: ui-docker
|
||||||
# changes to the UI assets that aren't checked in.
|
|
||||||
static-assets:
|
|
||||||
@go-bindata-assetfs -pkg uiserver -prefix pkg -o $(ASSETFS_PATH) ./pkg/web_ui/...
|
|
||||||
@go fmt $(ASSETFS_PATH)
|
|
||||||
|
|
||||||
|
# Build the static web ui with yarn. This is the version to commit.
|
||||||
|
.PHONY: ui-regen
|
||||||
|
ui-regen:
|
||||||
|
cd $(CURDIR)/ui && make && cd ..
|
||||||
|
rm -rf $(CURDIR)/agent/uiserver/dist
|
||||||
|
mv $(CURDIR)/ui/packages/consul-ui/dist $(CURDIR)/agent/uiserver/
|
||||||
|
|
||||||
# Build the static web ui and build static assets inside a Docker container
|
tools:
|
||||||
ui: ui-docker static-assets-docker
|
@$(SHELL) $(CURDIR)/build-support/scripts/devtools.sh
|
||||||
|
|
||||||
tools: proto-tools
|
.PHONY: lint-tools
|
||||||
@if [[ -d .gotools ]]; then rm -rf .gotools ; fi
|
lint-tools:
|
||||||
@for TOOL in $(GOTOOLS); do \
|
@$(SHELL) $(CURDIR)/build-support/scripts/devtools.sh -lint
|
||||||
echo "=== TOOL: $$TOOL" ; \
|
|
||||||
go install -v $$TOOL ; \
|
|
||||||
done
|
|
||||||
|
|
||||||
|
.PHONY: proto-tools
|
||||||
proto-tools:
|
proto-tools:
|
||||||
@$(SHELL) $(CURDIR)/build-support/scripts/protobuf.sh \
|
@$(SHELL) $(CURDIR)/build-support/scripts/devtools.sh -protobuf
|
||||||
--protoc-version "$(PROTOC_VERSION)" \
|
|
||||||
--tools-only
|
|
||||||
|
|
||||||
version:
|
version:
|
||||||
@echo -n "Version: "
|
@echo -n "Version: "
|
||||||
|
@ -317,15 +312,12 @@ docker-images: go-build-image ui-build-image
|
||||||
|
|
||||||
go-build-image:
|
go-build-image:
|
||||||
@echo "Building Golang build container"
|
@echo "Building Golang build container"
|
||||||
@docker build $(NOCACHE) $(QUIET) --build-arg 'GOTOOLS=$(GOTOOLS)' -t $(GO_BUILD_TAG) - < build-support/docker/Build-Go.dockerfile
|
@docker build $(NOCACHE) $(QUIET) -t $(GO_BUILD_TAG) - < build-support/docker/Build-Go.dockerfile
|
||||||
|
|
||||||
ui-build-image:
|
ui-build-image:
|
||||||
@echo "Building UI build container"
|
@echo "Building UI build container"
|
||||||
@docker build $(NOCACHE) $(QUIET) -t $(UI_BUILD_TAG) - < build-support/docker/Build-UI.dockerfile
|
@docker build $(NOCACHE) $(QUIET) -t $(UI_BUILD_TAG) - < build-support/docker/Build-UI.dockerfile
|
||||||
|
|
||||||
static-assets-docker: go-build-image
|
|
||||||
@$(SHELL) $(CURDIR)/build-support/scripts/build-docker.sh static-assets
|
|
||||||
|
|
||||||
consul-docker: go-build-image
|
consul-docker: go-build-image
|
||||||
@$(SHELL) $(CURDIR)/build-support/scripts/build-docker.sh consul
|
@$(SHELL) $(CURDIR)/build-support/scripts/build-docker.sh consul
|
||||||
|
|
||||||
|
@ -335,6 +327,27 @@ ui-docker: ui-build-image
|
||||||
test-envoy-integ: $(ENVOY_INTEG_DEPS)
|
test-envoy-integ: $(ENVOY_INTEG_DEPS)
|
||||||
@go test -v -timeout=30m -tags integration ./test/integration/connect/envoy
|
@go test -v -timeout=30m -tags integration ./test/integration/connect/envoy
|
||||||
|
|
||||||
|
.PHONY: test-compat-integ
|
||||||
|
test-compat-integ: dev-docker
|
||||||
|
ifeq ("$(GOTAGS)","")
|
||||||
|
@docker tag consul-dev:latest consul:local
|
||||||
|
@docker run --rm -t consul:local consul version
|
||||||
|
@cd ./test/integration/consul-container && \
|
||||||
|
go test -v -timeout=30m ./upgrade --target-version local --latest-version latest
|
||||||
|
else
|
||||||
|
@docker tag consul-dev:latest hashicorp/consul-enterprise:local
|
||||||
|
@docker run --rm -t hashicorp/consul-enterprise:local consul version
|
||||||
|
@cd ./test/integration/consul-container && \
|
||||||
|
go test -v -timeout=30m ./upgrade --tags $(GOTAGS) --target-version local --latest-version latest
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: test-metrics-integ
|
||||||
|
test-metrics-integ: dev-docker
|
||||||
|
@docker tag consul-dev:latest consul:local
|
||||||
|
@docker run --rm -t consul:local consul version
|
||||||
|
@cd ./test/integration/consul-container && \
|
||||||
|
go test -v -timeout=7m ./metrics --target-version local
|
||||||
|
|
||||||
test-connect-ca-providers:
|
test-connect-ca-providers:
|
||||||
ifeq ("$(CIRCLECI)","true")
|
ifeq ("$(CIRCLECI)","true")
|
||||||
# Run in CI
|
# Run in CI
|
||||||
|
@ -352,9 +365,17 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: proto
|
.PHONY: proto
|
||||||
proto:
|
proto: proto-tools
|
||||||
@$(SHELL) $(CURDIR)/build-support/scripts/protobuf.sh \
|
@$(SHELL) $(CURDIR)/build-support/scripts/protobuf.sh
|
||||||
--protoc-version "$(PROTOC_VERSION)"
|
|
||||||
|
.PHONY: proto-format
|
||||||
|
proto-format: proto-tools
|
||||||
|
@buf format -w
|
||||||
|
|
||||||
|
.PHONY: proto-lint
|
||||||
|
proto-lint: proto-tools
|
||||||
|
@buf lint --config proto/buf.yaml --path proto
|
||||||
|
@buf lint --config proto-public/buf.yaml --path proto-public
|
||||||
|
|
||||||
# utility to echo a makefile variable (i.e. 'make print-PROTOC_VERSION')
|
# utility to echo a makefile variable (i.e. 'make print-PROTOC_VERSION')
|
||||||
print-% : ; @echo $($*)
|
print-% : ; @echo $($*)
|
||||||
|
@ -381,6 +402,12 @@ envoy-regen:
|
||||||
@find "command/connect/envoy/testdata" -name '*.golden' -delete
|
@find "command/connect/envoy/testdata" -name '*.golden' -delete
|
||||||
@go test -tags '$(GOTAGS)' ./command/connect/envoy -update
|
@go test -tags '$(GOTAGS)' ./command/connect/envoy -update
|
||||||
|
|
||||||
.PHONY: all bin dev dist cov test test-internal cover lint ui static-assets tools proto-tools
|
.PHONY: help
|
||||||
.PHONY: docker-images go-build-image ui-build-image static-assets-docker consul-docker ui-docker
|
help:
|
||||||
|
$(info available make targets)
|
||||||
|
$(info ----------------------)
|
||||||
|
@grep "^[a-z0-9-][a-z0-9.-]*:" GNUmakefile | cut -d':' -f1 | sort
|
||||||
|
|
||||||
|
.PHONY: all bin dev dist cov test test-internal cover lint ui tools
|
||||||
|
.PHONY: docker-images go-build-image ui-build-image consul-docker ui-docker
|
||||||
.PHONY: version test-envoy-integ
|
.PHONY: version test-envoy-integ
|
||||||
|
|
|
@ -58,6 +58,10 @@ func (m *EnterpriseMeta) NamespaceOrDefault() string {
|
||||||
return DefaultNamespaceName
|
return DefaultNamespaceName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EqualNamespaces(_, _ string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func NamespaceOrDefault(_ string) string {
|
func NamespaceOrDefault(_ string) string {
|
||||||
return DefaultNamespaceName
|
return DefaultNamespaceName
|
||||||
}
|
}
|
||||||
|
@ -78,7 +82,9 @@ func (m *EnterpriseMeta) MergeNoWildcard(_ *EnterpriseMeta) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *EnterpriseMeta) Normalize() {}
|
func (_ *EnterpriseMeta) Normalize() {}
|
||||||
|
func (_ *EnterpriseMeta) NormalizePartition() {}
|
||||||
|
func (_ *EnterpriseMeta) NormalizeNamespace() {}
|
||||||
|
|
||||||
func (m *EnterpriseMeta) Matches(_ *EnterpriseMeta) bool {
|
func (m *EnterpriseMeta) Matches(_ *EnterpriseMeta) bool {
|
||||||
return true
|
return true
|
||||||
|
@ -100,6 +106,10 @@ func (m *EnterpriseMeta) UnsetPartition() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *EnterpriseMeta) OverridePartition(_ string) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
func NewEnterpriseMetaWithPartition(_, _ string) EnterpriseMeta {
|
func NewEnterpriseMetaWithPartition(_, _ string) EnterpriseMeta {
|
||||||
return emptyEnterpriseMeta
|
return emptyEnterpriseMeta
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
acl.Authorizer
|
||||||
|
// TODO: likely we can reduce this interface
|
||||||
|
ACLIdentity structs.ACLIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Result) AccessorID() string {
|
||||||
|
if a.ACLIdentity == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.ACLIdentity.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Result) Identity() structs.ACLIdentity {
|
||||||
|
return a.ACLIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Result) ToAllowAuthorizer() acl.AllowAuthorizer {
|
||||||
|
return acl.AllowAuthorizer{Authorizer: a, AccessorID: a.AccessorID()}
|
||||||
|
}
|
|
@ -122,10 +122,7 @@ func (s *HTTPHandlers) ACLPolicyCRUD(resp http.ResponseWriter, req *http.Request
|
||||||
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
policyID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/policy/")
|
policyID := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if policyID == "" && req.Method != "PUT" {
|
if policyID == "" && req.Method != "PUT" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing policy ID"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing policy ID"}
|
||||||
}
|
}
|
||||||
|
@ -170,10 +167,7 @@ func (s *HTTPHandlers) ACLPolicyReadByName(resp http.ResponseWriter, req *http.R
|
||||||
return nil, aclDisabled
|
return nil, aclDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
policyName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/policy/name/")
|
policyName := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/name/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if policyName == "" {
|
if policyName == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing policy Name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing policy Name"}
|
||||||
}
|
}
|
||||||
|
@ -308,10 +302,7 @@ func (s *HTTPHandlers) ACLTokenCRUD(resp http.ResponseWriter, req *http.Request)
|
||||||
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/token/")
|
tokenID := strings.TrimPrefix(req.URL.Path, "/v1/acl/token/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(tokenID, "/clone") && req.Method == "PUT" {
|
if strings.HasSuffix(tokenID, "/clone") && req.Method == "PUT" {
|
||||||
tokenID = tokenID[:len(tokenID)-6]
|
tokenID = tokenID[:len(tokenID)-6]
|
||||||
fn = s.ACLTokenClone
|
fn = s.ACLTokenClone
|
||||||
|
@ -541,10 +532,7 @@ func (s *HTTPHandlers) ACLRoleCRUD(resp http.ResponseWriter, req *http.Request)
|
||||||
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
roleID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/role/")
|
roleID := strings.TrimPrefix(req.URL.Path, "/v1/acl/role/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if roleID == "" && req.Method != "PUT" {
|
if roleID == "" && req.Method != "PUT" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing role ID"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing role ID"}
|
||||||
}
|
}
|
||||||
|
@ -557,10 +545,7 @@ func (s *HTTPHandlers) ACLRoleReadByName(resp http.ResponseWriter, req *http.Req
|
||||||
return nil, aclDisabled
|
return nil, aclDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
roleName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/role/name/")
|
roleName := strings.TrimPrefix(req.URL.Path, "/v1/acl/role/name/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if roleName == "" {
|
if roleName == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing role Name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing role Name"}
|
||||||
}
|
}
|
||||||
|
@ -711,10 +696,7 @@ func (s *HTTPHandlers) ACLBindingRuleCRUD(resp http.ResponseWriter, req *http.Re
|
||||||
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingRuleID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/binding-rule/")
|
bindingRuleID := strings.TrimPrefix(req.URL.Path, "/v1/acl/binding-rule/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if bindingRuleID == "" && req.Method != "PUT" {
|
if bindingRuleID == "" && req.Method != "PUT" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing binding rule ID"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing binding rule ID"}
|
||||||
}
|
}
|
||||||
|
@ -857,10 +839,7 @@ func (s *HTTPHandlers) ACLAuthMethodCRUD(resp http.ResponseWriter, req *http.Req
|
||||||
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
methodName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/acl/auth-method/")
|
methodName := strings.TrimPrefix(req.URL.Path, "/v1/acl/auth-method/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if methodName == "" && req.Method != "PUT" {
|
if methodName == "" && req.Method != "PUT" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing auth method name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing auth method name"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/acl/resolver"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
"github.com/hashicorp/consul/agent/local"
|
"github.com/hashicorp/consul/agent/local"
|
||||||
|
@ -71,7 +72,9 @@ func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzRe
|
||||||
Output: logBuffer,
|
Output: logBuffer,
|
||||||
TimeFormat: "04:05.000",
|
TimeFormat: "04:05.000",
|
||||||
})
|
})
|
||||||
bd.MetricsHandler = metrics.NewInmemSink(1*time.Second, time.Minute)
|
bd.MetricsConfig = &lib.MetricsConfig{
|
||||||
|
Handler: metrics.NewInmemSink(1*time.Second, time.Minute),
|
||||||
|
}
|
||||||
|
|
||||||
agent, err := New(bd)
|
agent, err := New(bd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -92,15 +95,15 @@ func (a *TestACLAgent) ResolveToken(secretID string) (acl.Authorizer, error) {
|
||||||
return authz, err
|
return authz, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (consul.ACLResolveResult, error) {
|
func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (resolver.Result, error) {
|
||||||
authz, err := a.ResolveToken(secretID)
|
authz, err := a.ResolveToken(secretID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return consul.ACLResolveResult{}, err
|
return resolver.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := a.resolveIdentFn(secretID)
|
identity, err := a.resolveIdentFn(secretID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return consul.ACLResolveResult{}, err
|
return resolver.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
|
// Default the EnterpriseMeta based on the Tokens meta or actual defaults
|
||||||
|
@ -114,7 +117,7 @@ func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *acl.
|
||||||
// Use the meta to fill in the ACL authorization context
|
// Use the meta to fill in the ACL authorization context
|
||||||
entMeta.FillAuthzContext(authzContext)
|
entMeta.FillAuthzContext(authzContext)
|
||||||
|
|
||||||
return consul.ACLResolveResult{Authorizer: authz, ACLIdentity: identity}, err
|
return resolver.Result{Authorizer: authz, ACLIdentity: identity}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// All of these are stubs to satisfy the interface
|
// All of these are stubs to satisfy the interface
|
||||||
|
|
123
agent/agent.go
123
agent/agent.go
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
"github.com/armon/go-metrics/prometheus"
|
"github.com/armon/go-metrics/prometheus"
|
||||||
"github.com/hashicorp/consul/proto/pbpeering"
|
|
||||||
"github.com/hashicorp/go-connlimit"
|
"github.com/hashicorp/go-connlimit"
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-memdb"
|
"github.com/hashicorp/go-memdb"
|
||||||
|
@ -31,6 +30,7 @@ import (
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/acl/resolver"
|
||||||
"github.com/hashicorp/consul/agent/ae"
|
"github.com/hashicorp/consul/agent/ae"
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
||||||
|
@ -41,6 +41,9 @@ import (
|
||||||
publicgrpc "github.com/hashicorp/consul/agent/grpc/public"
|
publicgrpc "github.com/hashicorp/consul/agent/grpc/public"
|
||||||
"github.com/hashicorp/consul/agent/local"
|
"github.com/hashicorp/consul/agent/local"
|
||||||
"github.com/hashicorp/consul/agent/proxycfg"
|
"github.com/hashicorp/consul/agent/proxycfg"
|
||||||
|
proxycfgglue "github.com/hashicorp/consul/agent/proxycfg-glue"
|
||||||
|
catalogproxycfg "github.com/hashicorp/consul/agent/proxycfg-sources/catalog"
|
||||||
|
localproxycfg "github.com/hashicorp/consul/agent/proxycfg-sources/local"
|
||||||
"github.com/hashicorp/consul/agent/rpcclient/health"
|
"github.com/hashicorp/consul/agent/rpcclient/health"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/agent/systemd"
|
"github.com/hashicorp/consul/agent/systemd"
|
||||||
|
@ -54,6 +57,7 @@ import (
|
||||||
"github.com/hashicorp/consul/lib/mutex"
|
"github.com/hashicorp/consul/lib/mutex"
|
||||||
"github.com/hashicorp/consul/lib/routine"
|
"github.com/hashicorp/consul/lib/routine"
|
||||||
"github.com/hashicorp/consul/logging"
|
"github.com/hashicorp/consul/logging"
|
||||||
|
"github.com/hashicorp/consul/proto/pbpeering"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
)
|
)
|
||||||
|
@ -174,7 +178,7 @@ type delegate interface {
|
||||||
// actions based on the permissions granted to the token.
|
// actions based on the permissions granted to the token.
|
||||||
// If either entMeta or authzContext are non-nil they will be populated with the
|
// If either entMeta or authzContext are non-nil they will be populated with the
|
||||||
// default partition and namespace from the token.
|
// default partition and namespace from the token.
|
||||||
ResolveTokenAndDefaultMeta(token string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (consul.ACLResolveResult, error)
|
ResolveTokenAndDefaultMeta(token string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (resolver.Result, error)
|
||||||
|
|
||||||
RPC(method string, args interface{}, reply interface{}) error
|
RPC(method string, args interface{}, reply interface{}) error
|
||||||
SnapshotRPC(args *structs.SnapshotRequest, in io.Reader, out io.Writer, replyFn structs.SnapshotReplyFn) error
|
SnapshotRPC(args *structs.SnapshotRequest, in io.Reader, out io.Writer, replyFn structs.SnapshotReplyFn) error
|
||||||
|
@ -245,6 +249,9 @@ type Agent struct {
|
||||||
// checkTCPs maps the check ID to an associated TCP check
|
// checkTCPs maps the check ID to an associated TCP check
|
||||||
checkTCPs map[structs.CheckID]*checks.CheckTCP
|
checkTCPs map[structs.CheckID]*checks.CheckTCP
|
||||||
|
|
||||||
|
// checkUDPs maps the check ID to an associated UDP check
|
||||||
|
checkUDPs map[structs.CheckID]*checks.CheckUDP
|
||||||
|
|
||||||
// checkGRPCs maps the check ID to an associated GRPC check
|
// checkGRPCs maps the check ID to an associated GRPC check
|
||||||
checkGRPCs map[structs.CheckID]*checks.CheckGRPC
|
checkGRPCs map[structs.CheckID]*checks.CheckGRPC
|
||||||
|
|
||||||
|
@ -398,6 +405,7 @@ func New(bd BaseDeps) (*Agent, error) {
|
||||||
checkHTTPs: make(map[structs.CheckID]*checks.CheckHTTP),
|
checkHTTPs: make(map[structs.CheckID]*checks.CheckHTTP),
|
||||||
checkH2PINGs: make(map[structs.CheckID]*checks.CheckH2PING),
|
checkH2PINGs: make(map[structs.CheckID]*checks.CheckH2PING),
|
||||||
checkTCPs: make(map[structs.CheckID]*checks.CheckTCP),
|
checkTCPs: make(map[structs.CheckID]*checks.CheckTCP),
|
||||||
|
checkUDPs: make(map[structs.CheckID]*checks.CheckUDP),
|
||||||
checkGRPCs: make(map[structs.CheckID]*checks.CheckGRPC),
|
checkGRPCs: make(map[structs.CheckID]*checks.CheckGRPC),
|
||||||
checkDockers: make(map[structs.CheckID]*checks.CheckDocker),
|
checkDockers: make(map[structs.CheckID]*checks.CheckDocker),
|
||||||
checkAliases: make(map[structs.CheckID]*checks.CheckAlias),
|
checkAliases: make(map[structs.CheckID]*checks.CheckAlias),
|
||||||
|
@ -625,12 +633,31 @@ func (a *Agent) Start(ctx context.Context) error {
|
||||||
go a.baseDeps.ViewStore.Run(&lib.StopChannelContext{StopCh: a.shutdownCh})
|
go a.baseDeps.ViewStore.Run(&lib.StopChannelContext{StopCh: a.shutdownCh})
|
||||||
|
|
||||||
// Start the proxy config manager.
|
// Start the proxy config manager.
|
||||||
|
proxyDataSources := proxycfg.DataSources{
|
||||||
|
CARoots: proxycfgglue.CacheCARoots(a.cache),
|
||||||
|
CompiledDiscoveryChain: proxycfgglue.CacheCompiledDiscoveryChain(a.cache),
|
||||||
|
ConfigEntry: proxycfgglue.CacheConfigEntry(a.cache),
|
||||||
|
ConfigEntryList: proxycfgglue.CacheConfigEntryList(a.cache),
|
||||||
|
Datacenters: proxycfgglue.CacheDatacenters(a.cache),
|
||||||
|
FederationStateListMeshGateways: proxycfgglue.CacheFederationStateListMeshGateways(a.cache),
|
||||||
|
GatewayServices: proxycfgglue.CacheGatewayServices(a.cache),
|
||||||
|
Health: proxycfgglue.Health(a.rpcClientHealth),
|
||||||
|
HTTPChecks: proxycfgglue.CacheHTTPChecks(a.cache),
|
||||||
|
Intentions: proxycfgglue.CacheIntentions(a.cache),
|
||||||
|
IntentionUpstreams: proxycfgglue.CacheIntentionUpstreams(a.cache),
|
||||||
|
InternalServiceDump: proxycfgglue.CacheInternalServiceDump(a.cache),
|
||||||
|
LeafCertificate: proxycfgglue.CacheLeafCertificate(a.cache),
|
||||||
|
PreparedQuery: proxycfgglue.CachePrepraredQuery(a.cache),
|
||||||
|
ResolvedServiceConfig: proxycfgglue.CacheResolvedServiceConfig(a.cache),
|
||||||
|
ServiceList: proxycfgglue.CacheServiceList(a.cache),
|
||||||
|
TrustBundle: proxycfgglue.CacheTrustBundle(a.cache),
|
||||||
|
TrustBundleList: proxycfgglue.CacheTrustBundleList(a.cache),
|
||||||
|
ExportedPeeredServices: proxycfgglue.CacheExportedPeeredServices(a.cache),
|
||||||
|
}
|
||||||
|
a.fillEnterpriseProxyDataSources(&proxyDataSources)
|
||||||
a.proxyConfig, err = proxycfg.NewManager(proxycfg.ManagerConfig{
|
a.proxyConfig, err = proxycfg.NewManager(proxycfg.ManagerConfig{
|
||||||
Cache: a.cache,
|
DataSources: proxyDataSources,
|
||||||
Health: a.rpcClientHealth,
|
Logger: a.logger.Named(logging.ProxyConfig),
|
||||||
Logger: a.logger.Named(logging.ProxyConfig),
|
|
||||||
State: a.State,
|
|
||||||
Tokens: a.baseDeps.Tokens,
|
|
||||||
Source: &structs.QuerySource{
|
Source: &structs.QuerySource{
|
||||||
Datacenter: a.config.Datacenter,
|
Datacenter: a.config.Datacenter,
|
||||||
Segment: a.config.SegmentName,
|
Segment: a.config.SegmentName,
|
||||||
|
@ -646,11 +673,17 @@ func (a *Agent) Start(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
|
||||||
if err := a.proxyConfig.Run(); err != nil {
|
go localproxycfg.Sync(
|
||||||
a.logger.Error("proxy config manager exited with error", "error", err)
|
&lib.StopChannelContext{StopCh: a.shutdownCh},
|
||||||
}
|
localproxycfg.SyncConfig{
|
||||||
}()
|
Manager: a.proxyConfig,
|
||||||
|
State: a.State,
|
||||||
|
Logger: a.proxyConfig.Logger.Named("agent-state"),
|
||||||
|
Tokens: a.baseDeps.Tokens,
|
||||||
|
NodeName: a.config.NodeName,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Start watching for critical services to deregister, based on their
|
// Start watching for critical services to deregister, based on their
|
||||||
// checks.
|
// checks.
|
||||||
|
@ -769,15 +802,34 @@ func (a *Agent) listenAndServeGRPC() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(agentless): rather than asserting the concrete type of delegate, we
|
||||||
|
// should add a method to the Delegate interface to build a ConfigSource.
|
||||||
|
var cfg xds.ProxyConfigSource = localproxycfg.NewConfigSource(a.proxyConfig)
|
||||||
|
if server, ok := a.delegate.(*consul.Server); ok {
|
||||||
|
catalogCfg := catalogproxycfg.NewConfigSource(catalogproxycfg.Config{
|
||||||
|
NodeName: a.config.NodeName,
|
||||||
|
LocalState: a.State,
|
||||||
|
LocalConfigSource: cfg,
|
||||||
|
Manager: a.proxyConfig,
|
||||||
|
GetStore: func() catalogproxycfg.Store { return server.FSM().State() },
|
||||||
|
Logger: a.proxyConfig.Logger.Named("server-catalog"),
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
<-a.shutdownCh
|
||||||
|
catalogCfg.Shutdown()
|
||||||
|
}()
|
||||||
|
cfg = catalogCfg
|
||||||
|
}
|
||||||
|
|
||||||
a.xdsServer = xds.NewServer(
|
a.xdsServer = xds.NewServer(
|
||||||
|
a.config.NodeName,
|
||||||
a.logger.Named(logging.Envoy),
|
a.logger.Named(logging.Envoy),
|
||||||
a.config.ConnectServerlessPluginEnabled,
|
a.config.ConnectServerlessPluginEnabled,
|
||||||
a.proxyConfig,
|
cfg,
|
||||||
func(id string) (acl.Authorizer, error) {
|
func(id string) (acl.Authorizer, error) {
|
||||||
return a.delegate.ResolveTokenAndDefaultMeta(id, nil, nil)
|
return a.delegate.ResolveTokenAndDefaultMeta(id, nil, nil)
|
||||||
},
|
},
|
||||||
a,
|
a,
|
||||||
a,
|
|
||||||
)
|
)
|
||||||
a.xdsServer.Register(a.publicGRPCServer)
|
a.xdsServer.Register(a.publicGRPCServer)
|
||||||
|
|
||||||
|
@ -1429,6 +1481,7 @@ func (a *Agent) ShutdownAgent() error {
|
||||||
// this would be cancelled anyways (by the closing of the shutdown ch) but
|
// this would be cancelled anyways (by the closing of the shutdown ch) but
|
||||||
// this should help them to be stopped more quickly
|
// this should help them to be stopped more quickly
|
||||||
a.baseDeps.AutoConfig.Stop()
|
a.baseDeps.AutoConfig.Stop()
|
||||||
|
a.baseDeps.MetricsConfig.Cancel()
|
||||||
|
|
||||||
a.stateLock.Lock()
|
a.stateLock.Lock()
|
||||||
defer a.stateLock.Unlock()
|
defer a.stateLock.Unlock()
|
||||||
|
@ -1450,6 +1503,9 @@ func (a *Agent) ShutdownAgent() error {
|
||||||
for _, chk := range a.checkTCPs {
|
for _, chk := range a.checkTCPs {
|
||||||
chk.Stop()
|
chk.Stop()
|
||||||
}
|
}
|
||||||
|
for _, chk := range a.checkUDPs {
|
||||||
|
chk.Stop()
|
||||||
|
}
|
||||||
for _, chk := range a.checkGRPCs {
|
for _, chk := range a.checkGRPCs {
|
||||||
chk.Stop()
|
chk.Stop()
|
||||||
}
|
}
|
||||||
|
@ -2749,6 +2805,31 @@ func (a *Agent) addCheck(check *structs.HealthCheck, chkType *structs.CheckType,
|
||||||
tcp.Start()
|
tcp.Start()
|
||||||
a.checkTCPs[cid] = tcp
|
a.checkTCPs[cid] = tcp
|
||||||
|
|
||||||
|
case chkType.IsUDP():
|
||||||
|
if existing, ok := a.checkUDPs[cid]; ok {
|
||||||
|
existing.Stop()
|
||||||
|
delete(a.checkUDPs, cid)
|
||||||
|
}
|
||||||
|
if chkType.Interval < checks.MinInterval {
|
||||||
|
a.logger.Warn("check has interval below minimum",
|
||||||
|
"check", cid.String(),
|
||||||
|
"minimum_interval", checks.MinInterval,
|
||||||
|
)
|
||||||
|
chkType.Interval = checks.MinInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
udp := &checks.CheckUDP{
|
||||||
|
CheckID: cid,
|
||||||
|
ServiceID: sid,
|
||||||
|
UDP: chkType.UDP,
|
||||||
|
Interval: chkType.Interval,
|
||||||
|
Timeout: chkType.Timeout,
|
||||||
|
Logger: a.logger,
|
||||||
|
StatusHandler: statusHandler,
|
||||||
|
}
|
||||||
|
udp.Start()
|
||||||
|
a.checkUDPs[cid] = udp
|
||||||
|
|
||||||
case chkType.IsGRPC():
|
case chkType.IsGRPC():
|
||||||
if existing, ok := a.checkGRPCs[cid]; ok {
|
if existing, ok := a.checkGRPCs[cid]; ok {
|
||||||
existing.Stop()
|
existing.Stop()
|
||||||
|
@ -3048,6 +3129,10 @@ func (a *Agent) cancelCheckMonitors(checkID structs.CheckID) {
|
||||||
check.Stop()
|
check.Stop()
|
||||||
delete(a.checkTCPs, checkID)
|
delete(a.checkTCPs, checkID)
|
||||||
}
|
}
|
||||||
|
if check, ok := a.checkUDPs[checkID]; ok {
|
||||||
|
check.Stop()
|
||||||
|
delete(a.checkUDPs, checkID)
|
||||||
|
}
|
||||||
if check, ok := a.checkGRPCs[checkID]; ok {
|
if check, ok := a.checkGRPCs[checkID]; ok {
|
||||||
check.Stop()
|
check.Stop()
|
||||||
delete(a.checkGRPCs, checkID)
|
delete(a.checkGRPCs, checkID)
|
||||||
|
@ -3773,7 +3858,7 @@ func (a *Agent) reloadConfig(autoReload bool) error {
|
||||||
// breaking some existing behavior.
|
// breaking some existing behavior.
|
||||||
newCfg.NodeID = a.config.NodeID
|
newCfg.NodeID = a.config.NodeID
|
||||||
|
|
||||||
//if auto reload is enabled, make sure we have the right certs file watched.
|
// if auto reload is enabled, make sure we have the right certs file watched.
|
||||||
if autoReload {
|
if autoReload {
|
||||||
for _, f := range []struct {
|
for _, f := range []struct {
|
||||||
oldCfg tlsutil.ProtocolConfig
|
oldCfg tlsutil.ProtocolConfig
|
||||||
|
@ -4045,15 +4130,21 @@ func (a *Agent) registerCache() {
|
||||||
|
|
||||||
a.cache.RegisterType(cachetype.GatewayServicesName, &cachetype.GatewayServices{RPC: a})
|
a.cache.RegisterType(cachetype.GatewayServicesName, &cachetype.GatewayServices{RPC: a})
|
||||||
|
|
||||||
a.cache.RegisterType(cachetype.ConfigEntriesName, &cachetype.ConfigEntries{RPC: a})
|
a.cache.RegisterType(cachetype.ConfigEntryListName, &cachetype.ConfigEntryList{RPC: a})
|
||||||
|
|
||||||
a.cache.RegisterType(cachetype.ConfigEntryName, &cachetype.ConfigEntry{RPC: a})
|
a.cache.RegisterType(cachetype.ConfigEntryName, &cachetype.ConfigEntry{RPC: a})
|
||||||
|
|
||||||
a.cache.RegisterType(cachetype.ServiceHTTPChecksName, &cachetype.ServiceHTTPChecks{Agent: a})
|
a.cache.RegisterType(cachetype.ServiceHTTPChecksName, &cachetype.ServiceHTTPChecks{Agent: a})
|
||||||
|
|
||||||
|
a.cache.RegisterType(cachetype.TrustBundleReadName, &cachetype.TrustBundle{Client: a.rpcClientPeering})
|
||||||
|
|
||||||
|
a.cache.RegisterType(cachetype.ExportedPeeredServicesName, &cachetype.ExportedPeeredServices{RPC: a})
|
||||||
|
|
||||||
a.cache.RegisterType(cachetype.FederationStateListMeshGatewaysName,
|
a.cache.RegisterType(cachetype.FederationStateListMeshGatewaysName,
|
||||||
&cachetype.FederationStateListMeshGateways{RPC: a})
|
&cachetype.FederationStateListMeshGateways{RPC: a})
|
||||||
|
|
||||||
|
a.cache.RegisterType(cachetype.TrustBundleListName, &cachetype.TrustBundles{Client: a.rpcClientPeering})
|
||||||
|
|
||||||
a.registerEntCache()
|
a.registerEntCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ func (s *HTTPHandlers) AgentSelf(resp http.ResponseWriter, req *http.Request) (i
|
||||||
Revision string
|
Revision string
|
||||||
Server bool
|
Server bool
|
||||||
Version string
|
Version string
|
||||||
|
BuildDate string
|
||||||
}{
|
}{
|
||||||
Datacenter: s.agent.config.Datacenter,
|
Datacenter: s.agent.config.Datacenter,
|
||||||
PrimaryDatacenter: s.agent.config.PrimaryDatacenter,
|
PrimaryDatacenter: s.agent.config.PrimaryDatacenter,
|
||||||
|
@ -100,8 +101,10 @@ func (s *HTTPHandlers) AgentSelf(resp http.ResponseWriter, req *http.Request) (i
|
||||||
Revision: s.agent.config.Revision,
|
Revision: s.agent.config.Revision,
|
||||||
Server: s.agent.config.ServerMode,
|
Server: s.agent.config.ServerMode,
|
||||||
// We expect the ent version to be part of the reported version string, and that's now part of the metadata, not the actual version.
|
// We expect the ent version to be part of the reported version string, and that's now part of the metadata, not the actual version.
|
||||||
Version: s.agent.config.VersionWithMetadata(),
|
Version: s.agent.config.VersionWithMetadata(),
|
||||||
|
BuildDate: s.agent.config.BuildDate.Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
Config: config,
|
Config: config,
|
||||||
DebugConfig: s.agent.config.Sanitized(),
|
DebugConfig: s.agent.config.Sanitized(),
|
||||||
|
@ -173,7 +176,7 @@ func (s *HTTPHandlers) AgentMetrics(resp http.ResponseWriter, req *http.Request)
|
||||||
handler.ServeHTTP(resp, req)
|
handler.ServeHTTP(resp, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return s.agent.baseDeps.MetricsHandler.DisplayMetrics(resp, req)
|
return s.agent.baseDeps.MetricsConfig.Handler.DisplayMetrics(resp, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentMetricsStream(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentMetricsStream(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
|
@ -210,7 +213,7 @@ func (s *HTTPHandlers) AgentMetricsStream(resp http.ResponseWriter, req *http.Re
|
||||||
flusher: flusher,
|
flusher: flusher,
|
||||||
}
|
}
|
||||||
enc.encoder.SetIndent("", " ")
|
enc.encoder.SetIndent("", " ")
|
||||||
s.agent.baseDeps.MetricsHandler.Stream(req.Context(), enc)
|
s.agent.baseDeps.MetricsConfig.Handler.Stream(req.Context(), enc)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,10 +385,7 @@ func (s *HTTPHandlers) AgentServices(resp http.ResponseWriter, req *http.Request
|
||||||
// blocking watch using hash-based blocking.
|
// blocking watch using hash-based blocking.
|
||||||
func (s *HTTPHandlers) AgentService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Get the proxy ID. Note that this is the ID of a proxy's service instance.
|
// Get the proxy ID. Note that this is the ID of a proxy's service instance.
|
||||||
id, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/service/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe block
|
// Maybe block
|
||||||
var queryOpts structs.QueryOptions
|
var queryOpts structs.QueryOptions
|
||||||
|
@ -404,7 +404,7 @@ func (s *HTTPHandlers) AgentService(resp http.ResponseWriter, req *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to resolve to default the meta
|
// need to resolve to default the meta
|
||||||
_, err = s.agent.delegate.ResolveTokenAndDefaultMeta(token, &entMeta, nil)
|
_, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, &entMeta, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -640,10 +640,7 @@ func (s *HTTPHandlers) AgentJoin(resp http.ResponseWriter, req *http.Request) (i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the address
|
// Get the address
|
||||||
addr, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/join/")
|
addr := strings.TrimPrefix(req.URL.Path, "/v1/agent/join/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if wan {
|
if wan {
|
||||||
if s.agent.config.ConnectMeshGatewayWANFederationEnabled {
|
if s.agent.config.ConnectMeshGatewayWANFederationEnabled {
|
||||||
|
@ -703,10 +700,7 @@ func (s *HTTPHandlers) AgentForceLeave(resp http.ResponseWriter, req *http.Reque
|
||||||
// Check if the WAN is being queried
|
// Check if the WAN is being queried
|
||||||
_, wan := req.URL.Query()["wan"]
|
_, wan := req.URL.Query()["wan"]
|
||||||
|
|
||||||
addr, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/force-leave/")
|
addr := strings.TrimPrefix(req.URL.Path, "/v1/agent/force-leave/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if wan {
|
if wan {
|
||||||
return nil, s.agent.ForceLeaveWAN(addr, prune, entMeta)
|
return nil, s.agent.ForceLeaveWAN(addr, prune, entMeta)
|
||||||
} else {
|
} else {
|
||||||
|
@ -792,11 +786,8 @@ func (s *HTTPHandlers) AgentRegisterCheck(resp http.ResponseWriter, req *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentDeregisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
ID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/check/deregister/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/deregister/")
|
||||||
if err != nil {
|
checkID := structs.NewCheckID(types.CheckID(id), nil)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
checkID := structs.NewCheckID(types.CheckID(ID), nil)
|
|
||||||
|
|
||||||
// Get the provided token, if any, and vet against any ACL policies.
|
// Get the provided token, if any, and vet against any ACL policies.
|
||||||
var token string
|
var token string
|
||||||
|
@ -829,21 +820,15 @@ func (s *HTTPHandlers) AgentDeregisterCheck(resp http.ResponseWriter, req *http.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentCheckPass(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentCheckPass(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
ID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/check/pass/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/pass/")
|
||||||
if err != nil {
|
checkID := types.CheckID(id)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
checkID := types.CheckID(ID)
|
|
||||||
note := req.URL.Query().Get("note")
|
note := req.URL.Query().Get("note")
|
||||||
return s.agentCheckUpdate(resp, req, checkID, api.HealthPassing, note)
|
return s.agentCheckUpdate(resp, req, checkID, api.HealthPassing, note)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentCheckWarn(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentCheckWarn(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
ID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/check/warn/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/warn/")
|
||||||
if err != nil {
|
checkID := types.CheckID(id)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
checkID := types.CheckID(ID)
|
|
||||||
note := req.URL.Query().Get("note")
|
note := req.URL.Query().Get("note")
|
||||||
|
|
||||||
return s.agentCheckUpdate(resp, req, checkID, api.HealthWarning, note)
|
return s.agentCheckUpdate(resp, req, checkID, api.HealthWarning, note)
|
||||||
|
@ -851,11 +836,8 @@ func (s *HTTPHandlers) AgentCheckWarn(resp http.ResponseWriter, req *http.Reques
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentCheckFail(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
ID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/check/fail/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/fail/")
|
||||||
if err != nil {
|
checkID := types.CheckID(id)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
checkID := types.CheckID(ID)
|
|
||||||
note := req.URL.Query().Get("note")
|
note := req.URL.Query().Get("note")
|
||||||
|
|
||||||
return s.agentCheckUpdate(resp, req, checkID, api.HealthCritical, note)
|
return s.agentCheckUpdate(resp, req, checkID, api.HealthCritical, note)
|
||||||
|
@ -890,12 +872,8 @@ func (s *HTTPHandlers) AgentCheckUpdate(resp http.ResponseWriter, req *http.Requ
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid check status: '%s'", update.Status)}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: fmt.Sprintf("Invalid check status: '%s'", update.Status)}
|
||||||
}
|
}
|
||||||
|
|
||||||
ID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/check/update/")
|
id := strings.TrimPrefix(req.URL.Path, "/v1/agent/check/update/")
|
||||||
if err != nil {
|
checkID := types.CheckID(id)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
checkID := types.CheckID(ID)
|
|
||||||
|
|
||||||
return s.agentCheckUpdate(resp, req, checkID, update.Status, update.Output)
|
return s.agentCheckUpdate(resp, req, checkID, update.Status, update.Output)
|
||||||
}
|
}
|
||||||
|
@ -977,10 +955,7 @@ func returnTextPlain(req *http.Request) bool {
|
||||||
// AgentHealthServiceByID return the local Service Health given its ID
|
// AgentHealthServiceByID return the local Service Health given its ID
|
||||||
func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Pull out the service id (service id since there may be several instance of the same service on this host)
|
// Pull out the service id (service id since there may be several instance of the same service on this host)
|
||||||
serviceID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/health/service/id/")
|
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/health/service/id/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if serviceID == "" {
|
if serviceID == "" {
|
||||||
return nil, &HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing serviceID"}
|
return nil, &HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing serviceID"}
|
||||||
}
|
}
|
||||||
|
@ -1038,11 +1013,7 @@ func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *htt
|
||||||
// AgentHealthServiceByName return the worse status of all the services with given name on an agent
|
// AgentHealthServiceByName return the worse status of all the services with given name on an agent
|
||||||
func (s *HTTPHandlers) AgentHealthServiceByName(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentHealthServiceByName(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Pull out the service name
|
// Pull out the service name
|
||||||
serviceName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/health/service/name/")
|
serviceName := strings.TrimPrefix(req.URL.Path, "/v1/agent/health/service/name/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if serviceName == "" {
|
if serviceName == "" {
|
||||||
return nil, &HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service Name"}
|
return nil, &HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service Name"}
|
||||||
}
|
}
|
||||||
|
@ -1247,11 +1218,7 @@ func (s *HTTPHandlers) AgentRegisterService(resp http.ResponseWriter, req *http.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
serviceID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/service/deregister/")
|
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/deregister/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := structs.NewServiceID(serviceID, nil)
|
sid := structs.NewServiceID(serviceID, nil)
|
||||||
|
|
||||||
// Get the provided token, if any, and vet against any ACL policies.
|
// Get the provided token, if any, and vet against any ACL policies.
|
||||||
|
@ -1287,11 +1254,7 @@ func (s *HTTPHandlers) AgentDeregisterService(resp http.ResponseWriter, req *htt
|
||||||
|
|
||||||
func (s *HTTPHandlers) AgentServiceMaintenance(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentServiceMaintenance(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Ensure we have a service ID
|
// Ensure we have a service ID
|
||||||
serviceID, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/service/maintenance/")
|
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/maintenance/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sid := structs.NewServiceID(serviceID, nil)
|
sid := structs.NewServiceID(serviceID, nil)
|
||||||
|
|
||||||
if sid.ID == "" {
|
if sid.ID == "" {
|
||||||
|
@ -1489,10 +1452,7 @@ func (s *HTTPHandlers) AgentToken(resp http.ResponseWriter, req *http.Request) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out the target token.
|
// Figure out the target token.
|
||||||
target, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/token/")
|
target := strings.TrimPrefix(req.URL.Path, "/v1/agent/token/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.agent.tokens.WithPersistenceLock(func() error {
|
err = s.agent.tokens.WithPersistenceLock(func() error {
|
||||||
triggerAntiEntropySync := false
|
triggerAntiEntropySync := false
|
||||||
|
@ -1565,10 +1525,9 @@ func (s *HTTPHandlers) AgentConnectCARoots(resp http.ResponseWriter, req *http.R
|
||||||
func (s *HTTPHandlers) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) AgentConnectCALeafCert(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Get the service name. Note that this is the name of the service,
|
// Get the service name. Note that this is the name of the service,
|
||||||
// not the ID of the service instance.
|
// not the ID of the service instance.
|
||||||
serviceName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/agent/connect/ca/leaf/")
|
serviceName := strings.TrimPrefix(req.URL.Path, "/v1/agent/connect/ca/leaf/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
// TODO(peering): expose way to get kind=mesh-gateway type cert with appropriate ACLs
|
||||||
}
|
|
||||||
|
|
||||||
args := cachetype.ConnectCALeafRequest{
|
args := cachetype.ConnectCALeafRequest{
|
||||||
Service: serviceName, // Need name not ID
|
Service: serviceName, // Need name not ID
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/acl/resolver"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/connect/ca"
|
"github.com/hashicorp/consul/agent/connect/ca"
|
||||||
|
@ -39,6 +40,7 @@ import (
|
||||||
tokenStore "github.com/hashicorp/consul/agent/token"
|
tokenStore "github.com/hashicorp/consul/agent/token"
|
||||||
"github.com/hashicorp/consul/agent/xds/proxysupport"
|
"github.com/hashicorp/consul/agent/xds/proxysupport"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
|
@ -1563,7 +1565,9 @@ func TestHTTPHandlers_AgentMetricsStream_ACLDeny(t *testing.T) {
|
||||||
bd := BaseDeps{}
|
bd := BaseDeps{}
|
||||||
bd.Tokens = new(tokenStore.Store)
|
bd.Tokens = new(tokenStore.Store)
|
||||||
sink := metrics.NewInmemSink(30*time.Millisecond, time.Second)
|
sink := metrics.NewInmemSink(30*time.Millisecond, time.Second)
|
||||||
bd.MetricsHandler = sink
|
bd.MetricsConfig = &lib.MetricsConfig{
|
||||||
|
Handler: sink,
|
||||||
|
}
|
||||||
d := fakeResolveTokenDelegate{authorizer: acl.DenyAll()}
|
d := fakeResolveTokenDelegate{authorizer: acl.DenyAll()}
|
||||||
agent := &Agent{
|
agent := &Agent{
|
||||||
baseDeps: bd,
|
baseDeps: bd,
|
||||||
|
@ -1590,7 +1594,9 @@ func TestHTTPHandlers_AgentMetricsStream(t *testing.T) {
|
||||||
bd := BaseDeps{}
|
bd := BaseDeps{}
|
||||||
bd.Tokens = new(tokenStore.Store)
|
bd.Tokens = new(tokenStore.Store)
|
||||||
sink := metrics.NewInmemSink(20*time.Millisecond, time.Second)
|
sink := metrics.NewInmemSink(20*time.Millisecond, time.Second)
|
||||||
bd.MetricsHandler = sink
|
bd.MetricsConfig = &lib.MetricsConfig{
|
||||||
|
Handler: sink,
|
||||||
|
}
|
||||||
d := fakeResolveTokenDelegate{authorizer: acl.ManageAll()}
|
d := fakeResolveTokenDelegate{authorizer: acl.ManageAll()}
|
||||||
agent := &Agent{
|
agent := &Agent{
|
||||||
baseDeps: bd,
|
baseDeps: bd,
|
||||||
|
@ -1640,8 +1646,8 @@ type fakeResolveTokenDelegate struct {
|
||||||
authorizer acl.Authorizer
|
authorizer acl.Authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f fakeResolveTokenDelegate) ResolveTokenAndDefaultMeta(_ string, _ *acl.EnterpriseMeta, _ *acl.AuthorizerContext) (consul.ACLResolveResult, error) {
|
func (f fakeResolveTokenDelegate) ResolveTokenAndDefaultMeta(_ string, _ *acl.EnterpriseMeta, _ *acl.AuthorizerContext) (resolver.Result, error) {
|
||||||
return consul.ACLResolveResult{Authorizer: f.authorizer}, nil
|
return resolver.Result{Authorizer: f.authorizer}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAgent_Reload(t *testing.T) {
|
func TestAgent_Reload(t *testing.T) {
|
||||||
|
@ -1698,6 +1704,7 @@ func TestAgent_Reload(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
shim := &delegateConfigReloadShim{delegate: a.delegate}
|
shim := &delegateConfigReloadShim{delegate: a.delegate}
|
||||||
|
// NOTE: this may require refactoring to remove a potential test race
|
||||||
a.delegate = shim
|
a.delegate = shim
|
||||||
if err := a.reloadConfigInternal(cfg2); err != nil {
|
if err := a.reloadConfigInternal(cfg2); err != nil {
|
||||||
t.Fatalf("got error %v want nil", err)
|
t.Fatalf("got error %v want nil", err)
|
||||||
|
@ -2508,6 +2515,48 @@ func TestAgent_RegisterCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgent_RegisterCheck_UDP(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
args := &structs.CheckDefinition{
|
||||||
|
UDP: "1.1.1.1",
|
||||||
|
Name: "test",
|
||||||
|
Interval: 10 * time.Second,
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest("PUT", "/v1/agent/check/register?token=abc123", jsonReader(args))
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
require.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
// Ensure we have a check mapping
|
||||||
|
checkID := structs.NewCheckID("test", nil)
|
||||||
|
if existing := a.State.Check(checkID); existing == nil {
|
||||||
|
t.Fatalf("missing test check")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := a.checkUDPs[checkID]; !ok {
|
||||||
|
t.Fatalf("missing test check udp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the token was configured
|
||||||
|
if token := a.State.CheckToken(checkID); token == "" {
|
||||||
|
t.Fatalf("missing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, checks start in critical state.
|
||||||
|
state := a.State.Check(checkID)
|
||||||
|
if state.Status != api.HealthCritical {
|
||||||
|
t.Fatalf("bad: %v", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This verifies all the forms of the new args-style check that we need to
|
// This verifies all the forms of the new args-style check that we need to
|
||||||
// support as a result of https://github.com/hashicorp/consul/issues/3587.
|
// support as a result of https://github.com/hashicorp/consul/issues/3587.
|
||||||
func TestAgent_RegisterCheck_Scripts(t *testing.T) {
|
func TestAgent_RegisterCheck_Scripts(t *testing.T) {
|
||||||
|
@ -3270,6 +3319,10 @@ func testAgent_RegisterService(t *testing.T, extraHCL string) {
|
||||||
{
|
{
|
||||||
TTL: 30 * time.Second,
|
TTL: 30 * time.Second,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
UDP: "1.1.1.1",
|
||||||
|
Interval: 5 * time.Second,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Weights: &structs.Weights{
|
Weights: &structs.Weights{
|
||||||
Passing: 100,
|
Passing: 100,
|
||||||
|
@ -3301,12 +3354,12 @@ func testAgent_RegisterService(t *testing.T, extraHCL string) {
|
||||||
|
|
||||||
// Ensure we have a check mapping
|
// Ensure we have a check mapping
|
||||||
checks := a.State.Checks(structs.WildcardEnterpriseMetaInDefaultPartition())
|
checks := a.State.Checks(structs.WildcardEnterpriseMetaInDefaultPartition())
|
||||||
if len(checks) != 3 {
|
if len(checks) != 4 {
|
||||||
t.Fatalf("bad: %v", checks)
|
t.Fatalf("bad: %v", checks)
|
||||||
}
|
}
|
||||||
for _, c := range checks {
|
for _, c := range checks {
|
||||||
if c.Type != "ttl" {
|
if c.Type != "ttl" && c.Type != "udp" {
|
||||||
t.Fatalf("expected ttl check type, got %s", c.Type)
|
t.Fatalf("expected ttl or udp check type, got %s", c.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3356,6 +3409,11 @@ func testAgent_RegisterService_ReRegister(t *testing.T, extraHCL string) {
|
||||||
CheckID: types.CheckID("check_2"),
|
CheckID: types.CheckID("check_2"),
|
||||||
TTL: 30 * time.Second,
|
TTL: 30 * time.Second,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CheckID: types.CheckID("check_3"),
|
||||||
|
UDP: "1.1.1.1",
|
||||||
|
Interval: 5 * time.Second,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Weights: &structs.Weights{
|
Weights: &structs.Weights{
|
||||||
Passing: 100,
|
Passing: 100,
|
||||||
|
@ -3381,6 +3439,11 @@ func testAgent_RegisterService_ReRegister(t *testing.T, extraHCL string) {
|
||||||
CheckID: types.CheckID("check_3"),
|
CheckID: types.CheckID("check_3"),
|
||||||
TTL: 30 * time.Second,
|
TTL: 30 * time.Second,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CheckID: types.CheckID("check_3"),
|
||||||
|
UDP: "1.1.1.1",
|
||||||
|
Interval: 5 * time.Second,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Weights: &structs.Weights{
|
Weights: &structs.Weights{
|
||||||
Passing: 100,
|
Passing: 100,
|
||||||
|
@ -3708,6 +3771,231 @@ func testAgent_RegisterService_TranslateKeys(t *testing.T, extraHCL string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgent_RegisterService_TranslateKeys_UDP(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("normal", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testAgent_RegisterService_TranslateKeys(t, "enable_central_service_config = false")
|
||||||
|
})
|
||||||
|
t.Run("service manager", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testAgent_RegisterService_TranslateKeys(t, "enable_central_service_config = true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAgent_RegisterService_TranslateKeys_UDP(t *testing.T, extraHCL string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ip string
|
||||||
|
expectedUDPCheckStart string
|
||||||
|
}{
|
||||||
|
{"127.0.0.1", "127.0.0.1:"}, // private network address
|
||||||
|
{"::1", "[::1]:"}, // shared address space
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.ip, func(t *testing.T) {
|
||||||
|
a := NewTestAgent(t, `
|
||||||
|
connect {}
|
||||||
|
`+extraHCL)
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
json := `
|
||||||
|
{
|
||||||
|
"name":"test",
|
||||||
|
"port":8000,
|
||||||
|
"enable_tag_override": true,
|
||||||
|
"tagged_addresses": {
|
||||||
|
"lan": {
|
||||||
|
"address": "1.2.3.4",
|
||||||
|
"port": 5353
|
||||||
|
},
|
||||||
|
"wan": {
|
||||||
|
"address": "2.3.4.5",
|
||||||
|
"port": 53
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"some": "meta",
|
||||||
|
"enable_tag_override": "meta is 'opaque' so should not get translated"
|
||||||
|
},
|
||||||
|
"kind": "connect-proxy",` +
|
||||||
|
// Note the uppercase P is important here - it ensures translation works
|
||||||
|
// correctly in case-insensitive way. Without it this test can pass even
|
||||||
|
// when translation is broken for other valid inputs.
|
||||||
|
`"Proxy": {
|
||||||
|
"destination_service_name": "web",
|
||||||
|
"destination_service_id": "web",
|
||||||
|
"local_service_port": 1234,
|
||||||
|
"local_service_address": "` + tt.ip + `",
|
||||||
|
"config": {
|
||||||
|
"destination_type": "proxy.config is 'opaque' so should not get translated"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"destination_type": "service",
|
||||||
|
"destination_namespace": "default",
|
||||||
|
"destination_partition": "default",
|
||||||
|
"destination_name": "db",
|
||||||
|
"local_bind_address": "` + tt.ip + `",
|
||||||
|
"local_bind_port": 1234,
|
||||||
|
"config": {
|
||||||
|
"destination_type": "proxy.upstreams.config is 'opaque' so should not get translated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connect": {
|
||||||
|
"sidecar_service": {
|
||||||
|
"name":"test-proxy",
|
||||||
|
"port":8001,
|
||||||
|
"enable_tag_override": true,
|
||||||
|
"meta": {
|
||||||
|
"some": "meta",
|
||||||
|
"enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated"
|
||||||
|
},
|
||||||
|
"kind": "connect-proxy",
|
||||||
|
"proxy": {
|
||||||
|
"destination_service_name": "test",
|
||||||
|
"destination_service_id": "test",
|
||||||
|
"local_service_port": 4321,
|
||||||
|
"local_service_address": "` + tt.ip + `",
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"destination_type": "service",
|
||||||
|
"destination_namespace": "default",
|
||||||
|
"destination_partition": "default",
|
||||||
|
"destination_name": "db",
|
||||||
|
"local_bind_address": "` + tt.ip + `",
|
||||||
|
"local_bind_port": 1234,
|
||||||
|
"config": {
|
||||||
|
"destination_type": "sidecar_service.proxy.upstreams.config is 'opaque' so should not get translated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weights":{
|
||||||
|
"passing": 16
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
req, _ := http.NewRequest("PUT", "/v1/agent/service/register", strings.NewReader(json))
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(rr, req)
|
||||||
|
require.Equal(t, 200, rr.Code, "body: %s", rr.Body)
|
||||||
|
|
||||||
|
svc := &structs.NodeService{
|
||||||
|
ID: "test",
|
||||||
|
Service: "test",
|
||||||
|
TaggedAddresses: map[string]structs.ServiceAddress{
|
||||||
|
"lan": {
|
||||||
|
Address: "1.2.3.4",
|
||||||
|
Port: 5353,
|
||||||
|
},
|
||||||
|
"wan": {
|
||||||
|
Address: "2.3.4.5",
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"some": "meta",
|
||||||
|
"enable_tag_override": "meta is 'opaque' so should not get translated",
|
||||||
|
},
|
||||||
|
Port: 8000,
|
||||||
|
EnableTagOverride: true,
|
||||||
|
Weights: &structs.Weights{Passing: 16, Warning: 0},
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "web",
|
||||||
|
DestinationServiceID: "web",
|
||||||
|
LocalServiceAddress: tt.ip,
|
||||||
|
LocalServicePort: 1234,
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"destination_type": "proxy.config is 'opaque' so should not get translated",
|
||||||
|
},
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
DestinationType: structs.UpstreamDestTypeService,
|
||||||
|
DestinationName: "db",
|
||||||
|
DestinationNamespace: "default",
|
||||||
|
DestinationPartition: "default",
|
||||||
|
LocalBindAddress: tt.ip,
|
||||||
|
LocalBindPort: 1234,
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"destination_type": "proxy.upstreams.config is 'opaque' so should not get translated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Connect: structs.ServiceConnect{
|
||||||
|
// The sidecar service is nilled since it is only config sugar and
|
||||||
|
// shouldn't be represented in state. We assert that the translations
|
||||||
|
// there worked by inspecting the registered sidecar below.
|
||||||
|
SidecarService: nil,
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
||||||
|
}
|
||||||
|
|
||||||
|
got := a.State.Service(structs.NewServiceID("test", nil))
|
||||||
|
require.Equal(t, svc, got)
|
||||||
|
|
||||||
|
sidecarSvc := &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "test-sidecar-proxy",
|
||||||
|
Service: "test-proxy",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"some": "meta",
|
||||||
|
"enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated",
|
||||||
|
},
|
||||||
|
TaggedAddresses: map[string]structs.ServiceAddress{},
|
||||||
|
Port: 8001,
|
||||||
|
EnableTagOverride: true,
|
||||||
|
Weights: &structs.Weights{Passing: 1, Warning: 1},
|
||||||
|
LocallyRegisteredAsSidecar: true,
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "test",
|
||||||
|
DestinationServiceID: "test",
|
||||||
|
LocalServiceAddress: tt.ip,
|
||||||
|
LocalServicePort: 4321,
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
DestinationType: structs.UpstreamDestTypeService,
|
||||||
|
DestinationName: "db",
|
||||||
|
DestinationNamespace: "default",
|
||||||
|
DestinationPartition: "default",
|
||||||
|
LocalBindAddress: tt.ip,
|
||||||
|
LocalBindPort: 1234,
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"destination_type": "sidecar_service.proxy.upstreams.config is 'opaque' so should not get translated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
||||||
|
}
|
||||||
|
gotSidecar := a.State.Service(structs.NewServiceID("test-sidecar-proxy", nil))
|
||||||
|
hasNoCorrectUDPCheck := true
|
||||||
|
for _, v := range a.checkUDPs {
|
||||||
|
if strings.HasPrefix(v.UDP, tt.expectedUDPCheckStart) {
|
||||||
|
hasNoCorrectUDPCheck = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Println("UDP Check:= ", v)
|
||||||
|
}
|
||||||
|
if hasNoCorrectUDPCheck {
|
||||||
|
t.Fatalf("Did not find the expected UDP Healtcheck '%s' in %#v ", tt.expectedUDPCheckStart, a.checkUDPs)
|
||||||
|
}
|
||||||
|
require.Equal(t, sidecarSvc, gotSidecar)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAgent_RegisterService_ACLDeny(t *testing.T) {
|
func TestAgent_RegisterService_ACLDeny(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
@ -4457,6 +4745,503 @@ func testAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T, extraHCL s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This tests local agent service registration with a sidecar service. Note we
|
||||||
|
// only test simple defaults for the sidecar here since the actual logic for
|
||||||
|
// handling sidecar defaults and port assignment is tested thoroughly in
|
||||||
|
// TestAgent_sidecarServiceFromNodeService. Note it also tests Deregister
|
||||||
|
// explicitly too since setup is identical.
|
||||||
|
func TestAgent_RegisterServiceDeregisterService_Sidecar_UDP(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("normal", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t, "enable_central_service_config = false")
|
||||||
|
})
|
||||||
|
t.Run("service manager", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t, "enable_central_service_config = true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAgent_RegisterServiceDeregisterService_Sidecar_UDP(t *testing.T, extraHCL string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
preRegister, preRegister2 *structs.NodeService
|
||||||
|
// Use raw JSON payloads rather than encoding to avoid subtleties with some
|
||||||
|
// internal representations and different ways they encode and decode. We
|
||||||
|
// rely on the payload being Unmarshalable to structs.ServiceDefinition
|
||||||
|
// directly.
|
||||||
|
json string
|
||||||
|
enableACL bool
|
||||||
|
tokenRules string
|
||||||
|
wantNS *structs.NodeService
|
||||||
|
wantErr string
|
||||||
|
wantSidecarIDLeftAfterDereg bool
|
||||||
|
assertStateFn func(t *testing.T, state *local.State)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "sanity check no sidecar case",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default sidecar",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: testDefaultSidecar("web", 1111),
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL OK defaults",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
tokenRules: `
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}`,
|
||||||
|
wantNS: testDefaultSidecar("web", 1111),
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL denied",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
tokenRules: ``, // No token rules means no valid token
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Permission denied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL OK for service but not for sidecar",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
// This will become more common/reasonable when ACLs support exact match.
|
||||||
|
tokenRules: `
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Permission denied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL OK for service and sidecar but not sidecar's overridden destination",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"proxy": {
|
||||||
|
"DestinationServiceName": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
tokenRules: `
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Permission denied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL OK for service but not for overridden sidecar",
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"name": "foo-sidecar-proxy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
tokenRules: `
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Permission denied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ACL OK for service but and overridden for sidecar",
|
||||||
|
// This test ensures that if the sidecar embeds it's own token with
|
||||||
|
// different privs from the main request token it will be honored for the
|
||||||
|
// sidecar registration. We use the test root token since that should have
|
||||||
|
// permission.
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"name": "foo",
|
||||||
|
"token": "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
enableACL: true,
|
||||||
|
tokenRules: `
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}`,
|
||||||
|
wantNS: testDefaultSidecar("web", 1111, func(ns *structs.NodeService) {
|
||||||
|
ns.Service = "foo"
|
||||||
|
}),
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid check definition in sidecar",
|
||||||
|
// Note no interval in the UDP check should fail validation
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"check": {
|
||||||
|
"UDP": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "invalid check in sidecar_service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid checks definitions in sidecar",
|
||||||
|
// Note no interval in the UDP check should fail validation
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"checks": [{
|
||||||
|
"UDP": "foo"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "invalid check in sidecar_service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid check status in sidecar",
|
||||||
|
// Note no interval in the UDP check should fail validation
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"check": {
|
||||||
|
"UDP": "foo",
|
||||||
|
"Interval": 10,
|
||||||
|
"Status": "unsupported-status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Status for checks must 'passing', 'warning', 'critical'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid checks status in sidecar",
|
||||||
|
// Note no interval in the UDP check should fail validation
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"checks": [{
|
||||||
|
"UDP": "foo",
|
||||||
|
"Interval": 10,
|
||||||
|
"Status": "unsupported-status"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantNS: nil,
|
||||||
|
wantErr: "Status for checks must 'passing', 'warning', 'critical'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "another service registered with same ID as a sidecar should not be deregistered",
|
||||||
|
// Add another service with the same ID that a sidecar for web would have
|
||||||
|
preRegister: &structs.NodeService{
|
||||||
|
ID: "web-sidecar-proxy",
|
||||||
|
Service: "fake-sidecar",
|
||||||
|
Port: 9999,
|
||||||
|
},
|
||||||
|
// Register web with NO SIDECAR
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// Note here that although the registration here didn't register it, we
|
||||||
|
// should still see the NodeService we pre-registered here.
|
||||||
|
wantNS: &structs.NodeService{
|
||||||
|
ID: "web-sidecar-proxy",
|
||||||
|
Service: "fake-sidecar",
|
||||||
|
Port: 9999,
|
||||||
|
TaggedAddresses: map[string]structs.ServiceAddress{},
|
||||||
|
Weights: &structs.Weights{
|
||||||
|
Passing: 1,
|
||||||
|
Warning: 1,
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
||||||
|
},
|
||||||
|
// After we deregister the web service above, the fake sidecar with
|
||||||
|
// clashing ID SHOULD NOT have been removed since it wasn't part of the
|
||||||
|
// original registration.
|
||||||
|
wantSidecarIDLeftAfterDereg: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updates to sidecar should work",
|
||||||
|
// Add a valid sidecar already registered
|
||||||
|
preRegister: &structs.NodeService{
|
||||||
|
ID: "web-sidecar-proxy",
|
||||||
|
Service: "web-sidecar-proxy",
|
||||||
|
LocallyRegisteredAsSidecar: true,
|
||||||
|
Port: 9999,
|
||||||
|
},
|
||||||
|
// Register web with Sidecar on different port
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 1111,
|
||||||
|
"connect": {
|
||||||
|
"SidecarService": {
|
||||||
|
"Port": 6666
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// Note here that although the registration here didn't register it, we
|
||||||
|
// should still see the NodeService we pre-registered here.
|
||||||
|
wantNS: &structs.NodeService{
|
||||||
|
Kind: "connect-proxy",
|
||||||
|
ID: "web-sidecar-proxy",
|
||||||
|
Service: "web-sidecar-proxy",
|
||||||
|
LocallyRegisteredAsSidecar: true,
|
||||||
|
Port: 6666,
|
||||||
|
TaggedAddresses: map[string]structs.ServiceAddress{},
|
||||||
|
Weights: &structs.Weights{
|
||||||
|
Passing: 1,
|
||||||
|
Warning: 1,
|
||||||
|
},
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "web",
|
||||||
|
DestinationServiceID: "web",
|
||||||
|
LocalServiceAddress: "127.0.0.1",
|
||||||
|
LocalServicePort: 1111,
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update that removes sidecar should NOT deregister it",
|
||||||
|
// Add web with a valid sidecar already registered
|
||||||
|
preRegister: &structs.NodeService{
|
||||||
|
ID: "web",
|
||||||
|
Service: "web",
|
||||||
|
Port: 1111,
|
||||||
|
},
|
||||||
|
preRegister2: testDefaultSidecar("web", 1111),
|
||||||
|
// Register (update) web and remove sidecar (and port for sanity check)
|
||||||
|
json: `
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"port": 2222
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// Sidecar should still be there such that API can update registration
|
||||||
|
// without accidentally removing a sidecar. This is equivalent to embedded
|
||||||
|
// checks which are not removed by just not being included in an update.
|
||||||
|
// We will document that sidecar registrations via API must be explicitiy
|
||||||
|
// deregistered.
|
||||||
|
wantNS: testDefaultSidecar("web", 1111),
|
||||||
|
// Sanity check the rest of the update happened though.
|
||||||
|
assertStateFn: func(t *testing.T, state *local.State) {
|
||||||
|
svc := state.Service(structs.NewServiceID("web", nil))
|
||||||
|
require.NotNil(t, svc)
|
||||||
|
require.Equal(t, 2222, svc.Port)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
// Constrain auto ports to 1 available to make it deterministic
|
||||||
|
hcl := `ports {
|
||||||
|
sidecar_min_port = 2222
|
||||||
|
sidecar_max_port = 2222
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if tt.enableACL {
|
||||||
|
hcl = hcl + TestACLConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
a := NewTestAgent(t, hcl+" "+extraHCL)
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
if tt.preRegister != nil {
|
||||||
|
require.NoError(t, a.addServiceFromSource(tt.preRegister, nil, false, "", ConfigSourceLocal))
|
||||||
|
}
|
||||||
|
if tt.preRegister2 != nil {
|
||||||
|
require.NoError(t, a.addServiceFromSource(tt.preRegister2, nil, false, "", ConfigSourceLocal))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an ACL token with require policy
|
||||||
|
var token string
|
||||||
|
if tt.enableACL && tt.tokenRules != "" {
|
||||||
|
token = testCreateToken(t, a, tt.tokenRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bytes.NewBufferString(tt.json)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token="+token, br)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Contains(t, strings.ToLower(resp.Body.String()), strings.ToLower(tt.wantErr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, 200, resp.Code, "request failed with body: %s",
|
||||||
|
resp.Body.String())
|
||||||
|
|
||||||
|
// Sanity the target service registration
|
||||||
|
svcs := a.State.AllServices()
|
||||||
|
|
||||||
|
// Parse the expected definition into a ServiceDefinition
|
||||||
|
var sd structs.ServiceDefinition
|
||||||
|
err := json.Unmarshal([]byte(tt.json), &sd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, sd.Name)
|
||||||
|
|
||||||
|
svcID := sd.ID
|
||||||
|
if svcID == "" {
|
||||||
|
svcID = sd.Name
|
||||||
|
}
|
||||||
|
sid := structs.NewServiceID(svcID, nil)
|
||||||
|
svc, ok := svcs[sid]
|
||||||
|
require.True(t, ok, "has service "+sid.String())
|
||||||
|
assert.Equal(t, sd.Name, svc.Service)
|
||||||
|
assert.Equal(t, sd.Port, svc.Port)
|
||||||
|
// Ensure that the actual registered service _doesn't_ still have it's
|
||||||
|
// sidecar info since it's duplicate and we don't want that synced up to
|
||||||
|
// the catalog or included in responses particularly - it's just
|
||||||
|
// registration syntax sugar.
|
||||||
|
assert.Nil(t, svc.Connect.SidecarService)
|
||||||
|
|
||||||
|
if tt.wantNS == nil {
|
||||||
|
// Sanity check that there was no service registered, we rely on there
|
||||||
|
// being no services at start of test so we can just use the count.
|
||||||
|
assert.Len(t, svcs, 1, "should be no sidecar registered")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure sidecar
|
||||||
|
svc, ok = svcs[structs.NewServiceID(tt.wantNS.ID, nil)]
|
||||||
|
require.True(t, ok, "no sidecar registered at "+tt.wantNS.ID)
|
||||||
|
assert.Equal(t, tt.wantNS, svc)
|
||||||
|
|
||||||
|
if tt.assertStateFn != nil {
|
||||||
|
tt.assertStateFn(t, a.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify deregistration also removes sidecar (if there was one and it
|
||||||
|
// was added via sidecar not just coincidental ID clash)
|
||||||
|
{
|
||||||
|
req := httptest.NewRequest("PUT",
|
||||||
|
"/v1/agent/service/deregister/"+svcID+"?token="+token, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
require.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
svcs := a.State.AllServices()
|
||||||
|
_, ok = svcs[structs.NewServiceID(tt.wantNS.ID, nil)]
|
||||||
|
if tt.wantSidecarIDLeftAfterDereg {
|
||||||
|
require.True(t, ok, "removed non-sidecar service at "+tt.wantNS.ID)
|
||||||
|
} else {
|
||||||
|
require.False(t, ok, "sidecar not deregistered with service "+svcID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// END HERE
|
||||||
|
|
||||||
// This tests that connect proxy validation is done for local agent
|
// This tests that connect proxy validation is done for local agent
|
||||||
// registration. This doesn't need to test validation exhaustively since
|
// registration. This doesn't need to test validation exhaustively since
|
||||||
// that is done via a table test in the structs package.
|
// that is done via a table test in the structs package.
|
||||||
|
@ -6205,13 +6990,6 @@ func TestAgentConnectCALeafCert_goodNotLocal(t *testing.T) {
|
||||||
func TestAgentConnectCALeafCert_nonBlockingQuery_after_blockingQuery_shouldNotBlock(t *testing.T) {
|
func TestAgentConnectCALeafCert_nonBlockingQuery_after_blockingQuery_shouldNotBlock(t *testing.T) {
|
||||||
// see: https://github.com/hashicorp/consul/issues/12048
|
// see: https://github.com/hashicorp/consul/issues/12048
|
||||||
|
|
||||||
runStep := func(t *testing.T, name string, fn func(t *testing.T)) {
|
|
||||||
t.Helper()
|
|
||||||
if !t.Run(name, fn) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
}
|
}
|
||||||
|
@ -6246,7 +7024,7 @@ func TestAgentConnectCALeafCert_nonBlockingQuery_after_blockingQuery_shouldNotBl
|
||||||
index string
|
index string
|
||||||
issued structs.IssuedCert
|
issued structs.IssuedCert
|
||||||
)
|
)
|
||||||
runStep(t, "do initial non-blocking query", func(t *testing.T) {
|
testutil.RunStep(t, "do initial non-blocking query", func(t *testing.T) {
|
||||||
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
a.srv.h.ServeHTTP(resp, req)
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
@ -6278,7 +7056,7 @@ func TestAgentConnectCALeafCert_nonBlockingQuery_after_blockingQuery_shouldNotBl
|
||||||
// in between both of these steps the data should still be there, causing
|
// in between both of these steps the data should still be there, causing
|
||||||
// this to be a HIT that completes in less than 10m (the default inner leaf
|
// this to be a HIT that completes in less than 10m (the default inner leaf
|
||||||
// cert blocking query timeout).
|
// cert blocking query timeout).
|
||||||
runStep(t, "do a non-blocking query that should not block", func(t *testing.T) {
|
testutil.RunStep(t, "do a non-blocking query that should not block", func(t *testing.T) {
|
||||||
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
a.srv.h.ServeHTTP(resp, req)
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/config"
|
"github.com/hashicorp/consul/agent/config"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
|
"github.com/hashicorp/consul/agent/proxycfg"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
@ -58,3 +59,5 @@ func (a *Agent) AgentEnterpriseMeta() *acl.EnterpriseMeta {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) registerEntCache() {}
|
func (a *Agent) registerEntCache() {}
|
||||||
|
|
||||||
|
func (*Agent) fillEnterpriseProxyDataSources(*proxycfg.DataSources) {}
|
||||||
|
|
|
@ -9,17 +9,17 @@ import (
|
||||||
|
|
||||||
// Recommended name for registration.
|
// Recommended name for registration.
|
||||||
const (
|
const (
|
||||||
ConfigEntriesName = "config-entries"
|
ConfigEntryListName = "config-entries"
|
||||||
ConfigEntryName = "config-entry"
|
ConfigEntryName = "config-entry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigEntries supports fetching discovering configuration entries
|
// ConfigEntryList supports fetching discovering configuration entries
|
||||||
type ConfigEntries struct {
|
type ConfigEntryList struct {
|
||||||
RegisterOptionsBlockingRefresh
|
RegisterOptionsBlockingRefresh
|
||||||
RPC RPC
|
RPC RPC
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigEntries) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
func (c *ConfigEntryList) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||||
var result cache.FetchResult
|
var result cache.FetchResult
|
||||||
|
|
||||||
// The request should be a ConfigEntryQuery.
|
// The request should be a ConfigEntryQuery.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
func TestConfigEntries(t *testing.T) {
|
func TestConfigEntries(t *testing.T) {
|
||||||
rpc := TestRPC(t)
|
rpc := TestRPC(t)
|
||||||
typ := &ConfigEntries{RPC: rpc}
|
typ := &ConfigEntryList{RPC: rpc}
|
||||||
|
|
||||||
// Expect the proper RPC call. This also sets the expected value
|
// Expect the proper RPC call. This also sets the expected value
|
||||||
// since that is return-by-pointer in the arguments.
|
// since that is return-by-pointer in the arguments.
|
||||||
|
@ -99,7 +99,7 @@ func TestConfigEntry(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigEntries_badReqType(t *testing.T) {
|
func TestConfigEntries_badReqType(t *testing.T) {
|
||||||
rpc := TestRPC(t)
|
rpc := TestRPC(t)
|
||||||
typ := &ConfigEntries{RPC: rpc}
|
typ := &ConfigEntryList{RPC: rpc}
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
||||||
|
|
|
@ -558,8 +558,19 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
|
||||||
}
|
}
|
||||||
dnsNames = append([]string{"localhost"}, req.DNSSAN...)
|
dnsNames = append([]string{"localhost"}, req.DNSSAN...)
|
||||||
ipAddresses = append([]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, req.IPSAN...)
|
ipAddresses = append([]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, req.IPSAN...)
|
||||||
|
} else if req.Kind != "" {
|
||||||
|
if req.Kind != structs.ServiceKindMeshGateway {
|
||||||
|
return result, fmt.Errorf("unsupported kind: %s", req.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
id = &connect.SpiffeIDMeshGateway{
|
||||||
|
Host: roots.TrustDomain,
|
||||||
|
Datacenter: req.Datacenter,
|
||||||
|
Partition: req.TargetPartition(),
|
||||||
|
}
|
||||||
|
dnsNames = append(dnsNames, req.DNSSAN...)
|
||||||
} else {
|
} else {
|
||||||
return result, errors.New("URI must be either service or agent")
|
return result, errors.New("URI must be either service, agent, or kind")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new private key
|
// Create a new private key
|
||||||
|
@ -665,8 +676,9 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
|
||||||
type ConnectCALeafRequest struct {
|
type ConnectCALeafRequest struct {
|
||||||
Token string
|
Token string
|
||||||
Datacenter string
|
Datacenter string
|
||||||
Service string // Service name, not ID
|
Service string // Service name, not ID
|
||||||
Agent string // Agent name, not ID
|
Agent string // Agent name, not ID
|
||||||
|
Kind structs.ServiceKind // only mesh-gateway for now
|
||||||
DNSSAN []string
|
DNSSAN []string
|
||||||
IPSAN []net.IP
|
IPSAN []net.IP
|
||||||
MinQueryIndex uint64
|
MinQueryIndex uint64
|
||||||
|
@ -677,20 +689,38 @@ type ConnectCALeafRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ConnectCALeafRequest) Key() string {
|
func (r *ConnectCALeafRequest) Key() string {
|
||||||
if len(r.Agent) > 0 {
|
|
||||||
return fmt.Sprintf("agent:%s", r.Agent)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.EnterpriseMeta.Normalize()
|
r.EnterpriseMeta.Normalize()
|
||||||
|
|
||||||
v, err := hashstructure.Hash([]interface{}{
|
switch {
|
||||||
r.Service,
|
case r.Agent != "":
|
||||||
r.EnterpriseMeta,
|
v, err := hashstructure.Hash([]interface{}{
|
||||||
r.DNSSAN,
|
r.Agent,
|
||||||
r.IPSAN,
|
r.PartitionOrDefault(),
|
||||||
}, nil)
|
}, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Sprintf("service:%d", v)
|
return fmt.Sprintf("agent:%d", v)
|
||||||
|
}
|
||||||
|
case r.Kind == structs.ServiceKindMeshGateway:
|
||||||
|
v, err := hashstructure.Hash([]interface{}{
|
||||||
|
r.PartitionOrDefault(),
|
||||||
|
r.DNSSAN,
|
||||||
|
r.IPSAN,
|
||||||
|
}, nil)
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Sprintf("kind:%d", v)
|
||||||
|
}
|
||||||
|
case r.Kind != "":
|
||||||
|
// this is not valid
|
||||||
|
default:
|
||||||
|
v, err := hashstructure.Hash([]interface{}{
|
||||||
|
r.Service,
|
||||||
|
r.EnterpriseMeta,
|
||||||
|
r.DNSSAN,
|
||||||
|
r.IPSAN,
|
||||||
|
}, nil)
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Sprintf("service:%d", v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is an error, we don't set the key. A blank key forces
|
// If there is an error, we don't set the key. A blank key forces
|
||||||
|
|
|
@ -1104,29 +1104,64 @@ func (r *testGatedRootsRPC) RPC(method string, args interface{}, reply interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectCALeaf_Key(t *testing.T) {
|
func TestConnectCALeaf_Key(t *testing.T) {
|
||||||
r1 := ConnectCALeafRequest{Service: "web"}
|
key := func(r ConnectCALeafRequest) string {
|
||||||
r2 := ConnectCALeafRequest{Service: "api"}
|
return r.Key()
|
||||||
|
}
|
||||||
r3 := ConnectCALeafRequest{DNSSAN: []string{"a.com"}}
|
t.Run("service", func(t *testing.T) {
|
||||||
r4 := ConnectCALeafRequest{DNSSAN: []string{"b.com"}}
|
t.Run("name", func(t *testing.T) {
|
||||||
|
r1 := key(ConnectCALeafRequest{Service: "web"})
|
||||||
r5 := ConnectCALeafRequest{IPSAN: []net.IP{net.ParseIP("192.168.4.139")}}
|
r2 := key(ConnectCALeafRequest{Service: "api"})
|
||||||
r6 := ConnectCALeafRequest{IPSAN: []net.IP{net.ParseIP("192.168.4.140")}}
|
require.True(t, strings.HasPrefix(r1, "service:"), "Key %s does not start with service:", r1)
|
||||||
// hashstructure will hash the service name + ent meta to produce this key
|
require.True(t, strings.HasPrefix(r2, "service:"), "Key %s does not start with service:", r2)
|
||||||
r1Key := r1.Key()
|
require.NotEqual(t, r1, r2, "Cache keys for different services should not be equal")
|
||||||
r2Key := r2.Key()
|
})
|
||||||
|
t.Run("dns-san", func(t *testing.T) {
|
||||||
r3Key := r3.Key()
|
r3 := key(ConnectCALeafRequest{Service: "foo", DNSSAN: []string{"a.com"}})
|
||||||
r4Key := r4.Key()
|
r4 := key(ConnectCALeafRequest{Service: "foo", DNSSAN: []string{"b.com"}})
|
||||||
|
require.NotEqual(t, r3, r4, "Cache keys for different DNSSAN should not be equal")
|
||||||
r5Key := r5.Key()
|
})
|
||||||
r6Key := r6.Key()
|
t.Run("ip-san", func(t *testing.T) {
|
||||||
|
r5 := key(ConnectCALeafRequest{Service: "foo", IPSAN: []net.IP{net.ParseIP("192.168.4.139")}})
|
||||||
require.True(t, strings.HasPrefix(r1Key, "service:"), "Key %s does not start with service:", r1Key)
|
r6 := key(ConnectCALeafRequest{Service: "foo", IPSAN: []net.IP{net.ParseIP("192.168.4.140")}})
|
||||||
require.True(t, strings.HasPrefix(r2Key, "service:"), "Key %s does not start with service:", r2Key)
|
require.NotEqual(t, r5, r6, "Cache keys for different IPSAN should not be equal")
|
||||||
require.NotEqual(t, r1Key, r2Key, "Cache keys for different services are not equal")
|
})
|
||||||
require.NotEqual(t, r3Key, r4Key, "Cache keys for different DNSSAN are not equal")
|
})
|
||||||
require.NotEqual(t, r5Key, r6Key, "Cache keys for different IPSAN are not equal")
|
t.Run("agent", func(t *testing.T) {
|
||||||
r := ConnectCALeafRequest{Agent: "abc"}
|
t.Run("name", func(t *testing.T) {
|
||||||
require.Equal(t, "agent:abc", r.Key())
|
r1 := key(ConnectCALeafRequest{Agent: "abc"})
|
||||||
|
require.True(t, strings.HasPrefix(r1, "agent:"), "Key %s does not start with agent:", r1)
|
||||||
|
})
|
||||||
|
t.Run("dns-san ignored", func(t *testing.T) {
|
||||||
|
r3 := key(ConnectCALeafRequest{Agent: "foo", DNSSAN: []string{"a.com"}})
|
||||||
|
r4 := key(ConnectCALeafRequest{Agent: "foo", DNSSAN: []string{"b.com"}})
|
||||||
|
require.Equal(t, r3, r4, "DNSSAN is ignored for agent type")
|
||||||
|
})
|
||||||
|
t.Run("ip-san ignored", func(t *testing.T) {
|
||||||
|
r5 := key(ConnectCALeafRequest{Agent: "foo", IPSAN: []net.IP{net.ParseIP("192.168.4.139")}})
|
||||||
|
r6 := key(ConnectCALeafRequest{Agent: "foo", IPSAN: []net.IP{net.ParseIP("192.168.4.140")}})
|
||||||
|
require.Equal(t, r5, r6, "IPSAN is ignored for agent type")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("kind", func(t *testing.T) {
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
r1 := key(ConnectCALeafRequest{Kind: "terminating-gateway"})
|
||||||
|
require.Empty(t, r1)
|
||||||
|
})
|
||||||
|
t.Run("mesh-gateway", func(t *testing.T) {
|
||||||
|
t.Run("normal", func(t *testing.T) {
|
||||||
|
r1 := key(ConnectCALeafRequest{Kind: "mesh-gateway"})
|
||||||
|
require.True(t, strings.HasPrefix(r1, "kind:"), "Key %s does not start with kind:", r1)
|
||||||
|
})
|
||||||
|
t.Run("dns-san", func(t *testing.T) {
|
||||||
|
r3 := key(ConnectCALeafRequest{Kind: "mesh-gateway", DNSSAN: []string{"a.com"}})
|
||||||
|
r4 := key(ConnectCALeafRequest{Kind: "mesh-gateway", DNSSAN: []string{"b.com"}})
|
||||||
|
require.NotEqual(t, r3, r4, "Cache keys for different DNSSAN should not be equal")
|
||||||
|
})
|
||||||
|
t.Run("ip-san", func(t *testing.T) {
|
||||||
|
r5 := key(ConnectCALeafRequest{Kind: "mesh-gateway", IPSAN: []net.IP{net.ParseIP("192.168.4.139")}})
|
||||||
|
r6 := key(ConnectCALeafRequest{Kind: "mesh-gateway", IPSAN: []net.IP{net.ParseIP("192.168.4.140")}})
|
||||||
|
require.NotEqual(t, r5, r6, "Cache keys for different IPSAN should not be equal")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recommended name for registration.
|
||||||
|
const ExportedPeeredServicesName = "exported-peered-services"
|
||||||
|
|
||||||
|
type ExportedPeeredServices struct {
|
||||||
|
RegisterOptionsBlockingRefresh
|
||||||
|
RPC RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ExportedPeeredServices) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||||
|
var result cache.FetchResult
|
||||||
|
|
||||||
|
// The request should be a DCSpecificRequest.
|
||||||
|
reqReal, ok := req.(*structs.DCSpecificRequest)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf(
|
||||||
|
"Internal cache failure: request wrong type: %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightweight copy this object so that manipulating QueryOptions doesn't race.
|
||||||
|
dup := *reqReal
|
||||||
|
reqReal = &dup
|
||||||
|
|
||||||
|
// Set the minimum query index to our current index so we block
|
||||||
|
reqReal.QueryOptions.MinQueryIndex = opts.MinIndex
|
||||||
|
reqReal.QueryOptions.MaxQueryTime = opts.Timeout
|
||||||
|
|
||||||
|
// Always allow stale - there's no point in hitting leader if the request is
|
||||||
|
// going to be served from cache and end up arbitrarily stale anyway. This
|
||||||
|
// allows cached service-discover to automatically read scale across all
|
||||||
|
// servers too.
|
||||||
|
reqReal.AllowStale = true
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
var reply structs.IndexedExportedServiceList
|
||||||
|
if err := c.RPC.RPC("Internal.ExportedPeeredServices", reqReal, &reply); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Value = &reply
|
||||||
|
result.Index = reply.QueryMeta.Index
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExportedPeeredServices(t *testing.T) {
|
||||||
|
rpc := TestRPC(t)
|
||||||
|
typ := &ExportedPeeredServices{RPC: rpc}
|
||||||
|
|
||||||
|
// Expect the proper RPC call. This also sets the expected value
|
||||||
|
// since that is return-by-pointer in the arguments.
|
||||||
|
var resp *structs.IndexedExportedServiceList
|
||||||
|
rpc.On("RPC", "Internal.ExportedPeeredServices", mock.Anything, mock.Anything).Return(nil).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*structs.DCSpecificRequest)
|
||||||
|
require.Equal(t, uint64(24), req.QueryOptions.MinQueryIndex)
|
||||||
|
require.Equal(t, 1*time.Second, req.QueryOptions.MaxQueryTime)
|
||||||
|
require.True(t, req.AllowStale)
|
||||||
|
|
||||||
|
reply := args.Get(2).(*structs.IndexedExportedServiceList)
|
||||||
|
reply.Services = map[string]structs.ServiceList{
|
||||||
|
"my-peer": {
|
||||||
|
structs.ServiceName{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
structs.ServiceName{
|
||||||
|
Name: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reply.QueryMeta.Index = 48
|
||||||
|
resp = reply
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
resultA, err := typ.Fetch(cache.FetchOptions{
|
||||||
|
MinIndex: 24,
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
}, &structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, cache.FetchResult{
|
||||||
|
Value: resp,
|
||||||
|
Index: 48,
|
||||||
|
}, resultA)
|
||||||
|
|
||||||
|
rpc.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportedPeeredServices_badReqType(t *testing.T) {
|
||||||
|
rpc := TestRPC(t)
|
||||||
|
typ := &ExportedPeeredServices{RPC: rpc}
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
||||||
|
t, cache.RequestInfo{Key: "foo", MinIndex: 64}))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "wrong type")
|
||||||
|
rpc.AssertExpectations(t)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntentionUpstreamsDestinationName Recommended name for registration.
|
||||||
|
const IntentionUpstreamsDestinationName = "intention-upstreams-destination"
|
||||||
|
|
||||||
|
// IntentionUpstreamsDestination supports fetching upstreams for a given gateway name.
|
||||||
|
type IntentionUpstreamsDestination struct {
|
||||||
|
RegisterOptionsBlockingRefresh
|
||||||
|
RPC RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IntentionUpstreamsDestination) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||||
|
var result cache.FetchResult
|
||||||
|
|
||||||
|
// The request should be a ServiceSpecificRequest.
|
||||||
|
reqReal, ok := req.(*structs.ServiceSpecificRequest)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf(
|
||||||
|
"Internal cache failure: request wrong type: %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightweight copy this object so that manipulating QueryOptions doesn't race.
|
||||||
|
dup := *reqReal
|
||||||
|
reqReal = &dup
|
||||||
|
|
||||||
|
// Set the minimum query index to our current index so we block
|
||||||
|
reqReal.QueryOptions.MinQueryIndex = opts.MinIndex
|
||||||
|
reqReal.QueryOptions.MaxQueryTime = opts.Timeout
|
||||||
|
|
||||||
|
// Always allow stale - there's no point in hitting leader if the request is
|
||||||
|
// going to be served from cache and end up arbitrarily stale anyway. This
|
||||||
|
// allows cached service-discover to automatically read scale across all
|
||||||
|
// servers too.
|
||||||
|
reqReal.AllowStale = true
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
var reply structs.IndexedServiceList
|
||||||
|
if err := i.RPC.RPC("Internal.IntentionUpstreamsDestination", reqReal, &reply); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Value = &reply
|
||||||
|
result.Index = reply.QueryMeta.Index
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntentionUpstreamsDestination(t *testing.T) {
|
||||||
|
rpc := TestRPC(t)
|
||||||
|
typ := &IntentionUpstreamsDestination{RPC: rpc}
|
||||||
|
|
||||||
|
// Expect the proper RPC call. This also sets the expected value
|
||||||
|
// since that is return-by-pointer in the arguments.
|
||||||
|
var resp *structs.IndexedServiceList
|
||||||
|
rpc.On("RPC", "Internal.IntentionUpstreamsDestination", mock.Anything, mock.Anything).Return(nil).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*structs.ServiceSpecificRequest)
|
||||||
|
require.Equal(t, uint64(24), req.QueryOptions.MinQueryIndex)
|
||||||
|
require.Equal(t, 1*time.Second, req.QueryOptions.MaxQueryTime)
|
||||||
|
require.True(t, req.AllowStale)
|
||||||
|
require.Equal(t, "foo", req.ServiceName)
|
||||||
|
|
||||||
|
services := structs.ServiceList{
|
||||||
|
{Name: "foo"},
|
||||||
|
}
|
||||||
|
reply := args.Get(2).(*structs.IndexedServiceList)
|
||||||
|
reply.Services = services
|
||||||
|
reply.QueryMeta.Index = 48
|
||||||
|
resp = reply
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
resultA, err := typ.Fetch(cache.FetchOptions{
|
||||||
|
MinIndex: 24,
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
}, &structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ServiceName: "foo",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, cache.FetchResult{
|
||||||
|
Value: resp,
|
||||||
|
Index: 48,
|
||||||
|
}, resultA)
|
||||||
|
|
||||||
|
rpc.AssertExpectations(t)
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Code generated by mockery v2.12.2. DO NOT EDIT.
|
||||||
|
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
pbpeering "github.com/hashicorp/consul/proto/pbpeering"
|
||||||
|
|
||||||
|
testing "testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockTrustBundleReader is an autogenerated mock type for the TrustBundleReader type
|
||||||
|
type MockTrustBundleReader struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustBundleRead provides a mock function with given fields: ctx, in, opts
|
||||||
|
func (_m *MockTrustBundleReader) TrustBundleRead(ctx context.Context, in *pbpeering.TrustBundleReadRequest, opts ...grpc.CallOption) (*pbpeering.TrustBundleReadResponse, error) {
|
||||||
|
_va := make([]interface{}, len(opts))
|
||||||
|
for _i := range opts {
|
||||||
|
_va[_i] = opts[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, ctx, in)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
var r0 *pbpeering.TrustBundleReadResponse
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *pbpeering.TrustBundleReadRequest, ...grpc.CallOption) *pbpeering.TrustBundleReadResponse); ok {
|
||||||
|
r0 = rf(ctx, in, opts...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*pbpeering.TrustBundleReadResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, *pbpeering.TrustBundleReadRequest, ...grpc.CallOption) error); ok {
|
||||||
|
r1 = rf(ctx, in, opts...)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTrustBundleReader creates a new instance of MockTrustBundleReader. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewMockTrustBundleReader(t testing.TB) *MockTrustBundleReader {
|
||||||
|
mock := &MockTrustBundleReader{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/proto/pbpeering"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recommended name for registration.
|
||||||
|
const TrustBundleReadName = "peer-trust-bundle"
|
||||||
|
|
||||||
|
// TrustBundle supports fetching discovering service instances via prepared
|
||||||
|
// queries.
|
||||||
|
type TrustBundle struct {
|
||||||
|
RegisterOptionsNoRefresh
|
||||||
|
Client TrustBundleReader
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate mockery --name TrustBundleReader --inpackage --testonly
|
||||||
|
type TrustBundleReader interface {
|
||||||
|
TrustBundleRead(
|
||||||
|
ctx context.Context, in *pbpeering.TrustBundleReadRequest, opts ...grpc.CallOption,
|
||||||
|
) (*pbpeering.TrustBundleReadResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TrustBundle) Fetch(_ cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||||
|
var result cache.FetchResult
|
||||||
|
|
||||||
|
// The request should be a TrustBundleReadRequest.
|
||||||
|
// We do not need to make a copy of this request type like in other cache types
|
||||||
|
// because the RequestInfo is synthetic.
|
||||||
|
reqReal, ok := req.(*pbpeering.TrustBundleReadRequest)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf(
|
||||||
|
"Internal cache failure: request wrong type: %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
reply, err := t.Client.TrustBundleRead(context.Background(), reqReal)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Value = reply
|
||||||
|
result.Index = reply.Index
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/proto/pbpeering"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrustBundle(t *testing.T) {
|
||||||
|
client := NewMockTrustBundleReader(t)
|
||||||
|
typ := &TrustBundle{Client: client}
|
||||||
|
|
||||||
|
resp := &pbpeering.TrustBundleReadResponse{
|
||||||
|
Index: 48,
|
||||||
|
Bundle: &pbpeering.PeeringTrustBundle{
|
||||||
|
PeerName: "peer1",
|
||||||
|
RootPEMs: []string{"peer1-roots"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the proper call.
|
||||||
|
// This also returns the canned response above.
|
||||||
|
client.On("TrustBundleRead", mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*pbpeering.TrustBundleReadRequest)
|
||||||
|
require.Equal(t, "foo", req.Name)
|
||||||
|
}).
|
||||||
|
Return(resp, nil)
|
||||||
|
|
||||||
|
// Fetch and assert against the result.
|
||||||
|
result, err := typ.Fetch(cache.FetchOptions{}, &pbpeering.TrustBundleReadRequest{
|
||||||
|
Name: "foo",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, cache.FetchResult{
|
||||||
|
Value: resp,
|
||||||
|
Index: 48,
|
||||||
|
}, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustBundle_badReqType(t *testing.T) {
|
||||||
|
client := pbpeering.NewPeeringServiceClient(nil)
|
||||||
|
typ := &TrustBundle{Client: client}
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
||||||
|
t, cache.RequestInfo{Key: "foo", MinIndex: 64}))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test asserts that we can continuously poll this cache type, given that it doesn't support blocking.
|
||||||
|
func TestTrustBundle_MultipleUpdates(t *testing.T) {
|
||||||
|
c := cache.New(cache.Options{})
|
||||||
|
|
||||||
|
client := NewMockTrustBundleReader(t)
|
||||||
|
|
||||||
|
// On each mock client call to TrustBundleList by service we will increment the index by 1
|
||||||
|
// to simulate new data arriving.
|
||||||
|
resp := &pbpeering.TrustBundleReadResponse{
|
||||||
|
Index: uint64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.On("TrustBundleRead", mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*pbpeering.TrustBundleReadRequest)
|
||||||
|
require.Equal(t, "foo", req.Name)
|
||||||
|
|
||||||
|
// Increment on each call.
|
||||||
|
resp.Index++
|
||||||
|
}).
|
||||||
|
Return(resp, nil)
|
||||||
|
|
||||||
|
c.RegisterType(TrustBundleReadName, &TrustBundle{Client: client})
|
||||||
|
|
||||||
|
ch := make(chan cache.UpdateEvent)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
err := c.Notify(ctx, TrustBundleReadName, &pbpeering.TrustBundleReadRequest{Name: "foo"}, "updates", ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
i := uint64(1)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case update := <-ch:
|
||||||
|
// Expect to receive updates for increasing indexes serially.
|
||||||
|
resp := update.Result.(*pbpeering.TrustBundleReadResponse)
|
||||||
|
require.Equal(t, i, resp.Index)
|
||||||
|
i++
|
||||||
|
|
||||||
|
if i > 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/proto/pbpeering"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recommended name for registration.
|
||||||
|
const TrustBundleListName = "trust-bundles"
|
||||||
|
|
||||||
|
// TrustBundles supports fetching discovering service instances via prepared
|
||||||
|
// queries.
|
||||||
|
type TrustBundles struct {
|
||||||
|
RegisterOptionsNoRefresh
|
||||||
|
Client TrustBundleLister
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustBundleLister interface {
|
||||||
|
TrustBundleListByService(
|
||||||
|
ctx context.Context, in *pbpeering.TrustBundleListByServiceRequest, opts ...grpc.CallOption,
|
||||||
|
) (*pbpeering.TrustBundleListByServiceResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TrustBundles) Fetch(_ cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
|
||||||
|
var result cache.FetchResult
|
||||||
|
|
||||||
|
// The request should be a TrustBundleListByServiceRequest.
|
||||||
|
// We do not need to make a copy of this request type like in other cache types
|
||||||
|
// because the RequestInfo is synthetic.
|
||||||
|
reqReal, ok := req.(*pbpeering.TrustBundleListByServiceRequest)
|
||||||
|
if !ok {
|
||||||
|
return result, fmt.Errorf(
|
||||||
|
"Internal cache failure: request wrong type: %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
reply, err := t.Client.TrustBundleListByService(context.Background(), reqReal)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Value = reply
|
||||||
|
result.Index = reply.Index
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/proto/pbpeering"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrustBundles(t *testing.T) {
|
||||||
|
client := NewMockTrustBundleLister(t)
|
||||||
|
typ := &TrustBundles{Client: client}
|
||||||
|
|
||||||
|
resp := &pbpeering.TrustBundleListByServiceResponse{
|
||||||
|
Index: 48,
|
||||||
|
Bundles: []*pbpeering.PeeringTrustBundle{
|
||||||
|
{
|
||||||
|
PeerName: "peer1",
|
||||||
|
RootPEMs: []string{"peer1-roots"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect the proper call.
|
||||||
|
// This also returns the canned response above.
|
||||||
|
client.On("TrustBundleListByService", mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*pbpeering.TrustBundleListByServiceRequest)
|
||||||
|
require.Equal(t, "foo", req.ServiceName)
|
||||||
|
}).
|
||||||
|
Return(resp, nil)
|
||||||
|
|
||||||
|
// Fetch and assert against the result.
|
||||||
|
result, err := typ.Fetch(cache.FetchOptions{}, &pbpeering.TrustBundleListByServiceRequest{
|
||||||
|
ServiceName: "foo",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, cache.FetchResult{
|
||||||
|
Value: resp,
|
||||||
|
Index: 48,
|
||||||
|
}, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrustBundles_badReqType(t *testing.T) {
|
||||||
|
client := pbpeering.NewPeeringServiceClient(nil)
|
||||||
|
typ := &TrustBundles{Client: client}
|
||||||
|
|
||||||
|
// Fetch
|
||||||
|
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
|
||||||
|
t, cache.RequestInfo{Key: "foo", MinIndex: 64}))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test asserts that we can continuously poll this cache type, given that it doesn't support blocking.
|
||||||
|
func TestTrustBundles_MultipleUpdates(t *testing.T) {
|
||||||
|
c := cache.New(cache.Options{})
|
||||||
|
|
||||||
|
client := NewMockTrustBundleLister(t)
|
||||||
|
|
||||||
|
// On each mock client call to TrustBundleList by service we will increment the index by 1
|
||||||
|
// to simulate new data arriving.
|
||||||
|
resp := &pbpeering.TrustBundleListByServiceResponse{
|
||||||
|
Index: uint64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.On("TrustBundleListByService", mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
req := args.Get(1).(*pbpeering.TrustBundleListByServiceRequest)
|
||||||
|
require.Equal(t, "foo", req.ServiceName)
|
||||||
|
|
||||||
|
// Increment on each call.
|
||||||
|
resp.Index++
|
||||||
|
}).
|
||||||
|
Return(resp, nil)
|
||||||
|
|
||||||
|
c.RegisterType(TrustBundleListName, &TrustBundles{Client: client})
|
||||||
|
|
||||||
|
ch := make(chan cache.UpdateEvent)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
err := c.Notify(ctx, TrustBundleListName, &pbpeering.TrustBundleListByServiceRequest{ServiceName: "foo"}, "updates", ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
i := uint64(1)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case update := <-ch:
|
||||||
|
// Expect to receive updates for increasing indexes serially.
|
||||||
|
resp := update.Result.(*pbpeering.TrustBundleListByServiceResponse)
|
||||||
|
require.Equal(t, i, resp.Index)
|
||||||
|
i++
|
||||||
|
|
||||||
|
if i > 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTrustBundleLister is an autogenerated mock type for the TrustBundleLister type
|
||||||
|
type MockTrustBundleLister struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustBundleListByService provides a mock function with given fields: ctx, in, opts
|
||||||
|
func (_m *MockTrustBundleLister) TrustBundleListByService(ctx context.Context, in *pbpeering.TrustBundleListByServiceRequest, opts ...grpc.CallOption) (*pbpeering.TrustBundleListByServiceResponse, error) {
|
||||||
|
_va := make([]interface{}, len(opts))
|
||||||
|
for _i := range opts {
|
||||||
|
_va[_i] = opts[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, ctx, in)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
var r0 *pbpeering.TrustBundleListByServiceResponse
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *pbpeering.TrustBundleListByServiceRequest, ...grpc.CallOption) *pbpeering.TrustBundleListByServiceResponse); ok {
|
||||||
|
r0 = rf(ctx, in, opts...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*pbpeering.TrustBundleListByServiceResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, *pbpeering.TrustBundleListByServiceRequest, ...grpc.CallOption) error); ok {
|
||||||
|
r1 = rf(ctx, in, opts...)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTrustBundleLister creates a new instance of MockTrustBundleLister. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewMockTrustBundleLister(t testing.TB) *MockTrustBundleLister {
|
||||||
|
mock := &MockTrustBundleLister{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
|
@ -23,6 +23,9 @@ type UpdateEvent struct {
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback is the function type accepted by NotifyCallback.
|
||||||
|
type Callback func(ctx context.Context, event UpdateEvent)
|
||||||
|
|
||||||
// Notify registers a desire to be updated about changes to a cache result.
|
// Notify registers a desire to be updated about changes to a cache result.
|
||||||
//
|
//
|
||||||
// It is a helper that abstracts code from performing their own "blocking" query
|
// It is a helper that abstracts code from performing their own "blocking" query
|
||||||
|
@ -56,6 +59,24 @@ func (c *Cache) Notify(
|
||||||
r Request,
|
r Request,
|
||||||
correlationID string,
|
correlationID string,
|
||||||
ch chan<- UpdateEvent,
|
ch chan<- UpdateEvent,
|
||||||
|
) error {
|
||||||
|
return c.NotifyCallback(ctx, t, r, correlationID, func(ctx context.Context, event UpdateEvent) {
|
||||||
|
select {
|
||||||
|
case ch <- event:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyCallback allows you to receive notifications about changes to a cache
|
||||||
|
// result in the same way as Notify, but accepts a callback function instead of
|
||||||
|
// a channel.
|
||||||
|
func (c *Cache) NotifyCallback(
|
||||||
|
ctx context.Context,
|
||||||
|
t string,
|
||||||
|
r Request,
|
||||||
|
correlationID string,
|
||||||
|
cb Callback,
|
||||||
) error {
|
) error {
|
||||||
c.typesLock.RLock()
|
c.typesLock.RLock()
|
||||||
tEntry, ok := c.types[t]
|
tEntry, ok := c.types[t]
|
||||||
|
@ -65,7 +86,7 @@ func (c *Cache) Notify(
|
||||||
}
|
}
|
||||||
|
|
||||||
if tEntry.Opts.SupportsBlocking {
|
if tEntry.Opts.SupportsBlocking {
|
||||||
go c.notifyBlockingQuery(ctx, newGetOptions(tEntry, r), correlationID, ch)
|
go c.notifyBlockingQuery(ctx, newGetOptions(tEntry, r), correlationID, cb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +94,11 @@ func (c *Cache) Notify(
|
||||||
if info.MaxAge == 0 {
|
if info.MaxAge == 0 {
|
||||||
return fmt.Errorf("Cannot use Notify for polling cache types without specifying the MaxAge")
|
return fmt.Errorf("Cannot use Notify for polling cache types without specifying the MaxAge")
|
||||||
}
|
}
|
||||||
go c.notifyPollingQuery(ctx, newGetOptions(tEntry, r), correlationID, ch)
|
go c.notifyPollingQuery(ctx, newGetOptions(tEntry, r), correlationID, cb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) notifyBlockingQuery(ctx context.Context, r getOptions, correlationID string, ch chan<- UpdateEvent) {
|
func (c *Cache) notifyBlockingQuery(ctx context.Context, r getOptions, correlationID string, cb Callback) {
|
||||||
// Always start at 0 index to deliver the initial (possibly currently cached
|
// Always start at 0 index to deliver the initial (possibly currently cached
|
||||||
// value).
|
// value).
|
||||||
index := uint64(0)
|
index := uint64(0)
|
||||||
|
@ -101,12 +122,7 @@ func (c *Cache) notifyBlockingQuery(ctx context.Context, r getOptions, correlati
|
||||||
// Check the index of the value returned in the cache entry to be sure it
|
// Check the index of the value returned in the cache entry to be sure it
|
||||||
// changed
|
// changed
|
||||||
if index == 0 || index < meta.Index {
|
if index == 0 || index < meta.Index {
|
||||||
u := UpdateEvent{correlationID, res, meta, err}
|
cb(ctx, UpdateEvent{correlationID, res, meta, err})
|
||||||
select {
|
|
||||||
case ch <- u:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update index for next request
|
// Update index for next request
|
||||||
index = meta.Index
|
index = meta.Index
|
||||||
|
@ -143,7 +159,7 @@ func (c *Cache) notifyBlockingQuery(ctx context.Context, r getOptions, correlati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) notifyPollingQuery(ctx context.Context, r getOptions, correlationID string, ch chan<- UpdateEvent) {
|
func (c *Cache) notifyPollingQuery(ctx context.Context, r getOptions, correlationID string, cb Callback) {
|
||||||
index := uint64(0)
|
index := uint64(0)
|
||||||
failures := uint(0)
|
failures := uint(0)
|
||||||
|
|
||||||
|
@ -166,12 +182,7 @@ func (c *Cache) notifyPollingQuery(ctx context.Context, r getOptions, correlatio
|
||||||
|
|
||||||
// Check for a change in the value or an index change
|
// Check for a change in the value or an index change
|
||||||
if index < meta.Index || !reflect.DeepEqual(lastValue, res) {
|
if index < meta.Index || !reflect.DeepEqual(lastValue, res) {
|
||||||
u := UpdateEvent{correlationID, res, meta, err}
|
cb(ctx, UpdateEvent{correlationID, res, meta, err})
|
||||||
select {
|
|
||||||
case ch <- u:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update index and lastValue
|
// Update index and lastValue
|
||||||
lastValue = res
|
lastValue = res
|
||||||
|
|
|
@ -3,6 +3,7 @@ package agent
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
metrics "github.com/armon/go-metrics"
|
metrics "github.com/armon/go-metrics"
|
||||||
"github.com/armon/go-metrics/prometheus"
|
"github.com/armon/go-metrics/prometheus"
|
||||||
|
@ -356,12 +357,12 @@ func (s *HTTPHandlers) catalogServiceNodes(resp http.ResponseWriter, req *http.R
|
||||||
args.TagFilter = true
|
args.TagFilter = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull out the service name
|
if _, ok := params["merge-central-config"]; ok {
|
||||||
var err error
|
args.MergeCentralConfig = true
|
||||||
args.ServiceName, err = getPathSuffixUnescaped(req.URL.Path, pathPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pull out the service name
|
||||||
|
args.ServiceName = strings.TrimPrefix(req.URL.Path, pathPrefix)
|
||||||
if args.ServiceName == "" {
|
if args.ServiceName == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service name"}
|
||||||
}
|
}
|
||||||
|
@ -432,11 +433,7 @@ func (s *HTTPHandlers) CatalogNodeServices(resp http.ResponseWriter, req *http.R
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull out the node name
|
// Pull out the node name
|
||||||
var err error
|
args.Node = strings.TrimPrefix(req.URL.Path, "/v1/catalog/node/")
|
||||||
args.Node, err = getPathSuffixUnescaped(req.URL.Path, "/v1/catalog/node/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if args.Node == "" {
|
if args.Node == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing node name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing node name"}
|
||||||
}
|
}
|
||||||
|
@ -497,15 +494,15 @@ func (s *HTTPHandlers) CatalogNodeServiceList(resp http.ResponseWriter, req *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull out the node name
|
// Pull out the node name
|
||||||
var err error
|
args.Node = strings.TrimPrefix(req.URL.Path, "/v1/catalog/node-services/")
|
||||||
args.Node, err = getPathSuffixUnescaped(req.URL.Path, "/v1/catalog/node-services/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if args.Node == "" {
|
if args.Node == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing node name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing node name"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := req.URL.Query()["merge-central-config"]; ok {
|
||||||
|
args.MergeCentralConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
// Make the RPC request
|
// Make the RPC request
|
||||||
var out structs.IndexedNodeServiceList
|
var out structs.IndexedNodeServiceList
|
||||||
defer setMeta(resp, &out.QueryMeta)
|
defer setMeta(resp, &out.QueryMeta)
|
||||||
|
@ -548,11 +545,7 @@ func (s *HTTPHandlers) CatalogGatewayServices(resp http.ResponseWriter, req *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull out the gateway's service name
|
// Pull out the gateway's service name
|
||||||
var err error
|
args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/catalog/gateway-services/")
|
||||||
args.ServiceName, err = getPathSuffixUnescaped(req.URL.Path, "/v1/catalog/gateway-services/")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if args.ServiceName == "" {
|
if args.ServiceName == "" {
|
||||||
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing gateway name"}
|
return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing gateway name"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
|
||||||
"github.com/hashicorp/serf/coordinate"
|
"github.com/hashicorp/serf/coordinate"
|
||||||
|
@ -602,6 +603,63 @@ func TestCatalogRegister_checkRegistration(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCatalogRegister_checkRegistration_UDP(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
// Register node with a service and check
|
||||||
|
check := structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: "foo-check",
|
||||||
|
Name: "foo check",
|
||||||
|
ServiceID: "api",
|
||||||
|
Definition: structs.HealthCheckDefinition{
|
||||||
|
UDP: "localhost:8888",
|
||||||
|
Interval: 5 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Service: "api",
|
||||||
|
},
|
||||||
|
Check: &check,
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := a.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/health/checks/api", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.HealthServiceChecks(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
r.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checks := obj.(structs.HealthChecks)
|
||||||
|
if len(checks) != 1 {
|
||||||
|
r.Fatalf("expected 1 check, got: %d", len(checks))
|
||||||
|
}
|
||||||
|
if checks[0].CheckID != check.CheckID {
|
||||||
|
r.Fatalf("expected check id %s, got %s", check.Type, checks[0].Type)
|
||||||
|
}
|
||||||
|
if checks[0].Type != "udp" {
|
||||||
|
r.Fatalf("expected check type udp, got %s", checks[0].Type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCatalogServiceNodes(t *testing.T) {
|
func TestCatalogServiceNodes(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
@ -1052,6 +1110,249 @@ func TestCatalogServiceNodes_ConnectProxy(t *testing.T) {
|
||||||
assert.Equal(t, args.Service.Proxy, nodes[0].ServiceProxy)
|
assert.Equal(t, args.Service.Proxy, nodes[0].ServiceProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerService(t *testing.T, a *TestAgent) (registerServiceReq *structs.RegisterRequest) {
|
||||||
|
t.Helper()
|
||||||
|
entMeta := acl.DefaultEnterpriseMeta()
|
||||||
|
registerServiceReq = structs.TestRegisterRequestProxy(t)
|
||||||
|
registerServiceReq.EnterpriseMeta = *entMeta
|
||||||
|
registerServiceReq.Service.EnterpriseMeta = *entMeta
|
||||||
|
registerServiceReq.Service.Proxy.Upstreams = structs.TestAddDefaultsToUpstreams(t, registerServiceReq.Service.Proxy.Upstreams, *entMeta)
|
||||||
|
registerServiceReq.Check = &structs.HealthCheck{
|
||||||
|
Node: registerServiceReq.Node,
|
||||||
|
Name: "check1",
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
require.NoError(t, a.RPC("Catalog.Register", registerServiceReq, &out))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerProxyDefaults(t *testing.T, a *TestAgent) (proxyGlobalEntry structs.ProxyConfigEntry) {
|
||||||
|
t.Helper()
|
||||||
|
// Register proxy-defaults
|
||||||
|
proxyGlobalEntry = structs.ProxyConfigEntry{
|
||||||
|
Kind: structs.ProxyDefaults,
|
||||||
|
Name: structs.ProxyConfigGlobal,
|
||||||
|
Mode: structs.ProxyModeDirect,
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"local_connect_timeout_ms": uint64(1000),
|
||||||
|
"handshake_timeout_ms": uint64(1000),
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
|
||||||
|
}
|
||||||
|
proxyDefaultsConfigEntryReq := &structs.ConfigEntryRequest{
|
||||||
|
Op: structs.ConfigEntryUpsert,
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Entry: &proxyGlobalEntry,
|
||||||
|
}
|
||||||
|
var proxyDefaultsConfigEntryResp bool
|
||||||
|
require.NoError(t, a.RPC("ConfigEntry.Apply", &proxyDefaultsConfigEntryReq, &proxyDefaultsConfigEntryResp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerServiceDefaults(t *testing.T, a *TestAgent, serviceName string) (serviceDefaultsConfigEntry structs.ServiceConfigEntry) {
|
||||||
|
t.Helper()
|
||||||
|
limits := 512
|
||||||
|
serviceDefaultsConfigEntry = structs.ServiceConfigEntry{
|
||||||
|
Kind: structs.ServiceDefaults,
|
||||||
|
Name: serviceName,
|
||||||
|
Mode: structs.ProxyModeTransparent,
|
||||||
|
UpstreamConfig: &structs.UpstreamConfiguration{
|
||||||
|
Defaults: &structs.UpstreamConfig{
|
||||||
|
MeshGateway: structs.MeshGatewayConfig{
|
||||||
|
Mode: structs.MeshGatewayModeLocal,
|
||||||
|
},
|
||||||
|
Limits: &structs.UpstreamLimits{
|
||||||
|
MaxConnections: &limits,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
|
||||||
|
}
|
||||||
|
serviceDefaultsConfigEntryReq := &structs.ConfigEntryRequest{
|
||||||
|
Op: structs.ConfigEntryUpsert,
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Entry: &serviceDefaultsConfigEntry,
|
||||||
|
}
|
||||||
|
var serviceDefaultsConfigEntryResp bool
|
||||||
|
require.NoError(t, a.RPC("ConfigEntry.Apply", &serviceDefaultsConfigEntryReq, &serviceDefaultsConfigEntryResp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMergeCentralConfigResponse(t *testing.T, v *structs.ServiceNode,
|
||||||
|
registerServiceReq *structs.RegisterRequest,
|
||||||
|
proxyGlobalEntry structs.ProxyConfigEntry,
|
||||||
|
serviceDefaultsConfigEntry structs.ServiceConfigEntry) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
require.Equal(t, registerServiceReq.Service.Service, v.ServiceName)
|
||||||
|
// validate proxy global defaults are resolved in the merged service config
|
||||||
|
require.Equal(t, proxyGlobalEntry.Config, v.ServiceProxy.Config)
|
||||||
|
// validate service defaults override proxy-defaults/global
|
||||||
|
require.NotEqual(t, proxyGlobalEntry.Mode, v.ServiceProxy.Mode)
|
||||||
|
require.Equal(t, serviceDefaultsConfigEntry.Mode, v.ServiceProxy.Mode)
|
||||||
|
// validate service defaults are resolved in the merged service config
|
||||||
|
// expected number of upstreams = (number of upstreams defined in the register request proxy config +
|
||||||
|
// 1 centrally configured default from service defaults)
|
||||||
|
require.Equal(t, len(registerServiceReq.Service.Proxy.Upstreams)+1, len(v.ServiceProxy.Upstreams))
|
||||||
|
for _, up := range v.ServiceProxy.Upstreams {
|
||||||
|
if up.DestinationType != "" && up.DestinationType != structs.UpstreamDestTypeService {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
require.Contains(t, up.Config, "limits")
|
||||||
|
upstreamLimits := up.Config["limits"].(*structs.UpstreamLimits)
|
||||||
|
require.Equal(t, serviceDefaultsConfigEntry.UpstreamConfig.Defaults.Limits.MaxConnections, upstreamLimits.MaxConnections)
|
||||||
|
require.Equal(t, serviceDefaultsConfigEntry.UpstreamConfig.Defaults.MeshGateway.Mode, up.MeshGateway.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListServiceNodes_MergeCentralConfig(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register the service
|
||||||
|
registerServiceReq := registerService(t, a)
|
||||||
|
// Register proxy-defaults
|
||||||
|
proxyGlobalEntry := registerProxyDefaults(t, a)
|
||||||
|
// Register service-defaults
|
||||||
|
serviceDefaultsConfigEntry := registerServiceDefaults(t, a, registerServiceReq.Service.Proxy.DestinationServiceName)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
testCaseName string
|
||||||
|
serviceName string
|
||||||
|
connect bool
|
||||||
|
}
|
||||||
|
|
||||||
|
run := func(t *testing.T, tc testCase) {
|
||||||
|
url := fmt.Sprintf("/v1/catalog/service/%s?merge-central-config", tc.serviceName)
|
||||||
|
if tc.connect {
|
||||||
|
url = fmt.Sprintf("/v1/catalog/connect/%s?merge-central-config", tc.serviceName)
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
var obj interface{}
|
||||||
|
var err error
|
||||||
|
if tc.connect {
|
||||||
|
obj, err = a.srv.CatalogConnectServiceNodes(resp, req)
|
||||||
|
} else {
|
||||||
|
obj, err = a.srv.CatalogServiceNodes(resp, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
serviceNodes := obj.(structs.ServiceNodes)
|
||||||
|
|
||||||
|
// validate response
|
||||||
|
require.Len(t, serviceNodes, 1)
|
||||||
|
v := serviceNodes[0]
|
||||||
|
|
||||||
|
validateMergeCentralConfigResponse(t, v, registerServiceReq, proxyGlobalEntry, serviceDefaultsConfigEntry)
|
||||||
|
}
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
testCaseName: "List service instances with merge-central-config",
|
||||||
|
serviceName: registerServiceReq.Service.Service,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testCaseName: "List connect capable service instances with merge-central-config",
|
||||||
|
serviceName: registerServiceReq.Service.Proxy.DestinationServiceName,
|
||||||
|
connect: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.testCaseName, func(t *testing.T) {
|
||||||
|
run(t, tc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCatalogServiceNodes_MergeCentralConfigBlocking(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register the service
|
||||||
|
registerServiceReq := registerService(t, a)
|
||||||
|
// Register proxy-defaults
|
||||||
|
proxyGlobalEntry := registerProxyDefaults(t, a)
|
||||||
|
|
||||||
|
// Run the query
|
||||||
|
rpcReq := structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ServiceName: registerServiceReq.Service.Service,
|
||||||
|
MergeCentralConfig: true,
|
||||||
|
}
|
||||||
|
var rpcResp structs.IndexedServiceNodes
|
||||||
|
require.NoError(t, a.RPC("Catalog.ServiceNodes", &rpcReq, &rpcResp))
|
||||||
|
|
||||||
|
require.Len(t, rpcResp.ServiceNodes, 1)
|
||||||
|
serviceNode := rpcResp.ServiceNodes[0]
|
||||||
|
require.Equal(t, registerServiceReq.Service.Service, serviceNode.ServiceName)
|
||||||
|
// validate proxy global defaults are resolved in the merged service config
|
||||||
|
require.Equal(t, proxyGlobalEntry.Config, serviceNode.ServiceProxy.Config)
|
||||||
|
require.Equal(t, proxyGlobalEntry.Mode, serviceNode.ServiceProxy.Mode)
|
||||||
|
|
||||||
|
// Async cause a change - register service defaults
|
||||||
|
waitIndex := rpcResp.Index
|
||||||
|
start := time.Now()
|
||||||
|
var serviceDefaultsConfigEntry structs.ServiceConfigEntry
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
// Register service-defaults
|
||||||
|
serviceDefaultsConfigEntry = registerServiceDefaults(t, a, registerServiceReq.Service.Proxy.DestinationServiceName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
const waitDuration = 3 * time.Second
|
||||||
|
RUN_BLOCKING_QUERY:
|
||||||
|
url := fmt.Sprintf("/v1/catalog/service/%s?merge-central-config&wait=%s&index=%d",
|
||||||
|
registerServiceReq.Service.Service, waitDuration.String(), waitIndex)
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.CatalogServiceNodes(resp, req)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
idx := getIndex(t, resp)
|
||||||
|
if idx < waitIndex {
|
||||||
|
t.Fatalf("bad index returned: %v", idx)
|
||||||
|
} else if idx == waitIndex {
|
||||||
|
if elapsed > waitDuration {
|
||||||
|
// This should prevent the loop from running longer than the waitDuration
|
||||||
|
t.Fatalf("too slow: %v", elapsed)
|
||||||
|
}
|
||||||
|
goto RUN_BLOCKING_QUERY
|
||||||
|
}
|
||||||
|
// Should block at least 100ms before getting the changed results
|
||||||
|
if elapsed < 100*time.Millisecond {
|
||||||
|
t.Fatalf("too fast: %v", elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceNodes := obj.(structs.ServiceNodes)
|
||||||
|
|
||||||
|
// validate response
|
||||||
|
require.Len(t, serviceNodes, 1)
|
||||||
|
v := serviceNodes[0]
|
||||||
|
|
||||||
|
validateMergeCentralConfigResponse(t, v, registerServiceReq, proxyGlobalEntry, serviceDefaultsConfigEntry)
|
||||||
|
}
|
||||||
|
|
||||||
// Test that the Connect-compatible endpoints can be queried for a
|
// Test that the Connect-compatible endpoints can be queried for a
|
||||||
// service via /v1/catalog/connect/:service.
|
// service via /v1/catalog/connect/:service.
|
||||||
func TestCatalogConnectServiceNodes_good(t *testing.T) {
|
func TestCatalogConnectServiceNodes_good(t *testing.T) {
|
||||||
|
@ -1228,6 +1529,111 @@ func TestCatalogNodeServiceList(t *testing.T) {
|
||||||
require.Equal(t, args.Service.Proxy, proxySvc.Proxy)
|
require.Equal(t, args.Service.Proxy, proxySvc.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCatalogNodeServiceList_MergeCentralConfig(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register the service
|
||||||
|
registerServiceReq := registerService(t, a)
|
||||||
|
// Register proxy-defaults
|
||||||
|
proxyGlobalEntry := registerProxyDefaults(t, a)
|
||||||
|
// Register service-defaults
|
||||||
|
serviceDefaultsConfigEntry := registerServiceDefaults(t, a, registerServiceReq.Service.Proxy.DestinationServiceName)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("/v1/catalog/node-services/%s?merge-central-config", registerServiceReq.Node)
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.CatalogNodeServiceList(resp, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
nodeServices := obj.(*structs.NodeServiceList)
|
||||||
|
// validate response
|
||||||
|
require.Len(t, nodeServices.Services, 1)
|
||||||
|
validateMergeCentralConfigResponse(t, nodeServices.Services[0].ToServiceNode(nodeServices.Node.Node), registerServiceReq, proxyGlobalEntry, serviceDefaultsConfigEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCatalogNodeServiceList_MergeCentralConfigBlocking(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
|
// Register the service
|
||||||
|
registerServiceReq := registerService(t, a)
|
||||||
|
// Register proxy-defaults
|
||||||
|
proxyGlobalEntry := registerProxyDefaults(t, a)
|
||||||
|
|
||||||
|
// Run the query
|
||||||
|
rpcReq := structs.NodeSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: registerServiceReq.Node,
|
||||||
|
MergeCentralConfig: true,
|
||||||
|
}
|
||||||
|
var rpcResp structs.IndexedNodeServiceList
|
||||||
|
require.NoError(t, a.RPC("Catalog.NodeServiceList", &rpcReq, &rpcResp))
|
||||||
|
require.Len(t, rpcResp.NodeServices.Services, 1)
|
||||||
|
nodeService := rpcResp.NodeServices.Services[0]
|
||||||
|
require.Equal(t, registerServiceReq.Service.Service, nodeService.Service)
|
||||||
|
// validate proxy global defaults are resolved in the merged service config
|
||||||
|
require.Equal(t, proxyGlobalEntry.Config, nodeService.Proxy.Config)
|
||||||
|
require.Equal(t, proxyGlobalEntry.Mode, nodeService.Proxy.Mode)
|
||||||
|
|
||||||
|
// Async cause a change - register service defaults
|
||||||
|
waitIndex := rpcResp.Index
|
||||||
|
start := time.Now()
|
||||||
|
var serviceDefaultsConfigEntry structs.ServiceConfigEntry
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
// Register service-defaults
|
||||||
|
serviceDefaultsConfigEntry = registerServiceDefaults(t, a, registerServiceReq.Service.Proxy.DestinationServiceName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
const waitDuration = 3 * time.Second
|
||||||
|
RUN_BLOCKING_QUERY:
|
||||||
|
|
||||||
|
url := fmt.Sprintf("/v1/catalog/node-services/%s?merge-central-config&wait=%s&index=%d",
|
||||||
|
registerServiceReq.Node, waitDuration.String(), waitIndex)
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.CatalogNodeServiceList(resp, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
idx := getIndex(t, resp)
|
||||||
|
if idx < waitIndex {
|
||||||
|
t.Fatalf("bad index returned: %v", idx)
|
||||||
|
} else if idx == waitIndex {
|
||||||
|
if elapsed > waitDuration {
|
||||||
|
// This should prevent the loop from running longer than the waitDuration
|
||||||
|
t.Fatalf("too slow: %v", elapsed)
|
||||||
|
}
|
||||||
|
goto RUN_BLOCKING_QUERY
|
||||||
|
}
|
||||||
|
// Should block at least 100ms before getting the changed results
|
||||||
|
if elapsed < 100*time.Millisecond {
|
||||||
|
t.Fatalf("too fast: %v", elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeServices := obj.(*structs.NodeServiceList)
|
||||||
|
// validate response
|
||||||
|
require.Len(t, nodeServices.Services, 1)
|
||||||
|
validateMergeCentralConfigResponse(t, nodeServices.Services[0].ToServiceNode(nodeServices.Node.Node), registerServiceReq, proxyGlobalEntry, serviceDefaultsConfigEntry)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCatalogNodeServices_Filter(t *testing.T) {
|
func TestCatalogNodeServices_Filter(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package checks
|
package checks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -703,6 +704,135 @@ func (c *CheckTCP) check() {
|
||||||
c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("TCP connect %s: Success", c.TCP))
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("TCP connect %s: Success", c.TCP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckUDP is used to periodically send a UDP datagram to determine the health of a given check.
|
||||||
|
// The check is passing if the connection succeeds, the response is bytes.Equal to the bytes passed
|
||||||
|
// in or if the error returned is a timeout error
|
||||||
|
// The check is critical if: the connection succeeds but the response is not equal to the bytes passed in,
|
||||||
|
// the connection succeeds but the error returned is not a timeout error or the connection fails
|
||||||
|
type CheckUDP struct {
|
||||||
|
CheckID structs.CheckID
|
||||||
|
ServiceID structs.ServiceID
|
||||||
|
UDP string
|
||||||
|
Message string
|
||||||
|
Interval time.Duration
|
||||||
|
Timeout time.Duration
|
||||||
|
Logger hclog.Logger
|
||||||
|
StatusHandler *StatusHandler
|
||||||
|
|
||||||
|
dialer *net.Dialer
|
||||||
|
stop bool
|
||||||
|
stopCh chan struct{}
|
||||||
|
stopLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckUDP) Start() {
|
||||||
|
c.stopLock.Lock()
|
||||||
|
defer c.stopLock.Unlock()
|
||||||
|
|
||||||
|
if c.dialer == nil {
|
||||||
|
// Create the socket dialer
|
||||||
|
c.dialer = &net.Dialer{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
if c.Timeout > 0 {
|
||||||
|
c.dialer.Timeout = c.Timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.stop = false
|
||||||
|
c.stopCh = make(chan struct{})
|
||||||
|
go c.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckUDP) Stop() {
|
||||||
|
c.stopLock.Lock()
|
||||||
|
defer c.stopLock.Unlock()
|
||||||
|
if !c.stop {
|
||||||
|
c.stop = true
|
||||||
|
close(c.stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckUDP) run() {
|
||||||
|
// Get the randomized initial pause time
|
||||||
|
initialPauseTime := lib.RandomStagger(c.Interval)
|
||||||
|
next := time.After(initialPauseTime)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-next:
|
||||||
|
c.check()
|
||||||
|
next = time.After(c.Interval)
|
||||||
|
case <-c.stopCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckUDP) check() {
|
||||||
|
|
||||||
|
conn, err := c.dialer.Dial(`udp`, c.UDP)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(net.Error); ok && e.Timeout() {
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.Logger.Warn("Check socket connection failed",
|
||||||
|
"check", c.CheckID.String(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
n, err := fmt.Fprintf(conn, c.Message)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Warn("Check socket write failed",
|
||||||
|
"check", c.CheckID.String(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(c.Message) {
|
||||||
|
c.Logger.Warn("Check socket short write",
|
||||||
|
"check", c.CheckID.String(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.Logger.Warn("Check socket write failed",
|
||||||
|
"check", c.CheckID.String(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = bufio.NewReader(conn).Read(make([]byte, 1))
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "i/o timeout") {
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.Logger.Warn("Check socket read failed",
|
||||||
|
"check", c.CheckID.String(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if err == nil {
|
||||||
|
c.StatusHandler.updateCheck(c.CheckID, api.HealthPassing, fmt.Sprintf("UDP connect %s: Success", c.UDP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CheckDocker is used to periodically invoke a script to
|
// CheckDocker is used to periodically invoke a script to
|
||||||
// determine the health of an application running inside a
|
// determine the health of an application running inside a
|
||||||
// Docker Container. We assume that the script is compatible
|
// Docker Container. We assume that the script is compatible
|
||||||
|
|
|
@ -2,20 +2,25 @@ package checks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/mock"
|
"github.com/hashicorp/consul/agent/mock"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/sdk/freeport"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
|
@ -1141,6 +1146,152 @@ func TestCheckTCPPassing(t *testing.T) {
|
||||||
tcpServer.Close()
|
tcpServer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendResponse(conn *net.UDPConn, addr *net.UDPAddr) {
|
||||||
|
_, err := conn.WriteToUDP([]byte("healthy"), addr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Couldn't send response %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockUDPServer(ctx context.Context, network string, port int) {
|
||||||
|
|
||||||
|
b := make([]byte, 1024)
|
||||||
|
addr := fmt.Sprintf(`127.0.0.1:%d`, port)
|
||||||
|
|
||||||
|
udpAddr, err := net.ResolveUDPAddr(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error resolving UDP address: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ser, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error listening UDP: ", err)
|
||||||
|
}
|
||||||
|
defer ser.Close()
|
||||||
|
|
||||||
|
chClose := make(chan interface{})
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
log.Print("Waiting for UDP message")
|
||||||
|
_, remoteaddr, err := ser.ReadFromUDP(b)
|
||||||
|
log.Printf("Read a message from %v %s \n", remoteaddr, b)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error reading from UDP %s", err.Error())
|
||||||
|
}
|
||||||
|
sendResponse(ser, remoteaddr)
|
||||||
|
select {
|
||||||
|
case <-chClose:
|
||||||
|
fmt.Println("cancelled")
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("cancelled")
|
||||||
|
close(chClose)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectUDPStatus(t *testing.T, udp string, status string) {
|
||||||
|
notif := mock.NewNotify()
|
||||||
|
logger := testutil.Logger(t)
|
||||||
|
statusHandler := NewStatusHandler(notif, logger, 0, 0, 0)
|
||||||
|
cid := structs.NewCheckID("foo", nil)
|
||||||
|
|
||||||
|
check := &CheckUDP{
|
||||||
|
CheckID: cid,
|
||||||
|
UDP: udp,
|
||||||
|
Interval: 10 * time.Millisecond,
|
||||||
|
Logger: logger,
|
||||||
|
StatusHandler: statusHandler,
|
||||||
|
}
|
||||||
|
check.Start()
|
||||||
|
defer check.Stop()
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
if got, want := notif.Updates(cid), 2; got < want {
|
||||||
|
r.Fatalf("got %d updates want at least %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := notif.State(cid), status; got != want {
|
||||||
|
r.Fatalf("got state %q want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectUDPTimeout(t *testing.T, udp string, status string) {
|
||||||
|
notif := mock.NewNotify()
|
||||||
|
logger := testutil.Logger(t)
|
||||||
|
statusHandler := NewStatusHandler(notif, logger, 0, 0, 0)
|
||||||
|
cid := structs.NewCheckID("foo", nil)
|
||||||
|
|
||||||
|
check := &CheckUDP{
|
||||||
|
CheckID: cid,
|
||||||
|
UDP: udp,
|
||||||
|
Interval: 10 * time.Millisecond,
|
||||||
|
Timeout: 5 * time.Nanosecond,
|
||||||
|
Logger: logger,
|
||||||
|
StatusHandler: statusHandler,
|
||||||
|
}
|
||||||
|
check.Start()
|
||||||
|
defer check.Stop()
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
if got, want := notif.Updates(cid), 2; got < want {
|
||||||
|
r.Fatalf("got %d updates want at least %d", got, want)
|
||||||
|
}
|
||||||
|
if got, want := notif.State(cid), status; got != want {
|
||||||
|
r.Fatalf("got state %q want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckUDPTimeoutPassing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
port := freeport.GetOne(t)
|
||||||
|
serverUrl := "127.0.0.1:" + strconv.Itoa(port)
|
||||||
|
|
||||||
|
go mockUDPServer(ctx, `udp`, port)
|
||||||
|
expectUDPTimeout(t, serverUrl, api.HealthPassing) // Should pass since timeout is handled as success, from specification
|
||||||
|
}
|
||||||
|
func TestCheckUDPCritical(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
port := freeport.GetOne(t)
|
||||||
|
notExistentPort := freeport.GetOne(t)
|
||||||
|
serverUrl := "127.0.0.1:" + strconv.Itoa(notExistentPort)
|
||||||
|
|
||||||
|
go mockUDPServer(ctx, `udp`, port)
|
||||||
|
|
||||||
|
expectUDPStatus(t, serverUrl, api.HealthCritical) // Should be unhealthy since we never connect to mocked udp server.
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckUDPPassing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
port := freeport.GetOne(t)
|
||||||
|
serverUrl := "127.0.0.1:" + strconv.Itoa(port)
|
||||||
|
|
||||||
|
go mockUDPServer(ctx, `udp`, port)
|
||||||
|
expectUDPStatus(t, serverUrl, api.HealthPassing)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckH2PING(t *testing.T) {
|
func TestCheckH2PING(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -804,6 +804,8 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
Version: stringVal(c.Version),
|
Version: stringVal(c.Version),
|
||||||
VersionPrerelease: stringVal(c.VersionPrerelease),
|
VersionPrerelease: stringVal(c.VersionPrerelease),
|
||||||
VersionMetadata: stringVal(c.VersionMetadata),
|
VersionMetadata: stringVal(c.VersionMetadata),
|
||||||
|
// What is a sensible default for BuildDate?
|
||||||
|
BuildDate: timeValWithDefault(c.BuildDate, time.Date(1970, 1, 00, 00, 00, 01, 0, time.UTC)),
|
||||||
|
|
||||||
// consul configuration
|
// consul configuration
|
||||||
ConsulCoordinateUpdateBatchSize: intVal(c.Consul.Coordinate.UpdateBatchSize),
|
ConsulCoordinateUpdateBatchSize: intVal(c.Consul.Coordinate.UpdateBatchSize),
|
||||||
|
@ -917,6 +919,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
DisableHostname: boolVal(c.Telemetry.DisableHostname),
|
DisableHostname: boolVal(c.Telemetry.DisableHostname),
|
||||||
DogstatsdAddr: stringVal(c.Telemetry.DogstatsdAddr),
|
DogstatsdAddr: stringVal(c.Telemetry.DogstatsdAddr),
|
||||||
DogstatsdTags: c.Telemetry.DogstatsdTags,
|
DogstatsdTags: c.Telemetry.DogstatsdTags,
|
||||||
|
RetryFailedConfiguration: boolVal(c.Telemetry.RetryFailedConfiguration),
|
||||||
FilterDefault: boolVal(c.Telemetry.FilterDefault),
|
FilterDefault: boolVal(c.Telemetry.FilterDefault),
|
||||||
AllowedPrefixes: telemetryAllowedPrefixes,
|
AllowedPrefixes: telemetryAllowedPrefixes,
|
||||||
BlockedPrefixes: telemetryBlockedPrefixes,
|
BlockedPrefixes: telemetryBlockedPrefixes,
|
||||||
|
@ -1945,6 +1948,13 @@ func stringVal(v *string) string {
|
||||||
return *v
|
return *v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timeValWithDefault(v *time.Time, defaultVal time.Time) time.Time {
|
||||||
|
if v == nil {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|
||||||
func float64ValWithDefault(v *float64, defaultVal float64) float64 {
|
func float64ValWithDefault(v *float64, defaultVal float64) float64 {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return defaultVal
|
return defaultVal
|
||||||
|
@ -2522,7 +2532,7 @@ func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error
|
||||||
|
|
||||||
// TLS is only enabled on the gRPC listener if there's an HTTPS port configured
|
// TLS is only enabled on the gRPC listener if there's an HTTPS port configured
|
||||||
// for historic and backwards-compatibility reasons.
|
// for historic and backwards-compatibility reasons.
|
||||||
if rt.HTTPSPort <= 0 && (t.GRPC != TLSProtocolConfig{}) {
|
if rt.HTTPSPort <= 0 && (t.GRPC != TLSProtocolConfig{} && t.GRPCModifiedByDeprecatedConfig == nil) {
|
||||||
b.warn("tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)")
|
b.warn("tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
|
|
||||||
|
@ -261,18 +262,19 @@ type Config struct {
|
||||||
SnapshotAgent map[string]interface{} `mapstructure:"snapshot_agent"`
|
SnapshotAgent map[string]interface{} `mapstructure:"snapshot_agent"`
|
||||||
|
|
||||||
// non-user configurable values
|
// non-user configurable values
|
||||||
AEInterval *string `mapstructure:"ae_interval"`
|
AEInterval *string `mapstructure:"ae_interval"`
|
||||||
CheckDeregisterIntervalMin *string `mapstructure:"check_deregister_interval_min"`
|
CheckDeregisterIntervalMin *string `mapstructure:"check_deregister_interval_min"`
|
||||||
CheckReapInterval *string `mapstructure:"check_reap_interval"`
|
CheckReapInterval *string `mapstructure:"check_reap_interval"`
|
||||||
Consul Consul `mapstructure:"consul"`
|
Consul Consul `mapstructure:"consul"`
|
||||||
Revision *string `mapstructure:"revision"`
|
Revision *string `mapstructure:"revision"`
|
||||||
SegmentLimit *int `mapstructure:"segment_limit"`
|
SegmentLimit *int `mapstructure:"segment_limit"`
|
||||||
SegmentNameLimit *int `mapstructure:"segment_name_limit"`
|
SegmentNameLimit *int `mapstructure:"segment_name_limit"`
|
||||||
SyncCoordinateIntervalMin *string `mapstructure:"sync_coordinate_interval_min"`
|
SyncCoordinateIntervalMin *string `mapstructure:"sync_coordinate_interval_min"`
|
||||||
SyncCoordinateRateTarget *float64 `mapstructure:"sync_coordinate_rate_target"`
|
SyncCoordinateRateTarget *float64 `mapstructure:"sync_coordinate_rate_target"`
|
||||||
Version *string `mapstructure:"version"`
|
Version *string `mapstructure:"version"`
|
||||||
VersionPrerelease *string `mapstructure:"version_prerelease"`
|
VersionPrerelease *string `mapstructure:"version_prerelease"`
|
||||||
VersionMetadata *string `mapstructure:"version_metadata"`
|
VersionMetadata *string `mapstructure:"version_metadata"`
|
||||||
|
BuildDate *time.Time `mapstructure:"build_date"`
|
||||||
|
|
||||||
// Enterprise Only
|
// Enterprise Only
|
||||||
Audit Audit `mapstructure:"audit"`
|
Audit Audit `mapstructure:"audit"`
|
||||||
|
@ -403,6 +405,7 @@ type CheckDefinition struct {
|
||||||
DisableRedirects *bool `mapstructure:"disable_redirects"`
|
DisableRedirects *bool `mapstructure:"disable_redirects"`
|
||||||
OutputMaxSize *int `mapstructure:"output_max_size"`
|
OutputMaxSize *int `mapstructure:"output_max_size"`
|
||||||
TCP *string `mapstructure:"tcp"`
|
TCP *string `mapstructure:"tcp"`
|
||||||
|
UDP *string `mapstructure:"udp"`
|
||||||
Interval *string `mapstructure:"interval"`
|
Interval *string `mapstructure:"interval"`
|
||||||
DockerContainerID *string `mapstructure:"docker_container_id" alias:"dockercontainerid"`
|
DockerContainerID *string `mapstructure:"docker_container_id" alias:"dockercontainerid"`
|
||||||
Shell *string `mapstructure:"shell"`
|
Shell *string `mapstructure:"shell"`
|
||||||
|
@ -674,6 +677,7 @@ type Telemetry struct {
|
||||||
DisableHostname *bool `mapstructure:"disable_hostname"`
|
DisableHostname *bool `mapstructure:"disable_hostname"`
|
||||||
DogstatsdAddr *string `mapstructure:"dogstatsd_addr"`
|
DogstatsdAddr *string `mapstructure:"dogstatsd_addr"`
|
||||||
DogstatsdTags []string `mapstructure:"dogstatsd_tags"`
|
DogstatsdTags []string `mapstructure:"dogstatsd_tags"`
|
||||||
|
RetryFailedConfiguration *bool `mapstructure:"retry_failed_connection"`
|
||||||
FilterDefault *bool `mapstructure:"filter_default"`
|
FilterDefault *bool `mapstructure:"filter_default"`
|
||||||
PrefixFilter []string `mapstructure:"prefix_filter"`
|
PrefixFilter []string `mapstructure:"prefix_filter"`
|
||||||
MetricsPrefix *string `mapstructure:"metrics_prefix"`
|
MetricsPrefix *string `mapstructure:"metrics_prefix"`
|
||||||
|
@ -870,4 +874,17 @@ type TLS struct {
|
||||||
InternalRPC TLSProtocolConfig `mapstructure:"internal_rpc"`
|
InternalRPC TLSProtocolConfig `mapstructure:"internal_rpc"`
|
||||||
HTTPS TLSProtocolConfig `mapstructure:"https"`
|
HTTPS TLSProtocolConfig `mapstructure:"https"`
|
||||||
GRPC TLSProtocolConfig `mapstructure:"grpc"`
|
GRPC TLSProtocolConfig `mapstructure:"grpc"`
|
||||||
|
|
||||||
|
// GRPCModifiedByDeprecatedConfig is a flag used to indicate that GRPC was
|
||||||
|
// modified by the deprecated field mapping (as apposed to a user-provided
|
||||||
|
// a grpc stanza). This prevents us from emitting a warning about an
|
||||||
|
// ineffectual grpc stanza when we modify GRPC to honor the legacy behaviour
|
||||||
|
// that setting `verify_incoming = true` at the top-level *does not* enable
|
||||||
|
// client certificate verification on the gRPC port.
|
||||||
|
//
|
||||||
|
// See: applyDeprecatedTLSConfig.
|
||||||
|
//
|
||||||
|
// Note: we use a *struct{} here because a simple bool isn't supported by our
|
||||||
|
// config merging logic.
|
||||||
|
GRPCModifiedByDeprecatedConfig *struct{} `mapstructure:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
|
|
||||||
|
@ -128,6 +129,7 @@ func DefaultSource() Source {
|
||||||
metrics_prefix = "consul"
|
metrics_prefix = "consul"
|
||||||
filter_default = true
|
filter_default = true
|
||||||
prefix_filter = []
|
prefix_filter = []
|
||||||
|
retry_failed_connection = true
|
||||||
}
|
}
|
||||||
raft_snapshot_threshold = ` + strconv.Itoa(int(cfg.RaftConfig.SnapshotThreshold)) + `
|
raft_snapshot_threshold = ` + strconv.Itoa(int(cfg.RaftConfig.SnapshotThreshold)) + `
|
||||||
raft_snapshot_interval = "` + cfg.RaftConfig.SnapshotInterval.String() + `"
|
raft_snapshot_interval = "` + cfg.RaftConfig.SnapshotInterval.String() + `"
|
||||||
|
@ -209,7 +211,7 @@ func NonUserSource() Source {
|
||||||
// versionSource creates a config source for the version parameters.
|
// versionSource creates a config source for the version parameters.
|
||||||
// This should be merged in the tail since these values are not
|
// This should be merged in the tail since these values are not
|
||||||
// user configurable.
|
// user configurable.
|
||||||
func versionSource(rev, ver, verPre, meta string) Source {
|
func versionSource(rev, ver, verPre, meta string, buildDate time.Time) Source {
|
||||||
return LiteralSource{
|
return LiteralSource{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
Config: Config{
|
Config: Config{
|
||||||
|
@ -217,6 +219,7 @@ func versionSource(rev, ver, verPre, meta string) Source {
|
||||||
Version: &ver,
|
Version: &ver,
|
||||||
VersionPrerelease: &verPre,
|
VersionPrerelease: &verPre,
|
||||||
VersionMetadata: &meta,
|
VersionMetadata: &meta,
|
||||||
|
BuildDate: &buildDate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +227,8 @@ func versionSource(rev, ver, verPre, meta string) Source {
|
||||||
// defaultVersionSource returns the version config source for the embedded
|
// defaultVersionSource returns the version config source for the embedded
|
||||||
// version numbers.
|
// version numbers.
|
||||||
func defaultVersionSource() Source {
|
func defaultVersionSource() Source {
|
||||||
return versionSource(version.GitCommit, version.Version, version.VersionPrerelease, version.VersionMetadata)
|
buildDate, _ := time.Parse(time.RFC3339, version.BuildDate) // This has been checked elsewhere
|
||||||
|
return versionSource(version.GitCommit, version.Version, version.VersionPrerelease, version.VersionMetadata, buildDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConsulSource returns the default configuration for the consul agent.
|
// DefaultConsulSource returns the default configuration for the consul agent.
|
||||||
|
|
|
@ -180,9 +180,11 @@ func applyDeprecatedConfig(d *decodeTarget) (Config, []string) {
|
||||||
func applyDeprecatedTLSConfig(dep DeprecatedConfig, cfg *Config) []string {
|
func applyDeprecatedTLSConfig(dep DeprecatedConfig, cfg *Config) []string {
|
||||||
var warns []string
|
var warns []string
|
||||||
|
|
||||||
defaults := &cfg.TLS.Defaults
|
tls := &cfg.TLS
|
||||||
internalRPC := &cfg.TLS.InternalRPC
|
defaults := &tls.Defaults
|
||||||
https := &cfg.TLS.HTTPS
|
internalRPC := &tls.InternalRPC
|
||||||
|
https := &tls.HTTPS
|
||||||
|
grpc := &tls.GRPC
|
||||||
|
|
||||||
if v := dep.CAFile; v != nil {
|
if v := dep.CAFile; v != nil {
|
||||||
if defaults.CAFile == nil {
|
if defaults.CAFile == nil {
|
||||||
|
@ -239,6 +241,16 @@ func applyDeprecatedTLSConfig(dep DeprecatedConfig, cfg *Config) []string {
|
||||||
if defaults.VerifyIncoming == nil {
|
if defaults.VerifyIncoming == nil {
|
||||||
defaults.VerifyIncoming = v
|
defaults.VerifyIncoming = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prior to Consul 1.12 it was not possible to enable client certificate
|
||||||
|
// verification on the gRPC port. We must override GRPC.VerifyIncoming to
|
||||||
|
// prevent it from inheriting Defaults.VerifyIncoming when we've mapped the
|
||||||
|
// deprecated top-level verify_incoming field.
|
||||||
|
if grpc.VerifyIncoming == nil {
|
||||||
|
grpc.VerifyIncoming = pBool(false)
|
||||||
|
tls.GRPCModifiedByDeprecatedConfig = &struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
warns = append(warns, deprecationWarning("verify_incoming", "tls.defaults.verify_incoming"))
|
warns = append(warns, deprecationWarning("verify_incoming", "tls.defaults.verify_incoming"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ tls_prefer_server_cipher_suites = true
|
||||||
|
|
||||||
require.False(t, rt.TLS.InternalRPC.VerifyIncoming)
|
require.False(t, rt.TLS.InternalRPC.VerifyIncoming)
|
||||||
require.False(t, rt.TLS.HTTPS.VerifyIncoming)
|
require.False(t, rt.TLS.HTTPS.VerifyIncoming)
|
||||||
require.True(t, rt.TLS.GRPC.VerifyIncoming)
|
require.False(t, rt.TLS.GRPC.VerifyIncoming)
|
||||||
require.True(t, rt.TLS.InternalRPC.VerifyOutgoing)
|
require.True(t, rt.TLS.InternalRPC.VerifyOutgoing)
|
||||||
require.True(t, rt.TLS.HTTPS.VerifyOutgoing)
|
require.True(t, rt.TLS.HTTPS.VerifyOutgoing)
|
||||||
require.True(t, rt.TLS.InternalRPC.VerifyServerHostname)
|
require.True(t, rt.TLS.InternalRPC.VerifyServerHostname)
|
||||||
|
|
|
@ -62,6 +62,7 @@ type RuntimeConfig struct {
|
||||||
Version string
|
Version string
|
||||||
VersionPrerelease string
|
VersionPrerelease string
|
||||||
VersionMetadata string
|
VersionMetadata string
|
||||||
|
BuildDate time.Time
|
||||||
|
|
||||||
// consul config
|
// consul config
|
||||||
ConsulCoordinateUpdateMaxBatches int
|
ConsulCoordinateUpdateMaxBatches int
|
||||||
|
@ -1700,6 +1701,10 @@ func sanitize(name string, v reflect.Value) reflect.Value {
|
||||||
x := v.Interface().(time.Duration)
|
x := v.Interface().(time.Duration)
|
||||||
return reflect.ValueOf(x.String())
|
return reflect.ValueOf(x.String())
|
||||||
|
|
||||||
|
case isTime(typ):
|
||||||
|
x := v.Interface().(time.Time)
|
||||||
|
return reflect.ValueOf(x.String())
|
||||||
|
|
||||||
case isString(typ):
|
case isString(typ):
|
||||||
if strings.HasPrefix(name, "RetryJoinLAN[") || strings.HasPrefix(name, "RetryJoinWAN[") {
|
if strings.HasPrefix(name, "RetryJoinLAN[") || strings.HasPrefix(name, "RetryJoinWAN[") {
|
||||||
x := v.Interface().(string)
|
x := v.Interface().(string)
|
||||||
|
@ -1771,6 +1776,7 @@ func sanitize(name string, v reflect.Value) reflect.Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
|
func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
|
||||||
|
func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) }
|
||||||
func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }
|
func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }
|
||||||
func isNetAddr(t reflect.Type) bool { return t.Implements(reflect.TypeOf((*net.Addr)(nil)).Elem()) }
|
func isNetAddr(t reflect.Type) bool { return t.Implements(reflect.TypeOf((*net.Addr)(nil)).Elem()) }
|
||||||
func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr }
|
func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr }
|
||||||
|
|
|
@ -5661,6 +5661,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
Version: "R909Hblt",
|
Version: "R909Hblt",
|
||||||
VersionPrerelease: "ZT1JOQLn",
|
VersionPrerelease: "ZT1JOQLn",
|
||||||
VersionMetadata: "GtTCa13",
|
VersionMetadata: "GtTCa13",
|
||||||
|
BuildDate: time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC),
|
||||||
|
|
||||||
// consul configuration
|
// consul configuration
|
||||||
ConsulCoordinateUpdateBatchSize: 128,
|
ConsulCoordinateUpdateBatchSize: 128,
|
||||||
|
@ -6306,6 +6307,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
DisableHostname: true,
|
DisableHostname: true,
|
||||||
DogstatsdAddr: "0wSndumK",
|
DogstatsdAddr: "0wSndumK",
|
||||||
DogstatsdTags: []string{"3N81zSUB", "Xtj8AnXZ"},
|
DogstatsdTags: []string{"3N81zSUB", "Xtj8AnXZ"},
|
||||||
|
RetryFailedConfiguration: true,
|
||||||
FilterDefault: true,
|
FilterDefault: true,
|
||||||
AllowedPrefixes: []string{"oJotS8XJ"},
|
AllowedPrefixes: []string{"oJotS8XJ"},
|
||||||
BlockedPrefixes: []string{"cazlEhGn", "ftO6DySn.rpc.server.call"},
|
BlockedPrefixes: []string{"cazlEhGn", "ftO6DySn.rpc.server.call"},
|
||||||
|
@ -6446,7 +6448,8 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
ConfigFiles: []string{"testdata/full-config." + format},
|
ConfigFiles: []string{"testdata/full-config." + format},
|
||||||
HCL: []string{fmt.Sprintf(`data_dir = "%s"`, dataDir)},
|
HCL: []string{fmt.Sprintf(`data_dir = "%s"`, dataDir)},
|
||||||
}
|
}
|
||||||
opts.Overrides = append(opts.Overrides, versionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn", "GtTCa13"))
|
opts.Overrides = append(opts.Overrides, versionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn", "GtTCa13",
|
||||||
|
time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC)))
|
||||||
r, err := Load(opts)
|
r, err := Load(opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
prototest.AssertDeepEqual(t, expected, r.RuntimeConfig)
|
prototest.AssertDeepEqual(t, expected, r.RuntimeConfig)
|
||||||
|
@ -6640,6 +6643,7 @@ func parseCIDR(t *testing.T, cidr string) *net.IPNet {
|
||||||
func TestRuntimeConfig_Sanitize(t *testing.T) {
|
func TestRuntimeConfig_Sanitize(t *testing.T) {
|
||||||
rt := RuntimeConfig{
|
rt := RuntimeConfig{
|
||||||
BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
|
BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
|
||||||
|
BuildDate: time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC),
|
||||||
CheckOutputMaxSize: checks.DefaultBufSize,
|
CheckOutputMaxSize: checks.DefaultBufSize,
|
||||||
SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
|
||||||
DNSAddrs: []net.Addr{
|
DNSAddrs: []net.Addr{
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
"BindAddr": "127.0.0.1",
|
"BindAddr": "127.0.0.1",
|
||||||
"Bootstrap": false,
|
"Bootstrap": false,
|
||||||
"BootstrapExpect": 0,
|
"BootstrapExpect": 0,
|
||||||
|
"BuildDate": "2019-11-20 05:00:00 +0000 UTC",
|
||||||
"Cache": {
|
"Cache": {
|
||||||
"EntryFetchMaxBurst": 42,
|
"EntryFetchMaxBurst": 42,
|
||||||
"EntryFetchRate": 0.334,
|
"EntryFetchRate": 0.334,
|
||||||
|
@ -118,7 +119,8 @@
|
||||||
"TLSSkipVerify": false,
|
"TLSSkipVerify": false,
|
||||||
"TTL": "0s",
|
"TTL": "0s",
|
||||||
"Timeout": "0s",
|
"Timeout": "0s",
|
||||||
"Token": "hidden"
|
"Token": "hidden",
|
||||||
|
"UDP": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ClientAddrs": [],
|
"ClientAddrs": [],
|
||||||
|
@ -324,7 +326,8 @@
|
||||||
"TLSServerName": "",
|
"TLSServerName": "",
|
||||||
"TLSSkipVerify": false,
|
"TLSSkipVerify": false,
|
||||||
"TTL": "0s",
|
"TTL": "0s",
|
||||||
"Timeout": "0s"
|
"Timeout": "0s",
|
||||||
|
"UDP": ""
|
||||||
},
|
},
|
||||||
"Checks": [],
|
"Checks": [],
|
||||||
"Connect": null,
|
"Connect": null,
|
||||||
|
@ -428,6 +431,7 @@
|
||||||
"Registerer": null,
|
"Registerer": null,
|
||||||
"SummaryDefinitions": []
|
"SummaryDefinitions": []
|
||||||
},
|
},
|
||||||
|
"RetryFailedConfiguration": false,
|
||||||
"StatsdAddr": "",
|
"StatsdAddr": "",
|
||||||
"StatsiteAddr": ""
|
"StatsiteAddr": ""
|
||||||
},
|
},
|
||||||
|
|
|
@ -647,6 +647,7 @@ telemetry {
|
||||||
disable_hostname = true
|
disable_hostname = true
|
||||||
dogstatsd_addr = "0wSndumK"
|
dogstatsd_addr = "0wSndumK"
|
||||||
dogstatsd_tags = [ "3N81zSUB","Xtj8AnXZ" ]
|
dogstatsd_tags = [ "3N81zSUB","Xtj8AnXZ" ]
|
||||||
|
retry_failed_connection = true
|
||||||
filter_default = true
|
filter_default = true
|
||||||
prefix_filter = [ "+oJotS8XJ","-cazlEhGn" ]
|
prefix_filter = [ "+oJotS8XJ","-cazlEhGn" ]
|
||||||
metrics_prefix = "ftO6DySn"
|
metrics_prefix = "ftO6DySn"
|
||||||
|
|
|
@ -644,6 +644,7 @@
|
||||||
"disable_hostname": true,
|
"disable_hostname": true,
|
||||||
"dogstatsd_addr": "0wSndumK",
|
"dogstatsd_addr": "0wSndumK",
|
||||||
"dogstatsd_tags": [ "3N81zSUB","Xtj8AnXZ" ],
|
"dogstatsd_tags": [ "3N81zSUB","Xtj8AnXZ" ],
|
||||||
|
"retry_failed_connection": true,
|
||||||
"filter_default": true,
|
"filter_default": true,
|
||||||
"prefix_filter": [ "+oJotS8XJ","-cazlEhGn" ],
|
"prefix_filter": [ "+oJotS8XJ","-cazlEhGn" ],
|
||||||
"metrics_prefix": "ftO6DySn",
|
"metrics_prefix": "ftO6DySn",
|
||||||
|
|
|
@ -33,10 +33,7 @@ func (s *HTTPHandlers) configGet(resp http.ResponseWriter, req *http.Request) (i
|
||||||
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
kindAndName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/config/")
|
kindAndName := strings.TrimPrefix(req.URL.Path, "/v1/config/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pathArgs := strings.SplitN(kindAndName, "/", 2)
|
pathArgs := strings.SplitN(kindAndName, "/", 2)
|
||||||
|
|
||||||
switch len(pathArgs) {
|
switch len(pathArgs) {
|
||||||
|
@ -84,10 +81,7 @@ func (s *HTTPHandlers) configDelete(resp http.ResponseWriter, req *http.Request)
|
||||||
var args structs.ConfigEntryRequest
|
var args structs.ConfigEntryRequest
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
kindAndName, err := getPathSuffixUnescaped(req.URL.Path, "/v1/config/")
|
kindAndName := strings.TrimPrefix(req.URL.Path, "/v1/config/")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pathArgs := strings.SplitN(kindAndName, "/", 2)
|
pathArgs := strings.SplitN(kindAndName, "/", 2)
|
||||||
|
|
||||||
if len(pathArgs) != 2 {
|
if len(pathArgs) != 2 {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
)
|
)
|
||||||
|
@ -92,15 +91,3 @@ func validateSignIntermediate(csr *x509.CertificateRequest, spiffeID *connect.Sp
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureTrailingNewline this is used to fix a case where the provider do not return a new line after
|
|
||||||
// the certificate as per the specification see GH-8178 for more context
|
|
||||||
func EnsureTrailingNewline(cert string) string {
|
|
||||||
if cert == "" {
|
|
||||||
return cert
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(cert, "\n") {
|
|
||||||
return cert
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s\n", cert)
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -363,15 +364,15 @@ func (a *AWSProvider) loadCACerts() error {
|
||||||
|
|
||||||
if a.isPrimary {
|
if a.isPrimary {
|
||||||
// Just use the cert as a root
|
// Just use the cert as a root
|
||||||
a.rootPEM = EnsureTrailingNewline(*output.Certificate)
|
a.rootPEM = lib.EnsureTrailingNewline(*output.Certificate)
|
||||||
} else {
|
} else {
|
||||||
a.intermediatePEM = EnsureTrailingNewline(*output.Certificate)
|
a.intermediatePEM = lib.EnsureTrailingNewline(*output.Certificate)
|
||||||
// TODO(banks) support user-supplied CA being a Subordinate even in the
|
// TODO(banks) support user-supplied CA being a Subordinate even in the
|
||||||
// primary DC. For now this assumes there is only one cert in the chain
|
// primary DC. For now this assumes there is only one cert in the chain
|
||||||
if output.CertificateChain == nil {
|
if output.CertificateChain == nil {
|
||||||
return fmt.Errorf("Subordinate CA %s returned no chain", a.arn)
|
return fmt.Errorf("Subordinate CA %s returned no chain", a.arn)
|
||||||
}
|
}
|
||||||
a.rootPEM = EnsureTrailingNewline(*output.CertificateChain)
|
a.rootPEM = lib.EnsureTrailingNewline(*output.CertificateChain)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -489,7 +490,7 @@ func (a *AWSProvider) signCSR(csrPEM string, templateARN string, ttl time.Durati
|
||||||
}
|
}
|
||||||
|
|
||||||
if certOutput.Certificate != nil {
|
if certOutput.Certificate != nil {
|
||||||
return true, EnsureTrailingNewline(*certOutput.Certificate), nil
|
return true, lib.EnsureTrailingNewline(*certOutput.Certificate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, "", nil
|
return false, "", nil
|
||||||
|
@ -532,8 +533,8 @@ func (a *AWSProvider) SetIntermediate(intermediatePEM string, rootPEM string) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// We successfully initialized, keep track of the root and intermediate certs.
|
// We successfully initialized, keep track of the root and intermediate certs.
|
||||||
a.rootPEM = EnsureTrailingNewline(rootPEM)
|
a.rootPEM = lib.EnsureTrailingNewline(rootPEM)
|
||||||
a.intermediatePEM = EnsureTrailingNewline(intermediatePEM)
|
a.intermediatePEM = lib.EnsureTrailingNewline(intermediatePEM)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,15 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/lib/decode"
|
|
||||||
"github.com/hashicorp/consul/lib/retry"
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
vaultapi "github.com/hashicorp/vault/api"
|
vaultapi "github.com/hashicorp/vault/api"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/consul/lib/decode"
|
||||||
|
"github.com/hashicorp/consul/lib/retry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -506,7 +507,7 @@ func (v *VaultProvider) getCA(namespace, path string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := EnsureTrailingNewline(string(bytes))
|
root := lib.EnsureTrailingNewline(string(bytes))
|
||||||
if root == "" {
|
if root == "" {
|
||||||
return "", ErrBackendNotInitialized
|
return "", ErrBackendNotInitialized
|
||||||
}
|
}
|
||||||
|
@ -535,7 +536,7 @@ func (v *VaultProvider) getCAChain(namespace, path string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := EnsureTrailingNewline(string(raw))
|
root := lib.EnsureTrailingNewline(string(raw))
|
||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,7 +601,7 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("certificate was not a string")
|
return "", fmt.Errorf("certificate was not a string")
|
||||||
}
|
}
|
||||||
return EnsureTrailingNewline(cert), nil
|
return lib.EnsureTrailingNewline(cert), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignIntermediate returns a signed CA certificate with a path length constraint
|
// SignIntermediate returns a signed CA certificate with a path length constraint
|
||||||
|
@ -637,7 +638,7 @@ func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
||||||
return "", fmt.Errorf("signed intermediate result is not a string")
|
return "", fmt.Errorf("signed intermediate result is not a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return EnsureTrailingNewline(intermediate), nil
|
return lib.EnsureTrailingNewline(intermediate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
|
// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
|
||||||
|
@ -677,7 +678,7 @@ func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
return "", fmt.Errorf("certificate was not a string")
|
return "", fmt.Errorf("certificate was not a string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return EnsureTrailingNewline(xcCert), nil
|
return lib.EnsureTrailingNewline(xcCert), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportsCrossSigning implements Provider
|
// SupportsCrossSigning implements Provider
|
||||||
|
|
|
@ -11,6 +11,7 @@ const (
|
||||||
internal = "internal"
|
internal = "internal"
|
||||||
version = "v1"
|
version = "v1"
|
||||||
internalVersion = internal + "-" + version
|
internalVersion = internal + "-" + version
|
||||||
|
external = "external"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpstreamSNI(u *structs.Upstream, subset string, dc string, trustDomain string) string {
|
func UpstreamSNI(u *structs.Upstream, subset string, dc string, trustDomain string) string {
|
||||||
|
@ -64,6 +65,21 @@ func ServiceSNI(service string, subset string, namespace string, partition strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PeeredServiceSNI(service, namespace, partition, peerName, trustDomain string) string {
|
||||||
|
if peerName == "" {
|
||||||
|
panic("peer name is a requirement for this function and does not make sense without it")
|
||||||
|
}
|
||||||
|
if namespace == "" {
|
||||||
|
namespace = structs.IntentionDefaultNamespace
|
||||||
|
}
|
||||||
|
if partition == "" {
|
||||||
|
// TODO(partitions) Make default available in OSS as a constant for uses like this one
|
||||||
|
partition = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotJoin(service, namespace, partition, peerName, external, trustDomain)
|
||||||
|
}
|
||||||
|
|
||||||
func dotJoin(parts ...string) string {
|
func dotJoin(parts ...string) string {
|
||||||
return strings.Join(parts, ".")
|
return strings.Join(parts, ".")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ package connect
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -164,6 +165,11 @@ func TestServiceSNI(t *testing.T) {
|
||||||
ServiceSNI("api", "canary", "neighbor", "part1", "foo", testTrustDomain2))
|
ServiceSNI("api", "canary", "neighbor", "part1", "foo", testTrustDomain2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPeeredServiceSNI(t *testing.T) {
|
||||||
|
require.Equal(t, "api.billing.default.webstuff.external."+testTrustDomainSuffix1,
|
||||||
|
PeeredServiceSNI("api", "billing", "", "webstuff", testTrustDomainSuffix1))
|
||||||
|
}
|
||||||
|
|
||||||
func TestQuerySNI(t *testing.T) {
|
func TestQuerySNI(t *testing.T) {
|
||||||
require.Equal(t, "magicquery.default.foo.query."+testTrustDomain1,
|
require.Equal(t, "magicquery.default.foo.query."+testTrustDomain1,
|
||||||
QuerySNI("magicquery", "foo", testTrustDomain1))
|
QuerySNI("magicquery", "foo", testTrustDomain1))
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/mitchellh/go-testing-interface"
|
"github.com/mitchellh/go-testing-interface"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ import (
|
||||||
//
|
//
|
||||||
// NOTE: this is duplicated in the api package as testClusterID
|
// NOTE: this is duplicated in the api package as testClusterID
|
||||||
const TestClusterID = "11111111-2222-3333-4444-555555555555"
|
const TestClusterID = "11111111-2222-3333-4444-555555555555"
|
||||||
|
const TestTrustDomain = TestClusterID + ".consul"
|
||||||
|
|
||||||
// testCACounter is just an atomically incremented counter for creating
|
// testCACounter is just an atomically incremented counter for creating
|
||||||
// unique names for the CA certs.
|
// unique names for the CA certs.
|
||||||
|
@ -295,6 +297,21 @@ func TestLeafWithNamespace(t testing.T, service, namespace string, root *structs
|
||||||
return certPEM, keyPEM
|
return certPEM, keyPEM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMeshGatewayLeaf(t testing.T, partition string, root *structs.CARoot) (string, string) {
|
||||||
|
// Build the SPIFFE ID
|
||||||
|
spiffeId := &SpiffeIDMeshGateway{
|
||||||
|
Host: fmt.Sprintf("%s.consul", TestClusterID),
|
||||||
|
Partition: acl.PartitionOrDefault(partition),
|
||||||
|
Datacenter: "dc1",
|
||||||
|
}
|
||||||
|
|
||||||
|
certPEM, keyPEM, err := testLeafWithID(t, spiffeId, root, DefaultPrivateKeyType, DefaultPrivateKeyBits, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
return certPEM, keyPEM
|
||||||
|
}
|
||||||
|
|
||||||
// TestCSR returns a CSR to sign the given service along with the PEM-encoded
|
// TestCSR returns a CSR to sign the given service along with the PEM-encoded
|
||||||
// private key for this certificate.
|
// private key for this certificate.
|
||||||
func TestCSR(t testing.T, uri CertURI) (string, string) {
|
func TestCSR(t testing.T, uri CertURI) (string, string) {
|
||||||
|
|
|
@ -24,6 +24,8 @@ var (
|
||||||
`^(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/]+)$`)
|
`^(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/]+)$`)
|
||||||
spiffeIDAgentRegexp = regexp.MustCompile(
|
spiffeIDAgentRegexp = regexp.MustCompile(
|
||||||
`^(?:/ap/([^/]+))?/agent/client/dc/([^/]+)/id/([^/]+)$`)
|
`^(?:/ap/([^/]+))?/agent/client/dc/([^/]+)/id/([^/]+)$`)
|
||||||
|
spiffeIDMeshGatewayRegexp = regexp.MustCompile(
|
||||||
|
`^(?:/ap/([^/]+))?/gateway/mesh/dc/([^/]+)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseCertURIFromString attempts to parse a string representation of a
|
// ParseCertURIFromString attempts to parse a string representation of a
|
||||||
|
@ -117,6 +119,31 @@ func ParseCertURI(input *url.URL) (CertURI, error) {
|
||||||
Datacenter: dc,
|
Datacenter: dc,
|
||||||
Agent: agent,
|
Agent: agent,
|
||||||
}, nil
|
}, nil
|
||||||
|
} else if v := spiffeIDMeshGatewayRegexp.FindStringSubmatch(path); v != nil {
|
||||||
|
// Determine the values. We assume they're reasonable to save cycles,
|
||||||
|
// but if the raw path is not empty that means that something is
|
||||||
|
// URL encoded so we go to the slow path.
|
||||||
|
ap := v[1]
|
||||||
|
dc := v[2]
|
||||||
|
if input.RawPath != "" {
|
||||||
|
var err error
|
||||||
|
if ap, err = url.PathUnescape(v[1]); err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid admin partition: %s", err)
|
||||||
|
}
|
||||||
|
if dc, err = url.PathUnescape(v[2]); err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid datacenter: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap == "" {
|
||||||
|
ap = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SpiffeIDMeshGateway{
|
||||||
|
Host: input.Host,
|
||||||
|
Partition: ap,
|
||||||
|
Datacenter: dc,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for signing ID
|
// Test for signing ID
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue