diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..eb4bfe6e0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..bb25c3a09 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,49 @@ +name: "CodeQL analysis" + +on: + push: + branches: [ master] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '35 13 * * 5' + workflow_dispatch: + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install go version + uses: actions/setup-go@v2 + with: + go-version: '^1.16' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - run: | + make build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/lint-test-chart.yaml b/.github/workflows/lint-test-chart.yaml new file mode 100644 index 000000000..064d2de31 --- /dev/null +++ b/.github/workflows/lint-test-chart.yaml @@ -0,0 +1,49 @@ +name: Lint and Test Chart + +on: + pull_request: + paths: + - "charts/external-dns/**" + +jobs: + lint-test: + if: github.repository == 'kubernetes-sigs/external-dns' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v1 + with: + version: v3.6.3 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.1.0 + + - name: Run chart-testing (list-changed) + id: list-changed + run: | + changed=$(ct list-changed) + if [[ -n "$changed" ]]; then + echo "::set-output name=changed::true" + fi + + - name: Run chart-testing (lint) + run: ct lint --check-version-increment=false + + - name: Create Kind cluster + uses: helm/kind-action@v1.2.0 + with: + wait: 120s + if: steps.list-changed.outputs.changed == 'true' + + - name: Run chart-testing (install) + run: ct install diff --git a/.github/workflows/release-chart.yaml b/.github/workflows/release-chart.yaml new file mode 100644 index 000000000..34bd1e779 --- /dev/null +++ b/.github/workflows/release-chart.yaml @@ -0,0 +1,34 @@ +name: Release Chart + +on: + push: + branches: + - master + paths: + - "charts/external-dns/Chart.yaml" + +jobs: + release: + if: github.repository == 'kubernetes-sigs/external-dns' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v1 + with: + version: v3.6.3 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.2.1 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CR_RELEASE_NAME_TEMPLATE: "external-dns-helm-chart-{{ .Version }}" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 000000000..ee6b6c0c5 --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,19 @@ +name: trivy vulnerability scanner +on: + push: + branches: + - master +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build an image from Dockerfile + run: | + make build.docker + - name: Run trivy + run: | + ./scripts/run-trivy.sh + diff --git a/.gitignore b/.gitignore index db4b5e4c8..80a6ea158 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,7 @@ vendor/ profile.cov # github codespaces -.venv/ \ No newline at end of file +.venv/ + +# Helm charts +!/charts/external-dns/ diff --git a/Dockerfile b/Dockerfile index dd9e80552..c2e81e50c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ COPY . . RUN make test build.$ARCH # final image -FROM $ARCH/alpine:3.13 +FROM $ARCH/alpine:3.14 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns diff --git a/Makefile b/Makefile index a971469ae..ccd3179ed 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ build.push/multiarch: for arch in $(ARCHS); do \ image="$(IMAGE):$(VERSION)-$${arch}" ;\ # pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\ - docker pull $${arch}/alpine:3.13 ;\ + docker pull $${arch}/alpine:3.14 ;\ docker pull golang:1.16 ;\ DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\ docker push $${image} ;\ diff --git a/OWNERS b/OWNERS index 613f70f9d..50f90e684 100644 --- a/OWNERS +++ b/OWNERS @@ -4,6 +4,7 @@ approvers: - raffo - njuettner + - seanmalloy reviewers: - njuettner diff --git a/README.md b/README.md index 49ee8bda2..a9b7d8cdb 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ The [FAQ](docs/faq.md) contains additional information and addresses several que To see ExternalDNS in action, have a look at this [video](https://www.youtube.com/watch?v=9HQ2XgL9YVI) or read this [blogpost](https://codemine.be/posts/20190125-devops-eks-externaldns/). -## The Latest Release: v0.8 +## The Latest Release -ExternalDNS' current release is `v0.8`. This version allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` in various cloud providers: +ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` in various cloud providers: * [Google Cloud DNS](https://cloud.google.com/dns/docs/) * [AWS Route 53](https://aws.amazon.com/route53/) * [AWS Cloud Map](https://docs.aws.amazon.com/cloud-map/) @@ -110,6 +110,13 @@ The following table clarifies the current status of the providers according to t | GoDaddy | Alpha | | | Gandi | Alpha | @packi | +## Kubernetes version compatibility + +| ExternalDNS | <= 0.9.x | >= 0.10.0 | +| ------------------ | :----------------: | :----------------: | +| Kubernetes <= 1.18 | :white_check_mark: | :x: | +| Kubernetes >= 1.19 | :x: | :white_check_mark: | + ## Running ExternalDNS: The are two ways of running ExternalDNS: @@ -139,7 +146,7 @@ The following tutorials are provided: * [Dyn](docs/tutorials/dyn.md) * [Exoscale](docs/tutorials/exoscale.md) * [ExternalName Services](docs/tutorials/externalname.md) -* Google Container Engine +* Google Kubernetes Engine * [Using Google's Default Ingress Controller](docs/tutorials/gke.md) * [Using the Nginx Ingress Controller](docs/tutorials/nginx-ingress.md) * [Headless Services](docs/tutorials/hostport.md) diff --git a/charts/OWNERS b/charts/OWNERS new file mode 100644 index 000000000..a5ec64441 --- /dev/null +++ b/charts/OWNERS @@ -0,0 +1,6 @@ +labels: + - chart +approvers: + - stevehipwell +reviewers: + - stevehipwell diff --git a/charts/external-dns/.helmignore b/charts/external-dns/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/external-dns/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/external-dns/Chart.yaml b/charts/external-dns/Chart.yaml new file mode 100644 index 000000000..eda102a0f --- /dev/null +++ b/charts/external-dns/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v2 +name: external-dns +description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers. +type: application +version: 1.6.0 +appVersion: 0.10.1 +keywords: + - kubernetes + - external-dns + - dns +home: https://github.com/kubernetes-sigs/external-dns/ +icon: https://github.com/kubernetes-sigs/external-dns/raw/master/img/external-dns.png +sources: + - https://github.com/kubernetes-sigs/external-dns/ +maintainers: + - name: stevehipwell + email: steve.hipwell@gmail.com +annotations: + artifacthub.io/changes: | + - kind: changed + description: "Allow specifying Service annotations." diff --git a/charts/external-dns/README.md b/charts/external-dns/README.md new file mode 100644 index 000000000..b9746dd70 --- /dev/null +++ b/charts/external-dns/README.md @@ -0,0 +1,67 @@ +# ExternalDNS + +[ExternalDNS](https://github.com/kubernetes-sigs/external-dns/) synchronizes exposed Kubernetes Services and Ingresses with DNS providers. + +## Installing the Chart + +Before you can install the chart you will need to add the `external-dns` repo to [Helm](https://helm.sh/). + +```shell +helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/ +``` + +After you've installed the repo you can install the chart. + +```shell +helm upgrade --install external-dns/external-dns +``` + +## Configuration + +The following table lists the configurable parameters of the _ExternalDNS_ chart and their default values. + +| Parameter | Description | Default | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| `image.repository` | Image repository. | `k8s.gcr.io/external-dns/external-dns` | +| `image.tag` | Image tag, will override the default tag derived from the chart app version. | `""` | +| `image.pullPolicy` | Image pull policy. | `IfNotPresent` | +| `imagePullSecrets` | Image pull secrets. | `[]` | +| `nameOverride` | Override the `name` of the chart. | `""` | +| `fullnameOverride` | Override the `fullname` of the chart. | `""` | +| `serviceAccount.create` | If `true`, create a new `serviceaccount`. | `true` | +| `serviceAccount.annotations` | Annotations to add to the service account. | `{}` | +| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` | +| `rbac.create` | If `true`, create the RBAC resources. | `true` | +| `podLabels` | Labels to add to the pod. | `{}` | +| `podAnnotations` | Annotations to add to the pod. | `{}` | +| `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ | +| `securityContext` | Security context for the _external-dns_ container, this supports the full [SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#securitycontext-v1-core) API. | _see values.yaml_ | +| `priorityClassName` | Priority class name to use for the pod. | `""` | +| `terminationGracePeriodSeconds` | Termination grace period for the pod. | `null` | +| `serviceMonitor.enabled` | If `true`, create a _Prometheus_ service monitor. | `false` | +| `serviceMonitor.additionalLabels` | Additional labels to be set on the ServiceMonitor. | `{}` | +| `serviceMonitor.interval` | _Prometheus_ scrape frequency. | `1m` | +| `serviceMonitor.scrapeTimeout` | _Prometheus_ scrape timeout. | `10s` | +| `env` | [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the _external-dns_ container, this supports the full [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) API including secrets and configmaps. | `[]` | +| `livenessProbe` | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ | +| `readinessProbe` | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ | +| `service.annotations` | Annotations to add to the service. | `{}` | +| `service.port` | Port to expose via the service. | `7979` | +| `extraVolumes` | Additional volumes for the pod, this supports the full [VolumeDevice](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumedevice-v1-core) API. | `[]` | +| `extraVolumeMounts` | Additional volume mounts for the _external-dns_ container, this supports the full [VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumemount-v1-core) API. | `[]` | +| `resources` | Resource requests and limits for the _external-dns_ container, this supports the full [ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#resourcerequirements-v1-core) API. | `{}` | +| `nodeSelector` | Node labels for pod assignment. | `{}` | +| `tolerations` | Tolerations for pod assignment, this supports the full [Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#toleration-v1-core) API. | `[]` | +| `affinity` | Affinity settings for pod assignment, this supports the full [Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#affinity-v1-core) API. | `{}` | +| `logLevel` | Verbosity of the logs, available values are: `panic`, `debug`, `info`, `warn`, `error`, `fatal`. | `info` | +| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` | +| `interval` | The interval for DNS updates. | `1m` | +| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` | +| `sources` | K8s resources type to be observed for new DNS entries. | See _values.yaml_ | +| `policy` | How DNS records are synchronized between sources and providers, available values are: `sync`, `upsert-only`. | `upsert-only` | +| `registry` | Registry Type, available types are: `txt`, `noop`. | `txt` | +| `txtOwnerId` | TXT registry identifier. | `""` | +| `txtPrefix` | Prefix to create a TXT record with a name following the pattern `prefix.`. | `""` | +| `domainFilters` | Limit possible target zones by domain suffixes. | `[]` | +| `provider` | DNS provider where the DNS records will be created, for the available providers and how to configure them see the [README](https://github.com/kubernetes-sigs/external-dns#deploying-to-a-cluster). | `aws` | +| `extraArgs` | Extra arguments to pass to the _external-dns_ container, these are needed for provider specific arguments. | `[]` | diff --git a/charts/external-dns/templates/NOTES.txt b/charts/external-dns/templates/NOTES.txt new file mode 100644 index 000000000..5e37ecca0 --- /dev/null +++ b/charts/external-dns/templates/NOTES.txt @@ -0,0 +1,7 @@ +*********************************************************************** +* External DNS * +*********************************************************************** + Chart version: {{ .Chart.Version }} + App version: {{ .Chart.AppVersion }} + Image tag: {{ include "external-dns.image" . }} +*********************************************************************** diff --git a/charts/external-dns/templates/_helpers.tpl b/charts/external-dns/templates/_helpers.tpl new file mode 100644 index 000000000..7f6e52f05 --- /dev/null +++ b/charts/external-dns/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "external-dns.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "external-dns.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "external-dns.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "external-dns.labels" -}} +helm.sh/chart: {{ include "external-dns.chart" . }} +{{ include "external-dns.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "external-dns.selectorLabels" -}} +app.kubernetes.io/name: {{ include "external-dns.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "external-dns.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "external-dns.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +The image to use +*/}} +{{- define "external-dns.image" -}} +{{- printf "%s:%s" .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) }} +{{- end }} diff --git a/charts/external-dns/templates/clusterrole.yaml b/charts/external-dns/templates/clusterrole.yaml new file mode 100644 index 000000000..1d73eb76c --- /dev/null +++ b/charts/external-dns/templates/clusterrole.yaml @@ -0,0 +1,44 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "external-dns.fullname" . }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} +rules: +{{- if or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) }} + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list","watch"] +{{- end }} + +{{- if or (has "pod" .Values.sources) (has "service" .Values.sources) }} + - apiGroups: [""] + resources: ["pods"] + verbs: ["get","watch","list"] +{{- end }} + +{{- if has "service" .Values.sources }} + - apiGroups: [""] + resources: ["services","endpoints"] + verbs: ["get","watch","list"] +{{- end }} + +{{- if has "ingress" .Values.sources }} + - apiGroups: ["extensions","networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +{{- end }} + +{{- if has "istio-gateway" .Values.sources }} + - apiGroups: ["networking.istio.io"] + resources: ["gateways"] + verbs: ["get","watch","list"] +{{- end }} + +{{- if has "istio-virtualservice" .Values.sources }} + - apiGroups: ["networking.istio.io"] + resources: ["virtualservices"] + verbs: ["get","watch","list"] +{{- end }} +{{- end }} diff --git a/charts/external-dns/templates/clusterrolebinding.yaml b/charts/external-dns/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..9028c6f96 --- /dev/null +++ b/charts/external-dns/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ printf "%s-viewer" (include "external-dns.fullname" .) }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "external-dns.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "external-dns.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/external-dns/templates/deployment.yaml b/charts/external-dns/templates/deployment.yaml new file mode 100644 index 000000000..4d7eda2f5 --- /dev/null +++ b/charts/external-dns/templates/deployment.yaml @@ -0,0 +1,109 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "external-dns.fullname" . }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "external-dns.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "external-dns.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "external-dns.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . | quote }} + {{- end }} + {{- with .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ . }} + {{- end }} + containers: + - name: external-dns + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: {{ include "external-dns.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - --log-level={{ .Values.logLevel }} + - --log-format={{ .Values.logFormat }} + - --interval={{ .Values.interval }} + {{- if .Values.triggerLoopOnEvent }} + - --events + {{- end }} + {{- range .Values.sources }} + - --source={{ . }} + {{- end }} + - --policy={{ .Values.policy }} + - --registry={{ .Values.registry }} + {{- if eq .Values.registry "txt" }} + {{- if .Values.txtOwnerId }} + - --txt-owner-id={{ .Values.txtOwnerId }} + {{- end }} + {{- if .Values.txtPrefix }} + - --txt-prefix={{ .Values.txtPrefix }} + {{- end }} + {{- end }} + {{- range .Values.domainFilters }} + - --domain-filter={{ . }} + {{- end }} + - --provider={{ .Values.provider }} + {{- range .Values.extraArgs }} + - {{ . }} + {{- end }} + ports: + - name: http + protocol: TCP + containerPort: 7979 + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- with .Values.extraVolumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.extraVolumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/external-dns/templates/service.yaml b/charts/external-dns/templates/service.yaml new file mode 100644 index 000000000..174e3dce0 --- /dev/null +++ b/charts/external-dns/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-dns.fullname" . }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + selector: + {{- include "external-dns.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + protocol: TCP diff --git a/charts/external-dns/templates/serviceaccount.yaml b/charts/external-dns/templates/serviceaccount.yaml new file mode 100644 index 000000000..aa6deed58 --- /dev/null +++ b/charts/external-dns/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "external-dns.serviceAccountName" . }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-dns/templates/servicemonitor.yaml b/charts/external-dns/templates/servicemonitor.yaml new file mode 100644 index 000000000..c5be44492 --- /dev/null +++ b/charts/external-dns/templates/servicemonitor.yaml @@ -0,0 +1,28 @@ +{{- if.Values.serviceMonitor.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "external-dns.fullname" . }} + labels: + {{- include "external-dns.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + jobLabel: {{ .Release.Name }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "external-dns.selectorLabels" . | nindent 6 }} + endpoints: + - port: http + path: /metrics + {{- with .Values.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} +{{- end }} diff --git a/charts/external-dns/values.yaml b/charts/external-dns/values.yaml new file mode 100644 index 000000000..ccb1d4af6 --- /dev/null +++ b/charts/external-dns/values.yaml @@ -0,0 +1,110 @@ +# Default values for external-dns. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + repository: k8s.gcr.io/external-dns/external-dns + # Overrides the image tag whose default is v{{ .Chart.AppVersion }} + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +rbac: + # Specifies whether RBAC resources should be created + create: true + +podLabels: {} + +podAnnotations: {} + +podSecurityContext: + fsGroup: 65534 + +securityContext: + runAsNonRoot: true + runAsUser: 65534 + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + +priorityClassName: "" + +terminationGracePeriodSeconds: + +serviceMonitor: + enabled: false + additionalLabels: {} + interval: 1m + scrapeTimeout: 10s + +env: [] + +livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 2 + successThreshold: 1 + +readinessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +service: + port: 7979 + annotations: {} + +extraVolumes: [] + +extraVolumeMounts: [] + +resources: {} + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +logLevel: info +logFormat: text + +interval: 1m +triggerLoopOnEvent: false + +sources: + - service + - ingress + +policy: upsert-only + +registry: txt +txtOwnerId: "" +txtPrefix: "" + +domainFilters: [] + +provider: aws + +extraArgs: [] diff --git a/cloudbuild.yaml b/cloudbuild.yaml index c6629fd0c..52576cc5b 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -3,7 +3,7 @@ timeout: 5000s options: substitution_option: ALLOW_LOOSE steps: - - name: "gcr.io/k8s-testimages/gcb-docker-gcloud:v20200824-5d057db" + - name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90" entrypoint: make env: - DOCKER_CLI_EXPERIMENTAL=enabled diff --git a/controller/controller.go b/controller/controller.go index 6769103dd..6070d66dd 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -94,6 +94,14 @@ var ( Help: "Number of Source errors.", }, ) + verifiedARecords = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "external_dns", + Subsystem: "controller", + Name: "verified_a_records", + Help: "Number of DNS A-records that exists both in source and registry.", + }, + ) ) func init() { @@ -105,6 +113,7 @@ func init() { prometheus.MustRegister(deprecatedRegistryErrors) prometheus.MustRegister(deprecatedSourceErrors) prometheus.MustRegister(controllerNoChangesTotal) + prometheus.MustRegister(verifiedARecords) } // Controller is responsible for orchestrating the different components. @@ -151,7 +160,8 @@ func (c *Controller) RunOnce(ctx context.Context) error { return err } sourceEndpointsTotal.Set(float64(len(endpoints))) - + vRecords := fetchMatchingARecords(endpoints, records) + verifiedARecords.Set(float64(len(vRecords))) endpoints = c.Registry.AdjustEndpoints(endpoints) plan := &plan.Plan{ @@ -160,7 +170,7 @@ func (c *Controller) RunOnce(ctx context.Context) error { Desired: endpoints, DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, c.Registry.GetDomainFilter()}, PropertyComparator: c.Registry.PropertyValuesEqual, - ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ManagedRecords: c.ManagedRecordTypes, } plan = plan.Calculate() @@ -181,6 +191,32 @@ func (c *Controller) RunOnce(ctx context.Context) error { return nil } +// Checks and returns the intersection of A records in endpoint and registry. +func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string { + aRecords := filterARecords(endpoints) + recordsMap := make(map[string]struct{}) + for _, regRecord := range registryRecords { + recordsMap[regRecord.DNSName] = struct{}{} + } + var cm []string + for _, sourceRecord := range aRecords { + if _, found := recordsMap[sourceRecord]; found { + cm = append(cm, sourceRecord) + } + } + return cm +} + +func filterARecords(endpoints []*endpoint.Endpoint) []string { + var aRecords []string + for _, endPoint := range endpoints { + if endPoint.RecordType == endpoint.RecordTypeA { + aRecords = append(aRecords, endPoint.DNSName) + } + } + return aRecords +} + // ScheduleRunOnce makes sure execution happens at most once per interval. func (c *Controller) ScheduleRunOnce(now time.Time) { c.nextRunAtMux.Lock() diff --git a/controller/controller_test.go b/controller/controller_test.go index 89ac590f0..591c80d24 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -19,12 +19,15 @@ package controller import ( "context" "errors" + "github.com/prometheus/client_golang/prometheus" + "math" "reflect" "testing" "time" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" "sigs.k8s.io/external-dns/registry" @@ -48,6 +51,10 @@ type filteredMockProvider struct { ApplyChangesCalls []*plan.Changes } +type errorMockProvider struct { + mockProvider +} + func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface { return p.domainFilter } @@ -69,6 +76,10 @@ func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error return p.RecordsStore, nil } +func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + return nil, errors.New("error for testing") +} + // ApplyChanges validates that the passed in changes satisfy the assumptions. func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { if len(changes.Create) != len(p.ExpectChanges.Create) { @@ -119,6 +130,8 @@ func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) prov func TestRunOnce(t *testing.T) { // Fake some desired endpoints coming from our source. source := new(testutils.MockSource) + cfg := externaldns.NewConfig() + cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME} source.On("Endpoints").Return([]*endpoint.Endpoint{ { DNSName: "create-record", @@ -167,15 +180,23 @@ func TestRunOnce(t *testing.T) { // Run our controller once to trigger the validation. ctrl := &Controller{ - Source: source, - Registry: r, - Policy: &plan.SyncPolicy{}, + Source: source, + Registry: r, + Policy: &plan.SyncPolicy{}, + ManagedRecordTypes: cfg.ManagedDNSRecordTypes, } assert.NoError(t, ctrl.RunOnce(context.Background())) // Validate that the mock source was called. source.AssertExpectations(t) + // check the verified records + assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords)) +} + +func valueFromMetric(metric prometheus.Gauge) uint64 { + ref := reflect.ValueOf(metric) + return reflect.Indirect(ref).FieldByName("valBits").Uint() } func TestShouldRunOnce(t *testing.T) { @@ -219,6 +240,9 @@ func TestShouldRunOnce(t *testing.T) { func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) { t.Helper() + cfg := externaldns.NewConfig() + cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME} + source := new(testutils.MockSource) source.On("Endpoints").Return(configuredEndpoints, nil) @@ -231,10 +255,11 @@ func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint. require.NoError(t, err) ctrl := &Controller{ - Source: source, - Registry: r, - Policy: &plan.SyncPolicy{}, - DomainFilter: domainFilter, + Source: source, + Registry: r, + Policy: &plan.SyncPolicy{}, + DomainFilter: domainFilter, + ManagedRecordTypes: cfg.ManagedDNSRecordTypes, } assert.NoError(t, ctrl.RunOnce(context.Background())) @@ -368,3 +393,80 @@ func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) { }, ) } + +func TestVerifyARecords(t *testing.T) { + testControllerFiltersDomains( + t, + []*endpoint.Endpoint{ + { + DNSName: "create-record.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, + }, + { + DNSName: "some-record.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.8.8"}, + }, + }, + endpoint.NewDomainFilter([]string{"used.tld"}), + []*endpoint.Endpoint{ + { + DNSName: "some-record.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.8.8"}, + }, + { + DNSName: "create-record.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, + }, + }, + []*plan.Changes{}, + ) + assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords)) + + testControllerFiltersDomains( + t, + []*endpoint.Endpoint{ + { + DNSName: "some-record.1.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, + }, + { + DNSName: "some-record.2.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.8.8"}, + }, + { + DNSName: "some-record.3.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"24.24.24.24"}, + }, + }, + endpoint.NewDomainFilter([]string{"used.tld"}), + []*endpoint.Endpoint{ + { + DNSName: "some-record.1.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, + }, + { + DNSName: "some-record.2.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.8.8"}, + }, + }, + []*plan.Changes{{ + Create: []*endpoint.Endpoint{ + { + DNSName: "some-record.3.used.tld", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"24.24.24.24"}, + }, + }, + }}, + ) + assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords)) +} diff --git a/docs/contributing/chart.md b/docs/contributing/chart.md new file mode 100644 index 000000000..da16c6d14 --- /dev/null +++ b/docs/contributing/chart.md @@ -0,0 +1,5 @@ +# Helm Chart + +## Chart Changes + +When contributing chart changes please follow the same process as when contributing other content but also please **DON'T** modify _Chart.yaml_ in the PR as this would result in a chart release when merged and will mean that your PR will need modifying before it can be accepted. The chart version will be updated as part of the PR to release the chart. diff --git a/docs/faq.md b/docs/faq.md index 5baf2bd12..7a3c0f06d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ### How is ExternalDNS useful to me? -You've probably created many deployments. Typically, you expose your deployment to the Internet by creating a Service with `type=LoadBalancer`. Depending on your environment, this usually assigns a random publicly available endpoint to your service that you can access from anywhere in the world. On Google Container Engine, this is a public IP address: +You've probably created many deployments. Typically, you expose your deployment to the Internet by creating a Service with `type=LoadBalancer`. Depending on your environment, this usually assigns a random publicly available endpoint to your service that you can access from anywhere in the world. On Google Kubernetes Engine, this is a public IP address: ```console $ kubectl get svc @@ -54,7 +54,7 @@ Yes, you can. Pass in a comma separated list to `--fqdn-template`. Beaware this ### Which Service and Ingress controllers are supported? -Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Container Engine, and possibly other clusters running on Google Compute Engine. +Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Kubernetes Engine, and possibly other clusters running on Google Compute Engine. Regarding Ingress, we'll support: * Google's Ingress Controller on GKE that integrates with their Layer 7 load balancers (GLBC) @@ -185,6 +185,8 @@ Here is the full list of available metrics provided by ExternalDNS: | external_dns_registry_errors_total | Number of Registry errors | Counter | | external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge | | external_dns_source_errors_total | Number of Source errors | Counter | +| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge | +| | source & registry | | ### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects? @@ -255,7 +257,7 @@ The internal one should provision hostnames used on the internal network (perhap one to expose DNS to the internet. To do this with ExternalDNS you can use the `--annotation-filter` to specifically tie an instance of ExternalDNS to -an instance of a ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` +an instance of an ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-internal)` and one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)`. @@ -265,6 +267,11 @@ If you need to search for multiple values of said annotation, you can provide a Beware when using multiple sources, e.g. `--source=service --source=ingress`, `--annotation-filter` will filter every given source objects. If you need to filter only one specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`. +**Note:** Filtering based on annotation means that the external-dns controller will receive all resources of that kind and then filter on the client-side. +In larger clusters with many resources which change frequently this can cause performance issues. If only some resources need to be managed by an instance +of external-dns then label filtering can be used instead of annotation filtering. This means that only those resources which match the selector specified +in `--label-filter` will be passed to the controller. + ### How do I specify that I want the DNS record to point to either the Node's public or private IP when it has both? If your Nodes have both public and private IP addresses, you might want to write DNS records with one or the other. diff --git a/docs/release.md b/docs/release.md index dcfe0de5d..e4ce413b5 100644 --- a/docs/release.md +++ b/docs/release.md @@ -24,10 +24,21 @@ You must be an official maintainer of the project to be able to do a release. ### Steps -- Run `scripts/releaser.sh` to create a new GitHub release. +- Run `scripts/releaser.sh` to create a new GitHub release. Alternatively you can create a release in the GitHub UI making sure to click on the autogenerate release node feature. - The step above will trigger the Kubernetes based CI/CD system [Prow](https://prow.k8s.io/?repo=kubernetes-sigs%2Fexternal-dns). Verify that a new image was built and uploaded to `gcr.io/k8s-staging-external-dns/external-dns`. - Create a PR in the [k8s.io repo](https://github.com/kubernetes/k8s.io) (see https://github.com/kubernetes/k8s.io/pull/540 for reference) by taking the current staging image using the sha256 digest. Once the PR is merged, the image will be live with the corresponding tag specified in the PR. - Verify that the image is pullable with the given tag (i.e. `v0.7.5`). - Branch out from the default branch and run `scripts/kustomize-version-udapter.sh` to update the image tag used in the kustomization.yaml. +- Create an issue to release the corresponding Helm chart via the chart release process (below) assigned to a chart maintainer - Create a PR with the kustomize change. - Once the PR is merged, all is done :-) + +## How to release a new chart version + +The chart needs to be released in response to an ExternalDNS image release or on an as-needed basis; this should be triggered by an issue to release the chart. + +### Steps + +- Create a PR to update _Chart.yaml_ with the ExternalDNS version in `appVersion`, agreed on chart release version in `version` and `annotations` showing the changes +- Validate that the chart linting is successful +- Merge the PR to trigger a GitHub action to release the chart diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 75dfbfc6f..5bb808b65 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -464,6 +464,58 @@ $ aws route53 delete-hosted-zone --id /hostedzone/ZEWFWZ4R16P7IB ## Throttling Route53 has a [5 API requests per second per account hard quota](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-route-53). -Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to circumvent that issue includes: -* Augment the synchronization interval (`--interval`), at the cost of slower changes propagation. -* If the ExternalDNS managed zones list doesn't change frequently, set `--aws-zones-cache-duration` (zones list cache time-to-live) to a larger value. Note that zones list cache can be disabled with `--aws-zones-cache-duration=0s`. +Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to reduce the request rate include: +* Reduce the polling loop's synchronization interval at the possible cost of slower change propagation (but see `--events` below to reduce the impact). + * `--interval=5m` (default `1m`) +* Trigger the polling loop on changes to K8s objects, rather than only at `interval`, to have responsive updates with long poll intervals + * `--events` +* Limit the [sources watched](https://github.com/kubernetes-sigs/external-dns/blob/master/pkg/apis/externaldns/types.go#L364) when the `--events` flag is specified to specific types, namespaces, labels, or annotations + * `--source=ingress --source=service` - specify multiple times for multiple sources + * `--namespace=my-app` + * `--label-filter=app in (my-app)` + * `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)` - note that this filter would apply to services too.. +* Limit services watched by type (not applicable to ingress or other types) + * `--service-type-filter=LoadBalancer` default `all` +* Limit the hosted zones considered + * `--zone-id-filter=ABCDEF12345678` - specify multiple times if needed + * `--domain-filter=example.com` by domain suffix - specify multiple times if needed + * `--regex-domain-filter=example*` by domain suffix but as a regex - overrides domain-filter + * `--exclude-domains=ignore.this.example.com` to exclude a domain or subdomain + * `--regex-domain-exclusion=ignore*` subtracts it's matches from `regex-domain-filter`'s matches + * `--aws-zone-type=public` only sync zones of this type `[public|private]` + * `--aws-zone-tags=owner=k8s` only sync zones with this tag +* If the list of zones managed by ExternalDNS doesn't change frequently, cache it by setting a TTL. + * `--aws-zones-cache-duration=3h` (default `0` - disabled) +* Increase the number of changes applied to Route53 in each batch + * `--aws-batch-change-size=4000` (default `1000`) +* Increase the interval between changes + * `--aws-batch-change-interval=10s` (default `1s`) +* Introducing some jitter to the pod initialization, so that when multiple instances of ExternalDNS are updated at the same time they do not make their requests on the same second. + +A simple way to implement randomised startup is with an init container: + +``` +... + spec: + initContainers: + - name: init-jitter + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + command: + - /bin/sh + - -c + - 'FOR=$((RANDOM % 10))s;echo "Sleeping for $FOR";sleep $FOR' + containers: +... +``` + +### EKS + +An effective starting point for EKS with an ingress controller might look like: + +```bash +--interval=5m +--events +--source=ingress +--domain-filter=example.com +--aws-zones-cache-duration=1h +``` diff --git a/docs/tutorials/gke.md b/docs/tutorials/gke.md index 8c452cbd2..319135f26 100644 --- a/docs/tutorials/gke.md +++ b/docs/tutorials/gke.md @@ -1,4 +1,4 @@ -# Setting up ExternalDNS on Google Container Engine +# Setting up ExternalDNS on Google Kubernetes Engine This tutorial describes how to setup ExternalDNS for usage within a GKE cluster. Make sure to use **>=0.4** version of ExternalDNS for this tutorial @@ -123,6 +123,7 @@ spec: - --domain-filter=external-dns-test.gcp.zalan.do # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - --provider=google # - --google-project=zalando-external-dns-test # Use this to specify a project different from the one external-dns is running inside + - --google-zone-visibility=private # Use this to filter to only zones with this visibility. Set to either 'public' or 'private'. Omitting will match public and private zones - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - --registry=txt - --txt-owner-id=my-identifier diff --git a/docs/tutorials/infoblox.md b/docs/tutorials/infoblox.md index 71a3cafc3..bab67dfaf 100644 --- a/docs/tutorials/infoblox.md +++ b/docs/tutorials/infoblox.md @@ -78,6 +78,7 @@ spec: - --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443". - --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1" - --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification. + - --infoblox-create-ptr # (optional) Use --infoblox-create-ptr to create a ptr entry in addition to an entry. env: - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". @@ -158,6 +159,7 @@ spec: - --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443". - --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1" - --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification. + - --infoblox-create-ptr # (optional) Use --infoblox-create-ptr to create a ptr entry in addition to an entry. env: - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". @@ -268,3 +270,11 @@ There is also the ability to filter results from the Infoblox zone_auth service ``` --infoblox-fqdn-regex=^staging.*test.com$ ``` + +## Infoblox PTR record support + +There is an option to enable PTR records support for infoblox provider. PTR records allow to do reverse dns search. To enable PTR records support, add following into arguments for external-dns: +`--infoblox-create-ptr` to allow management of PTR records. +You can also add a filter for reverse dns zone to limit PTR records to specific zones only: +`--domain-filter=10.196.0.0/16` change this to the reverse zone(s) as defined in your infoblox. +Now external-dns will manage PTR records for you. diff --git a/docs/tutorials/openshift.md b/docs/tutorials/openshift.md index c90e029a0..2fd983ecd 100644 --- a/docs/tutorials/openshift.md +++ b/docs/tutorials/openshift.md @@ -2,6 +2,60 @@ This tutorial describes how to configure ExternalDNS to use the OpenShift Route source. It is meant to supplement the other provider-specific setup tutorials. +### For OCP 4.x + +In OCP 4.x, if you have multiple ingress controllers then you must specify an ingress controller name or a router name(you can get it from the route's Status.Ingress.RouterName field). +If you don't specify an ingress controller's or router name when you have multiple ingresscontrollers in your environment then the route gets populated with multiple entries of router canonical hostnames which causes external dns to create a CNAME record with multiple router canonical hostnames pointing to the route host which is a violation of RFC 1912 and is not allowed by Cloud Providers which leads to failure of record creation. +Once you specify the ingresscontroller or router name then that will be matched by the external-dns and the router canonical hostname corresponding to this routerName(which is present in route's Status.Ingress.RouterName field) is selected and a CNAME record of this route host pointing to this router canonical hostname is created. + +Your externaldns CR shall be created as per the following example. +Replace names in the domain section and zone ID as per your environment. +This is example is for AWS environment. + +```yaml + + apiVersion: externaldns.olm.openshift.io/v1alpha1 + kind: ExternalDNS + metadata: + name: sample1 + spec: + domains: + - filterType: Include + matchType: Exact + names: apps.miheer.externaldns + provider: + type: AWS + source: + hostnameAnnotation: Allow + openshiftRouteOptions: + routerName: default + type: OpenShiftRoute + zones: + - Z05387772BD5723IZFRX3 + +``` + +This will create an externaldns pod with the following container args under spec in the external-dns namespace where `- --source=openshift-route` and `- --openshift-router-name=default` is added by the external-dns-operator. + +``` +spec: + containers: + - args: + - --domain-filter=apps.misalunk.externaldns + - --metrics-address=127.0.0.1:7979 + - --txt-owner-id=external-dns-sample1 + - --provider=aws + - --source=openshift-route + - --policy=sync + - --registry=txt + - --log-level=debug + - --zone-id-filter=Z05387772BD5723IZFRX3 + - --openshift-router-name=default + - --txt-prefix=external-dns- + +``` + +### For OCP 3.11 environment ### Prepare ROUTER_CANONICAL_HOSTNAME in default/router deployment Read and go through [Finding the Host Name of the Router](https://docs.openshift.com/container-platform/3.11/install_config/router/default_haproxy_router.html#finding-router-hostname). If no ROUTER_CANONICAL_HOSTNAME is set, you must annotate each route with external-dns.alpha.kubernetes.io/target! diff --git a/docs/tutorials/ultradns.md b/docs/tutorials/ultradns.md index 7b7312553..5f37093c5 100644 --- a/docs/tutorials/ultradns.md +++ b/docs/tutorials/ultradns.md @@ -237,7 +237,7 @@ spec: ``` - Then, create service file called 'expose-apple-banana-app.yaml' to expose the services. For more information to deploy ingress controller, refer to (https://kubernetes.github.io/ingress-nginx/deploy/) ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress @@ -252,8 +252,10 @@ spec: paths: - path: /apple backend: - serviceName: example-service - servicePort: 5678 + service: + name: example-service + port: + number: 5678 ``` - Then, create the deployment and service: ```console @@ -298,7 +300,7 @@ $ kubectl delete -f external-dns.yaml ports: - port: 5678 # Default port for image --- - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress @@ -313,8 +315,10 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service - servicePort: 5678 + service: + name: example-service + port: + number: 5678 ``` - _Config File Example – Kubernetes cluster service from different cloud vendors_ ```yaml @@ -434,7 +438,7 @@ $ kubectl delete -f external-dns.yaml ports: - port: 5680 # Default port for image --- - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress @@ -449,10 +453,12 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service - servicePort: 5678 + service: + name: example-service + port: + number: 5678 --- - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress1 @@ -467,10 +473,12 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service1 - servicePort: 5679 + service: + name: example-service1 + port: + number: 5679 --- - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress2 @@ -485,8 +493,10 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service2 - servicePort: 5680 + service: + name: example-service2 + port: + number: 5680 ``` - _Config File Example – Kubernetes cluster service from different cloud vendors_ ```yaml @@ -572,6 +582,7 @@ $ kubectl delete -f external-dns.yaml ports: - port: 5679 # Default port for image --- + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress @@ -586,10 +597,12 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service - servicePort: 5678 + service: + name: example-service + port: + number: 5678 --- - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress1 @@ -604,8 +617,10 @@ $ kubectl delete -f external-dns.yaml paths: - path: /apple backend: - serviceName: example-service1 - servicePort: 5679 + service: + name: example-service1 + port: + number: 5679 ``` - Then, create the deployment and service: ```console diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index 7d7be8e00..9a78ffc97 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -35,6 +35,8 @@ const ( RecordTypeSRV = "SRV" // RecordTypeNS is a RecordType enum value RecordTypeNS = "NS" + // RecordTypePTR is a RecordType enum value + RecordTypePTR = "PTR" ) // TTL is a structure defining the TTL of a DNS record diff --git a/go.mod b/go.mod index 3a1ab95a3..c41477dbb 100644 --- a/go.mod +++ b/go.mod @@ -3,79 +3,83 @@ module sigs.k8s.io/external-dns go 1.16 require ( - cloud.google.com/go v0.50.0 + cloud.google.com/go v0.97.0 git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d - github.com/Azure/azure-sdk-for-go v45.1.0+incompatible - github.com/Azure/go-autorest/autorest v0.11.10 - github.com/Azure/go-autorest/autorest/adal v0.9.5 + github.com/Azure/azure-sdk-for-go v46.4.0+incompatible + github.com/Azure/go-autorest/autorest v0.11.21 + github.com/Azure/go-autorest/autorest/adal v0.9.16 github.com/Azure/go-autorest/autorest/to v0.4.0 - github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 + github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 + github.com/StackExchange/dnscontrol v0.2.8 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/kingpin v2.2.5+incompatible github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 - github.com/aws/aws-sdk-go v1.31.4 + github.com/aws/aws-sdk-go v1.40.53 github.com/bodgit/tsig v0.0.2 - github.com/cloudflare/cloudflare-go v0.10.1 + github.com/cloudflare/cloudflare-go v0.13.2 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/datawire/ambassador v1.6.0 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba - github.com/digitalocean/godo v1.36.0 + github.com/digitalocean/godo v1.69.1 github.com/dnsimple/dnsimple-go v0.60.0 - github.com/exoscale/egoscale v0.18.1 - github.com/fatih/structs v1.1.0 // indirect + github.com/exoscale/egoscale v0.73.2 github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09 + github.com/go-logr/logr v1.1.0 // indirect github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f - github.com/google/go-cmp v0.5.2 - github.com/gophercloud/gophercloud v0.1.0 - github.com/gorilla/mux v1.7.4 // indirect - github.com/hooklift/gowsdl v0.4.0 + github.com/google/go-cmp v0.5.6 + github.com/gophercloud/gophercloud v0.21.0 + github.com/hooklift/gowsdl v0.5.0 github.com/infobloxopen/infoblox-go-client v1.1.1 - github.com/linki/instrumented_http v0.2.0 - github.com/linode/linodego v0.19.0 - github.com/maxatome/go-testdeep v1.4.0 + github.com/json-iterator/go v1.1.12 // indirect + github.com/linki/instrumented_http v0.3.0 + github.com/linode/linodego v0.32.2 + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/maxatome/go-testdeep v1.10.1 github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1 + github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/nesv/go-dynect v0.6.0 github.com/nic-at/rc0go v1.1.1 + github.com/onsi/gomega v1.14.0 // indirect github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73 github.com/oracle/oci-go-sdk v21.4.0+incompatible github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 github.com/pkg/errors v0.9.1 - github.com/projectcontour/contour v1.5.0 - github.com/prometheus/client_golang v1.7.1 + github.com/projectcontour/contour v1.18.1 + github.com/prometheus/client_golang v1.11.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f - github.com/sirupsen/logrus v1.6.0 + github.com/sirupsen/logrus v1.8.1 github.com/smartystreets/gunit v1.3.4 // indirect - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/terra-farm/udnssdk v1.3.5 // indirect - github.com/transip/gotransip/v6 v6.6.0 + github.com/transip/gotransip/v6 v6.6.2 github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 - github.com/vultr/govultr/v2 v2.5.1 - go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 - go.uber.org/ratelimit v0.1.0 - golang.org/x/net v0.0.0-20201224014010-6772e930b67b - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - golang.org/x/tools v0.0.0-20200708003708-134513de8882 // indirect - google.golang.org/api v0.15.0 + github.com/vultr/govultr/v2 v2.9.0 + go.etcd.io/etcd/api/v3 v3.5.0 + go.etcd.io/etcd/client/v3 v3.5.0 + go.uber.org/ratelimit v0.2.0 + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b + golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/api v0.58.0 + gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1 - gopkg.in/yaml.v2 v2.3.0 - honnef.co/go/tools v0.0.1-2020.1.4 // indirect + gopkg.in/yaml.v2 v2.4.0 istio.io/api v0.0.0-20210128181506-0c4b8e54850f istio.io/client-go v0.0.0-20210128182905-ee2edd059e02 - k8s.io/api v0.18.8 - k8s.io/apimachinery v0.18.8 - k8s.io/client-go v0.18.8 - k8s.io/kubernetes v1.13.0 + k8s.io/api v0.22.2 + k8s.io/apimachinery v0.22.2 + k8s.io/client-go v0.22.2 + k8s.io/klog/v2 v2.20.0 // indirect + k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) -replace ( - github.com/golang/glog => github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d - // TODO(jpg): Pin gRPC to work around breaking change until all dependences are upgraded: https://github.com/etcd-io/etcd/issues/11563 - google.golang.org/grpc => google.golang.org/grpc v1.26.0 - k8s.io/klog => github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b -) +replace k8s.io/klog/v2 => github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a diff --git a/go.sum b/go.sum index c69b9d724..e1c8538f9 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,51 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0 h1:0E3eE8MX426vUOs7aHfI7aN1BrIzzzf4ccKCSfSjGmc= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -18,34 +53,50 @@ git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d h1:d6sdozgfqt git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= git.lukeshu.com/go/libsystemd v0.5.3/go.mod h1:FfDoP0i92r4p5Vn4NCLxvjkd7rCOe6otPa4L6hZg9WM= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v45.1.0+incompatible h1:kxtaPD8n2z5Za+9e3sKsYG2IX6PG2R6VXtgS7gAbh3A= -github.com/Azure/azure-sdk-for-go v45.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v46.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v46.4.0+incompatible h1:fCN6Pi+tEiEwFa8RSmtVlFHRXEZ+DJm9gfx/MKqYWw4= +github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.11.10 h1:j5sGbX7uj1ieYYkQ3Mpvewd4DCsEQ+ZeJpqnSM9pjnM= -github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.6/go.mod h1:V6p3pKZx1KKkJubbxnDWrzNhEIfOy/pTGasLqzHIPHs= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk= +github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.4/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc= +github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -63,21 +114,28 @@ github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHS github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a h1:4D87FDUGSrfLp/NJmYxybiC8+OR8s5eULyNgmmxAI9U= +github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a/go.mod h1:AsBYmtPY5rgsIyjQZDLO+Dwdx91Jmi6uxu7LO6Xf9iw= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ= +github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8= +github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 h1:FJF58TWBaQnGqTIcziIP5/z3TTqWUn8fh26z03oZE2c= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= +github.com/ahmetb/gen-crd-api-reference-docs v0.2.1-0.20201224172655-df869c1245d4/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= +github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= @@ -91,14 +149,18 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 h1:3ynCSeUh9OtJLd/OzLapM1DLDv2g+0yyDdkLqSfZCaQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -114,8 +176,9 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.31.4 h1:YZ0uEYIWeanGuAomElHmRWMAbXVqrQixxgf2vtIjO6M= -github.com/aws/aws-sdk-go v1.31.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.30/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go v1.40.53 h1:wi4UAslOQ1HfF2NjnIwI6st8n7sQg7shUUNLkaCgIpc= +github.com/aws/aws-sdk-go v1.40.53/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -124,11 +187,14 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bodgit/tsig v0.0.2 h1:seNt23SrPW8dkWoyRYzdeuqFEzr+lDc0dAJvo94xB8U= github.com/bodgit/tsig v0.0.2/go.mod h1:0mYe0t9it36SOvDQyeFekc7bLtvljFz7H9vHS/nYbgc= +github.com/bombsimon/logrusr v1.0.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= @@ -141,16 +207,21 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.1 h1:d2CL6F9k2O0Ux0w27LgogJ5UOzZRj6a/hDPFqPP68d8= -github.com/cloudflare/cloudflare-go v0.10.1/go.mod h1:C0Y6eWnTJPMK2ceuOxx2pjh78UUHihcXeTTHb8r7QjU= +github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A= +github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= github.com/cncf/udpa v0.0.0-20200324003616-bae28a880fdb/go.mod h1:HNVadOiXCy7Jk3R2knJ+qm++zkncJxxBMpjdGgJ+UJc= -github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200324003616-bae28a880fdb/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= @@ -166,10 +237,9 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -178,33 +248,39 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/datawire/ambassador v1.6.0 h1:4KduhY/wqtv0jK8sMVQNtENHy9fmoXugsuFp/UrM0Ts= github.com/datawire/ambassador v1.6.0/go.mod h1:mV5EhoG/NnHBsffmLnjrq+x4ZNkYDWFZXW9R+AueUiE= github.com/datawire/pf v0.0.0-20180510150411-31a823f9495a/go.mod h1:H8uUmE8qqo7z9u30MYB9riLyRckPHOPBk9ZdCuH+dQQ= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw= +github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba h1:p6poVbjHDkKa+wtC8frBMwQtT3BmqGYBjzMwJ63tuR4= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.36.0 h1:eRF8wNzHZyU7/wI3De/MQgiVSWdseDaf27bXj2gnOO0= -github.com/digitalocean/godo v1.36.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/digitalocean/godo v1.44.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/digitalocean/godo v1.69.1 h1:aCyfwth8R3DeOaWB9J9E8v7cjlDIlF19eXTt8R3XhTE= +github.com/digitalocean/godo v1.69.1/go.mod h1:epPuOzTOOJujNo0nduDj2D5O1zu8cSpp9R+DdN0W9I0= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk= @@ -225,61 +301,84 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ecodia/golang-awaitility v0.0.0-20180710094957-fb55e59708c7/go.mod h1:etn7NbLy5UviLk20XMZbSn/0AigF3Zfx7wwaEZ3fyIk= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210614203518-782de910ff04/go.mod h1:+baROYa9cKpDyN21rZlsSq5zgBrZOrMTNu78Lm3fFJQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.3.0-java.0.20200609174644-bd816e4522c1/go.mod h1:bjmEhrMDubXDd0uKxnWwRmgSsiEv2CkJliIHnj6ETm8= -github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI= -github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exoscale/egoscale v0.73.2 h1:YGy0YufwuaRduaqTM6ptyZQpBxr2L9eA6kKfmGW8jKg= +github.com/exoscale/egoscale v0.73.2/go.mod h1:Fpy/cIVjiUkI0DntkoFl4fZpaeqRFQ6SVcX1A0APg0E= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 h1:jmwW6QWvUO2OPe22YfgFvBaaZlSr8Rlrac5lZvG6IdM= github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99/go.mod h1:4mP9w9+vYGw2jUx2+2v03IA+phyQQjNRR4AL3uxlNrs= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09 h1:w+iZczt5J4LJa13RX5uguKI866vIEMOESgXr4XcwrwA= github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09/go.mod h1:Vv36tv/GTi8FNAFIQ88+9GPHm4CAihAuJu7rfqRJ9aY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.2.1-0.20200730175230-ee2de8da5be6/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.1.0 h1:nAbevmWlS2Ic4m4+/An5NXkaGqlqpbBgdcuThZxnZyI= +github.com/go-logr/logr v1.1.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= @@ -295,6 +394,7 @@ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= @@ -332,94 +432,147 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f h1:kSqKc8ouCLIBHqdj9a9xxhtxlZhNqbePClixA4HoM44= github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gookit/color v1.2.3/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= -github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.21.0 h1:21rxoQM7cSaZCPgfP45h71Vt1amZa942l7AtUWLOI2I= +github.com/gophercloud/gophercloud v0.21.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -428,38 +581,38 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -468,42 +621,55 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1: github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hooklift/gowsdl v0.4.0 h1:luskQG8h3M0CYrcSFl9ObpWs3pzIsEfYou1cuSwKiCk= -github.com/hooklift/gowsdl v0.4.0/go.mod h1:TYmt7jpe3F5zLlMtKGetjHLwUBIAF5JCd+NYq+mQ/Zk= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hooklift/gowsdl v0.5.0 h1:DE8RevqhGPLchumV/V7OwbCzfJ8lcozFg1uWC/ESCBQ= +github.com/hooklift/gowsdl v0.5.0/go.mod h1:9kRc402w9Ci/Mek5a1DNgTmU14yPY8fMumxNVvxhis4= +github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= +github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g= +github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -517,31 +683,38 @@ github.com/jcmturner/gokrb5/v8 v8.4.1/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZ github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jetstack/cert-manager v1.3.0/go.mod h1:Hfe4GE3QuRzbrsuReQD5R3PXZqrdfJ2kZ42K67V/V0w= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -549,17 +722,20 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d h1:JV46OtdhH2vVt8mJ1EWUE94k99vbN9fZs1WQ8kcEapU= github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:CHQ3o5KBH1PIS2Fb1mRLTIWO5YzP9kSUB3KoCICwlvA= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -569,13 +745,15 @@ github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linki/instrumented_http v0.2.0 h1:zLhcB3Q/McQQqml3qd5kzdZ0cGnL3vquPFIW2338f5Y= -github.com/linki/instrumented_http v0.2.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk= -github.com/linode/linodego v0.19.0 h1:JxYBTxUcXcOlCwLMuugc7Il0RMtJ7riaddqz6gG/ACA= -github.com/linode/linodego v0.19.0/go.mod h1:XOWXRHjqeU2uPS84tKLgfWIfTlv3TYzCS0io4GOQzEI= +github.com/linki/instrumented_http v0.3.0 h1:dsN92+mXpfZtjJraartcQ99jnuw7fqsnPDjr85ma2dA= +github.com/linki/instrumented_http v0.3.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk= +github.com/linode/linodego v0.32.2 h1:ubQMQuQGt73N1hMzY15lx2MRdZbcGbuvEVlSpJEB+lc= +github.com/linode/linodego v0.32.2/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -585,22 +763,33 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/maxatome/go-testdeep v1.4.0 h1:vKQh3/lHKAMsxggya/fXB6fLbf70c7k6wlLveuS9sKE= -github.com/maxatome/go-testdeep v1.4.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxatome/go-testdeep v1.10.1 h1:3kcvVpYaKneKON75eNXVCVNAJNcMMcDaZzLclSklRIE= +github.com/maxatome/go-testdeep v1.10.1/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -614,27 +803,34 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/protoc-gen-go-json v0.0.0-20190813154521-ece073100ced/go.mod h1:VhkV06JZBMrulb3fNiUvM5Lmz3yc7ei3omzocl9UtCw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/crd-schema-fuzz v1.0.0/go.mod h1:4z/rcm37JxUkSsExFcLL6ZIT1SgDRdLiu7qq1evdVS0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -649,33 +845,42 @@ github.com/nesv/go-dynect v0.6.0 h1:Ow/DiSm4LAISwnFku/FITSQHnU6pBvhQMsUE5Gu6Oq4= github.com/nesv/go-dynect v0.6.0/go.mod h1:GHRBRKzTwjAMhosHJQq/KrZaFkXIFyJ5zRE7thGXXrs= github.com/nic-at/rc0go v1.1.1 h1:bf2gTwYecJEh7qmnOEuarXKueZn4A8N08U1Uop3K8+s= github.com/nic-at/rc0go v1.1.1/go.mod h1:KEa3H5fmDNXCaXSqOeAZxkKnG/8ggr1OHIG25Ve7fjU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= +github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -710,8 +915,11 @@ github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible/go.mod h1:xlUlxe/2ItGlQyMTstqeDv9r3U4obH7xYd26TbDQutY= +github.com/pavel-v-chernykh/keystore-go/v4 v4.1.0/go.mod h1:2ejgys4qY+iNVW1IittZhyRYA6MNv8TgM6VHqojbB9g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= @@ -726,24 +934,22 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/projectcontour/contour v1.5.0 h1:4zz4XWKKb1Nk2zXVQ27ZpoTivjG2DQvYLwrStcK8Fqc= -github.com/projectcontour/contour v1.5.0/go.mod h1:y1MEsorL/Q8lBG5BZz8Gzryi9L5ryVALOuHicmAdfW8= +github.com/projectcontour/contour v1.18.1 h1:J1MmqchDFuou066Gkr13eYtPaBnfb5j7h5ETH9OfIfQ= +github.com/projectcontour/contour v1.18.1/go.mod h1:prL5IyPK2ek6MUWwm8C5S1pHsWOE/2Y8Ym7ak3feVpo= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -754,31 +960,30 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -787,7 +992,10 @@ github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlp github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE= @@ -801,12 +1009,15 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= @@ -816,8 +1027,6 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/smartystreets/gunit v1.3.4 h1:iHc8Rfhb/uCOc9a3KGuD3ut22L+hLIVaqR1o5fS6zC4= github.com/smartystreets/gunit v1.3.4/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -829,6 +1038,7 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -838,29 +1048,32 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/terra-farm/udnssdk v1.3.5 h1:MNR3adfuuEK/l04+jzo8WW/0fnorY+nW515qb3vEr6I= github.com/terra-farm/udnssdk v1.3.5/go.mod h1:8RnM56yZTR7mYyUIvrDgXzdRaEyFIzqdEi7+um26Sv8= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/transip/gotransip/v6 v6.6.0 h1:dAHCTZzX98H6QE2kA4R9acAXu5RPPTwMSUFtpKZF3Nk= -github.com/transip/gotransip/v6 v6.6.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= +github.com/transip/gotransip/v6 v6.6.2 h1:+d3QO5Cyfh9n/J5OZxz8roer4JQIdmYvHVHExOP9loE= +github.com/transip/gotransip/v6 v6.6.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= @@ -868,37 +1081,49 @@ github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 h1:n7unet github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60/go.mod h1:43vmy6GEvRuVMpGEWfJ/JoEM6RIqUQI1/tb8JqZR1zI= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 h1:UbVjBjgJUYGD8MlobEdOR+yTeNqaNa2Gf1/nskVNCSE= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= -github.com/vultr/govultr/v2 v2.5.1 h1:Bh3G7nqHs0Gv7OQRExfYFppbuscwVKFDK05b8XBYYnQ= -github.com/vultr/govultr/v2 v2.5.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU= +github.com/vultr/govultr/v2 v2.9.0 h1:n0a0fGOiHAE07twu1VR3jWTDFDE0+DJ/cIqZqX9IlNw= +github.com/vultr/govultr/v2 v2.9.0/go.mod h1:JjUljQdSZx+MELCAJvZ/JH32bJotmflnsyS0NOjb8Jg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 h1:C7kWARE8r64ppRadl40yfNo6pag+G6ocvGU2xZ6yNes= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -906,26 +1131,34 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= -go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= +go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -940,43 +1173,59 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -998,7 +1247,6 @@ golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1006,30 +1254,67 @@ golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180425194835-bb9c189858d9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1037,11 +1322,13 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1053,42 +1340,107 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1096,56 +1448,121 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190929041059-e7abfedfabcf/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200708003708-134513de8882 h1:x4Two2lSwHxTqR+eal4lB4ydUnTvmDDpPQeL92ZHDgA= -golang.org/x/tools v0.0.0-20200708003708-134513de8882/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1153,25 +1570,117 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba h1:pRj9OXZbwNtbtZtOB4dLwfK4u+EVRMvP+e9zKkg2grM= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 h1:5Tbluzus3QxoAJx4IefGt1W0HQZW4nuMrVk684jI74Q= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -1180,18 +1689,21 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1 h1:+fgY/3ngqdBW9oLQCMwL5g+QRkKFPJH05fx2/pipqRQ= gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -1199,23 +1711,32 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= helm.sh/helm/v3 v3.2.4/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= istio.io/api v0.0.0-20210128181506-0c4b8e54850f h1:zUFsawgPj5oI9p5cf91YCExRlxLIVsEkIunN9ODUSJs= istio.io/api v0.0.0-20210128181506-0c4b8e54850f/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64= @@ -1224,104 +1745,159 @@ istio.io/client-go v0.0.0-20210128182905-ee2edd059e02/go.mod h1:oXMjFUWhxlReUSbg istio.io/gogo-genproto v0.0.0-20190904133402-ee07f2785480/go.mod h1:uKtbae4K9k2rjjX4ToV0l6etglbc1i7gqQ94XdkshzY= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a h1:w7zILua2dnYo9CxImhpNW4NE/8ZxEoc/wfBfHrhUhrE= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= -k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= -k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.1/go.mod h1:3My4jorQWzSs5a+l7Ge6JBbIxChLnY8HnuT58ZWolss= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= -k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4= -k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= -k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= -k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= +k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= +k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= +k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU= +k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= -k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= -k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= +k8s.io/apiextensions-apiserver v0.19.0/go.mod h1:znfQxNpjqz/ZehvbfMg5N6fvBJW5Lqu5HVLTJQdP4Fs= +k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= +k8s.io/apiextensions-apiserver v0.20.2/go.mod h1:F6TXp389Xntt+LUq3vw6HFOLttPa0V8821ogLGwb6Zs= +k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= -k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= -k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= -k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM= +k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= +k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA= +k8s.io/apiserver v0.21.2/go.mod h1:lN4yBoGyiNT7SC1dmNk0ue6a5Wi6O3SWOIw91TsucQw= k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g= -k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/cli-runtime v0.19.0/go.mod h1:tun9l0eUklT8IHIM0jors17KmUjcrAxn0myoBYwuNuo= k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= -k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= -k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= -k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= -k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= +k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= +k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA= +k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= +k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= -k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= -k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= +k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= +k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= +k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0= +k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/helm v2.16.9+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.8.1-0.20210504170414-0cc9b8363efc/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.20.0 h1:tlyxlSvd63k7axjhuchckaRJm+a92z5GSOrTOQY5sHw= +k8s.io/klog/v2 v2.20.0/go.mod h1:Gm8eSIfQN6457haJuPaMxZw4wyP5k+ykPFlrhQDvhvw= +k8s.io/kube-aggregator v0.19.0/go.mod h1:1Ln45PQggFAG8xOqWPIYMxUq8WNtpPnYsbUJ39DpF/A= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= -k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8= +k8s.io/kubectl v0.19.0/go.mod h1:gPCjjsmE6unJzgaUNXIFGZGafiUp5jh0If3F/x7/rRg= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/metrics v0.19.0/go.mod h1:WykpW8B60OeAJx1imdwUgyOID2kDljr/Q+1zrPJ98Wo= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210305010621-2afb4311ab10/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e h1:ldQh+neBabomh7+89dTpiFAB8tGdfVmuIzAHbvtl+9I= +k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= -sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= -sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= -sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= -sigs.k8s.io/controller-tools v0.2.9/go.mod h1:ArP7w60JQKkZf7UU2oWTVnEhoNGA+sOMyuSuS+JFNDQ= +sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= +sigs.k8s.io/controller-runtime v0.9.2 h1:MnCAsopQno6+hI9SgJHKddzXpmv2wtouZz6931Eax+Q= +sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk= +sigs.k8s.io/controller-tools v0.2.9-0.20200414181213-645d44dca7c0/go.mod h1:YKE/iHvcKITCljdnlqHYe+kAt7ZldvtAwUzQff0k1T0= sigs.k8s.io/controller-tools v0.3.1-0.20200517180335-820a4a27ea84/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= +sigs.k8s.io/controller-tools v0.5.0/go.mod h1:JTsstrMpxs+9BUj6eGuAaEb6SDSPTeVtUyp0jmnAM/I= +sigs.k8s.io/gateway-api v0.3.0/go.mod h1:Wb8bx7QhGVZxOSEU3i9vw/JqTB5Nlai9MLMYVZeDmRQ= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/kustomize/kyaml v0.1.1/go.mod h1:/NdPPfrperSCGjm55cwEro1loBVtbtVIXSb7FguK6uk= -sigs.k8s.io/service-apis v0.0.0-20200213014236-51691dd89266/go.mod h1:s6Cwc0sg5w4xxZ/HEr88qPkQ4CITr80lnMpYZUzL9VY= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ= +software.sslmate.com/src/go-pkcs12 v0.0.0-20200830195227-52f69702a001/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/kustomize/external-dns-clusterrole.yaml b/kustomize/external-dns-clusterrole.yaml index 2ca6ceed2..5bcc705b4 100644 --- a/kustomize/external-dns-clusterrole.yaml +++ b/kustomize/external-dns-clusterrole.yaml @@ -9,6 +9,9 @@ rules: - apiGroups: ['extensions'] resources: ['ingresses'] verbs: ['get', 'watch', 'list'] - - apiGroups: [''] - resources: ['nodes'] - verbs: ['list'] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["watch", "list"] diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml index ed5dd8058..ac086b49c 100644 --- a/kustomize/kustomization.yaml +++ b/kustomize/kustomization.yaml @@ -3,7 +3,7 @@ kind: Kustomization images: - name: k8s.gcr.io/external-dns/external-dns - newTag: v0.8.0 + newTag: v0.10.0 resources: - ./external-dns-deployment.yaml diff --git a/main.go b/main.go index 3473c2c2d..d7d24ae1f 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/labels" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/external-dns/controller" @@ -100,11 +101,14 @@ func main() { go serveMetrics(cfg.MetricsAddress) go handleSigterm(cancel) + // error is explicitly ignored because the filter is already validated in validation.ValidateConfig + labelSelector, _ := labels.Parse(cfg.LabelFilter) + // Create a source.Config from the flags passed by the user. sourceCfg := &source.Config{ Namespace: cfg.Namespace, AnnotationFilter: cfg.AnnotationFilter, - LabelFilter: cfg.LabelFilter, + LabelFilter: labelSelector, FQDNTemplate: cfg.FQDNTemplate, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, @@ -128,6 +132,7 @@ func main() { SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion, RequestTimeout: cfg.RequestTimeout, DefaultTargets: cfg.DefaultTargets, + OCPRouterName: cfg.OCPRouterName, } // Lookup all the selected sources by names and pass them the desired configuration. @@ -219,7 +224,7 @@ func main() { case "rcodezero": p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt) case "google": - p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun) + p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun) case "digitalocean": p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize) case "hetzner": @@ -245,6 +250,7 @@ func main() { MaxResults: cfg.InfobloxMaxResults, DryRun: cfg.DryRun, FQDNRexEx: cfg.InfobloxFQDNRegEx, + CreatePTR: cfg.InfobloxCreatePTR, }, ) case "dyn": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index b283e7b35..09f5496b1 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -23,6 +23,8 @@ import ( "strconv" "time" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/external-dns/endpoint" "github.com/alecthomas/kingpin" @@ -67,6 +69,7 @@ type Config struct { GoogleProject string GoogleBatchChangeSize int GoogleBatchChangeInterval time.Duration + GoogleZoneVisibility string DomainFilter []string ExcludeDomains []string RegexDomainFilter *regexp.Regexp @@ -108,6 +111,7 @@ type Config struct { InfobloxView string InfobloxMaxResults int InfobloxFQDNRegEx string + InfobloxCreatePTR bool DynCustomerName string DynUsername string DynPassword string `secure:"yes"` @@ -173,6 +177,7 @@ type Config struct { GoDaddySecretKey string `secure:"yes"` GoDaddyTTL int64 GoDaddyOTE bool + OCPRouterName string } var defaultConfig = &Config{ @@ -186,7 +191,7 @@ var defaultConfig = &Config{ Sources: nil, Namespace: "", AnnotationFilter: "", - LabelFilter: "", + LabelFilter: labels.Everything().String(), FQDNTemplate: "", CombineFQDNAndAnnotation: false, IgnoreHostnameAnnotation: false, @@ -200,6 +205,7 @@ var defaultConfig = &Config{ GoogleProject: "", GoogleBatchChangeSize: 1000, GoogleBatchChangeInterval: time.Second, + GoogleZoneVisibility: "", DomainFilter: []string{}, ExcludeDomains: []string{}, RegexDomainFilter: regexp.MustCompile(""), @@ -237,6 +243,7 @@ var defaultConfig = &Config{ InfobloxView: "", InfobloxMaxResults: 0, InfobloxFQDNRegEx: "", + InfobloxCreatePTR: false, OCIConfigFile: "/etc/kubernetes/oci.yaml", InMemoryZones: []string{}, OVHEndpoint: "ovh-eu", @@ -357,11 +364,12 @@ func (cfg *Config) ParseFlags(args []string) error { // Flags related to Skipper RouteGroup app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion) - // Flags related to processing sources + // Flags related to processing source app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress") + app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName) app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace) app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter) - app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently only supported by source CRD").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) + app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently supported by source types CRD, ingress, service and openshift-route").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate) app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation) app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation) @@ -389,6 +397,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject) app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize) app.Flag("google-batch-change-interval", "When using the Google provider, set the interval between batch changes.").Default(defaultConfig.GoogleBatchChangeInterval.String()).DurationVar(&cfg.GoogleBatchChangeInterval) + app.Flag("google-zone-visibility", "When using the Google provider, filter for zones with this visibility (optional, options: public, private)").Default(defaultConfig.GoogleZoneVisibility).EnumVar(&cfg.GoogleZoneVisibility, "", "public", "private") app.Flag("alibaba-cloud-config-file", "When using the Alibaba Cloud provider, specify the Alibaba Cloud configuration file (required when --provider=alibabacloud").Default(defaultConfig.AlibabaCloudConfigFile).StringVar(&cfg.AlibabaCloudConfigFile) app.Flag("alibaba-cloud-zone-type", "When using the Alibaba Cloud provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AlibabaCloudZoneType).EnumVar(&cfg.AlibabaCloudZoneType, "", "public", "private") app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private") @@ -423,6 +432,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("infoblox-view", "DNS view (default: \"\")").Default(defaultConfig.InfobloxView).StringVar(&cfg.InfobloxView) app.Flag("infoblox-max-results", "Add _max_results as query parameter to the URL on all API requests. The default is 0 which means _max_results is not set and the default of the server is used.").Default(strconv.Itoa(defaultConfig.InfobloxMaxResults)).IntVar(&cfg.InfobloxMaxResults) app.Flag("infoblox-fqdn-regex", "Apply this regular expression as a filter for obtaining zone_auth objects. This is disabled by default.").Default(defaultConfig.InfobloxFQDNRegEx).StringVar(&cfg.InfobloxFQDNRegEx) + app.Flag("infoblox-create-ptr", "When using the Infoblox provider, create a ptr entry in addition to an entry").Default(strconv.FormatBool(defaultConfig.InfobloxCreatePTR)).BoolVar(&cfg.InfobloxCreatePTR) app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName) app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername) app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index a90279047..5ecde3765 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -46,6 +46,7 @@ var ( GoogleProject: "", GoogleBatchChangeSize: 1000, GoogleBatchChangeInterval: time.Second, + GoogleZoneVisibility: "", DomainFilter: []string{""}, ExcludeDomains: []string{""}, RegexDomainFilter: regexp.MustCompile(""), @@ -114,6 +115,7 @@ var ( DigitalOceanAPIPageSize: 50, ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, RFC2136BatchChangeSize: 50, + OCPRouterName: "default", } overriddenConfig = &Config{ @@ -134,6 +136,7 @@ var ( GoogleProject: "project", GoogleBatchChangeSize: 100, GoogleBatchChangeInterval: time.Second * 2, + GoogleZoneVisibility: "private", DomainFilter: []string{"example.org", "company.com"}, ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"}, RegexDomainFilter: regexp.MustCompile("(example\\.org|company\\.com)$"), @@ -223,6 +226,7 @@ func TestParseFlags(t *testing.T) { args: []string{ "--source=service", "--provider=google", + "--openshift-router-name=default", }, envVars: map[string]string{}, expected: minimalConfig, @@ -249,6 +253,7 @@ func TestParseFlags(t *testing.T) { "--google-project=project", "--google-batch-change-size=100", "--google-batch-change-interval=2s", + "--google-zone-visibility=private", "--azure-config-file=azure.json", "--azure-resource-group=arg", "--azure-subscription-id=arg", @@ -351,6 +356,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_GOOGLE_PROJECT": "project", "EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_SIZE": "100", "EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_INTERVAL": "2s", + "EXTERNAL_DNS_GOOGLE_ZONE_VISIBILITY": "private", "EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json", "EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg", "EXTERNAL_DNS_AZURE_SUBSCRIPTION_ID": "arg", diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index 9f8c3889a..c5ebc3845 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" ) @@ -110,5 +112,9 @@ func ValidateConfig(cfg *externaldns.Config) error { return errors.New("txt-prefix and txt-suffix are mutual exclusive") } + _, err := labels.Parse(cfg.LabelFilter) + if err != nil { + return errors.New("--label-filter does not specify a valid label selector") + } return nil } diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 51bce1f43..beff1d5f3 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -414,14 +414,9 @@ func (p *AWSProvider) doRecords(ctx context.Context, action string, endpoints [] return errors.Wrapf(err, "failed to list zones, aborting %s doRecords action", action) } - records, err := p.records(ctx, zones) - if err != nil { - log.Errorf("failed to list records while preparing %s doRecords action: %s", action, err) - } - p.AdjustEndpoints(endpoints) - return p.submitChanges(ctx, p.newChanges(action, endpoints, records, zones), zones) + return p.submitChanges(ctx, p.newChanges(action, endpoints), zones) } // UpdateRecords updates a given set of old records to a new set of records in a given hosted zone. @@ -431,15 +426,10 @@ func (p *AWSProvider) UpdateRecords(ctx context.Context, updates, current []*end return errors.Wrapf(err, "failed to list zones, aborting UpdateRecords") } - records, err := p.records(ctx, zones) - if err != nil { - log.Errorf("failed to list records while preparing UpdateRecords: %s", err) - } - - return p.submitChanges(ctx, p.createUpdateChanges(updates, current, records, zones), zones) + return p.submitChanges(ctx, p.createUpdateChanges(updates, current), zones) } -func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints []*endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) []*route53.Change { +func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints []*endpoint.Endpoint) []*route53.Change { var deletes []*endpoint.Endpoint var creates []*endpoint.Endpoint var updates []*endpoint.Endpoint @@ -459,9 +449,9 @@ func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints []*endpoint } combined := make([]*route53.Change, 0, len(deletes)+len(creates)+len(updates)) - combined = append(combined, p.newChanges(route53.ChangeActionCreate, creates, recordsCache, zones)...) - combined = append(combined, p.newChanges(route53.ChangeActionUpsert, updates, recordsCache, zones)...) - combined = append(combined, p.newChanges(route53.ChangeActionDelete, deletes, recordsCache, zones)...) + combined = append(combined, p.newChanges(route53.ChangeActionCreate, creates)...) + combined = append(combined, p.newChanges(route53.ChangeActionUpsert, updates)...) + combined = append(combined, p.newChanges(route53.ChangeActionDelete, deletes)...) return combined } @@ -487,20 +477,11 @@ func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e return errors.Wrap(err, "failed to list zones, not applying changes") } - records, ok := ctx.Value(provider.RecordsContextKey).([]*endpoint.Endpoint) - if !ok { - var err error - records, err = p.records(ctx, zones) - if err != nil { - log.Errorf("failed to get records while preparing to applying changes: %s", err) - } - } - - updateChanges := p.createUpdateChanges(changes.UpdateNew, changes.UpdateOld, records, zones) + updateChanges := p.createUpdateChanges(changes.UpdateNew, changes.UpdateOld) combinedChanges := make([]*route53.Change, 0, len(changes.Delete)+len(changes.Create)+len(updateChanges)) - combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionCreate, changes.Create, records, zones)...) - combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionDelete, changes.Delete, records, zones)...) + combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionCreate, changes.Create)...) + combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionDelete, changes.Delete)...) combinedChanges = append(combinedChanges, updateChanges...) return p.submitChanges(ctx, combinedChanges, zones) @@ -567,11 +548,11 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan } // newChanges returns a collection of Changes based on the given records and action. -func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) []*route53.Change { +func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint) []*route53.Change { changes := make([]*route53.Change, 0, len(endpoints)) for _, endpoint := range endpoints { - change, dualstack := p.newChange(action, endpoint, recordsCache, zones) + change, dualstack := p.newChange(action, endpoint) changes = append(changes, change) if dualstack { // make a copy of change, modify RRS type to AAAA, then add new change @@ -619,7 +600,7 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoin // returned Change is based on the given record by the given action, e.g. // action=ChangeActionCreate returns a change for creation of the record and // action=ChangeActionDelete returns a change for deletion of the record. -func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) (*route53.Change, bool) { +func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*route53.Change, bool) { change := &route53.Change{ Action: aws.String(action), ResourceRecordSet: &route53.ResourceRecordSet{ diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index 1eb810573..cba42201a 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -501,7 +501,7 @@ func TestAWSApplyChanges(t *testing.T) { setup func(p *AWSProvider) context.Context listRRSets int }{ - {"no cache", func(p *AWSProvider) context.Context { return context.Background() }, 3}, + {"no cache", func(p *AWSProvider) context.Context { return context.Background() }, 0}, {"cached", func(p *AWSProvider) context.Context { ctx := context.Background() records, err := p.Records(ctx) @@ -781,7 +781,7 @@ func TestAWSsubmitChanges(t *testing.T) { zones, _ := provider.Zones(ctx) records, _ := provider.Records(ctx) cs := make([]*route53.Change, 0, len(endpoints)) - cs = append(cs, provider.newChanges(route53.ChangeActionCreate, endpoints, records, zones)...) + cs = append(cs, provider.newChanges(route53.ChangeActionCreate, endpoints)...) require.NoError(t, provider.submitChanges(ctx, cs, zones)) @@ -798,11 +798,9 @@ func TestAWSsubmitChangesError(t *testing.T) { ctx := context.Background() zones, err := provider.Zones(ctx) require.NoError(t, err) - records, err := provider.Records(ctx) - require.NoError(t, err) ep := endpoint.NewEndpointWithTTL("fail.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.0.0.1") - cs := provider.newChanges(route53.ChangeActionCreate, []*endpoint.Endpoint{ep}, records, zones) + cs := provider.newChanges(route53.ChangeActionCreate, []*endpoint.Endpoint{ep}) require.Error(t, provider.submitChanges(ctx, cs, zones)) } diff --git a/provider/azure/azure_test.go b/provider/azure/azure_test.go index fa5e2341d..1c58b2c3d 100644 --- a/provider/azure/azure_test.go +++ b/provider/azure/azure_test.go @@ -18,12 +18,10 @@ package azure import ( "context" - "strings" "testing" "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/stretchr/testify/assert" @@ -446,59 +444,6 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC } } -func TestAzureGetAccessToken(t *testing.T) { - env := azure.PublicCloud - cfg := config{ - ClientID: "", - ClientSecret: "", - TenantID: "", - UseManagedIdentityExtension: false, - } - - _, err := getAccessToken(cfg, env) - if err == nil { - t.Fatalf("expected to fail, but got no error") - } - - // Expect to use managed identity in this case - cfg = config{ - ClientID: "msi", - ClientSecret: "msi", - TenantID: "cefe8aef-5127-4d65-a299-012053f81f60", - UserAssignedIdentityID: "userAssignedIdentityClientID", - UseManagedIdentityExtension: true, - } - token, err := getAccessToken(cfg, env) - if err != nil { - t.Fatalf("expected to construct a token successfully, but got error %v", err) - } - _, err = token.MarshalJSON() - if err == nil || - !strings.Contains(err.Error(), "marshalling ServicePrincipalMSISecret is not supported") { - t.Fatalf("expected to fail to marshal token, but got %v", err) - } - - // Expect to use SPN in this case - cfg = config{ - ClientID: "SPNClientID", - ClientSecret: "SPNSecret", - TenantID: "cefe8aef-5127-4d65-a299-012053f81f60", - UserAssignedIdentityID: "userAssignedIdentityClientID", - UseManagedIdentityExtension: true, - } - token, err = getAccessToken(cfg, env) - if err != nil { - t.Fatalf("expected to construct a token successfully, but got error %v", err) - } - innerToken, err := token.MarshalJSON() - if err != nil { - t.Fatalf("expected to marshal token successfully, but got error %v", err) - } - if !strings.Contains(string(innerToken), "SPNClientID") { - t.Fatalf("expect the clientID of the token is SPNClientID, but got token %s", string(innerToken)) - } -} - func TestAzureNameFilter(t *testing.T) { provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", &[]dns.Zone{ diff --git a/provider/azure/config.go b/provider/azure/config.go index 34c13713e..5b282af8b 100644 --- a/provider/azure/config.go +++ b/provider/azure/config.go @@ -103,14 +103,13 @@ func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePri // Try to retrieve token with MSI. if cfg.UseManagedIdentityExtension { log.Info("Using managed identity extension to retrieve access token for Azure API.") - msiEndpoint, err := adal.GetMSIVMEndpoint() - if err != nil { - return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) - } if cfg.UserAssignedIdentityID != "" { log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID) - token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, environment.ServiceManagementEndpoint, cfg.UserAssignedIdentityID) + token, err := adal.NewServicePrincipalTokenFromManagedIdentity(environment.ServiceManagementEndpoint, &adal.ManagedIdentityOptions{ + ClientID: cfg.UserAssignedIdentityID, + }) + if err != nil { return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) } @@ -118,7 +117,7 @@ func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePri } log.Info("Resolving to system assigned identity.") - token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) + token, err := adal.NewServicePrincipalTokenFromManagedIdentity(environment.ServiceManagementEndpoint, nil) if err != nil { return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) } diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 1235186c3..033a0084f 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -308,7 +308,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud } err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecord) if err != nil { - log.WithFields(logFields).Errorf("failed to delete record: %v", err) + log.WithFields(logFields).Errorf("failed to update record: %v", err) } } else if change.Action == cloudFlareDelete { recordID := p.getRecordID(records, change.ResourceRecord) diff --git a/provider/coredns/coredns.go b/provider/coredns/coredns.go index 5dceaefa3..f4d16755c 100644 --- a/provider/coredns/coredns.go +++ b/provider/coredns/coredns.go @@ -31,7 +31,7 @@ import ( "time" log "github.com/sirupsen/logrus" - etcdcv3 "go.etcd.io/etcd/clientv3" + etcdcv3 "go.etcd.io/etcd/client/v3" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" diff --git a/provider/digitalocean/digital_ocean_test.go b/provider/digitalocean/digital_ocean_test.go index 939a38fe0..22c6849d9 100644 --- a/provider/digitalocean/digital_ocean_test.go +++ b/provider/digitalocean/digital_ocean_test.go @@ -35,6 +35,21 @@ import ( type mockDigitalOceanClient struct{} +func (m *mockDigitalOceanClient) RecordsByName(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + +func (m *mockDigitalOceanClient) RecordsByTypeAndName(context.Context, string, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + +func (m *mockDigitalOceanClient) RecordsByType(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + func (m *mockDigitalOceanClient) List(ctx context.Context, opt *godo.ListOptions) ([]godo.Domain, *godo.Response, error) { if opt == nil || opt.Page == 0 { return []godo.Domain{{Name: "foo.com"}, {Name: "example.com"}}, &godo.Response{ @@ -112,6 +127,21 @@ func (m *mockDigitalOceanClient) Records(ctx context.Context, domain string, opt type mockDigitalOceanRecordsFail struct{} +func (m *mockDigitalOceanRecordsFail) RecordsByName(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + +func (m *mockDigitalOceanRecordsFail) RecordsByTypeAndName(context.Context, string, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + +func (m *mockDigitalOceanRecordsFail) RecordsByType(context.Context, string, string, *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) { + // not used, here only to correctly implement the interface + return nil, nil, nil +} + func (m *mockDigitalOceanRecordsFail) List(context.Context, *godo.ListOptions) ([]godo.Domain, *godo.Response, error) { return []godo.Domain{{Name: "foo.com"}, {Name: "bar.com"}}, nil, nil } diff --git a/provider/google/google.go b/provider/google/google.go index ef744dbe5..4c68e7c76 100644 --- a/provider/google/google.go +++ b/provider/google/google.go @@ -110,6 +110,8 @@ type GoogleProvider struct { batchChangeInterval time.Duration // only consider hosted zones managing domains ending in this suffix domainFilter endpoint.DomainFilter + // filter for zones based on visibility + zoneTypeFilter provider.ZoneTypeFilter // only consider hosted zones ending with this zone id zoneIDFilter provider.ZoneIDFilter // A client for managing resource record sets @@ -123,7 +125,7 @@ type GoogleProvider struct { } // NewGoogleProvider initializes a new Google CloudDNS based Provider. -func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, dryRun bool) (*GoogleProvider, error) { +func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, zoneVisibility string, dryRun bool) (*GoogleProvider, error) { gcloud, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope) if err != nil { return nil, err @@ -143,18 +145,22 @@ func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoin if project == "" { mProject, mErr := metadata.ProjectID() - if mErr == nil { - log.Infof("Google project auto-detected: %s", mProject) - project = mProject + if mErr != nil { + return nil, fmt.Errorf("failed to auto-detect the project id: %w", mErr) } + log.Infof("Google project auto-detected: %s", mProject) + project = mProject } + zoneTypeFilter := provider.NewZoneTypeFilter(zoneVisibility) + provider := &GoogleProvider{ project: project, dryRun: dryRun, batchChangeSize: batchChangeSize, batchChangeInterval: batchChangeInterval, domainFilter: domainFilter, + zoneTypeFilter: zoneTypeFilter, zoneIDFilter: zoneIDFilter, resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets}, managedZonesClient: managedZonesService{dnsClient.ManagedZones}, @@ -171,11 +177,11 @@ func (p *GoogleProvider) Zones(ctx context.Context) (map[string]*dns.ManagedZone f := func(resp *dns.ManagedZonesListResponse) error { for _, zone := range resp.ManagedZones { - if p.domainFilter.Match(zone.DnsName) && (p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) || p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Name))) { + if p.domainFilter.Match(zone.DnsName) && p.zoneTypeFilter.Match(zone.Visibility) && (p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) || p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Name))) { zones[zone.Name] = zone - log.Debugf("Matched %s (zone: %s)", zone.DnsName, zone.Name) + log.Debugf("Matched %s (zone: %s) (visibility: %s)", zone.DnsName, zone.Name, zone.Visibility) } else { - log.Debugf("Filtered %s (zone: %s)", zone.DnsName, zone.Name) + log.Debugf("Filtered %s (zone: %s) (visibility: %s)", zone.DnsName, zone.Name, zone.Visibility) } } diff --git a/provider/google/google_test.go b/provider/google/google_test.go index 7f780359e..9037326c8 100644 --- a/provider/google/google_test.go +++ b/provider/google/google_test.go @@ -194,24 +194,46 @@ func hasTrailingDot(target string) bool { } func TestGoogleZonesIDFilter(t *testing.T) { - provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"10002"}), false, []*endpoint.Endpoint{}) + provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"10002"}), provider.NewZoneTypeFilter(""), false, []*endpoint.Endpoint{}) zones, err := provider.Zones(context.Background()) require.NoError(t, err) validateZones(t, zones, map[string]*dns.ManagedZone{ - "internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002}, + "internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002, Visibility: "private"}, }) } func TestGoogleZonesNameFilter(t *testing.T) { - provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"internal-2"}), false, []*endpoint.Endpoint{}) + provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"internal-2"}), provider.NewZoneTypeFilter(""), false, []*endpoint.Endpoint{}) zones, err := provider.Zones(context.Background()) require.NoError(t, err) validateZones(t, zones, map[string]*dns.ManagedZone{ - "internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002}, + "internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002, Visibility: "private"}, + }) +} + +func TestGoogleZonesVisibilityFilterPublic(t *testing.T) { + provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"split-horizon-1"}), provider.NewZoneTypeFilter("public"), false, []*endpoint.Endpoint{}) + + zones, err := provider.Zones(context.Background()) + require.NoError(t, err) + + validateZones(t, zones, map[string]*dns.ManagedZone{ + "split-horizon-1": {Name: "split-horizon-1", DnsName: "cluster.local.", Id: 10001, Visibility: "public"}, + }) +} + +func TestGoogleZonesVisibilityFilterPrivate(t *testing.T) { + provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"split-horizon-1"}), provider.NewZoneTypeFilter("public"), false, []*endpoint.Endpoint{}) + + zones, err := provider.Zones(context.Background()) + require.NoError(t, err) + + validateZones(t, zones, map[string]*dns.ManagedZone{ + "split-horizon-1": {Name: "split-horizon-1", DnsName: "cluster.local.", Id: 10001, Visibility: "public"}, }) } @@ -650,6 +672,7 @@ func validateZones(t *testing.T, zones map[string]*dns.ManagedZone, expected map func validateZone(t *testing.T, zone *dns.ManagedZone, expected *dns.ManagedZone) { assert.Equal(t, expected.Name, zone.Name) assert.Equal(t, expected.DnsName, zone.DnsName) + assert.Equal(t, expected.Visibility, zone.Visibility) } func validateChange(t *testing.T, change *dns.Change, expected *dns.Change) { @@ -672,33 +695,51 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected assert.Equal(t, expected.Type, record.Type) } -func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider { +func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider { provider := &GoogleProvider{ project: "zalando-external-dns-test", dryRun: false, domainFilter: domainFilter, zoneIDFilter: zoneIDFilter, + zoneTypeFilter: zoneTypeFilter, resourceRecordSetsClient: &mockResourceRecordSetsClient{}, managedZonesClient: &mockManagedZonesClient{}, changesClient: &mockChangesClient{}, } createZone(t, provider, &dns.ManagedZone{ - Name: "internal-1", - DnsName: "cluster.local.", - Id: 10001, + Name: "internal-1", + DnsName: "cluster.local.", + Id: 10001, + Visibility: "private", }) createZone(t, provider, &dns.ManagedZone{ - Name: "internal-2", - DnsName: "cluster.local.", - Id: 10002, + Name: "internal-2", + DnsName: "cluster.local.", + Id: 10002, + Visibility: "private", }) createZone(t, provider, &dns.ManagedZone{ - Name: "internal-3", - DnsName: "cluster.local.", - Id: 10003, + Name: "internal-3", + DnsName: "cluster.local.", + Id: 10003, + Visibility: "private", + }) + + createZone(t, provider, &dns.ManagedZone{ + Name: "split-horizon-1", + DnsName: "cluster.local.", + Id: 10004, + Visibility: "public", + }) + + createZone(t, provider, &dns.ManagedZone{ + Name: "split-horizon-1", + DnsName: "cluster.local.", + Id: 10004, + Visibility: "private", }) provider.dryRun = dryRun diff --git a/provider/infoblox/infoblox.go b/provider/infoblox/infoblox.go index 9c98adf2e..f38f7a67f 100644 --- a/provider/infoblox/infoblox.go +++ b/provider/infoblox/infoblox.go @@ -19,12 +19,14 @@ package infoblox import ( "context" "fmt" + "net" "net/http" "os" "sort" "strconv" "strings" + transform "github.com/StackExchange/dnscontrol/pkg/transform" ibclient "github.com/infobloxopen/infoblox-go-client" "github.com/sirupsen/logrus" @@ -33,6 +35,11 @@ import ( "sigs.k8s.io/external-dns/provider" ) +const ( + // provider specific key to track if PTR record was already created or not for A records + providerSpecificInfobloxPtrRecord = "infoblox-ptr-record-exists" +) + // InfobloxConfig clarifies the method signature type InfobloxConfig struct { DomainFilter endpoint.DomainFilter @@ -47,6 +54,7 @@ type InfobloxConfig struct { View string MaxResults int FQDNRexEx string + CreatePTR bool } // InfobloxProvider implements the DNS provider for Infoblox. @@ -58,6 +66,7 @@ type InfobloxProvider struct { view string dryRun bool fqdnRegEx string + createPTR bool } type infobloxRecordSet struct { @@ -143,6 +152,7 @@ func NewInfobloxProvider(infobloxConfig InfobloxConfig) (*InfobloxProvider, erro dryRun: infobloxConfig.DryRun, view: infobloxConfig.View, fqdnRegEx: infobloxConfig.FQDNRexEx, + createPTR: infobloxConfig.CreatePTR, } return provider, nil @@ -170,6 +180,9 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E } for _, res := range resA { newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, res.Ipv4Addr) + if p.createPTR { + newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "false") + } // Check if endpoint already exists and add to existing endpoint if it does foundExisting := false for _, ep := range endpoints { @@ -203,7 +216,13 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E } for _, res := range resH { for _, ip := range res.Ipv4Addrs { - endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr)) + // host record is an abstraction in infoblox that combines A and PTR records + // for any host record we already should have a PTR record in infoblox, so mark it as created + newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr) + if p.createPTR { + newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + endpoints = append(endpoints, newEndpoint) } } @@ -222,6 +241,29 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeCNAME, res.Canonical)) } + if p.createPTR { + // infoblox doesn't accept reverse zone's fqdn, and instead expects .in-addr.arpa zone + // so convert our zone fqdn (if it is a correct cidr block) into in-addr.arpa address and pass that into infoblox + // example: 10.196.38.0/24 becomes 38.196.10.in-addr.arpa + arpaZone, err := transform.ReverseDomainName(zone.Fqdn) + if err == nil { + var resP []ibclient.RecordPTR + objP := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Zone: arpaZone, + View: p.view, + }, + ) + err = p.client.GetObject(objP, "", &resP) + if err != nil { + return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %s", zone.Fqdn, err) + } + for _, res := range resP { + endpoints = append(endpoints, endpoint.NewEndpoint(res.PtrdName, endpoint.RecordTypePTR, res.Ipv4Addr)) + } + } + } + var resT []ibclient.RecordTXT objT := ibclient.NewRecordTXT( ibclient.RecordTXT{ @@ -242,10 +284,66 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text)) } } + + // update A records that have PTR record created for them already + if p.createPTR { + // save all ptr records into map for a quick look up + ptrRecordsMap := make(map[string]bool) + for _, ptrRecord := range endpoints { + if ptrRecord.RecordType != endpoint.RecordTypePTR { + continue + } + ptrRecordsMap[ptrRecord.DNSName] = true + } + + for i := range endpoints { + if endpoints[i].RecordType != endpoint.RecordTypeA { + continue + } + // if PTR record already exists for A record, then mark it as such + if ptrRecordsMap[endpoints[i].DNSName] { + found := false + for j := range endpoints[i].ProviderSpecific { + if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { + endpoints[i].ProviderSpecific[j].Value = "true" + found = true + } + } + if !found { + endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + } + } + } logrus.Debugf("fetched %d records from infoblox", len(endpoints)) return endpoints, nil } +func (p *InfobloxProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint { + if !p.createPTR { + return endpoints + } + + // for all A records, we want to create PTR records + // so add provider specific property to track if the record was created or not + for i := range endpoints { + if endpoints[i].RecordType == endpoint.RecordTypeA { + found := false + for j := range endpoints[i].ProviderSpecific { + if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { + endpoints[i].ProviderSpecific[j].Value = "true" + found = true + } + } + if !found { + endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + } + } + + return endpoints +} + // ApplyChanges applies the given changes. func (p *InfobloxProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { zones, err := p.zones() @@ -301,6 +399,17 @@ func (p *InfobloxProvider) mapChanges(zones []ibclient.ZoneAuth, changes *plan.C } // Ensure the record type is suitable changeMap[zone.Fqdn] = append(changeMap[zone.Fqdn], change) + + if p.createPTR && change.RecordType == endpoint.RecordTypeA { + reverseZone := p.findReverseZone(zones, change.Targets[0]) + if reverseZone == nil { + logrus.Debugf("Ignoring changes to '%s' because a suitable Infoblox DNS reverse zone was not found.", change.Targets[0]) + return + } + changecopy := *change + changecopy.RecordType = endpoint.RecordTypePTR + changeMap[reverseZone.Fqdn] = append(changeMap[reverseZone.Fqdn], &changecopy) + } } for _, change := range changes.Delete { @@ -338,6 +447,28 @@ func (p *InfobloxProvider) findZone(zones []ibclient.ZoneAuth, name string) *ibc return result } +func (p *InfobloxProvider) findReverseZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { + ip := net.ParseIP(name) + networks := map[int]*ibclient.ZoneAuth{} + maxMask := 0 + + for i, zone := range zones { + _, net, err := net.ParseCIDR(zone.Fqdn) + if err != nil { + logrus.WithError(err).Debugf("fqdn %s is no cidr", zone.Fqdn) + } else { + if net.Contains(ip) { + _, mask := net.Mask.Size() + networks[mask] = &zones[i] + if mask > maxMask { + maxMask = mask + } + } + } + } + return networks[maxMask] +} + func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targetIndex int) (recordSet infobloxRecordSet, err error) { switch ep.RecordType { case endpoint.RecordTypeA: @@ -359,6 +490,25 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targ obj: obj, res: &res, } + case endpoint.RecordTypePTR: + var res []ibclient.RecordPTR + obj := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + PtrdName: ep.DNSName, + Ipv4Addr: ep.Targets[targetIndex], + View: p.view, + }, + ) + if getObject { + err = p.client.GetObject(obj, "", &res) + if err != nil { + return + } + } + recordSet = infobloxRecordSet{ + obj: obj, + res: &res, + } case endpoint.RecordTypeCNAME: var res []ibclient.RecordCNAME obj := ibclient.NewRecordCNAME( @@ -483,6 +633,10 @@ func (p *InfobloxProvider) deleteRecords(deleted infobloxChangeMap) { for _, record := range *recordSet.res.(*[]ibclient.RecordA) { _, err = p.client.DeleteObject(record.Ref) } + case endpoint.RecordTypePTR: + for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) { + _, err = p.client.DeleteObject(record.Ref) + } case endpoint.RecordTypeCNAME: for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) { _, err = p.client.DeleteObject(record.Ref) diff --git a/provider/infoblox/infoblox_test.go b/provider/infoblox/infoblox_test.go index a91e03664..9a9a57d3d 100644 --- a/provider/infoblox/infoblox_test.go +++ b/provider/infoblox/infoblox_test.go @@ -25,6 +25,7 @@ import ( "testing" ibclient "github.com/infobloxopen/infoblox-go-client" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "sigs.k8s.io/external-dns/endpoint" @@ -89,6 +90,21 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string, ) obj.(*ibclient.RecordTXT).Ref = ref ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordTXT).Name)), obj.(*ibclient.RecordTXT).Name) + case "record:ptr": + client.createdEndpoints = append( + client.createdEndpoints, + endpoint.NewEndpoint( + obj.(*ibclient.RecordPTR).PtrdName, + endpoint.RecordTypePTR, + obj.(*ibclient.RecordPTR).Ipv4Addr, + ), + ) + obj.(*ibclient.RecordPTR).Ref = ref + reverseAddr, err := dns.ReverseAddr(obj.(*ibclient.RecordPTR).Ipv4Addr) + if err != nil { + return ref, fmt.Errorf("unable to create reverse addr from %s", obj.(*ibclient.RecordPTR).Ipv4Addr) + } + ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordPTR).PtrdName)), reverseAddr) } *client.mockInfobloxObjects = append( *client.mockInfobloxObjects, @@ -163,6 +179,22 @@ func (client *mockIBConnector) GetObject(obj ibclient.IBObject, ref string, res } } *res.(*[]ibclient.RecordTXT) = result + case "record:ptr": + var result []ibclient.RecordPTR + for _, object := range *client.mockInfobloxObjects { + if object.ObjectType() == "record:ptr" { + if ref != "" && + ref != object.(*ibclient.RecordPTR).Ref { + continue + } + if obj.(*ibclient.RecordPTR).PtrdName != "" && + obj.(*ibclient.RecordPTR).PtrdName != object.(*ibclient.RecordPTR).PtrdName { + continue + } + result = append(result, *object.(*ibclient.RecordPTR)) + } + } + *res.(*[]ibclient.RecordPTR) = result case "zone_auth": *res.(*[]ibclient.ZoneAuth) = *client.mockInfobloxZones } @@ -246,6 +278,24 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro ), ) } + case "record:ptr": + var records []ibclient.RecordPTR + obj := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Name: result[2], + }, + ) + client.GetObject(obj, ref, &records) + for _, record := range records { + client.deletedEndpoints = append( + client.deletedEndpoints, + endpoint.NewEndpoint( + record.PtrdName, + endpoint.RecordTypePTR, + "", + ), + ) + } } return "", nil } @@ -339,16 +389,25 @@ func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject }, }, ) + case endpoint.RecordTypePTR: + return ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Ref: ref, + PtrdName: name, + Ipv4Addr: value, + }, + ) } return nil } -func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider { +func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, createPTR bool, client ibclient.IBConnector) *InfobloxProvider { return &InfobloxProvider{ client: client, domainFilter: domainFilter, zoneIDFilter: zoneIDFilter, dryRun: dryRun, + createPTR: createPTR, } } @@ -376,7 +435,7 @@ func TestInfobloxRecords(t *testing.T) { }, } - provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client) + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) actual, err := provider.Records(context.Background()) if err != nil { @@ -399,10 +458,66 @@ func TestInfobloxRecords(t *testing.T) { validateEndpoints(t, actual, expected) } +func TestInfobloxAdjustEndpoints(t *testing.T) { + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("example.com"), + createMockInfobloxZone("other.com"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{ + createMockInfobloxObject("example.com", endpoint.RecordTypeA, "123.123.123.122"), + createMockInfobloxObject("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), + createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), + createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"), + }, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, true, &client) + actual, err := provider.Records(context.Background()) + if err != nil { + t.Fatal(err) + } + provider.AdjustEndpoints(actual) + + expected := []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), + endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), + endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), + } + validateEndpoints(t, actual, expected) +} + +func TestInfobloxRecordsReverse(t *testing.T) { + + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("10.0.0.0/24"), + createMockInfobloxZone("10.0.1.0/24"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{ + createMockInfobloxObject("example.com", endpoint.RecordTypePTR, "10.0.0.1"), + createMockInfobloxObject("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), + }, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"10.0.0.0/24"}), provider.NewZoneIDFilter([]string{""}), true, true, &client) + actual, err := provider.Records(context.Background()) + + if err != nil { + t.Fatal(err) + } + expected := []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "10.0.0.1"), + endpoint.NewEndpoint("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), + } + validateEndpoints(t, actual, expected) +} + func TestInfobloxApplyChanges(t *testing.T) { client := mockIBConnector{} - testInfobloxApplyChangesInternal(t, false, &client) + testInfobloxApplyChangesInternal(t, false, false, &client) validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), @@ -423,7 +538,39 @@ func TestInfobloxApplyChanges(t *testing.T) { endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), - endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeTXT, ""), + endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), + }) + + validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) +} + +func TestInfobloxApplyChangesReverse(t *testing.T) { + client := mockIBConnector{} + + testInfobloxApplyChangesInternal(t, false, true, &client) + + validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "1.2.3.4"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypePTR, "1.2.3.4"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"), + endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"), + endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), + endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), + endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "1.2.3.4,3.4.5.6,8.9.10.11"), + endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "tag-multiple-A-records"), + }) + + validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{ + endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), + endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), + endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), + endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, ""), endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), }) @@ -435,7 +582,7 @@ func TestInfobloxApplyChangesDryRun(t *testing.T) { mockInfobloxObjects: &[]ibclient.IBObject{}, } - testInfobloxApplyChangesInternal(t, true, &client) + testInfobloxApplyChangesInternal(t, true, false, &client) validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{}) @@ -444,14 +591,16 @@ func TestInfobloxApplyChangesDryRun(t *testing.T) { validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) } -func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient.IBConnector) { +func testInfobloxApplyChangesInternal(t *testing.T, dryRun, createPTR bool, client ibclient.IBConnector) { client.(*mockIBConnector).mockInfobloxZones = &[]ibclient.ZoneAuth{ createMockInfobloxZone("example.com"), createMockInfobloxZone("other.com"), + createMockInfobloxZone("1.2.3.0/24"), } client.(*mockIBConnector).mockInfobloxObjects = &[]ibclient.IBObject{ createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeTXT, "test-deleting-txt"), + createMockInfobloxObject("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212"), createMockInfobloxObject("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), createMockInfobloxObject("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), createMockInfobloxObject("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), @@ -461,6 +610,7 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient endpoint.NewDomainFilter([]string{""}), provider.NewZoneIDFilter([]string{""}), dryRun, + createPTR, client, ) @@ -493,11 +643,14 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient deleteRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), - endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeTXT, "test-deleting-txt"), endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"), } + if createPTR { + deleteRecords = append(deleteRecords, endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212")) + } + changes := &plan.Changes{ Create: createRecords, UpdateNew: updateNewRecords, @@ -516,11 +669,12 @@ func TestInfobloxZones(t *testing.T) { createMockInfobloxZone("example.com"), createMockInfobloxZone("lvl1-1.example.com"), createMockInfobloxZone("lvl2-1.lvl1-1.example.com"), + createMockInfobloxZone("1.2.3.0/24"), }, mockInfobloxObjects: &[]ibclient.IBObject{}, } - provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client) + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) zones, _ := provider.zones() var emptyZoneAuth *ibclient.ZoneAuth assert.Equal(t, provider.findZone(zones, "example.com").Fqdn, "example.com") @@ -531,6 +685,26 @@ func TestInfobloxZones(t *testing.T) { assert.Equal(t, provider.findZone(zones, "lvl2-1.lvl1-1.example.com").Fqdn, "lvl2-1.lvl1-1.example.com") assert.Equal(t, provider.findZone(zones, "lvl2-2.lvl1-1.example.com").Fqdn, "lvl1-1.example.com") assert.Equal(t, provider.findZone(zones, "lvl2-2.lvl1-2.example.com").Fqdn, "example.com") + assert.Equal(t, provider.findZone(zones, "1.2.3.0/24").Fqdn, "1.2.3.0/24") +} + +func TestInfobloxReverseZones(t *testing.T) { + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("example.com"), + createMockInfobloxZone("1.2.3.0/24"), + createMockInfobloxZone("10.0.0.0/8"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{}, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24", "10.0.0.0/8"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) + zones, _ := provider.zones() + var emptyZoneAuth *ibclient.ZoneAuth + assert.Equal(t, provider.findReverseZone(zones, "nomatch-example.com"), emptyZoneAuth) + assert.Equal(t, provider.findReverseZone(zones, "192.168.0.1"), emptyZoneAuth) + assert.Equal(t, provider.findReverseZone(zones, "1.2.3.4").Fqdn, "1.2.3.0/24") + assert.Equal(t, provider.findReverseZone(zones, "10.28.29.30").Fqdn, "10.0.0.0/8") } func TestExtendedRequestFDQDRegExBuilder(t *testing.T) { diff --git a/provider/rdns/rdns.go b/provider/rdns/rdns.go index b64325bb7..4331059c3 100644 --- a/provider/rdns/rdns.go +++ b/provider/rdns/rdns.go @@ -31,7 +31,7 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "go.etcd.io/etcd/clientv3" + clientv3 "go.etcd.io/etcd/client/v3" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" diff --git a/provider/rdns/rdns_test.go b/provider/rdns/rdns_test.go index 450834313..4bfed1ab9 100644 --- a/provider/rdns/rdns_test.go +++ b/provider/rdns/rdns_test.go @@ -24,8 +24,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.etcd.io/etcd/clientv3" - "go.etcd.io/etcd/mvcc/mvccpb" + "go.etcd.io/etcd/api/v3/mvccpb" + clientv3 "go.etcd.io/etcd/client/v3" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" diff --git a/provider/scaleway/scaleway.go b/provider/scaleway/scaleway.go index c145d5fe2..614c877c8 100644 --- a/provider/scaleway/scaleway.go +++ b/provider/scaleway/scaleway.go @@ -278,7 +278,7 @@ func endpointToScalewayRecords(zoneName string, ep *endpoint.Endpoint) []*domain } var priority = scalewayDefaultPriority if prop, ok := ep.GetProviderSpecificProperty(scalewayPriorityKey); ok { - prio, err := strconv.ParseUint(prop.Value, 10, 64) + prio, err := strconv.ParseUint(prop.Value, 10, 32) if err != nil { log.Errorf("Failed parsing value of %s: %s: %v; using priority of %d", scalewayPriorityKey, prop.Value, err, scalewayDefaultPriority) } else { diff --git a/provider/zone_type_filter.go b/provider/zone_type_filter.go index 4ba4af3aa..14ceac0e8 100644 --- a/provider/zone_type_filter.go +++ b/provider/zone_type_filter.go @@ -37,24 +37,34 @@ func NewZoneTypeFilter(zoneType string) ZoneTypeFilter { } // Match checks whether a zone matches the zone type that's filtered for. -func (f ZoneTypeFilter) Match(zone *route53.HostedZone) bool { +func (f ZoneTypeFilter) Match(rawZoneType interface{}) bool { // An empty zone filter includes all hosted zones. if f.zoneType == "" { return true } - // If the zone has no config we assume it's a public zone since the config's field - // `PrivateZone` is false by default in go. - if zone.Config == nil { - return f.zoneType == zoneTypePublic - } - + switch zoneType := rawZoneType.(type) { // Given a zone type we return true if the given zone matches this type. - switch f.zoneType { - case zoneTypePublic: - return !aws.BoolValue(zone.Config.PrivateZone) - case zoneTypePrivate: - return aws.BoolValue(zone.Config.PrivateZone) + case string: + switch f.zoneType { + case zoneTypePublic: + return zoneType == zoneTypePublic + case zoneTypePrivate: + return zoneType == zoneTypePrivate + } + case *route53.HostedZone: + // If the zone has no config we assume it's a public zone since the config's field + // `PrivateZone` is false by default in go. + if zoneType.Config == nil { + return f.zoneType == zoneTypePublic + } + + switch f.zoneType { + case zoneTypePublic: + return !aws.BoolValue(zoneType.Config.PrivateZone) + case zoneTypePrivate: + return aws.BoolValue(zoneType.Config.PrivateZone) + } } // We return false on any other path, e.g. unknown zone type filter value. diff --git a/provider/zone_type_filter_test.go b/provider/zone_type_filter_test.go index c7d97a8ea..129fef036 100644 --- a/provider/zone_type_filter_test.go +++ b/provider/zone_type_filter_test.go @@ -26,46 +26,38 @@ import ( ) func TestZoneTypeFilterMatch(t *testing.T) { - publicZone := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(false)}} - privateZone := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(true)}} + publicZoneStr := "public" + privateZoneStr := "private" + publicZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(false)}} + privateZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(true)}} for _, tc := range []struct { zoneTypeFilter string - zone *route53.HostedZone matches bool + zones []interface{} }{ { - "", publicZone, true, + "", true, []interface{}{ publicZoneStr, privateZoneStr, &route53.HostedZone{} }, }, { - "", privateZone, true, + "public", true, []interface{}{ publicZoneStr, publicZoneAWS, &route53.HostedZone{} }, }, { - "public", publicZone, true, + "public", false, []interface{}{ privateZoneStr, privateZoneAWS }, }, { - "public", privateZone, false, + "private", true, []interface{}{ privateZoneStr, privateZoneAWS }, }, { - "private", publicZone, false, + "private", false, []interface{}{ publicZoneStr, publicZoneAWS, &route53.HostedZone{} }, }, { - "private", privateZone, true, - }, - { - "unknown", publicZone, false, - }, - { - "", &route53.HostedZone{}, true, - }, - { - "public", &route53.HostedZone{}, true, - }, - { - "private", &route53.HostedZone{}, false, + "unknown", false, []interface{}{ publicZoneStr }, }, } { zoneTypeFilter := NewZoneTypeFilter(tc.zoneTypeFilter) - assert.Equal(t, tc.matches, zoneTypeFilter.Match(tc.zone)) + for _, zone := range tc.zones { + assert.Equal(t, tc.matches, zoneTypeFilter.Match(zone)) + } } } diff --git a/scripts/run-trivy.sh b/scripts/run-trivy.sh new file mode 100755 index 000000000..ddf238edb --- /dev/null +++ b/scripts/run-trivy.sh @@ -0,0 +1,11 @@ +#! /bin/bash +set -e + +# install trivy +curl -LO https://github.com/aquasecurity/trivy/releases/download/v0.20.2/trivy_0.20.2_Linux-64bit.tar.gz +echo "38a6de48e21a34e0fa0d2cf63439c0afcbbae0e78fb3feada7a84a9cf6e7f60c trivy_0.20.2_Linux-64bit.tar.gz" | sha256sum -c +tar -xvf trivy_0.20.2_Linux-64bit.tar.gz +chmod +x trivy + +# run trivy +./trivy image --exit-code 1 us.gcr.io/k8s-artifacts-prod/external-dns/external-dns:$(git describe --tags --always --dirty) diff --git a/scripts/update_route53_k8s_txt_owner.py b/scripts/update_route53_k8s_txt_owner.py index ceb1afb31..e81c9025c 100644 --- a/scripts/update_route53_k8s_txt_owner.py +++ b/scripts/update_route53_k8s_txt_owner.py @@ -54,7 +54,7 @@ if external_dns_manages_services: k8s_domains.extend(annotations['domainName'].split(',')) if external_dns_manages_ingresses: - ev1 = client.ExtensionsV1beta1Api() + ev1 = client.NetworkingV1Api() ings = ev1.list_ingress_for_all_namespaces() for i in ings.items: for r in i.spec.rules: diff --git a/source/ambassador_host.go b/source/ambassador_host.go index 2f4579e5d..04e26b5ff 100644 --- a/source/ambassador_host.go +++ b/source/ambassador_host.go @@ -21,7 +21,6 @@ import ( "fmt" "sort" "strings" - "time" ambassador "github.com/datawire/ambassador/pkg/api/getambassador.io/v2" "github.com/pkg/errors" @@ -38,7 +37,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" - api "k8s.io/kubernetes/pkg/apis/core" "sigs.k8s.io/external-dns/endpoint" ) @@ -87,12 +85,8 @@ func NewAmbassadorHostSource( // TODO informer is not explicitly stopped since controller is not passing in its channel. informerFactory.Start(wait.NeverStop) - // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return ambassadorHostInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, errors.Wrapf(err, "failed to sync cache") + if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } uc, err := newUnstructuredConverter() @@ -240,7 +234,7 @@ func parseAmbLoadBalancerService(service string) (namespace, name string, err er // If here, we have no separator, so the whole string is the service, and // we can assume the default namespace. name := service - namespace := api.NamespaceDefault + namespace := "default" return namespace, name, nil } else if len(parts) == 2 { diff --git a/source/connector_test.go b/source/connector_test.go index 708110215..3feba3800 100644 --- a/source/connector_test.go +++ b/source/connector_test.go @@ -36,8 +36,8 @@ func (suite *ConnectorSuite) SetupTest() { } -func startServerToServeTargets(t *testing.T, server string, endpoints []*endpoint.Endpoint) { - ln, err := net.Listen("tcp", server) +func startServerToServeTargets(t *testing.T, endpoints []*endpoint.Endpoint) net.Listener { + ln, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatal(err) } @@ -52,10 +52,13 @@ func startServerToServeTargets(t *testing.T, server string, endpoints []*endpoin enc.Encode(endpoints) ln.Close() }() - t.Logf("Server listening on %s", server) + t.Logf("Server listening on %s", ln.Addr().String()) + return ln } func TestConnectorSource(t *testing.T) { + t.Parallel() + suite.Run(t, new(ConnectorSuite)) t.Run("Interface", testConnectorSourceImplementsSource) t.Run("Endpoints", testConnectorSourceEndpoints) @@ -69,28 +72,24 @@ func testConnectorSourceImplementsSource(t *testing.T) { // testConnectorSourceEndpoints tests that NewConnectorSource doesn't return an error. func testConnectorSourceEndpoints(t *testing.T) { for _, ti := range []struct { - title string - serverListenAddress string - serverAddress string - expected []*endpoint.Endpoint - expectError bool + title string + server bool + expected []*endpoint.Endpoint + expectError bool }{ { - title: "invalid remote server", - serverListenAddress: "", - serverAddress: "localhost:8091", - expectError: true, + title: "invalid remote server", + server: false, + expectError: true, }, { - title: "valid remote server with no endpoints", - serverListenAddress: "127.0.0.1:8080", - serverAddress: "127.0.0.1:8080", - expectError: false, + title: "valid remote server with no endpoints", + server: true, + expectError: false, }, { - title: "valid remote server", - serverListenAddress: "127.0.0.1:8081", - serverAddress: "127.0.0.1:8081", + title: "valid remote server", + server: true, expected: []*endpoint.Endpoint{ {DNSName: "abc.example.org", Targets: endpoint.Targets{"1.2.3.4"}, @@ -101,9 +100,8 @@ func testConnectorSourceEndpoints(t *testing.T) { expectError: false, }, { - title: "valid remote server with multiple endpoints", - serverListenAddress: "127.0.0.1:8082", - serverAddress: "127.0.0.1:8082", + title: "valid remote server with multiple endpoints", + server: true, expected: []*endpoint.Endpoint{ {DNSName: "abc.example.org", Targets: endpoint.Targets{"1.2.3.4"}, @@ -119,11 +117,17 @@ func testConnectorSourceEndpoints(t *testing.T) { expectError: false, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { - if ti.serverListenAddress != "" { - startServerToServeTargets(t, ti.serverListenAddress, ti.expected) + t.Parallel() + + addr := "localhost:9999" + if ti.server { + ln := startServerToServeTargets(t, ti.expected) + defer ln.Close() + addr = ln.Addr().String() } - cs, _ := NewConnectorSource(ti.serverAddress) + cs, _ := NewConnectorSource(addr) endpoints, err := cs.Endpoints(context.Background()) if ti.expectError { diff --git a/source/httpproxy.go b/source/contour_httpproxy.go similarity index 88% rename from source/httpproxy.go rename to source/contour_httpproxy.go index 257cc2203..7fa23acd0 100644 --- a/source/httpproxy.go +++ b/source/contour_httpproxy.go @@ -17,13 +17,10 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" - "strings" "text/template" - "time" "github.com/pkg/errors" projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" @@ -63,17 +60,9 @@ func NewContourHTTPProxySource( combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ) (Source, error) { - var ( - tmpl *template.Template - err error - ) - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informer to listen for add/update/delete of HTTPProxys in the specified namespace. @@ -93,11 +82,8 @@ func NewContourHTTPProxySource( informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return httpProxyInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, errors.Wrap(err, "failed to sync cache") + if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } uc, err := NewUnstructuredConverter() @@ -197,22 +183,17 @@ func (sc *httpProxySource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, } func (sc *httpProxySource) endpointsFromTemplate(httpProxy *projectcontour.HTTPProxy) ([]*endpoint.Endpoint, error) { - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, httpProxy) + hostnames, err := execTemplate(sc.fqdnTemplate, httpProxy) if err != nil { - return nil, errors.Wrapf(err, "failed to apply template on HTTPProxy %s/%s", httpProxy.Namespace, httpProxy.Name) + return nil, err } - hostnames := buf.String() - ttl, err := getTTLFromAnnotations(httpProxy.Annotations) if err != nil { log.Warn(err) } targets := getTargetsFromTargetAnnotation(httpProxy.Annotations) - if len(targets) == 0 { for _, lb := range httpProxy.Status.LoadBalancer.Ingress { if lb.IP != "" { @@ -227,10 +208,7 @@ func (sc *httpProxySource) endpointsFromTemplate(httpProxy *projectcontour.HTTPP providerSpecific, setIdentifier := getProviderSpecificAnnotations(httpProxy.Annotations) var endpoints []*endpoint.Endpoint - // splits the FQDN template and removes the trailing periods - hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") - for _, hostname := range hostnameList { - hostname = strings.TrimSuffix(hostname, ".") + for _, hostname := range hostnames { endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) } return endpoints, nil @@ -324,17 +302,5 @@ func (sc *httpProxySource) AddEventHandler(ctx context.Context, handler func()) // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 - sc.httpProxyInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.httpProxyInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } diff --git a/source/httpproxy_test.go b/source/contour_httpproxy_test.go similarity index 95% rename from source/httpproxy_test.go rename to source/contour_httpproxy_test.go index 8e0d5da9d..227f95960 100644 --- a/source/httpproxy_test.go +++ b/source/contour_httpproxy_test.go @@ -18,14 +18,16 @@ package source import ( "context" - v1 "k8s.io/api/core/v1" "testing" + fakeDynamic "k8s.io/client-go/dynamic/fake" + "github.com/pkg/errors" projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -41,6 +43,46 @@ type HTTPProxySuite struct { httpProxy *projectcontour.HTTPProxy } +func newDynamicKubernetesClient() (*fakeDynamic.FakeDynamicClient, *runtime.Scheme) { + s := runtime.NewScheme() + _ = projectcontour.AddToScheme(s) + return fakeDynamic.NewSimpleDynamicClient(s), s +} + +type fakeLoadBalancerService struct { + ips []string + hostnames []string + namespace string + name string +} + +func (ig fakeLoadBalancerService) Service() *v1.Service { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ig.namespace, + Name: ig.name, + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{}, + }, + }, + } + + for _, ip := range ig.ips { + svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ + IP: ip, + }) + } + for _, hostname := range ig.hostnames { + svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ + Hostname: hostname, + }) + } + + return svc +} + func (suite *HTTPProxySuite) SetupTest() { fakeDynamicClient, s := newDynamicKubernetesClient() var err error @@ -87,12 +129,16 @@ func convertHTTPProxyToUnstructured(hp *projectcontour.HTTPProxy, s *runtime.Sch } func TestHTTPProxy(t *testing.T) { + t.Parallel() + suite.Run(t, new(HTTPProxySuite)) t.Run("endpointsFromHTTPProxy", testEndpointsFromHTTPProxy) t.Run("Endpoints", testHTTPProxyEndpoints) } func TestNewContourHTTPProxySource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -131,7 +177,10 @@ func TestNewContourHTTPProxySource(t *testing.T) { annotationFilter: "contour.heptio.com/ingress.class=contour", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + fakeDynamicClient, _ := newDynamicKubernetesClient() _, err := NewContourHTTPProxySource( @@ -152,6 +201,8 @@ func TestNewContourHTTPProxySource(t *testing.T) { } func testEndpointsFromHTTPProxy(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string httpProxy fakeHTTPProxy @@ -233,7 +284,10 @@ func testEndpointsFromHTTPProxy(t *testing.T) { expected: []*endpoint.Endpoint{}, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + if source, err := newTestHTTPProxySource(); err != nil { require.NoError(t, err) } else if endpoints, err := source.endpointsFromHTTPProxy(ti.httpProxy.HTTPProxy()); err != nil { @@ -246,6 +300,8 @@ func testEndpointsFromHTTPProxy(t *testing.T) { } func testHTTPProxyEndpoints(t *testing.T) { + t.Parallel() + namespace := "testing" for _, ti := range []struct { title string @@ -958,7 +1014,10 @@ func testHTTPProxyEndpoints(t *testing.T) { ignoreHostnameAnnotation: true, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + httpProxies := make([]*projectcontour.HTTPProxy, 0) for _, item := range ti.httpProxyItems { item.loadBalancer = ti.loadBalancer @@ -1071,7 +1130,7 @@ func (ir fakeHTTPProxy) HTTPProxy() *projectcontour.HTTPProxy { Annotations: ir.annotations, }, Spec: spec, - Status: projectcontour.Status{ + Status: projectcontour.HTTPProxyStatus{ CurrentStatus: status, LoadBalancer: lb, }, diff --git a/source/crd.go b/source/crd.go index a897d45aa..705d770bc 100644 --- a/source/crd.go +++ b/source/crd.go @@ -43,7 +43,7 @@ type crdSource struct { crdResource string codec runtime.ParameterCodec annotationFilter string - labelFilter string + labelSelector labels.Selector } func addKnownTypes(scheme *runtime.Scheme, groupVersion schema.GroupVersion) error { @@ -103,12 +103,12 @@ func NewCRDClientForAPIVersionKind(client kubernetes.Interface, kubeConfig, apiS } // NewCRDSource creates a new crdSource with the given config. -func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, labelFilter string, scheme *runtime.Scheme) (Source, error) { +func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, labelSelector labels.Selector, scheme *runtime.Scheme) (Source, error) { return &crdSource{ crdResource: strings.ToLower(kind) + "s", namespace: namespace, annotationFilter: annotationFilter, - labelFilter: labelFilter, + labelSelector: labelSelector, crdClient: crdClient, codec: runtime.NewParameterCodec(scheme), }, nil @@ -126,11 +126,7 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error err error ) - if cs.labelFilter != "" { - result, err = cs.List(ctx, &metav1.ListOptions{LabelSelector: cs.labelFilter}) - } else { - result, err = cs.List(ctx, &metav1.ListOptions{}) - } + result, err = cs.List(ctx, &metav1.ListOptions{LabelSelector: cs.labelSelector.String()}) if err != nil { return nil, err } diff --git a/source/crd_test.go b/source/crd_test.go index 919388a82..a88fb4616 100644 --- a/source/crd_test.go +++ b/source/crd_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -57,7 +58,7 @@ func objBody(codec runtime.Encoder, obj runtime.Object) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) } -func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string, annotations map[string]string, labels map[string]string, t *testing.T) rest.Interface { +func fakeRESTClient(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string, annotations map[string]string, labels map[string]string, t *testing.T) rest.Interface { groupVersion, _ := schema.ParseGroupVersion(apiVersion) scheme := runtime.NewScheme() addKnownTypes(scheme, groupVersion) @@ -372,15 +373,22 @@ func testCRDSourceEndpoints(t *testing.T) { expectError: false, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { - restClient := startCRDServerToServeTargets(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "test", ti.annotations, ti.labels, t) + t.Parallel() + + restClient := fakeRESTClient(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "test", ti.annotations, ti.labels, t) groupVersion, err := schema.ParseGroupVersion(ti.apiVersion) require.NoError(t, err) scheme := runtime.NewScheme() - addKnownTypes(scheme, groupVersion) + require.NoError(t, addKnownTypes(scheme, groupVersion)) - cs, _ := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, ti.labelFilter, scheme) + labelSelector, err := labels.Parse(ti.labelFilter) + require.NoError(t, err) + + cs, err := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, labelSelector, scheme) + require.NoError(t, err) receivedEndpoints, err := cs.Endpoints(context.Background()) if ti.expectError { diff --git a/source/dedup_source.go b/source/dedupsource.go similarity index 100% rename from source/dedup_source.go rename to source/dedupsource.go diff --git a/source/dedup_source_test.go b/source/dedupsource_test.go similarity index 100% rename from source/dedup_source_test.go rename to source/dedupsource_test.go diff --git a/source/gloo.go b/source/gloo_proxy.go similarity index 100% rename from source/gloo.go rename to source/gloo_proxy.go diff --git a/source/gloo_test.go b/source/gloo_proxy_test.go similarity index 97% rename from source/gloo_test.go rename to source/gloo_proxy_test.go index dfc4d9436..2666704be 100644 --- a/source/gloo_test.go +++ b/source/gloo_proxy_test.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" fakeDynamic "k8s.io/client-go/dynamic/fake" fakeKube "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" @@ -211,8 +212,13 @@ var externalProxySource = metav1.PartialObjectMetadata{ } func TestGlooSource(t *testing.T) { + t.Parallel() + fakeKubernetesClient := fakeKube.NewSimpleClientset() - fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) + fakeDynamicClient := fakeDynamic.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), + map[schema.GroupVersionResource]string{ + proxyGVR: "ProxyList", + }) source, err := NewGlooSource(fakeDynamicClient, fakeKubernetesClient, defaultGlooNamespace) assert.NoError(t, err) @@ -263,7 +269,7 @@ func TestGlooSource(t *testing.T) { endpoints, err := source.Endpoints(context.Background()) assert.NoError(t, err) assert.Len(t, endpoints, 5) - assert.Equal(t, endpoints, []*endpoint.Endpoint{ + assert.ElementsMatch(t, endpoints, []*endpoint.Endpoint{ &endpoint.Endpoint{ DNSName: "a.test", Targets: []string{internalProxySvc.Status.LoadBalancer.Ingress[0].IP, internalProxySvc.Status.LoadBalancer.Ingress[1].IP, internalProxySvc.Status.LoadBalancer.Ingress[2].IP}, diff --git a/source/ingress.go b/source/ingress.go index 932650a7f..9890f1fe0 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -17,20 +17,18 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" "strings" "text/template" - "time" log "github.com/sirupsen/logrus" - "k8s.io/api/extensions/v1beta1" + networkv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/wait" kubeinformers "k8s.io/client-go/informers" - extinformers "k8s.io/client-go/informers/extensions/v1beta1" + netinformers "k8s.io/client-go/informers/networking/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" @@ -59,30 +57,23 @@ type ingressSource struct { fqdnTemplate *template.Template combineFQDNAnnotation bool ignoreHostnameAnnotation bool - ingressInformer extinformers.IngressInformer + ingressInformer netinformers.IngressInformer ignoreIngressTLSSpec bool ignoreIngressRulesSpec bool + labelSelector labels.Selector } // NewIngressSource creates a new ingressSource with the given config. -func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool) (Source, error) { - var ( - tmpl *template.Template - err error - ) - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } +func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector) (Source, error) { + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informer to listen for add/update/delete of ingresses in the specified namespace. // Set resync period to 0, to prevent processing when nothing has changed. informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace)) - ingressInformer := informerFactory.Extensions().V1beta1().Ingresses() + ingressInformer := informerFactory.Networking().V1().Ingresses() // Add default resource event handlers to properly initialize informer. ingressInformer.Informer().AddEventHandler( @@ -96,11 +87,8 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return ingressInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } sc := &ingressSource{ @@ -113,6 +101,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt ingressInformer: ingressInformer, ignoreIngressTLSSpec: ignoreIngressTLSSpec, ignoreIngressRulesSpec: ignoreIngressRulesSpec, + labelSelector: labelSelector, } return sc, nil } @@ -120,7 +109,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt // Endpoints returns endpoint objects for each host-target combination that should be processed. // Retrieves all ingress resources on all namespaces func (sc *ingressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - ingresses, err := sc.ingressInformer.Lister().Ingresses(sc.namespace).List(labels.Everything()) + ingresses, err := sc.ingressInformer.Lister().Ingresses(sc.namespace).List(sc.labelSelector) if err != nil { return nil, err } @@ -174,23 +163,18 @@ func (sc *ingressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e return endpoints, nil } -func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoint.Endpoint, error) { - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, ing) +func (sc *ingressSource) endpointsFromTemplate(ing *networkv1.Ingress) ([]*endpoint.Endpoint, error) { + hostnames, err := execTemplate(sc.fqdnTemplate, ing) if err != nil { - return nil, fmt.Errorf("failed to apply template on ingress %s: %v", ing.String(), err) + return nil, err } - hostnames := buf.String() - ttl, err := getTTLFromAnnotations(ing.Annotations) if err != nil { log.Warn(err) } targets := getTargetsFromTargetAnnotation(ing.Annotations) - if len(targets) == 0 { targets = targetsFromIngressStatus(ing.Status) } @@ -198,17 +182,14 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin providerSpecific, setIdentifier := getProviderSpecificAnnotations(ing.Annotations) var endpoints []*endpoint.Endpoint - // splits the FQDN template and removes the trailing periods - hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") - for _, hostname := range hostnameList { - hostname = strings.TrimSuffix(hostname, ".") + for _, hostname := range hostnames { endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) } return endpoints, nil } // filterByAnnotations filters a list of ingresses by a given annotation selector. -func (sc *ingressSource) filterByAnnotations(ingresses []*v1beta1.Ingress) ([]*v1beta1.Ingress, error) { +func (sc *ingressSource) filterByAnnotations(ingresses []*networkv1.Ingress) ([]*networkv1.Ingress, error) { selector, err := getLabelSelector(sc.annotationFilter) if err != nil { return nil, err @@ -219,7 +200,7 @@ func (sc *ingressSource) filterByAnnotations(ingresses []*v1beta1.Ingress) ([]*v return ingresses, nil } - filteredList := []*v1beta1.Ingress{} + filteredList := []*networkv1.Ingress{} for _, ingress := range ingresses { // include ingress if its annotations match the selector @@ -231,13 +212,13 @@ func (sc *ingressSource) filterByAnnotations(ingresses []*v1beta1.Ingress) ([]*v return filteredList, nil } -func (sc *ingressSource) setResourceLabel(ingress *v1beta1.Ingress, endpoints []*endpoint.Endpoint) { +func (sc *ingressSource) setResourceLabel(ingress *networkv1.Ingress, endpoints []*endpoint.Endpoint) { for _, ep := range endpoints { ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("ingress/%s/%s", ingress.Namespace, ingress.Name) } } -func (sc *ingressSource) setDualstackLabel(ingress *v1beta1.Ingress, endpoints []*endpoint.Endpoint) { +func (sc *ingressSource) setDualstackLabel(ingress *networkv1.Ingress, endpoints []*endpoint.Endpoint) { val, ok := ingress.Annotations[ALBDualstackAnnotationKey] if ok && val == ALBDualstackAnnotationValue { log.Debugf("Adding dualstack label to ingress %s/%s.", ingress.Namespace, ingress.Name) @@ -248,7 +229,7 @@ func (sc *ingressSource) setDualstackLabel(ingress *v1beta1.Ingress, endpoints [ } // endpointsFromIngress extracts the endpoints from ingress object -func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool) []*endpoint.Endpoint { +func endpointsFromIngress(ing *networkv1.Ingress, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool) []*endpoint.Endpoint { ttl, err := getTTLFromAnnotations(ing.Annotations) if err != nil { log.Warn(err) @@ -311,7 +292,7 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, i return endpoints } -func targetsFromIngressStatus(status v1beta1.IngressStatus) endpoint.Targets { +func targetsFromIngressStatus(status networkv1.IngressStatus) endpoint.Targets { var targets endpoint.Targets for _, lb := range status.LoadBalancer.Ingress { @@ -331,17 +312,5 @@ func (sc *ingressSource) AddEventHandler(ctx context.Context, handler func()) { // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 - sc.ingressInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.ingressInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } diff --git a/source/ingress_test.go b/source/ingress_test.go index c5bf50404..32805b7ad 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -19,14 +19,14 @@ package source import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" v1 "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" + networkv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" @@ -38,12 +38,22 @@ var _ Source = &ingressSource{} type IngressSuite struct { suite.Suite sc Source - fooWithTargets *v1beta1.Ingress + fooWithTargets *networkv1.Ingress } func (suite *IngressSuite) SetupTest() { fakeClient := fake.NewSimpleClientset() - var err error + + suite.fooWithTargets = (fakeIngress{ + name: "foo-with-targets", + namespace: "default", + dnsnames: []string{"foo"}, + ips: []string{"8.8.8.8"}, + hostnames: []string{"v1"}, + annotations: map[string]string{ALBDualstackAnnotationKey: ALBDualstackAnnotationValue}, + }).Ingress() + _, err := fakeClient.NetworkingV1().Ingresses(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{}) + suite.NoError(err, "should succeed") suite.sc, err = NewIngressSource( fakeClient, @@ -54,19 +64,9 @@ func (suite *IngressSuite) SetupTest() { false, false, false, + labels.Everything(), ) suite.NoError(err, "should initialize ingress source") - - suite.fooWithTargets = (fakeIngress{ - name: "foo-with-targets", - namespace: "default", - dnsnames: []string{"foo"}, - ips: []string{"8.8.8.8"}, - hostnames: []string{"v1"}, - annotations: map[string]string{ALBDualstackAnnotationKey: ALBDualstackAnnotationValue}, - }).Ingress() - _, err = fakeClient.ExtensionsV1beta1().Ingresses(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{}) - suite.NoError(err, "should succeed") } func (suite *IngressSuite) TestResourceLabelIsSet() { @@ -84,6 +84,8 @@ func (suite *IngressSuite) TestDualstackLabelIsSet() { } func TestIngress(t *testing.T) { + t.Parallel() + suite.Run(t, new(IngressSuite)) t.Run("endpointsFromIngress", testEndpointsFromIngress) t.Run("endpointsFromIngressHostnameSourceAnnotation", testEndpointsFromIngressHostnameSourceAnnotation) @@ -91,6 +93,8 @@ func TestIngress(t *testing.T) { } func TestNewIngressSource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -129,7 +133,10 @@ func TestNewIngressSource(t *testing.T) { annotationFilter: "kubernetes.io/ingress.class=nginx", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewIngressSource( fake.NewSimpleClientset(), "", @@ -139,6 +146,7 @@ func TestNewIngressSource(t *testing.T) { false, false, false, + labels.Everything(), ) if ti.expectError { assert.Error(t, err) @@ -150,6 +158,8 @@ func TestNewIngressSource(t *testing.T) { } func testEndpointsFromIngress(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string ingress fakeIngress @@ -271,8 +281,8 @@ func testEndpointsFromIngressHostnameSourceAnnotation(t *testing.T) { { title: "No ingress-hostname-source annotation, one rule.host", ingress: fakeIngress{ - dnsnames: []string{"foo.bar"}, - hostnames: []string{"lb.com"}, + dnsnames: []string{"foo.bar"}, + hostnames: []string{"lb.com"}, }, expected: []*endpoint.Endpoint{ { @@ -336,6 +346,8 @@ func testEndpointsFromIngressHostnameSourceAnnotation(t *testing.T) { } func testIngressEndpoints(t *testing.T) { + t.Parallel() + namespace := "testing" for _, ti := range []struct { title string @@ -349,6 +361,7 @@ func testIngressEndpoints(t *testing.T) { ignoreHostnameAnnotation bool ignoreIngressTLSSpec bool ignoreIngressRulesSpec bool + ingressLabelSelector labels.Selector }{ { title: "no ingress", @@ -995,6 +1008,9 @@ func testIngressEndpoints(t *testing.T) { DNSName: "example.org", Targets: endpoint.Targets{"ingress-target.com"}, RecordType: endpoint.RecordTypeCNAME, + ProviderSpecific: endpoint.ProviderSpecific{{ + Name: "alias", Value: "true", + }}, }, }, }, @@ -1157,14 +1173,57 @@ func testIngressEndpoints(t *testing.T) { }, }, }, + { + ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}), + title: "ingress with matching labels", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + dnsnames: []string{"example.org"}, + ips: []string{"8.8.8.8"}, + labels: map[string]string{"app": "web-external", "name": "reverse-proxy"}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"8.8.8.8"}, + }, + }, + }, + { + ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}), + title: "ingress without matching labels", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + dnsnames: []string{"example.org"}, + ips: []string{"8.8.8.8"}, + labels: map[string]string{"app": "web-internal", "name": "reverse-proxy"}, + }, + }, + expected: []*endpoint.Endpoint{}, + }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { - ingresses := make([]*v1beta1.Ingress, 0) - for _, item := range ti.ingressItems { - ingresses = append(ingresses, item.Ingress()) - } + t.Parallel() fakeClient := fake.NewSimpleClientset() + for _, item := range ti.ingressItems { + ingress := item.Ingress() + _, err := fakeClient.NetworkingV1().Ingresses(ingress.Namespace).Create(context.Background(), ingress, metav1.CreateOptions{}) + require.NoError(t, err) + } + + if ti.ingressLabelSelector == nil { + ti.ingressLabelSelector = labels.Everything() + } + source, _ := NewIngressSource( fakeClient, ti.targetNamespace, @@ -1174,36 +1233,8 @@ func testIngressEndpoints(t *testing.T) { ti.ignoreHostnameAnnotation, ti.ignoreIngressTLSSpec, ti.ignoreIngressRulesSpec, + ti.ingressLabelSelector, ) - for _, ingress := range ingresses { - _, err := fakeClient.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(context.Background(), ingress, metav1.CreateOptions{}) - require.NoError(t, err) - } - - // Wait for the Ingress resources to be visible to the source. We check the - // source's informer cache to detect when this occurs. (This violates encapsulation - // but is okay as this is a test and we want to ensure the informer's cache updates.) - concreteIngressSource := source.(*ingressSource) - ingressLister := concreteIngressSource.ingressInformer.Lister() - err := poll(250*time.Millisecond, 6*time.Second, func() (bool, error) { - allIngressesPresent := true - for _, ingress := range ingresses { - // Skip ingresses that the source would also skip. - if ti.targetNamespace != "" && ti.targetNamespace != ingress.Namespace { - continue - } - - // Check for the presence of this ingress. - _, err := ingressLister.Ingresses(ingress.Namespace).Get(ingress.Name) - if err != nil { - allIngressesPresent = false - break - } - } - return allIngressesPresent, nil - }) - require.NoError(t, err) - // Informer cache has all of the ingresses. Retrieve and validate their endpoints. res, err := source.Endpoints(context.Background()) if ti.expectError { @@ -1225,31 +1256,33 @@ type fakeIngress struct { namespace string name string annotations map[string]string + labels map[string]string } -func (ing fakeIngress) Ingress() *v1beta1.Ingress { - ingress := &v1beta1.Ingress{ +func (ing fakeIngress) Ingress() *networkv1.Ingress { + ingress := &networkv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Namespace: ing.namespace, Name: ing.name, Annotations: ing.annotations, + Labels: ing.labels, }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{}, + Spec: networkv1.IngressSpec{ + Rules: []networkv1.IngressRule{}, }, - Status: v1beta1.IngressStatus{ + Status: networkv1.IngressStatus{ LoadBalancer: v1.LoadBalancerStatus{ Ingress: []v1.LoadBalancerIngress{}, }, }, } for _, dnsname := range ing.dnsnames { - ingress.Spec.Rules = append(ingress.Spec.Rules, v1beta1.IngressRule{ + ingress.Spec.Rules = append(ingress.Spec.Rules, networkv1.IngressRule{ Host: dnsname, }) } for _, hosts := range ing.tlsdnsnames { - ingress.Spec.TLS = append(ingress.Spec.TLS, v1beta1.IngressTLS{ + ingress.Spec.TLS = append(ingress.Spec.TLS, networkv1.IngressTLS{ Hosts: hosts, }) } diff --git a/source/ingressroute.go b/source/ingressroute.go deleted file mode 100644 index 1301f6559..000000000 --- a/source/ingressroute.go +++ /dev/null @@ -1,358 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package source - -import ( - "bytes" - "context" - "fmt" - "sort" - "strings" - "text/template" - "time" - - "github.com/pkg/errors" - contour "github.com/projectcontour/contour/apis/contour/v1beta1" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/dynamic/dynamicinformer" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - - "sigs.k8s.io/external-dns/endpoint" -) - -// ingressRouteSource is an implementation of Source for ProjectContour IngressRoute objects. -// The IngressRoute implementation uses the spec.virtualHost.fqdn value for the hostname. -// Use targetAnnotationKey to explicitly set Endpoint. -type ingressRouteSource struct { - dynamicKubeClient dynamic.Interface - kubeClient kubernetes.Interface - contourLoadBalancerService string - namespace string - annotationFilter string - fqdnTemplate *template.Template - combineFQDNAnnotation bool - ignoreHostnameAnnotation bool - ingressRouteInformer informers.GenericInformer - unstructuredConverter *UnstructuredConverter -} - -// NewContourIngressRouteSource creates a new contourIngressRouteSource with the given config. -func NewContourIngressRouteSource( - dynamicKubeClient dynamic.Interface, - kubeClient kubernetes.Interface, - contourLoadBalancerService string, - namespace string, - annotationFilter string, - fqdnTemplate string, - combineFqdnAnnotation bool, - ignoreHostnameAnnotation bool, -) (Source, error) { - var ( - tmpl *template.Template - err error - ) - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } - } - - if _, _, err = parseContourLoadBalancerService(contourLoadBalancerService); err != nil { - return nil, err - } - - // Use shared informer to listen for add/update/delete of ingressroutes in the specified namespace. - // Set resync period to 0, to prevent processing when nothing has changed. - informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil) - ingressRouteInformer := informerFactory.ForResource(contour.IngressRouteGVR) - - // Add default resource event handlers to properly initialize informer. - ingressRouteInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) - - // TODO informer is not explicitly stopped since controller is not passing in its channel. - informerFactory.Start(wait.NeverStop) - - // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return ingressRouteInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) - } - - uc, err := NewUnstructuredConverter() - if err != nil { - return nil, fmt.Errorf("failed to setup Unstructured Converter: %v", err) - } - - return &ingressRouteSource{ - dynamicKubeClient: dynamicKubeClient, - kubeClient: kubeClient, - contourLoadBalancerService: contourLoadBalancerService, - namespace: namespace, - annotationFilter: annotationFilter, - fqdnTemplate: tmpl, - combineFQDNAnnotation: combineFqdnAnnotation, - ignoreHostnameAnnotation: ignoreHostnameAnnotation, - ingressRouteInformer: ingressRouteInformer, - unstructuredConverter: uc, - }, nil -} - -// Endpoints returns endpoint objects for each host-target combination that should be processed. -// Retrieves all ingressroute resources in the source's namespace(s). -func (sc *ingressRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - irs, err := sc.ingressRouteInformer.Lister().ByNamespace(sc.namespace).List(labels.Everything()) - if err != nil { - return nil, err - } - - // Convert to []*contour.IngressRoute - var ingressRoutes []*contour.IngressRoute - for _, ir := range irs { - unstrucuredIR, ok := ir.(*unstructured.Unstructured) - if !ok { - return nil, errors.New("could not convert") - } - - irConverted := &contour.IngressRoute{} - err := sc.unstructuredConverter.scheme.Convert(unstrucuredIR, irConverted, nil) - if err != nil { - return nil, err - } - ingressRoutes = append(ingressRoutes, irConverted) - } - - ingressRoutes, err = sc.filterByAnnotations(ingressRoutes) - if err != nil { - return nil, err - } - - endpoints := []*endpoint.Endpoint{} - - for _, ir := range ingressRoutes { - // Check controller annotation to see if we are responsible. - controller, ok := ir.Annotations[controllerAnnotationKey] - if ok && controller != controllerAnnotationValue { - log.Debugf("Skipping ingressroute %s/%s because controller value does not match, found: %s, required: %s", - ir.Namespace, ir.Name, controller, controllerAnnotationValue) - continue - } else if ir.CurrentStatus != "valid" { - log.Debugf("Skipping ingressroute %s/%s because it is not valid", ir.Namespace, ir.Name) - continue - } - - irEndpoints, err := sc.endpointsFromIngressRoute(ctx, ir) - if err != nil { - return nil, err - } - - // apply template if fqdn is missing on ingressroute - if (sc.combineFQDNAnnotation || len(irEndpoints) == 0) && sc.fqdnTemplate != nil { - tmplEndpoints, err := sc.endpointsFromTemplate(ctx, ir) - if err != nil { - return nil, err - } - - if sc.combineFQDNAnnotation { - irEndpoints = append(irEndpoints, tmplEndpoints...) - } else { - irEndpoints = tmplEndpoints - } - } - - if len(irEndpoints) == 0 { - log.Debugf("No endpoints could be generated from ingressroute %s/%s", ir.Namespace, ir.Name) - continue - } - - log.Debugf("Endpoints generated from ingressroute: %s/%s: %v", ir.Namespace, ir.Name, irEndpoints) - sc.setResourceLabel(ir, irEndpoints) - endpoints = append(endpoints, irEndpoints...) - } - - for _, ep := range endpoints { - sort.Sort(ep.Targets) - } - - return endpoints, nil -} - -func (sc *ingressRouteSource) endpointsFromTemplate(ctx context.Context, ingressRoute *contour.IngressRoute) ([]*endpoint.Endpoint, error) { - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, ingressRoute) - if err != nil { - return nil, fmt.Errorf("failed to apply template on ingressroute %s/%s: %v", ingressRoute.Namespace, ingressRoute.Name, err) - } - - hostnames := buf.String() - - ttl, err := getTTLFromAnnotations(ingressRoute.Annotations) - if err != nil { - log.Warn(err) - } - - targets := getTargetsFromTargetAnnotation(ingressRoute.Annotations) - - if len(targets) == 0 { - targets, err = sc.targetsFromContourLoadBalancer(ctx) - if err != nil { - return nil, err - } - } - - providerSpecific, setIdentifier := getProviderSpecificAnnotations(ingressRoute.Annotations) - - var endpoints []*endpoint.Endpoint - // splits the FQDN template and removes the trailing periods - hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") - for _, hostname := range hostnameList { - hostname = strings.TrimSuffix(hostname, ".") - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) - } - return endpoints, nil -} - -// filterByAnnotations filters a list of configs by a given annotation selector. -func (sc *ingressRouteSource) filterByAnnotations(ingressRoutes []*contour.IngressRoute) ([]*contour.IngressRoute, error) { - labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter) - if err != nil { - return nil, err - } - selector, err := metav1.LabelSelectorAsSelector(labelSelector) - if err != nil { - return nil, err - } - - // empty filter returns original list - if selector.Empty() { - return ingressRoutes, nil - } - - filteredList := []*contour.IngressRoute{} - - for _, ingressRoute := range ingressRoutes { - // convert the ingressroute's annotations to an equivalent label selector - annotations := labels.Set(ingressRoute.Annotations) - - // include ingressroute if its annotations match the selector - if selector.Matches(annotations) { - filteredList = append(filteredList, ingressRoute) - } - } - - return filteredList, nil -} - -func (sc *ingressRouteSource) setResourceLabel(ingressRoute *contour.IngressRoute, endpoints []*endpoint.Endpoint) { - for _, ep := range endpoints { - ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("ingressroute/%s/%s", ingressRoute.Namespace, ingressRoute.Name) - } -} - -func (sc *ingressRouteSource) targetsFromContourLoadBalancer(ctx context.Context) (targets endpoint.Targets, err error) { - lbNamespace, lbName, err := parseContourLoadBalancerService(sc.contourLoadBalancerService) - if err != nil { - return nil, err - } - if svc, err := sc.kubeClient.CoreV1().Services(lbNamespace).Get(ctx, lbName, metav1.GetOptions{}); err != nil { - log.Warn(err) - } else { - for _, lb := range svc.Status.LoadBalancer.Ingress { - if lb.IP != "" { - targets = append(targets, lb.IP) - } - if lb.Hostname != "" { - targets = append(targets, lb.Hostname) - } - } - } - - return -} - -// endpointsFromIngressRouteConfig extracts the endpoints from a Contour IngressRoute object -func (sc *ingressRouteSource) endpointsFromIngressRoute(ctx context.Context, ingressRoute *contour.IngressRoute) ([]*endpoint.Endpoint, error) { - if ingressRoute.CurrentStatus != "valid" { - log.Warn(errors.Errorf("cannot generate endpoints for ingressroute with status %s", ingressRoute.CurrentStatus)) - return nil, nil - } - - var endpoints []*endpoint.Endpoint - - ttl, err := getTTLFromAnnotations(ingressRoute.Annotations) - if err != nil { - log.Warn(err) - } - - targets := getTargetsFromTargetAnnotation(ingressRoute.Annotations) - - if len(targets) == 0 { - targets, err = sc.targetsFromContourLoadBalancer(ctx) - if err != nil { - return nil, err - } - } - - providerSpecific, setIdentifier := getProviderSpecificAnnotations(ingressRoute.Annotations) - - if virtualHost := ingressRoute.Spec.VirtualHost; virtualHost != nil { - if fqdn := virtualHost.Fqdn; fqdn != "" { - endpoints = append(endpoints, endpointsForHostname(fqdn, targets, ttl, providerSpecific, setIdentifier)...) - } - } - - // Skip endpoints if we do not want entries from annotations - if !sc.ignoreHostnameAnnotation { - hostnameList := getHostnamesFromAnnotations(ingressRoute.Annotations) - for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) - } - } - - return endpoints, nil -} - -func parseContourLoadBalancerService(service string) (namespace, name string, err error) { - parts := strings.Split(service, "/") - if len(parts) != 2 { - err = fmt.Errorf("invalid contour load balancer service (namespace/name) found '%v'", service) - } else { - namespace, name = parts[0], parts[1] - } - - return -} - -func (sc *ingressRouteSource) AddEventHandler(ctx context.Context, handler func()) { -} diff --git a/source/ingressroute_test.go b/source/ingressroute_test.go deleted file mode 100644 index 8a18182c5..000000000 --- a/source/ingressroute_test.go +++ /dev/null @@ -1,1156 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package source - -import ( - "context" - "testing" - - "github.com/pkg/errors" - contour "github.com/projectcontour/contour/apis/contour/v1beta1" - projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - fakeDynamic "k8s.io/client-go/dynamic/fake" - fakeKube "k8s.io/client-go/kubernetes/fake" - - "sigs.k8s.io/external-dns/endpoint" -) - -// This is a compile-time validation that ingressRouteSource is a Source. -var _ Source = &ingressRouteSource{} - -type IngressRouteSuite struct { - suite.Suite - source Source - loadBalancer *v1.Service - ingressRoute *contour.IngressRoute -} - -func (suite *IngressRouteSuite) SetupTest() { - fakeKubernetesClient := fakeKube.NewSimpleClientset() - fakeDynamicClient, s := newDynamicKubernetesClient() - var err error - - suite.loadBalancer = (fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"v1"}, - namespace: "heptio-contour/contour", - name: "contour", - }).Service() - - _, err = fakeKubernetesClient.CoreV1().Services(suite.loadBalancer.Namespace).Create(context.Background(), suite.loadBalancer, metav1.CreateOptions{}) - suite.NoError(err, "should succeed") - - suite.source, err = NewContourIngressRouteSource( - fakeDynamicClient, - fakeKubernetesClient, - "heptio-contour/contour", - "default", - "", - "{{.Name}}", - false, - false, - ) - suite.NoError(err, "should initialize ingressroute source") - - suite.ingressRoute = (fakeIngressRoute{ - name: "foo-ingressroute-with-targets", - namespace: "default", - host: "example.com", - }).IngressRoute() - - // Convert to unstructured - unstructuredIngressRoute, err := convertIngressRouteToUnstructured(suite.ingressRoute, s) - if err != nil { - suite.Error(err) - } - - _, err = fakeDynamicClient.Resource(contour.IngressRouteGVR).Namespace(suite.ingressRoute.Namespace).Create(context.Background(), unstructuredIngressRoute, metav1.CreateOptions{}) - suite.NoError(err, "should succeed") -} - -func (suite *IngressRouteSuite) TestResourceLabelIsSet() { - endpoints, _ := suite.source.Endpoints(context.Background()) - for _, ep := range endpoints { - suite.Equal("ingressroute/default/foo-ingressroute-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label") - } -} - -func newDynamicKubernetesClient() (*fakeDynamic.FakeDynamicClient, *runtime.Scheme) { - s := runtime.NewScheme() - _ = contour.AddToScheme(s) - _ = projectcontour.AddToScheme(s) - return fakeDynamic.NewSimpleDynamicClient(s), s -} - -func convertIngressRouteToUnstructured(ir *contour.IngressRoute, s *runtime.Scheme) (*unstructured.Unstructured, error) { - unstructuredIngressRoute := &unstructured.Unstructured{} - if err := s.Convert(ir, unstructuredIngressRoute, context.Background()); err != nil { - return nil, err - } - return unstructuredIngressRoute, nil -} - -func TestIngressRoute(t *testing.T) { - suite.Run(t, new(IngressRouteSuite)) - t.Run("endpointsFromIngressRoute", testEndpointsFromIngressRoute) - t.Run("Endpoints", testIngressRouteEndpoints) -} - -func TestNewContourIngressRouteSource(t *testing.T) { - for _, ti := range []struct { - title string - annotationFilter string - fqdnTemplate string - combineFQDNAndAnnotation bool - expectError bool - }{ - { - title: "invalid template", - expectError: true, - fqdnTemplate: "{{.Name", - }, - { - title: "valid empty template", - expectError: false, - }, - { - title: "valid template", - expectError: false, - fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com", - }, - { - title: "valid template", - expectError: false, - fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com", - }, - { - title: "valid template", - expectError: false, - fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com", - combineFQDNAndAnnotation: true, - }, - { - title: "non-empty annotation filter label", - expectError: false, - annotationFilter: "contour.heptio.com/ingress.class=contour", - }, - } { - t.Run(ti.title, func(t *testing.T) { - fakeDynamicClient, _ := newDynamicKubernetesClient() - - _, err := NewContourIngressRouteSource( - fakeDynamicClient, - fakeKube.NewSimpleClientset(), - "heptio-contour/contour", - "", - ti.annotationFilter, - ti.fqdnTemplate, - ti.combineFQDNAndAnnotation, - false, - ) - if ti.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func testEndpointsFromIngressRoute(t *testing.T) { - for _, ti := range []struct { - title string - loadBalancer fakeLoadBalancerService - ingressRoute fakeIngressRoute - expected []*endpoint.Endpoint - }{ - { - title: "one rule.host one lb.hostname", - loadBalancer: fakeLoadBalancerService{ - hostnames: []string{"lb.com"}, // Kubernetes omits the trailing dot - }, - ingressRoute: fakeIngressRoute{ - host: "foo.bar", // Kubernetes requires removal of trailing dot - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "foo.bar", - Targets: endpoint.Targets{"lb.com"}, - }, - }, - }, - { - title: "one rule.host one lb.IP", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRoute: fakeIngressRoute{ - host: "foo.bar", - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "foo.bar", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - }, - }, - { - title: "one rule.host two lb.IP and two lb.Hostname", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8", "127.0.0.1"}, - hostnames: []string{"elb.com", "alb.com"}, - }, - ingressRoute: fakeIngressRoute{ - host: "foo.bar", - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "foo.bar", - Targets: endpoint.Targets{"8.8.8.8", "127.0.0.1"}, - }, - { - DNSName: "foo.bar", - Targets: endpoint.Targets{"elb.com", "alb.com"}, - }, - }, - }, - { - title: "no rule.host", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8", "127.0.0.1"}, - hostnames: []string{"elb.com", "alb.com"}, - }, - ingressRoute: fakeIngressRoute{}, - expected: []*endpoint.Endpoint{}, - }, - { - title: "one rule.host invalid ingressroute", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8", "127.0.0.1"}, - hostnames: []string{"elb.com", "alb.com"}, - }, - ingressRoute: fakeIngressRoute{ - host: "foo.bar", - invalid: true, - }, - expected: []*endpoint.Endpoint{}, - }, - { - title: "no targets", - loadBalancer: fakeLoadBalancerService{}, - ingressRoute: fakeIngressRoute{}, - expected: []*endpoint.Endpoint{}, - }, - { - title: "delegate ingressroute", - loadBalancer: fakeLoadBalancerService{ - hostnames: []string{"lb.com"}, - }, - ingressRoute: fakeIngressRoute{ - delegate: true, - }, - expected: []*endpoint.Endpoint{}, - }, - } { - t.Run(ti.title, func(t *testing.T) { - if source, err := newTestIngressRouteSource(ti.loadBalancer); err != nil { - require.NoError(t, err) - } else if endpoints, err := source.endpointsFromIngressRoute(context.Background(), ti.ingressRoute.IngressRoute()); err != nil { - require.NoError(t, err) - } else { - validateEndpoints(t, endpoints, ti.expected) - } - }) - } -} - -func testIngressRouteEndpoints(t *testing.T) { - namespace := "testing" - for _, ti := range []struct { - title string - targetNamespace string - annotationFilter string - loadBalancer fakeLoadBalancerService - ingressRouteItems []fakeIngressRoute - expected []*endpoint.Endpoint - expectError bool - fqdnTemplate string - combineFQDNAndAnnotation bool - ignoreHostnameAnnotation bool - }{ - { - title: "no ingressroute", - targetNamespace: "", - }, - { - title: "two simple ingressroutes", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"lb.com"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - host: "example.org", - }, - { - name: "fake2", - namespace: namespace, - host: "new.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "example.org", - Targets: endpoint.Targets{"lb.com"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"lb.com"}, - }, - }, - }, - { - title: "two simple ingressroutes on different namespaces", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"lb.com"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: "testing1", - host: "example.org", - }, - { - name: "fake2", - namespace: "testing2", - host: "new.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "example.org", - Targets: endpoint.Targets{"lb.com"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"lb.com"}, - }, - }, - }, - { - title: "two simple ingressroutes on different namespaces and a target namespace", - targetNamespace: "testing1", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"lb.com"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: "testing1", - host: "example.org", - }, - { - name: "fake2", - namespace: "testing2", - host: "new.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "example.org", - Targets: endpoint.Targets{"lb.com"}, - }, - }, - }, - { - title: "valid matching annotation filter expression", - targetNamespace: "", - annotationFilter: "contour.heptio.com/ingress.class in (alb, contour)", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - "contour.heptio.com/ingress.class": "contour", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - }, - }, - { - title: "valid non-matching annotation filter expression", - targetNamespace: "", - annotationFilter: "contour.heptio.com/ingress.class in (alb, contour)", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - "contour.heptio.com/ingress.class": "tectonic", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{}, - }, - { - title: "invalid annotation filter expression", - targetNamespace: "", - annotationFilter: "contour.heptio.com/ingress.name in (a b)", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - "contour.heptio.com/ingress.class": "alb", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{}, - expectError: true, - }, - { - title: "valid matching annotation filter label", - targetNamespace: "", - annotationFilter: "contour.heptio.com/ingress.class=contour", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - "contour.heptio.com/ingress.class": "contour", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - }, - }, - { - title: "valid non-matching annotation filter label", - targetNamespace: "", - annotationFilter: "contour.heptio.com/ingress.class=contour", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - "contour.heptio.com/ingress.class": "alb", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{}, - }, - { - title: "our controller type is dns-controller", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - controllerAnnotationKey: controllerAnnotationValue, - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - }, - }, - { - title: "different controller types are ignored", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - controllerAnnotationKey: "some-other-tool", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{}, - }, - { - title: "template for ingressroute if host is missing", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"elb.com"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - controllerAnnotationKey: controllerAnnotationValue, - }, - host: "", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "fake1.ext-dns.test.com", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "fake1.ext-dns.test.com", - Targets: endpoint.Targets{"elb.com"}, - }, - }, - fqdnTemplate: "{{.Name}}.ext-dns.test.com", - }, - { - title: "another controller annotation skipped even with template", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - controllerAnnotationKey: "other-controller", - }, - host: "", - }, - }, - expected: []*endpoint.Endpoint{}, - fqdnTemplate: "{{.Name}}.ext-dns.test.com", - }, - { - title: "multiple FQDN template hostnames", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{}, - host: "", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "fake1.ext-dns.test.com", - Targets: endpoint.Targets{"8.8.8.8"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "fake1.ext-dna.test.com", - Targets: endpoint.Targets{"8.8.8.8"}, - RecordType: endpoint.RecordTypeA, - }, - }, - fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com", - }, - { - title: "multiple FQDN template hostnames", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{}, - host: "", - }, - { - name: "fake2", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "fake1.ext-dns.test.com", - Targets: endpoint.Targets{"8.8.8.8"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "fake1.ext-dna.test.com", - Targets: endpoint.Targets{"8.8.8.8"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "example.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "fake2.ext-dns.test.com", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "fake2.ext-dna.test.com", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - }, - fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com", - combineFQDNAndAnnotation: true, - }, - { - title: "ingressroute rules with annotation", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - }, - host: "example.org", - }, - { - name: "fake2", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - }, - host: "example2.org", - }, - { - name: "fake3", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "1.2.3.4", - }, - host: "example3.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "example2.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "example3.org", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - }, - }, - { - title: "ingressroute rules with hostname annotation", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"1.2.3.4"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - hostnameAnnotationKey: "dns-through-hostname.com", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "dns-through-hostname.com", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - }, - }, - { - title: "ingressroute rules with hostname annotation having multiple hostnames", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"1.2.3.4"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - hostnameAnnotationKey: "dns-through-hostname.com, another-dns-through-hostname.com", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "dns-through-hostname.com", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - { - DNSName: "another-dns-through-hostname.com", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - }, - }, - { - title: "ingressroute rules with hostname and target annotation", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - hostnameAnnotationKey: "dns-through-hostname.com", - targetAnnotationKey: "ingressroute-target.com", - }, - host: "example.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "dns-through-hostname.com", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - }, - }, - { - title: "ingressroute rules with annotation and custom TTL", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - ttlAnnotationKey: "6", - }, - host: "example.org", - }, - { - name: "fake2", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - ttlAnnotationKey: "1", - }, - host: "example2.org", - }, - { - name: "fake3", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - ttlAnnotationKey: "10s", - }, - host: "example3.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordTTL: endpoint.TTL(6), - }, - { - DNSName: "example2.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordTTL: endpoint.TTL(1), - }, - { - DNSName: "example3.org", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordTTL: endpoint.TTL(10), - }, - }, - }, - { - title: "template for ingressroute with annotation", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{}, - hostnames: []string{}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - }, - host: "", - }, - { - name: "fake2", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "ingressroute-target.com", - }, - host: "", - }, - { - name: "fake3", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "1.2.3.4", - }, - host: "", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "fake1.ext-dns.test.com", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "fake2.ext-dns.test.com", - Targets: endpoint.Targets{"ingressroute-target.com"}, - RecordType: endpoint.RecordTypeCNAME, - }, - { - DNSName: "fake3.ext-dns.test.com", - Targets: endpoint.Targets{"1.2.3.4"}, - RecordType: endpoint.RecordTypeA, - }, - }, - fqdnTemplate: "{{.Name}}.ext-dns.test.com", - }, - { - title: "ingressroute with empty annotation", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{}, - hostnames: []string{}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - targetAnnotationKey: "", - }, - host: "", - }, - }, - expected: []*endpoint.Endpoint{}, - fqdnTemplate: "{{.Name}}.ext-dns.test.com", - }, - { - title: "ignore hostname annotations", - targetNamespace: "", - loadBalancer: fakeLoadBalancerService{ - ips: []string{"8.8.8.8"}, - hostnames: []string{"lb.com"}, - }, - ingressRouteItems: []fakeIngressRoute{ - { - name: "fake1", - namespace: namespace, - annotations: map[string]string{ - hostnameAnnotationKey: "ignore.me", - }, - host: "example.org", - }, - { - name: "fake2", - namespace: namespace, - annotations: map[string]string{ - hostnameAnnotationKey: "ignore.me.too", - }, - host: "new.org", - }, - }, - expected: []*endpoint.Endpoint{ - { - DNSName: "example.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "example.org", - Targets: endpoint.Targets{"lb.com"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"8.8.8.8"}, - }, - { - DNSName: "new.org", - Targets: endpoint.Targets{"lb.com"}, - }, - }, - ignoreHostnameAnnotation: true, - }, - } { - t.Run(ti.title, func(t *testing.T) { - ingressRoutes := make([]*contour.IngressRoute, 0) - for _, item := range ti.ingressRouteItems { - ingressRoutes = append(ingressRoutes, item.IngressRoute()) - } - - fakeKubernetesClient := fakeKube.NewSimpleClientset() - - lbService := ti.loadBalancer.Service() - _, err := fakeKubernetesClient.CoreV1().Services(lbService.Namespace).Create(context.Background(), lbService, metav1.CreateOptions{}) - if err != nil { - require.NoError(t, err) - } - - fakeDynamicClient, scheme := newDynamicKubernetesClient() - for _, ingressRoute := range ingressRoutes { - converted, err := convertIngressRouteToUnstructured(ingressRoute, scheme) - require.NoError(t, err) - _, err = fakeDynamicClient.Resource(contour.IngressRouteGVR).Namespace(ingressRoute.Namespace).Create(context.Background(), converted, metav1.CreateOptions{}) - require.NoError(t, err) - } - - ingressRouteSource, err := NewContourIngressRouteSource( - fakeDynamicClient, - fakeKubernetesClient, - lbService.Namespace+"/"+lbService.Name, - ti.targetNamespace, - ti.annotationFilter, - ti.fqdnTemplate, - ti.combineFQDNAndAnnotation, - ti.ignoreHostnameAnnotation, - ) - require.NoError(t, err) - - res, err := ingressRouteSource.Endpoints(context.Background()) - if ti.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - - validateEndpoints(t, res, ti.expected) - }) - } -} - -// ingressroute specific helper functions -func newTestIngressRouteSource(loadBalancer fakeLoadBalancerService) (*ingressRouteSource, error) { - fakeKubernetesClient := fakeKube.NewSimpleClientset() - fakeDynamicClient, _ := newDynamicKubernetesClient() - - lbService := loadBalancer.Service() - _, err := fakeKubernetesClient.CoreV1().Services(lbService.Namespace).Create(context.Background(), lbService, metav1.CreateOptions{}) - if err != nil { - return nil, err - } - - src, err := NewContourIngressRouteSource( - fakeDynamicClient, - fakeKubernetesClient, - lbService.Namespace+"/"+lbService.Name, - "default", - "", - "{{.Name}}", - false, - false, - ) - if err != nil { - return nil, err - } - - irsrc, ok := src.(*ingressRouteSource) - if !ok { - return nil, errors.New("underlying source type was not ingressroute") - } - - return irsrc, nil -} - -type fakeLoadBalancerService struct { - ips []string - hostnames []string - namespace string - name string -} - -func (ig fakeLoadBalancerService) Service() *v1.Service { - svc := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ig.namespace, - Name: ig.name, - }, - Status: v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{}, - }, - }, - } - - for _, ip := range ig.ips { - svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ - IP: ip, - }) - } - for _, hostname := range ig.hostnames { - svc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, v1.LoadBalancerIngress{ - Hostname: hostname, - }) - } - - return svc -} - -type fakeIngressRoute struct { - namespace string - name string - annotations map[string]string - - host string - invalid bool - delegate bool -} - -func (ir fakeIngressRoute) IngressRoute() *contour.IngressRoute { - var status string - if ir.invalid { - status = "invalid" - } else { - status = "valid" - } - - var spec contour.IngressRouteSpec - if ir.delegate { - spec = contour.IngressRouteSpec{} - } else { - spec = contour.IngressRouteSpec{ - VirtualHost: &contour.VirtualHost{ - Fqdn: ir.host, - }, - } - } - - ingressRoute := &contour.IngressRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ir.namespace, - Name: ir.name, - Annotations: ir.annotations, - }, - Spec: spec, - Status: projectcontour.Status{ - CurrentStatus: status, - }, - } - - return ingressRoute -} diff --git a/source/gateway.go b/source/istio_gateway.go similarity index 86% rename from source/gateway.go rename to source/istio_gateway.go index 68047a9a6..9472c6750 100644 --- a/source/gateway.go +++ b/source/istio_gateway.go @@ -17,13 +17,11 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" "strings" "text/template" - "time" log "github.com/sirupsen/logrus" networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" @@ -66,18 +64,9 @@ func NewIstioGatewaySource( combineFQDNAnnotation bool, ignoreHostnameAnnotation bool, ) (Source, error) { - var ( - tmpl *template.Template - err error - ) - - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informers to listen for add/update/delete of services/pods/nodes in the specified namespace. @@ -109,19 +98,11 @@ func NewIstioGatewaySource( istioInformerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return serviceInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } - - // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return gatewayInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), istioInformerFactory); err != nil { + return nil, err } return &gatewaySource{ @@ -169,7 +150,7 @@ func (sc *gatewaySource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e // apply template if host is missing on gateway if (sc.combineFQDNAnnotation || len(gwHostnames) == 0) && sc.fqdnTemplate != nil { - iHostnames, err := sc.hostNamesFromTemplate(gateway) + iHostnames, err := execTemplate(sc.fqdnTemplate, &gateway) if err != nil { return nil, err } @@ -207,19 +188,7 @@ func (sc *gatewaySource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e func (sc *gatewaySource) AddEventHandler(ctx context.Context, handler func()) { log.Debug("Adding event handler for Istio Gateway") - sc.gatewayInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.gatewayInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } // filterByAnnotations filters a list of configs by a given annotation selector. @@ -345,18 +314,6 @@ func (sc *gatewaySource) hostNamesFromGateway(gateway networkingv1alpha3.Gateway return hostnames, nil } -func (sc *gatewaySource) hostNamesFromTemplate(gateway networkingv1alpha3.Gateway) ([]string, error) { - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, gateway) - if err != nil { - return nil, fmt.Errorf("failed to apply template on istio gateway %v: %v", gateway, err) - } - - hostnames := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",") - return hostnames, nil -} - func gatewaySelectorMatchesServiceSelector(gwSelector, svcSelector map[string]string) bool { for k, v := range gwSelector { if lbl, ok := svcSelector[k]; !ok || lbl != v { diff --git a/source/gateway_test.go b/source/istio_gateway_test.go similarity index 98% rename from source/gateway_test.go rename to source/istio_gateway_test.go index 12addfbba..c94a06690 100644 --- a/source/gateway_test.go +++ b/source/istio_gateway_test.go @@ -26,7 +26,6 @@ import ( "github.com/stretchr/testify/suite" networkingv1alpha3api "istio.io/api/networking/v1alpha3" networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" - istioclient "istio.io/client-go/pkg/clientset/versioned" istiofake "istio.io/client-go/pkg/clientset/versioned/fake" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -46,7 +45,7 @@ type GatewaySuite struct { func (suite *GatewaySuite) SetupTest() { fakeKubernetesClient := fake.NewSimpleClientset() - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() var err error suite.lbServices = []*v1.Service{ @@ -90,12 +89,16 @@ func (suite *GatewaySuite) TestResourceLabelIsSet() { } func TestGateway(t *testing.T) { + t.Parallel() + suite.Run(t, new(GatewaySuite)) t.Run("endpointsFromGatewayConfig", testEndpointsFromGatewayConfig) t.Run("Endpoints", testGatewayEndpoints) } func TestNewIstioGatewaySource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -134,10 +137,13 @@ func TestNewIstioGatewaySource(t *testing.T) { annotationFilter: "kubernetes.io/gateway.class=nginx", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewIstioGatewaySource( fake.NewSimpleClientset(), - NewFakeConfigStore(), + istiofake.NewSimpleClientset(), "", ti.annotationFilter, ti.fqdnTemplate, @@ -154,6 +160,8 @@ func TestNewIstioGatewaySource(t *testing.T) { } func testEndpointsFromGatewayConfig(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string lbServices []fakeIngressGatewayService @@ -306,7 +314,10 @@ func testEndpointsFromGatewayConfig(t *testing.T) { }, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + gatewayCfg := ti.config.Config() if source, err := newTestGatewaySource(ti.lbServices); err != nil { require.NoError(t, err) @@ -322,6 +333,8 @@ func testEndpointsFromGatewayConfig(t *testing.T) { } func testGatewayEndpoints(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string targetNamespace string @@ -1132,7 +1145,9 @@ func testGatewayEndpoints(t *testing.T) { }, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() fakeKubernetesClient := fake.NewSimpleClientset() @@ -1142,7 +1157,7 @@ func testGatewayEndpoints(t *testing.T) { require.NoError(t, err) } - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() for _, config := range ti.configItems { gatewayCfg := config.Config() _, err := fakeIstioClient.NetworkingV1alpha3().Gateways(ti.targetNamespace).Create(context.Background(), &gatewayCfg, metav1.CreateOptions{}) @@ -1175,7 +1190,7 @@ func testGatewayEndpoints(t *testing.T) { // gateway specific helper functions func newTestGatewaySource(loadBalancerList []fakeIngressGatewayService) (*gatewaySource, error) { fakeKubernetesClient := fake.NewSimpleClientset() - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() for _, lb := range loadBalancerList { service := lb.Service() @@ -1276,7 +1291,3 @@ func (c fakeGatewayConfig) Config() networkingv1alpha3.Gateway { return gw } - -func NewFakeConfigStore() istioclient.Interface { - return istiofake.NewSimpleClientset() -} diff --git a/source/virtualservice.go b/source/istio_virtualservice.go similarity index 91% rename from source/virtualservice.go rename to source/istio_virtualservice.go index f62ef6df7..4a96ceb9b 100644 --- a/source/virtualservice.go +++ b/source/istio_virtualservice.go @@ -17,13 +17,11 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" "strings" "text/template" - "time" networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" @@ -70,18 +68,9 @@ func NewIstioVirtualServiceSource( combineFQDNAnnotation bool, ignoreHostnameAnnotation bool, ) (Source, error) { - var ( - tmpl *template.Template - err error - ) - - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informers to listen for add/update/delete of services/pods/nodes in the specified namespace. @@ -113,18 +102,11 @@ func NewIstioVirtualServiceSource( istioInformerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = wait.Poll(time.Second, 60*time.Second, func() (bool, error) { - return serviceInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } - - err = wait.Poll(time.Second, 60*time.Second, func() (bool, error) { - return virtualServiceInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), istioInformerFactory); err != nil { + return nil, err } return &virtualServiceSource{ @@ -205,19 +187,7 @@ func (sc *virtualServiceSource) Endpoints(ctx context.Context) ([]*endpoint.Endp func (sc *virtualServiceSource) AddEventHandler(ctx context.Context, handler func()) { log.Debug("Adding event handler for Istio VirtualService") - sc.virtualserviceInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.virtualserviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } func (sc *virtualServiceSource) getGateway(ctx context.Context, gatewayStr string, virtualService networkingv1alpha3.VirtualService) *networkingv1alpha3.Gateway { @@ -249,28 +219,20 @@ func (sc *virtualServiceSource) getGateway(ctx context.Context, gatewayStr strin } func (sc *virtualServiceSource) endpointsFromTemplate(ctx context.Context, virtualService networkingv1alpha3.VirtualService) ([]*endpoint.Endpoint, error) { - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, virtualService) + hostnames, err := execTemplate(sc.fqdnTemplate, &virtualService) if err != nil { - return nil, fmt.Errorf("failed to apply template on istio config %v: %v", virtualService, err) + return nil, err } - hostnamesTemplate := buf.String() - ttl, err := getTTLFromAnnotations(virtualService.Annotations) if err != nil { log.Warn(err) } - var endpoints []*endpoint.Endpoint - providerSpecific, setIdentifier := getProviderSpecificAnnotations(virtualService.Annotations) - // splits the FQDN template and removes the trailing periods - hostnames := strings.Split(strings.Replace(hostnamesTemplate, " ", "", -1), ",") + var endpoints []*endpoint.Endpoint for _, hostname := range hostnames { - hostname = strings.TrimSuffix(hostname, ".") targets, err := sc.targetsFromVirtualService(ctx, virtualService, hostname) if err != nil { return endpoints, err diff --git a/source/virtualservice_test.go b/source/istio_virtualservice_test.go similarity index 99% rename from source/virtualservice_test.go rename to source/istio_virtualservice_test.go index 3f8aff4a1..e7dc1ce4f 100644 --- a/source/virtualservice_test.go +++ b/source/istio_virtualservice_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" istionetworking "istio.io/api/networking/v1alpha3" networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" + istiofake "istio.io/client-go/pkg/clientset/versioned/fake" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/fake" @@ -47,7 +48,7 @@ type VirtualServiceSuite struct { func (suite *VirtualServiceSuite) SetupTest() { fakeKubernetesClient := fake.NewSimpleClientset() - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() var err error suite.lbServices = []*v1.Service{ @@ -70,17 +71,6 @@ func (suite *VirtualServiceSuite) SetupTest() { suite.NoError(err, "should succeed") } - suite.source, err = NewIstioVirtualServiceSource( - fakeKubernetesClient, - fakeIstioClient, - "", - "", - "{{.Name}}", - false, - false, - ) - suite.NoError(err, "should initialize virtualservice source") - suite.gwconfig = (fakeGatewayConfig{ name: "foo-gateway-with-targets", namespace: "istio-system", @@ -97,6 +87,17 @@ func (suite *VirtualServiceSuite) SetupTest() { }).Config() _, err = fakeIstioClient.NetworkingV1alpha3().VirtualServices(suite.vsconfig.Namespace).Create(context.Background(), &suite.vsconfig, metav1.CreateOptions{}) suite.NoError(err, "should succeed") + + suite.source, err = NewIstioVirtualServiceSource( + fakeKubernetesClient, + fakeIstioClient, + "", + "", + "{{.Name}}", + false, + false, + ) + suite.NoError(err, "should initialize virtualservice source") } func (suite *VirtualServiceSuite) TestResourceLabelIsSet() { @@ -109,6 +110,8 @@ func (suite *VirtualServiceSuite) TestResourceLabelIsSet() { } func TestVirtualService(t *testing.T) { + t.Parallel() + suite.Run(t, new(VirtualServiceSuite)) t.Run("virtualServiceBindsToGateway", testVirtualServiceBindsToGateway) t.Run("endpointsFromVirtualServiceConfig", testEndpointsFromVirtualServiceConfig) @@ -117,6 +120,8 @@ func TestVirtualService(t *testing.T) { } func TestNewIstioVirtualServiceSource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -155,10 +160,13 @@ func TestNewIstioVirtualServiceSource(t *testing.T) { annotationFilter: "kubernetes.io/gateway.class=nginx", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewIstioVirtualServiceSource( fake.NewSimpleClientset(), - NewFakeConfigStore(), + istiofake.NewSimpleClientset(), "", ti.annotationFilter, ti.fqdnTemplate, @@ -358,6 +366,8 @@ func testVirtualServiceBindsToGateway(t *testing.T) { } func testEndpointsFromVirtualServiceConfig(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string lbServices []fakeIngressGatewayService @@ -537,7 +547,10 @@ func testEndpointsFromVirtualServiceConfig(t *testing.T) { }, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + if source, err := newTestVirtualServiceSource(ti.lbServices, []fakeGatewayConfig{ti.gwconfig}); err != nil { require.NoError(t, err) } else if endpoints, err := source.endpointsFromVirtualService(context.Background(), ti.vsconfig.Config()); err != nil { @@ -550,6 +563,8 @@ func testEndpointsFromVirtualServiceConfig(t *testing.T) { } func testVirtualServiceEndpoints(t *testing.T) { + t.Parallel() + namespace := "testing" for _, ti := range []struct { title string @@ -1432,7 +1447,10 @@ func testVirtualServiceEndpoints(t *testing.T) { fqdnTemplate: "{{.Name}}.ext-dns.test.com", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + var gateways []networkingv1alpha3.Gateway var virtualservices []networkingv1alpha3.VirtualService @@ -1451,7 +1469,7 @@ func testVirtualServiceEndpoints(t *testing.T) { require.NoError(t, err) } - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() for _, gateway := range gateways { _, err := fakeIstioClient.NetworkingV1alpha3().Gateways(gateway.Namespace).Create(context.Background(), &gateway, metav1.CreateOptions{}) @@ -1520,7 +1538,7 @@ func testGatewaySelectorMatchesService(t *testing.T) { func newTestVirtualServiceSource(loadBalancerList []fakeIngressGatewayService, gwList []fakeGatewayConfig) (*virtualServiceSource, error) { fakeKubernetesClient := fake.NewSimpleClientset() - fakeIstioClient := NewFakeConfigStore() + fakeIstioClient := istiofake.NewSimpleClientset() for _, lb := range loadBalancerList { service := lb.Service() diff --git a/source/kong_tcpingress.go b/source/kong_tcpingress.go index 1fe0b1111..028f0fb32 100644 --- a/source/kong_tcpingress.go +++ b/source/kong_tcpingress.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "sort" - "time" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -78,11 +77,8 @@ func NewKongTCPIngressSource(dynamicKubeClient dynamic.Interface, kubeClient kub informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return kongTCPIngressInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, errors.Wrapf(err, "failed to sync cache") + if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } uc, err := newKongUnstructuredConverter() @@ -243,19 +239,7 @@ func (sc *kongTCPIngressSource) AddEventHandler(ctx context.Context, handler fun // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 - sc.kongTCPIngressInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.kongTCPIngressInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } // newUnstructuredConverter returns a new unstructuredConverter initialized diff --git a/source/kong_tcpingress_test.go b/source/kong_tcpingress_test.go index b076ecd7b..bb9dcc4be 100644 --- a/source/kong_tcpingress_test.go +++ b/source/kong_tcpingress_test.go @@ -19,6 +19,8 @@ package source import ( "context" "encoding/json" + "testing" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,7 +29,6 @@ import ( fakeDynamic "k8s.io/client-go/dynamic/fake" fakeKube "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" - "testing" ) // This is a compile-time validation that glooSource is a Source. @@ -36,6 +37,8 @@ var _ Source = &kongTCPIngressSource{} const defaultKongNamespace = "kong" func TestKongTCPIngressEndpoints(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string tcpProxy TCPIngress @@ -218,7 +221,10 @@ func TestKongTCPIngressEndpoints(t *testing.T) { }, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + fakeKubernetesClient := fakeKube.NewSimpleClientset() scheme := runtime.NewScheme() scheme.AddKnownTypes(kongGroupdVersionResource.GroupVersion(), &TCPIngress{}, &TCPIngressList{}) diff --git a/source/multi_source.go b/source/multisource.go similarity index 100% rename from source/multi_source.go rename to source/multisource.go diff --git a/source/multi_source_test.go b/source/multisource_test.go similarity index 99% rename from source/multi_source_test.go rename to source/multisource_test.go index d299deedb..5657b8900 100644 --- a/source/multi_source_test.go +++ b/source/multisource_test.go @@ -29,6 +29,8 @@ import ( ) func TestMultiSource(t *testing.T) { + t.Parallel() + t.Run("Interface", testMultiSourceImplementsSource) t.Run("Endpoints", testMultiSourceEndpoints) t.Run("EndpointsWithError", testMultiSourceEndpointsWithError) @@ -71,7 +73,10 @@ func testMultiSourceEndpoints(t *testing.T) { []*endpoint.Endpoint{foo, bar}, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Prepare the nested mock sources. sources := make([]Source, 0, len(tc.nestedEndpoints)) diff --git a/source/node.go b/source/node.go index 31f330043..62e83b1bb 100644 --- a/source/node.go +++ b/source/node.go @@ -17,12 +17,9 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" - "strings" "text/template" - "time" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -46,18 +43,9 @@ type nodeSource struct { // NewNodeSource creates a new nodeSource with the given config. func NewNodeSource(kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string) (Source, error) { - var ( - tmpl *template.Template - err error - ) - - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informers to listen for add/update/delete of nodes. @@ -78,11 +66,8 @@ func NewNodeSource(kubeClient kubernetes.Interface, annotationFilter, fqdnTempla informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return nodeInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } return &nodeSource{ @@ -131,14 +116,15 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro } if ns.fqdnTemplate != nil { - // Process the whole template string - var buf bytes.Buffer - err := ns.fqdnTemplate.Execute(&buf, node) + hostnames, err := execTemplate(ns.fqdnTemplate, node) if err != nil { - return nil, fmt.Errorf("failed to apply template on node %s: %v", node.Name, err) + return nil, err } - - ep.DNSName = buf.String() + hostname := "" + if len(hostnames) > 0 { + hostname = hostnames[0] + } + ep.DNSName = hostname log.Debugf("applied template for %s, converting to %s", node.Name, ep.DNSName) } else { ep.DNSName = node.Name diff --git a/source/node_test.go b/source/node_test.go index e6ce849c9..4d24dfbf0 100644 --- a/source/node_test.go +++ b/source/node_test.go @@ -30,12 +30,16 @@ import ( ) func TestNodeSource(t *testing.T) { + t.Parallel() + t.Run("NewNodeSource", testNodeSourceNewNodeSource) t.Run("Endpoints", testNodeSourceEndpoints) } // testNodeSourceNewNodeSource tests that NewNodeService doesn't return an error. func testNodeSourceNewNodeSource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -62,7 +66,10 @@ func testNodeSourceNewNodeSource(t *testing.T) { annotationFilter: "kubernetes.io/ingress.class=nginx", }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewNodeSource( fake.NewSimpleClientset(), ti.annotationFilter, @@ -80,6 +87,8 @@ func testNodeSourceNewNodeSource(t *testing.T) { // testNodeSourceEndpoints tests that various node generate the correct endpoints. func testNodeSourceEndpoints(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string annotationFilter string @@ -321,7 +330,10 @@ func testNodeSourceEndpoints(t *testing.T) { false, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() diff --git a/source/ocproute.go b/source/openshift_route.go similarity index 82% rename from source/ocproute.go rename to source/openshift_route.go index 2b0eb6f31..e9c16c505 100644 --- a/source/ocproute.go +++ b/source/openshift_route.go @@ -17,13 +17,10 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" - "strings" "text/template" - "time" routev1 "github.com/openshift/api/route/v1" versioned "github.com/openshift/client-go/route/clientset/versioned" @@ -51,6 +48,8 @@ type ocpRouteSource struct { combineFQDNAnnotation bool ignoreHostnameAnnotation bool routeInformer routeInformer.RouteInformer + labelSelector labels.Selector + ocpRouterName string } // NewOcpRouteSource creates a new ocpRouteSource with the given config. @@ -61,27 +60,21 @@ func NewOcpRouteSource( fqdnTemplate string, combineFQDNAnnotation bool, ignoreHostnameAnnotation bool, + labelSelector labels.Selector, + ocpRouterName string, ) (Source, error) { - var ( - tmpl *template.Template - err error - ) - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use a shared informer to listen for add/update/delete of Routes in the specified namespace. // Set resync period to 0, to prevent processing when nothing has changed. - informerFactory := extInformers.NewFilteredSharedInformerFactory(ocpClient, 0, namespace, nil) - routeInformer := informerFactory.Route().V1().Routes() + informerFactory := extInformers.NewSharedInformerFactoryWithOptions(ocpClient, 0, extInformers.WithNamespace(namespace)) + informer := informerFactory.Route().V1().Routes() // Add default resource event handlers to properly initialize informer. - routeInformer.Informer().AddEventHandler( + informer.Informer().AddEventHandler( cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { }, @@ -92,11 +85,8 @@ func NewOcpRouteSource( informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return routeInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } return &ocpRouteSource{ @@ -106,7 +96,9 @@ func NewOcpRouteSource( fqdnTemplate: tmpl, combineFQDNAnnotation: combineFQDNAnnotation, ignoreHostnameAnnotation: ignoreHostnameAnnotation, - routeInformer: routeInformer, + routeInformer: informer, + labelSelector: labelSelector, + ocpRouterName: ocpRouterName, }, nil } @@ -118,7 +110,7 @@ func (ors *ocpRouteSource) AddEventHandler(ctx context.Context, handler func()) // Retrieves all OpenShift Route resources on all namespaces, unless an explicit namespace // is specified in ocpRouteSource. func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - ocpRoutes, err := ors.routeInformer.Lister().Routes(ors.namespace).List(labels.Everything()) + ocpRoutes, err := ors.routeInformer.Lister().Routes(ors.namespace).List(ors.labelSelector) if err != nil { return nil, err } @@ -139,7 +131,7 @@ func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, continue } - orEndpoints := endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation) + orEndpoints := ors.endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation) // apply template if host is missing on OpenShift Route if (ors.combineFQDNAnnotation || len(orEndpoints) == 0) && ors.fqdnTemplate != nil { @@ -173,33 +165,25 @@ func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, } func (ors *ocpRouteSource) endpointsFromTemplate(ocpRoute *routev1.Route) ([]*endpoint.Endpoint, error) { - // Process the whole template string - var buf bytes.Buffer - err := ors.fqdnTemplate.Execute(&buf, ocpRoute) + hostnames, err := execTemplate(ors.fqdnTemplate, ocpRoute) if err != nil { - return nil, fmt.Errorf("failed to apply template on OpenShift Route %s: %s", ocpRoute.Name, err) + return nil, err } - hostnames := buf.String() - ttl, err := getTTLFromAnnotations(ocpRoute.Annotations) if err != nil { log.Warn(err) } targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations) - if len(targets) == 0 { - targets = targetsFromOcpRouteStatus(ocpRoute.Status) + targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status) } providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations) var endpoints []*endpoint.Endpoint - // splits the FQDN template and removes the trailing periods - hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") - for _, hostname := range hostnameList { - hostname = strings.TrimSuffix(hostname, ".") + for _, hostname := range hostnames { endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...) } return endpoints, nil @@ -242,7 +226,7 @@ func (ors *ocpRouteSource) setResourceLabel(ocpRoute *routev1.Route, endpoints [ } // endpointsFromOcpRoute extracts the endpoints from a OpenShift Route object -func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { +func (ors *ocpRouteSource) endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint ttl, err := getTTLFromAnnotations(ocpRoute.Annotations) @@ -253,7 +237,7 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations) if len(targets) == 0 { - targets = targetsFromOcpRouteStatus(ocpRoute.Status) + targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status) } providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations) @@ -272,14 +256,18 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo return endpoints } -func targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets { +func (ors *ocpRouteSource) targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets { var targets endpoint.Targets - for _, ing := range status.Ingress { - if ing.RouterCanonicalHostname != "" { + if len(ors.ocpRouterName) != 0 { + if ing.RouterName == ors.ocpRouterName { + targets = append(targets, ing.RouterCanonicalHostname) + return targets + } + } else if ing.RouterCanonicalHostname != "" { targets = append(targets, ing.RouterCanonicalHostname) + return targets } } - return targets } diff --git a/source/ocproute_test.go b/source/openshift_route_test.go similarity index 55% rename from source/ocproute_test.go rename to source/openshift_route_test.go index d7c8a1821..7dc851990 100644 --- a/source/ocproute_test.go +++ b/source/openshift_route_test.go @@ -19,11 +19,11 @@ package source import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/labels" routev1 "github.com/openshift/api/route/v1" fake "github.com/openshift/client-go/route/clientset/versioned/fake" @@ -49,6 +49,8 @@ func (suite *OCPRouteSuite) SetupTest() { "{{.Name}}", false, false, + labels.Everything(), + "", ) suite.routeWithTargets = &routev1.Route{ @@ -83,6 +85,8 @@ func (suite *OCPRouteSuite) TestResourceLabelIsSet() { } func TestOcpRouteSource(t *testing.T) { + t.Parallel() + suite.Run(t, new(OCPRouteSuite)) t.Run("Interface", testOcpRouteSourceImplementsSource) t.Run("NewOcpRouteSource", testOcpRouteSourceNewOcpRouteSource) @@ -96,11 +100,14 @@ func testOcpRouteSourceImplementsSource(t *testing.T) { // testOcpRouteSourceNewOcpRouteSource tests that NewOcpRouteSource doesn't return an error. func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string fqdnTemplate string expectError bool + labelFilter string }{ { title: "invalid template", @@ -121,8 +128,18 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { expectError: false, annotationFilter: "kubernetes.io/ingress.class=nginx", }, + { + title: "valid label selector", + expectError: false, + labelFilter: "app=web-external", + }, } { + ti := ti + labelSelector, err := labels.Parse(ti.labelFilter) + require.NoError(t, err) t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewOcpRouteSource( fake.NewSimpleClientset(), "", @@ -130,6 +147,8 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { ti.fqdnTemplate, false, false, + labelSelector, + "", ) if ti.expectError { @@ -152,6 +171,8 @@ func testOcpRouteSourceEndpoints(t *testing.T) { ocpRoute *routev1.Route expected []*endpoint.Endpoint expectError bool + labelFilter string + ocpRouterName string }{ { title: "route with basic hostname and route status target", @@ -176,6 +197,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, + ocpRouterName: "", expected: []*endpoint.Endpoint{ { DNSName: "my-domain.com", @@ -186,6 +208,119 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, expectError: false, }, + { + title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterNames defined", + targetNamespace: "", + annotationFilter: "", + fqdnTemplate: "", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + Spec: routev1.RouteSpec{ + Host: "my-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-with-target", + Annotations: map[string]string{}, + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + RouterName: "default", + RouterCanonicalHostname: "router-default.my-domain.com", + }, + }, + }, + }, + ocpRouterName: "default", + expected: []*endpoint.Endpoint{ + { + DNSName: "my-domain.com", + Targets: []string{ + "router-default.my-domain.com", + }, + }, + }, + expectError: false, + }, + { + title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterNames defined and two router canonical names", + targetNamespace: "", + annotationFilter: "", + fqdnTemplate: "", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + Spec: routev1.RouteSpec{ + Host: "my-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-with-target", + Annotations: map[string]string{}, + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + RouterName: "default", + RouterCanonicalHostname: "router-default.my-domain.com", + }, + { + RouterName: "test", + RouterCanonicalHostname: "router-test.my-domain.com", + }, + }, + }, + }, + ocpRouterName: "default", + expected: []*endpoint.Endpoint{ + { + DNSName: "my-domain.com", + Targets: []string{ + "router-default.my-domain.com", + }, + }, + }, + expectError: false, + }, + { + title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterName defined and two router canonical names", + targetNamespace: "", + annotationFilter: "", + fqdnTemplate: "", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + Spec: routev1.RouteSpec{ + Host: "my-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-with-target", + Annotations: map[string]string{}, + }, + Status: routev1.RouteStatus{ + Ingress: []routev1.RouteIngress{ + { + RouterName: "default", + RouterCanonicalHostname: "router-default.my-domain.com", + }, + { + RouterName: "test", + RouterCanonicalHostname: "router-test.my-domain.com", + }, + }, + }, + }, + ocpRouterName: "default", + expected: []*endpoint.Endpoint{ + { + DNSName: "my-domain.com", + Targets: []string{ + "router-default.my-domain.com", + }, + }, + }, + expectError: false, + }, { title: "route with incorrect externalDNS controller annotation", targetNamespace: "", @@ -201,8 +336,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, - expected: []*endpoint.Endpoint{}, - expectError: false, + ocpRouterName: "", + expected: []*endpoint.Endpoint{}, + expectError: false, }, { title: "route with basic hostname and annotation target", @@ -222,6 +358,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, + ocpRouterName: "", expected: []*endpoint.Endpoint{ { DNSName: "my-annotation-domain.com", @@ -232,14 +369,75 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, expectError: false, }, + { + title: "route with matching labels", + labelFilter: "app=web-external", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + + Spec: routev1.RouteSpec{ + Host: "my-annotation-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-with-matching-labels", + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "my.site.foo.com", + }, + Labels: map[string]string{ + "app": "web-external", + "name": "service-frontend", + }, + }, + }, + ocpRouterName: "", + expected: []*endpoint.Endpoint{ + { + DNSName: "my-annotation-domain.com", + Targets: []string{ + "my.site.foo.com", + }, + }, + }, + expectError: false, + }, + { + title: "route without matching labels", + labelFilter: "app=web-external", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + + Spec: routev1.RouteSpec{ + Host: "my-annotation-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-without-matching-labels", + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "my.site.foo.com", + }, + Labels: map[string]string{ + "app": "web-internal", + "name": "service-frontend", + }, + }, + }, + ocpRouterName: "", + expected: []*endpoint.Endpoint{}, + expectError: false, + }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() // Create a Kubernetes testing client fakeClient := fake.NewSimpleClientset() - _, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(context.Background(), tc.ocpRoute, metav1.CreateOptions{}) require.NoError(t, err) + labelSelector, err := labels.Parse(tc.labelFilter) + require.NoError(t, err) + source, err := NewOcpRouteSource( fakeClient, "", @@ -247,21 +445,13 @@ func testOcpRouteSourceEndpoints(t *testing.T) { "{{.Name}}", false, false, + labelSelector, + tc.ocpRouterName, ) + require.NoError(t, err) - var res []*endpoint.Endpoint - - // wait up to a few seconds for new resources to appear in informer cache. - err = poll(time.Second, 3*time.Second, func() (bool, error) { - res, err = source.Endpoints(context.Background()) - if err != nil { - // stop waiting if we get an error - return true, err - } - return len(res) >= len(tc.expected), nil - }) - + res, err := source.Endpoints(context.Background()) if tc.expectError { require.Error(t, err) } else { diff --git a/source/pod.go b/source/pod.go index 57a33de63..b2df23bfa 100644 --- a/source/pod.go +++ b/source/pod.go @@ -18,8 +18,6 @@ package source import ( "context" - "fmt" - "time" "sigs.k8s.io/external-dns/endpoint" @@ -63,12 +61,8 @@ func NewPodSource(kubeClient kubernetes.Interface, namespace string, compatibili informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err := poll(time.Second, 60*time.Second, func() (bool, error) { - return podInformer.Informer().HasSynced() && - nodeInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } return &podSource{ diff --git a/source/pod_test.go b/source/pod_test.go index 95dfca616..675b8cfb8 100644 --- a/source/pod_test.go +++ b/source/pod_test.go @@ -29,6 +29,8 @@ import ( // testPodSource tests that various services generate the correct endpoints. func TestPodSource(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -387,7 +389,10 @@ func TestPodSource(t *testing.T) { }, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() ctx := context.Background() diff --git a/source/service.go b/source/service.go index a6d258311..e49ff2f49 100644 --- a/source/service.go +++ b/source/service.go @@ -17,13 +17,11 @@ limitations under the License. package source import ( - "bytes" "context" "fmt" "sort" "strings" "text/template" - "time" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -65,21 +63,14 @@ type serviceSource struct { podInformer coreinformers.PodInformer nodeInformer coreinformers.NodeInformer serviceTypeFilter map[string]struct{} + labelSelector labels.Selector } // NewServiceSource creates a new serviceSource with the given config. -func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool) (Source, error) { - var ( - tmpl *template.Template - err error - ) - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - if err != nil { - return nil, err - } +func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector) (Source, error) { + tmpl, err := parseTemplate(fqdnTemplate) + if err != nil { + return nil, err } // Use shared informers to listen for add/update/delete of services/pods/nodes in the specified namespace. @@ -120,14 +111,8 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt informerFactory.Start(wait.NeverStop) // wait for the local cache to be populated. - err = poll(time.Second, 60*time.Second, func() (bool, error) { - return serviceInformer.Informer().HasSynced() && - endpointsInformer.Informer().HasSynced() && - podInformer.Informer().HasSynced() && - nodeInformer.Informer().HasSynced(), nil - }) - if err != nil { - return nil, fmt.Errorf("failed to sync cache: %v", err) + if err := waitForCacheSync(context.Background(), informerFactory); err != nil { + return nil, err } // Transform the slice into a map so it will @@ -153,12 +138,13 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt podInformer: podInformer, nodeInformer: nodeInformer, serviceTypeFilter: serviceTypes, + labelSelector: labelSelector, }, nil } // Endpoints returns endpoint objects for each service that should be processed. func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(labels.Everything()) + services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(sc.labelSelector) if err != nil { return nil, err } @@ -237,6 +223,7 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e lastMergedEndpoint := len(mergedEndpoints) - 1 if mergedEndpoints[lastMergedEndpoint].DNSName == endpoints[i].DNSName && mergedEndpoints[lastMergedEndpoint].RecordType == endpoints[i].RecordType && + mergedEndpoints[lastMergedEndpoint].SetIdentifier == endpoints[i].SetIdentifier && mergedEndpoints[lastMergedEndpoint].RecordTTL == endpoints[i].RecordTTL { mergedEndpoints[lastMergedEndpoint].Targets = append(mergedEndpoints[lastMergedEndpoint].Targets, endpoints[i].Targets[0]) } else { @@ -353,18 +340,15 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri } func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service) ([]*endpoint.Endpoint, error) { - var endpoints []*endpoint.Endpoint - - // Process the whole template string - var buf bytes.Buffer - err := sc.fqdnTemplate.Execute(&buf, svc) + hostnames, err := execTemplate(sc.fqdnTemplate, svc) if err != nil { - return nil, fmt.Errorf("failed to apply template on service %s: %v", svc.String(), err) + return nil, err } providerSpecific, setIdentifier := getProviderSpecificAnnotations(svc.Annotations) - hostnameList := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",") - for _, hostname := range hostnameList { + + var endpoints []*endpoint.Endpoint + for _, hostname := range hostnames { endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier, false)...) } @@ -670,17 +654,5 @@ func (sc *serviceSource) AddEventHandler(ctx context.Context, handler func()) { // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 - sc.serviceInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - handler() - }, - UpdateFunc: func(old interface{}, new interface{}) { - handler() - }, - DeleteFunc: func(obj interface{}) { - handler() - }, - }, - ) + sc.serviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } diff --git a/source/service_test.go b/source/service_test.go index 0aece2bf6..57a2056e8 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -22,13 +22,13 @@ import ( "sort" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" @@ -42,21 +42,7 @@ type ServiceSuite struct { func (suite *ServiceSuite) SetupTest() { fakeClient := fake.NewSimpleClientset() - var err error - suite.sc, err = NewServiceSource( - fakeClient, - "", - "", - "{{.Name}}", - false, - "", - false, - false, - false, - []string{}, - false, - ) suite.fooWithTargets = &v1.Service{ Spec: v1.ServiceSpec{ Type: v1.ServiceTypeLoadBalancer, @@ -75,12 +61,24 @@ func (suite *ServiceSuite) SetupTest() { }, }, } - - suite.NoError(err, "should initialize service source") - - _, err = fakeClient.CoreV1().Services(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{}) + _, err := fakeClient.CoreV1().Services(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{}) suite.NoError(err, "should successfully create service") + suite.sc, err = NewServiceSource( + fakeClient, + "", + "", + "{{.Name}}", + false, + "", + false, + false, + false, + []string{}, + false, + labels.Everything(), + ) + suite.NoError(err, "should initialize service source") } func (suite *ServiceSuite) TestResourceLabelIsSet() { @@ -91,6 +89,8 @@ func (suite *ServiceSuite) TestResourceLabelIsSet() { } func TestServiceSource(t *testing.T) { + t.Parallel() + suite.Run(t, new(ServiceSuite)) t.Run("Interface", testServiceSourceImplementsSource) t.Run("NewServiceSource", testServiceSourceNewServiceSource) @@ -105,6 +105,8 @@ func testServiceSourceImplementsSource(t *testing.T) { // testServiceSourceNewServiceSource tests that NewServiceSource doesn't return an error. func testServiceSourceNewServiceSource(t *testing.T) { + t.Parallel() + for _, ti := range []struct { title string annotationFilter string @@ -137,7 +139,10 @@ func testServiceSourceNewServiceSource(t *testing.T) { serviceTypesFilter: []string{string(v1.ServiceTypeClusterIP)}, }, } { + ti := ti t.Run(ti.title, func(t *testing.T) { + t.Parallel() + _, err := NewServiceSource( fake.NewSimpleClientset(), "", @@ -150,6 +155,7 @@ func testServiceSourceNewServiceSource(t *testing.T) { false, ti.serviceTypesFilter, false, + labels.Everything(), ) if ti.expectError { @@ -163,6 +169,8 @@ func testServiceSourceNewServiceSource(t *testing.T) { // testServiceSourceEndpoints tests that various services generate the correct endpoints. func testServiceSourceEndpoints(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -182,1059 +190,810 @@ func testServiceSourceEndpoints(t *testing.T) { serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool + serviceLabelSelector string }{ { - "no annotated services return no endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "no annotated services return no endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "no annotated services return no endpoints when ignoring annotations", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - true, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "no annotated services return no endpoints when ignoring annotations", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated services return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "hostname annotation on services is ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - true, - map[string]string{}, - map[string]string{ + title: "hostname annotation on services is ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated ClusterIp aren't processed without explicit authorization", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated ClusterIp aren't processed without explicit authorization", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []string{}, - []string{}, - []*endpoint.Endpoint{}, - false, + clusterIP: "1.2.3.4", + externalIPs: []string{}, + lbs: []string{}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "FQDN template with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + title: "FQDN template with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - false, - true, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + title: "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template and annotation both with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - true, - false, - map[string]string{}, - map[string]string{ + title: "FQDN template and annotation both with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + combineFQDNAndAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - true, - true, - map[string]string{}, - map[string]string{ + title: "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + combineFQDNAndAnnotation: true, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services with multiple hostnames and without trailing period return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with multiple hostnames and without trailing period return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org, bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services return an endpoint with target hostname", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services return an endpoint with target hostname", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"lb.example.com"}, // Kubernetes omits the trailing dot - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"lb.example.com"}, // Kubernetes omits the trailing dot + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "annotated services can omit trailing dot", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services can omit trailing dot", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted }, - "", - []string{}, - []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "our controller type is kops dns controller", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "our controller type is kops dns controller", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ controllerAnnotationKey: controllerAnnotationValue, hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "different controller types are ignored even (with template specified)", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.ext-dns.test.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "different controller types are ignored even (with template specified)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.ext-dns.test.com", + labels: map[string]string{}, + annotations: map[string]string{ controllerAnnotationKey: "some-other-tool", hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services are found in target namespace", - "testing", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services are found in target namespace", + targetNamespace: "testing", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "services that are not in target namespace are ignored", - "testing", - "", - "other-testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services that are not in target namespace are ignored", + targetNamespace: "testing", + svcNamespace: "other-testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services are found in all namespaces", - "", - "", - "other-testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services are found in all namespaces", + svcNamespace: "other-testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid matching annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid matching annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid non-matching annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid non-matching annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "SomethingElse", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "invalid annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "invalid annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - true, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, + expectError: true, }, { - "valid matching annotation filter label", - "", - "service.beta.kubernetes.io/external-traffic=Global", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid matching annotation filter label", + annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "Global", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid non-matching annotation filter label", - "", - "service.beta.kubernetes.io/external-traffic=Global", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid non-matching annotation filter label", + annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "no external entrypoints return no endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "no external entrypoints return no endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated service with externalIPs returns a single endpoint with multiple targets", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated service with externalIPs returns a single endpoint with multiple targets", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{"10.2.3.4", "11.2.3.4"}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{"10.2.3.4", "11.2.3.4"}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}}, }, - false, }, { - "multiple external entrypoints return a single endpoint with multiple targets", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "multiple external entrypoints return a single endpoint with multiple targets", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4", "8.8.8.8"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "8.8.8.8"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}}, }, - false, }, { - "services annotated with legacy mate annotations are ignored in default mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services annotated with legacy mate annotations are ignored in default mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services annotated with legacy mate annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "mate", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services annotated with legacy mate annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "mate", + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "services annotated with legacy molecule annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "molecule", - "", - false, - false, - map[string]string{ + title: "services annotated with legacy molecule annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "molecule", + labels: map[string]string{ "dns": "route53", }, - map[string]string{ + annotations: map[string]string{ "domainName": "foo.example.org., bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "kops-dns-controller", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org", }, - "", - []string{}, - []string{"1.2.3.4", "lb.example.com"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "lb.example.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "kops-dns-controller", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, - { - "not annotated services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4", "elb.com"}, - []string{}, - []*endpoint.Endpoint{ + title: "not annotated services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "elb.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}}, }, - false, }, { - "annotated services with set fqdnTemplate annotation takes precedence", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with set fqdnTemplate annotation takes precedence", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4", "elb.com"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "elb.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}}, }, - false, }, { - "compatibility annotated services with tmpl. compatibility takes precedence", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "mate", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "compatibility annotated services with tmpl. compatibility takes precedence", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "mate", + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "mate.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "not annotated services with unknown tmpl field should not return anything", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Calibre}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - true, + title: "not annotated services with unknown tmpl field should not return anything", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Calibre}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, + expectError: true, }, { - "ttl not annotated should have RecordTTL.IsConfigured set to false", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl not annotated should have RecordTTL.IsConfigured set to false", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "ttl annotated but invalid should have RecordTTL.IsConfigured set to false", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated but invalid should have RecordTTL.IsConfigured set to false", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "foo", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "ttl annotated and is valid should set Record.TTL", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated and is valid should set Record.TTL", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "10", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)}, }, - false, }, { - "ttl annotated (in duration format) and is valid should set Record.TTL", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated (in duration format) and is valid should set Record.TTL", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "1m", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)}, }, - false, }, { - "Negative ttl is not valid", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "Negative ttl is not valid", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "-10", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "filter on service types should include matching services", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "filter on service types should include matching services", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{string(v1.ServiceTypeLoadBalancer)}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "filter on service types should exclude non-matching services", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "filter on service types should exclude non-matching services", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{string(v1.ServiceTypeLoadBalancer)}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + expected: []*endpoint.Endpoint{}, }, { - "internal-host annotated services return an endpoint with Cluster IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "internal-host annotated services return an endpoint with Cluster IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ internalHostnameAnnotationKey: "foo.internal.example.org.", }, - "1.1.1.1", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, }, - false, }, { - "internal-host annotated and host annotated services return an endpoint with Cluster IP and an endpoint with lb IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "internal-host annotated and host annotated services return an endpoint with Cluster IP and an endpoint with lb IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", internalHostnameAnnotationKey: "foo.internal.example.org.", }, - "1.1.1.1", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, + }, + { + title: "service with matching labels and fqdn filter should be included", + svcNamespace: "testing", + svcName: "fqdn", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-external", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{ + {DNSName: "fqdn.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, + }, + }, + { + title: "service with matching labels and hostname annotation should be included", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-external", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, + expected: []*endpoint.Endpoint{ + {DNSName: "annotation.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, + }, + }, + { + title: "service without matching labels and fqdn filter should be excluded", + svcNamespace: "testing", + svcName: "fqdn", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-internal", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{}, + }, + { + title: "service without matching labels and hostname annotation should be excluded", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-internal", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, + expected: []*endpoint.Endpoint{}, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() @@ -1270,8 +1029,16 @@ func testServiceSourceEndpoints(t *testing.T) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(t, err) + var sourceLabel labels.Selector + if tc.serviceLabelSelector != "" { + sourceLabel, err = labels.Parse(tc.serviceLabelSelector) + require.NoError(t, err) + } else { + sourceLabel = labels.Everything() + } + // Create our object under test and get the endpoints. - client, _ := NewServiceSource( + client, err := NewServiceSource( kubernetes, tc.targetNamespace, tc.annotationFilter, @@ -1283,21 +1050,12 @@ func testServiceSourceEndpoints(t *testing.T) { false, tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, + sourceLabel, ) + require.NoError(t, err) - var res []*endpoint.Endpoint - - // wait up to a few seconds for new resources to appear in informer cache. - err = poll(time.Second, 3*time.Second, func() (bool, error) { - res, err = client.Endpoints(context.Background()) - if err != nil { - // stop waiting if we get an error - return true, err - } - return len(res) >= len(tc.expected), nil - }) - + res, err := client.Endpoints(context.Background()) if tc.expectError { require.Error(t, err) } else { @@ -1312,6 +1070,8 @@ func testServiceSourceEndpoints(t *testing.T) { // testMultipleServicesEndpoints tests that multiple services generate correct merged endpoints func testMultipleServicesEndpoints(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -1325,7 +1085,7 @@ func testMultipleServicesEndpoints(t *testing.T) { ignoreHostnameAnnotation bool labels map[string]string clusterIP string - hostnames map[string]string + services map[string]map[string]string serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool @@ -1343,8 +1103,8 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.4": "foo.example.org", + map[string]map[string]string{ + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1365,10 +1125,10 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.4": "foo.example.org", - "1.2.3.5": "foo.example.org", - "1.2.3.6": "foo.example.org", + map[string]map[string]string{ + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, + "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, + "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1389,14 +1149,14 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.5": "foo.example.org", - "10.1.1.3": "bar.example.org", - "10.1.1.1": "bar.example.org", - "1.2.3.4": "foo.example.org", - "10.1.1.2": "bar.example.org", - "20.1.1.1": "foobar.example.org", - "1.2.3.6": "foo.example.org", + map[string]map[string]string{ + "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, + "10.1.1.3": {hostnameAnnotationKey: "bar.example.org"}, + "10.1.1.1": {hostnameAnnotationKey: "bar.example.org"}, + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, + "10.1.1.2": {hostnameAnnotationKey: "bar.example.org"}, + "20.1.1.1": {hostnameAnnotationKey: "foobar.example.org"}, + "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1406,19 +1166,42 @@ func testMultipleServicesEndpoints(t *testing.T) { }, false, }, + { + "test that services with different set-identifier do not get merged together", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "", + false, + false, + map[string]string{}, + "", + map[string]map[string]string{ + "a.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "a"}, + "b.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "b"}, + }, + []string{}, + []*endpoint.Endpoint{ + {DNSName: "foo.example.org", Targets: endpoint.Targets{"a.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/fooa.elb.com"}, SetIdentifier: "a"}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"b.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foob.elb.com"}, SetIdentifier: "b"}, + }, + false, + }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() // Create services to test against - for serviceip, hostname := range tc.hostnames { + for lb, annotations := range tc.services { ingresses := []v1.LoadBalancerIngress{} - ingresses = append(ingresses, v1.LoadBalancerIngress{IP: serviceip}) - - annotations := make(map[string]string) - annotations[hostnameAnnotationKey] = hostname + ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) service := &v1.Service{ Spec: v1.ServiceSpec{ @@ -1427,7 +1210,7 @@ func testMultipleServicesEndpoints(t *testing.T) { }, ObjectMeta: metav1.ObjectMeta{ Namespace: tc.svcNamespace, - Name: tc.svcName + serviceip, + Name: tc.svcName + lb, Labels: tc.labels, Annotations: annotations, }, @@ -1455,21 +1238,11 @@ func testMultipleServicesEndpoints(t *testing.T) { false, tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) - var res []*endpoint.Endpoint - - // wait up to a few seconds for new resources to appear in informer cache. - err = poll(time.Second, 3*time.Second, func() (bool, error) { - res, err = client.Endpoints(context.Background()) - if err != nil { - // stop waiting if we get an error - return true, err - } - return len(res) >= len(tc.expected), nil - }) - + res, err := client.Endpoints(context.Background()) if tc.expectError { require.Error(t, err) } else { @@ -1497,6 +1270,8 @@ func testMultipleServicesEndpoints(t *testing.T) { // testServiceSourceEndpoints tests that various services generate the correct endpoints. func TestClusterIpServices(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -1510,101 +1285,87 @@ func TestClusterIpServices(t *testing.T) { labels map[string]string annotations map[string]string clusterIP string - lbs []string expected []*endpoint.Endpoint expectError bool + labelSelector string }{ { - "annotated ClusterIp services return an endpoint with Cluster IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated ClusterIp services return an endpoint with Cluster IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.2.3.4", + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "hostname annotated ClusterIp services are ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - true, - map[string]string{}, - map[string]string{ + title: "hostname annotated ClusterIp services are ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + ignoreHostnameAnnotation: true, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []*endpoint.Endpoint{}, - false, + clusterIP: "1.2.3.4", + expected: []*endpoint.Endpoint{}, }, { - "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "{{.Name}}.bar.example.com", - false, - map[string]string{}, - map[string]string{}, - "4.5.6.7", - []string{}, - []*endpoint.Endpoint{ + title: "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}}, }, - false, }, { - "Headless services do not generate endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - map[string]string{}, - map[string]string{}, - v1.ClusterIPNone, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "Headless services do not generate endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + clusterIP: v1.ClusterIPNone, + expected: []*endpoint.Endpoint{}, + }, + { + title: "ClusterIP service with matching label generates an endpoint", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{"app": "web-internal"}, + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{ + {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}}, + }, + labelSelector: "app=web-internal", + }, + { + title: "ClusterIP service without matching label generates an endpoint", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{"app": "web-internal"}, + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{}, + labelSelector: "app=web-external", }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() // Create a service to test against - ingresses := []v1.LoadBalancerIngress{} - for _, lb := range tc.lbs { - if net.ParseIP(lb) != nil { - ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) - } else { - ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb}) - } - } - service := &v1.Service{ Spec: v1.ServiceSpec{ Type: tc.svcType, @@ -1616,16 +1377,18 @@ func TestClusterIpServices(t *testing.T) { Labels: tc.labels, Annotations: tc.annotations, }, - Status: v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: ingresses, - }, - }, } _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(t, err) + var labelSelector labels.Selector + if tc.labelSelector != "" { + labelSelector, err = labels.Parse(tc.labelSelector) + require.NoError(t, err) + } else { + labelSelector = labels.Everything() + } // Create our object under test and get the endpoints. client, _ := NewServiceSource( kubernetes, @@ -1639,6 +1402,7 @@ func TestClusterIpServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labelSelector, ) require.NoError(t, err) @@ -1657,6 +1421,8 @@ func TestClusterIpServices(t *testing.T) { // testNodePortServices tests that various services generate the correct endpoints. func TestServiceSourceNodePortServices(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -1674,32 +1440,25 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected []*endpoint.Endpoint expectError bool nodes []*v1.Node - podnames []string + podNames []string nodeIndex []int phases []v1.PodPhase + labelSelector labels.Selector }{ { - "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1720,29 +1479,18 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "hostname annotated NodePort services are ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - true, - map[string]string{}, - map[string]string{ + title: "hostname annotated NodePort services are ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + ignoreHostnameAnnotation: true, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{}, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1763,30 +1511,20 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, + expected: []*endpoint.Endpoint{}, }, { - "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "{{.Name}}.bar.example.com", - false, - map[string]string{}, - map[string]string{}, - nil, - []*endpoint.Endpoint{ + title: "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1807,32 +1545,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1851,32 +1578,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeLocal, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1897,32 +1613,25 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{"pod-0"}, - []int{1}, - []v1.PodPhase{v1.PodRunning}, + podNames: []string{"pod-0"}, + nodeIndex: []int{1}, + phases: []v1.PodPhase{v1.PodRunning}, }, { - "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeLocal, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1943,33 +1652,26 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{"pod-0", "pod-1"}, - []int{1, 1}, - []v1.PodPhase{v1.PodRunning, v1.PodRunning}, + podNames: []string{"pod-0", "pod-1"}, + nodeIndex: []int{1, 1}, + phases: []v1.PodPhase{v1.PodRunning, v1.PodRunning}, }, { - "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", accessAnnotationKey: "private", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1990,33 +1692,23 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", accessAnnotationKey: "public", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -2037,32 +1729,23 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"10.0.1.1"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"10.0.1.1"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2089,32 +1772,22 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2141,32 +1814,22 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + annotations: map[string]string{ kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2193,30 +1856,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with both kops dns controller annotations return an empty set of addons", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with both kops dns controller annotations return an empty set of addons", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - nil, - []*endpoint.Endpoint{}, - false, - []*v1.Node{{ + expected: []*endpoint.Endpoint{}, + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2243,12 +1897,12 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() @@ -2260,7 +1914,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { } // Create pods - for i, podname := range tc.podnames { + for i, podname := range tc.podNames { pod := &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{}, @@ -2317,6 +1971,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -2335,6 +1990,8 @@ func TestServiceSourceNodePortServices(t *testing.T) { // TestHeadlessServices tests that headless services generate the correct endpoints. func TestHeadlessServices(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -2560,7 +2217,10 @@ func TestHeadlessServices(t *testing.T) { false, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() @@ -2646,6 +2306,7 @@ func TestHeadlessServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -2664,6 +2325,8 @@ func TestHeadlessServices(t *testing.T) { // TestHeadlessServices tests that headless services generate the correct endpoints. func TestHeadlessServicesHostIP(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -2913,7 +2576,10 @@ func TestHeadlessServicesHostIP(t *testing.T) { false, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() @@ -2996,6 +2662,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -3014,6 +2681,8 @@ func TestHeadlessServicesHostIP(t *testing.T) { // TestExternalServices tests that external services generate the correct endpoints. func TestExternalServices(t *testing.T) { + t.Parallel() + for _, tc := range []struct { title string targetNamespace string @@ -3068,7 +2737,10 @@ func TestExternalServices(t *testing.T) { false, }, } { + tc := tc t.Run(tc.title, func(t *testing.T) { + t.Parallel() + // Create a Kubernetes testing client kubernetes := fake.NewSimpleClientset() @@ -3101,6 +2773,7 @@ func TestExternalServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -3141,7 +2814,20 @@ func BenchmarkServiceEndpoints(b *testing.B) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(b, err) - client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false, false, []string{}, false) + client, err := NewServiceSource( + kubernetes, + v1.NamespaceAll, + "", + "", + false, + "", + false, + false, + false, + []string{}, + false, + labels.Everything(), + ) require.NoError(b, err) for i := 0; i < b.N; i++ { diff --git a/source/shared_test.go b/source/shared_test.go index 9f9060836..7114b0dd7 100644 --- a/source/shared_test.go +++ b/source/shared_test.go @@ -17,27 +17,46 @@ limitations under the License. package source import ( - "sort" - "strings" - "testing" "reflect" + "sort" + "testing" "sigs.k8s.io/external-dns/endpoint" ) -// test helper functions +func sortEndpoints(endpoints []*endpoint.Endpoint) { + for _, ep := range endpoints { + sort.Strings([]string(ep.Targets)) + } + sort.Slice(endpoints, func(i, k int) bool { + // Sort by DNSName and Targets + ei, ek := endpoints[i], endpoints[k] + if ei.DNSName != ek.DNSName { + return ei.DNSName < ek.DNSName + } + // Targets are sorted ahead of time. + for j, ti := range ei.Targets { + if j >= len(ek.Targets) { + return true + } + if tk := ek.Targets[j]; ti != tk { + return ti < tk + } + } + return false + }) +} func validateEndpoints(t *testing.T, endpoints, expected []*endpoint.Endpoint) { + t.Helper() + if len(endpoints) != len(expected) { t.Fatalf("expected %d endpoints, got %d", len(expected), len(endpoints)) } + // Make sure endpoints are sorted - validateEndpoint() depends on it. - sort.SliceStable(endpoints, func(i, j int) bool { - return strings.Compare(endpoints[i].DNSName, endpoints[j].DNSName) < 0 - }) - sort.SliceStable(expected, func(i, j int) bool { - return strings.Compare(expected[i].DNSName, expected[j].DNSName) < 0 - }) + sortEndpoints(endpoints) + sortEndpoints(expected) for i := range endpoints { validateEndpoint(t, endpoints[i], expected[i]) @@ -45,25 +64,36 @@ func validateEndpoints(t *testing.T, endpoints, expected []*endpoint.Endpoint) { } func validateEndpoint(t *testing.T, endpoint, expected *endpoint.Endpoint) { + t.Helper() + if endpoint.DNSName != expected.DNSName { - t.Errorf("expected %s, got %s", expected.DNSName, endpoint.DNSName) + t.Errorf("DNSName expected %q, got %q", expected.DNSName, endpoint.DNSName) } if !endpoint.Targets.Same(expected.Targets) { - t.Errorf("expected %s, got %s", expected.Targets, endpoint.Targets) + t.Errorf("Targets expected %q, got %q", expected.Targets, endpoint.Targets) } if endpoint.RecordTTL != expected.RecordTTL { - t.Errorf("expected %v, got %v", expected.RecordTTL, endpoint.RecordTTL) + t.Errorf("RecordTTL expected %v, got %v", expected.RecordTTL, endpoint.RecordTTL) } // if non-empty record type is expected, check that it matches. if expected.RecordType != "" && endpoint.RecordType != expected.RecordType { - t.Errorf("expected %s, got %s", expected.RecordType, endpoint.RecordType) + t.Errorf("RecordType expected %q, got %q", expected.RecordType, endpoint.RecordType) } // if non-empty labels are expected, check that they matches. - if expected.Labels != nil && !reflect.DeepEqual(endpoint.Labels,expected.Labels) { - t.Errorf("expected %s, got %s", expected.Labels, endpoint.Labels) + if expected.Labels != nil && !reflect.DeepEqual(endpoint.Labels, expected.Labels) { + t.Errorf("Labels expected %s, got %s", expected.Labels, endpoint.Labels) + } + + if (len(expected.ProviderSpecific) != 0 || len(endpoint.ProviderSpecific) != 0) && + !reflect.DeepEqual(endpoint.ProviderSpecific, expected.ProviderSpecific) { + t.Errorf("ProviderSpecific expected %s, got %s", expected.ProviderSpecific, endpoint.ProviderSpecific) + } + + if endpoint.SetIdentifier != expected.SetIdentifier { + t.Errorf("SetIdentifier expected %q, got %q", expected.SetIdentifier, endpoint.SetIdentifier) } } diff --git a/source/routegroup.go b/source/skipper_routegroup.go similarity index 98% rename from source/routegroup.go rename to source/skipper_routegroup.go index 2c48e336a..1cf0f8ce5 100644 --- a/source/routegroup.go +++ b/source/skipper_routegroup.go @@ -190,15 +190,6 @@ func (cli *routeGroupClient) do(req *http.Request) (*http.Response, error) { return cli.client.Do(req) } -func parseTemplate(fqdnTemplate string) (tmpl *template.Template, err error) { - if fqdnTemplate != "" { - tmpl, err = template.New("endpoint").Funcs(template.FuncMap{ - "trimPrefix": strings.TrimPrefix, - }).Parse(fqdnTemplate) - } - return tmpl, err -} - // NewRouteGroupSource creates a new routeGroupSource with the given config. func NewRouteGroupSource(timeout time.Duration, token, tokenPath, apiServerURL, namespace, annotationFilter, fqdnTemplate, routegroupVersion string, combineFqdnAnnotation, ignoreHostnameAnnotation bool) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) diff --git a/source/routegroup_test.go b/source/skipper_routegroup_test.go similarity index 99% rename from source/routegroup_test.go rename to source/skipper_routegroup_test.go index b2f45eab7..75881e274 100644 --- a/source/routegroup_test.go +++ b/source/skipper_routegroup_test.go @@ -44,6 +44,8 @@ func createTestRouteGroup(ns, name string, annotations map[string]string, hosts } func TestEndpointsFromRouteGroups(t *testing.T) { + t.Parallel() + for _, tt := range []struct { name string source *routeGroupSource diff --git a/source/source.go b/source/source.go index 53e550e9f..91214b5f3 100644 --- a/source/source.go +++ b/source/source.go @@ -17,20 +17,24 @@ limitations under the License. package source import ( + "bytes" "context" "fmt" "math" "net" + "reflect" "strconv" "strings" + "text/template" "time" + "unicode" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/external-dns/endpoint" - "sigs.k8s.io/external-dns/internal/config" ) const ( @@ -106,6 +110,35 @@ func parseTTL(s string) (ttlSeconds int64, err error) { return int64(ttlDuration.Seconds()), nil } +type kubeObject interface { + runtime.Object + metav1.Object +} + +func execTemplate(tmpl *template.Template, obj kubeObject) (hostnames []string, err error) { + var buf bytes.Buffer + if err := tmpl.Execute(&buf, obj); err != nil { + kind := obj.GetObjectKind().GroupVersionKind().Kind + return nil, fmt.Errorf("failed to apply template on %s %s/%s: %w", kind, obj.GetNamespace(), obj.GetName(), err) + } + for _, name := range strings.Split(buf.String(), ",") { + name = strings.TrimFunc(name, unicode.IsSpace) + name = strings.TrimSuffix(name, ".") + hostnames = append(hostnames, name) + } + return hostnames, nil +} + +func parseTemplate(fqdnTemplate string) (tmpl *template.Template, err error) { + if fqdnTemplate == "" { + return nil, nil + } + funcs := template.FuncMap{ + "trimPrefix": strings.TrimPrefix, + } + return template.New("endpoint").Funcs(funcs).Parse(fqdnTemplate) +} + func getHostnamesFromAnnotations(annotations map[string]string) []string { hostnameAnnotation, exists := annotations[hostnameAnnotationKey] if !exists { @@ -253,23 +286,48 @@ func matchLabelSelector(selector labels.Selector, srcAnnotations map[string]stri return selector.Matches(annotations) } -func poll(interval time.Duration, timeout time.Duration, condition wait.ConditionFunc) error { - if config.FastPoll { - time.Sleep(5 * time.Millisecond) +type eventHandlerFunc func() - ok, err := condition() +func (fn eventHandlerFunc) OnAdd(obj interface{}) { fn() } +func (fn eventHandlerFunc) OnUpdate(oldObj, newObj interface{}) { fn() } +func (fn eventHandlerFunc) OnDelete(obj interface{}) { fn() } - if err != nil { - return err - } - - if ok { - return nil - } - - interval = 50 * time.Millisecond - timeout = 10 * time.Second - } - - return wait.Poll(interval, timeout, condition) +type informerFactory interface { + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool +} + +func waitForCacheSync(ctx context.Context, factory informerFactory) error { + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + for typ, done := range factory.WaitForCacheSync(ctx.Done()) { + if !done { + select { + case <-ctx.Done(): + return fmt.Errorf("failed to sync %v: %v", typ, ctx.Err()) + default: + return fmt.Errorf("failed to sync %v", typ) + } + } + } + return nil +} + +type dynamicInformerFactory interface { + WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool +} + +func waitForDynamicCacheSync(ctx context.Context, factory dynamicInformerFactory) error { + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + for typ, done := range factory.WaitForCacheSync(ctx.Done()) { + if !done { + select { + case <-ctx.Done(): + return fmt.Errorf("failed to sync %v: %v", typ, ctx.Err()) + default: + return fmt.Errorf("failed to sync %v", typ) + } + } + } + return nil } diff --git a/source/store.go b/source/store.go index 82cd2f6ec..4e01d2d18 100644 --- a/source/store.go +++ b/source/store.go @@ -29,6 +29,7 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" istioclient "istio.io/client-go/pkg/clientset/versioned" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -42,7 +43,7 @@ var ErrSourceNotFound = errors.New("source not found") type Config struct { Namespace string AnnotationFilter string - LabelFilter string + LabelFilter labels.Selector FQDNTemplate string CombineFQDNAndAnnotation bool IgnoreHostnameAnnotation bool @@ -66,6 +67,7 @@ type Config struct { SkipperRouteGroupVersion string RequestTimeout time.Duration DefaultTargets []string + OCPRouterName string } // ClientGenerator provides clients @@ -183,13 +185,13 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation) + return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter) case "ingress": client, err := p.KubeClient() if err != nil { return nil, err } - return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec) + return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter) case "pod": client, err := p.KubeClient() if err != nil { @@ -232,16 +234,6 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err return nil, err } return NewAmbassadorHostSource(dynamicClient, kubernetesClient, cfg.Namespace) - case "contour-ingressroute": - kubernetesClient, err := p.KubeClient() - if err != nil { - return nil, err - } - dynamicClient, err := p.DynamicKubernetesClient() - if err != nil { - return nil, err - } - return NewContourIngressRouteSource(dynamicClient, kubernetesClient, cfg.ContourLoadBalancerService, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) case "contour-httpproxy": dynamicClient, err := p.DynamicKubernetesClient() if err != nil { @@ -263,7 +255,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) + return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName) case "fake": return NewFakeSource(cfg.FQDNTemplate) case "connector": diff --git a/source/store_test.go b/source/store_test.go index 4fa787bbf..bc4950559 100644 --- a/source/store_test.go +++ b/source/store_test.go @@ -25,7 +25,11 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" istioclient "istio.io/client-go/pkg/clientset/versioned" + istiofake "istio.io/client-go/pkg/clientset/versioned/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + fakeDynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" fakeKube "k8s.io/client-go/kubernetes/fake" ) @@ -89,16 +93,31 @@ type ByNamesTestSuite struct { } func (suite *ByNamesTestSuite) TestAllInitialized() { - fakeDynamic, _ := newDynamicKubernetesClient() - mockClientGenerator := new(MockClientGenerator) mockClientGenerator.On("KubeClient").Return(fakeKube.NewSimpleClientset(), nil) - mockClientGenerator.On("IstioClient").Return(NewFakeConfigStore(), nil) - mockClientGenerator.On("DynamicKubernetesClient").Return(fakeDynamic, nil) + mockClientGenerator.On("IstioClient").Return(istiofake.NewSimpleClientset(), nil) + mockClientGenerator.On("DynamicKubernetesClient").Return(fakeDynamic.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), + map[schema.GroupVersionResource]string{ + { + Group: "projectcontour.io", + Version: "v1", + Resource: "httpproxies", + }: "HTTPPRoxiesList", + { + Group: "contour.heptio.com", + Version: "v1beta1", + Resource: "tcpingresses", + }: "TCPIngressesList", + { + Group: "configuration.konghq.com", + Version: "v1beta1", + Resource: "tcpingresses", + }: "TCPIngressesList", + }), nil) - sources, err := ByNames(mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-ingressroute", "contour-httpproxy", "kong-tcpingress", "fake"}, minimalConfig) + sources, err := ByNames(mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "fake"}, minimalConfig) suite.NoError(err, "should not generate errors") - suite.Len(sources, 7, "should generate all six sources") + suite.Len(sources, 6, "should generate all six sources") } func (suite *ByNamesTestSuite) TestOnlyFake() { @@ -133,9 +152,6 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() { _, err = ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig) suite.Error(err, "should return an error if kubernetes client cannot be created") - _, err = ByNames(mockClientGenerator, []string{"contour-ingressroute"}, minimalConfig) - suite.Error(err, "should return an error if kubernetes client cannot be created") - _, err = ByNames(mockClientGenerator, []string{"kong-tcpingress"}, minimalConfig) suite.Error(err, "should return an error if kubernetes client cannot be created") } @@ -149,8 +165,6 @@ func (suite *ByNamesTestSuite) TestIstioClientFails() { _, err := ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig) suite.Error(err, "should return an error if istio client cannot be created") - _, err = ByNames(mockClientGenerator, []string{"contour-ingressroute"}, minimalConfig) - suite.Error(err, "should return an error if contour client cannot be created") _, err = ByNames(mockClientGenerator, []string{"contour-httpproxy"}, minimalConfig) suite.Error(err, "should return an error if contour client cannot be created") } diff --git a/source/unstructured_converter.go b/source/unstructured_converter.go index a87194e9f..cb6ab27e6 100644 --- a/source/unstructured_converter.go +++ b/source/unstructured_converter.go @@ -17,7 +17,6 @@ limitations under the License. package source import ( - contour "github.com/projectcontour/contour/apis/contour/v1beta1" projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" @@ -36,7 +35,6 @@ func NewUnstructuredConverter() (*UnstructuredConverter, error) { } // Setup converter to understand custom CRD types - _ = contour.AddToScheme(uc.scheme) _ = projectcontour.AddToScheme(uc.scheme) // Add the core types we need