diff --git a/example/service.yml b/example/service.yml index a309465..24559d0 100644 --- a/example/service.yml +++ b/example/service.yml @@ -8,6 +8,6 @@ spec: selector: app: nginx ports: - - port: 80 + - port: 11111 targetPort: 80 type: LoadBalancer diff --git a/serveis/altres/caddy/font b/serveis/altres/caddy/font new file mode 100644 index 0000000..15c5ac3 --- /dev/null +++ b/serveis/altres/caddy/font @@ -0,0 +1 @@ +https://github.com/caddyserver/ingress diff --git a/serveis/altres/caddy/ingress/.dockerignore b/serveis/altres/caddy/ingress/.dockerignore new file mode 100644 index 0000000..c267599 --- /dev/null +++ b/serveis/altres/caddy/ingress/.dockerignore @@ -0,0 +1,13 @@ +/.github +/bin +/charts +/kubernetes + +/.gitignore +/.goreleaser.yaml +/CONTRIBUTING.md +/ct.yaml +/skaffold.yaml +/README.md +/Makefile +/LICENSE.txt \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/.gitignore b/serveis/altres/caddy/ingress/.gitignore new file mode 100644 index 0000000..241d36d --- /dev/null +++ b/serveis/altres/caddy/ingress/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +bin +vendor +.idea/ +dist/ +ingress-controller diff --git a/serveis/altres/caddy/ingress/.goreleaser.yaml b/serveis/altres/caddy/ingress/.goreleaser.yaml new file mode 100644 index 0000000..328f079 --- /dev/null +++ b/serveis/altres/caddy/ingress/.goreleaser.yaml @@ -0,0 +1,112 @@ +project_name: ingress +before: + hooks: + - go mod tidy +builds: + - main: ./cmd/caddy + binary: ingress-controller + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm64 + - s390x + mod_timestamp: "{{ .CommitTimestamp }}" + +dockers: + - use: buildx + goos: linux + goarch: amd64 + dockerfile: ./Dockerfile + skip_push: "true" + image_templates: + - "caddy/{{ .ProjectName }}:test-image" + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: amd64 + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-amd64" + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: arm64 + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8" + build_flag_templates: + - "--platform=linux/arm64/v8" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: s390x + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-s390x" + build_flag_templates: + - "--platform=linux/s390x" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + +docker_manifests: + # https://goreleaser.com/customization/docker_manifest/ + - name_template: caddy/{{ .ProjectName }}:latest + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:v{{ .Major }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:{{ .Tag }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + +release: + disable: true + +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^ci:" diff --git a/serveis/altres/caddy/ingress/CONTRIBUTING.md b/serveis/altres/caddy/ingress/CONTRIBUTING.md new file mode 100644 index 0000000..0646565 --- /dev/null +++ b/serveis/altres/caddy/ingress/CONTRIBUTING.md @@ -0,0 +1,54 @@ +## Requirements + + - A running kubernetes cluster (if you don't have one see *Setup a local cluster* section) + - [skaffold](https://skaffold.dev/) installed on your machine + - [helm 3](https://helm.sh/) installed on your machine + - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl/) installed on your machine + +### Setup a local cluster + + - You need a machine with [docker](https://docker.io) up & running + - If you are using Docker Desktop enable the kubernetes cluster from the config (see [docs](https://docs.docker.com/desktop/kubernetes/) here) + +Otherwise you can decide to use [kind]() or [minukube]() + +## Setup development env + +Start `skaffold` using the command: +``` +make dev +``` + +this will automatically: + + - build your docker image every time you change some code + - update the helm release every time you change the helm chart + - expose the caddy ingress controller (port 8080 and 8443) + +You can test that all work as expected with: +``` +curl -D - -s -k https://example1.kubernetes.localhost:8443/hello1 --resolve example1.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example1.kubernetes.localhost:8443/hello2 --resolve example1.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example2.kubernetes.localhost:8443/hello1 --resolve example2.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example2.kubernetes.localhost:8443/hello2 --resolve example2.kubernetes.localhost:8443:127.0.0.1 +``` + +You can change domains defined in `kuberentes/sample` folder with some domain that are risolved on your local machine or you can add them in the `/etc/host` file to be automatically resolved as localhost. + +## Notes + + - You can change local port forwarded by skaffold by changing the port values in the `skaffold.yaml` file on section `portForward` `localPort`. Remind that you can forward only port greater than 1024 if you execute it as non-root user + - We use an internal CA for test to simplify the installation, but then you can decide to use external CA for tests, see the `values.yaml` in the helm chart for the info on how to configure it. + +## Releasing new helm chart version + +If you want to release a new version of the `caddy-ingress-controller` chart, you'll need +to create a new PR with: +- The new chart's `version` in `Chart.yaml` +- The new `image.tag` in `values.yaml` (if you want to update the default image used in the chart) +- The new `appVersion` in `Chart.yaml` (if you did the previous line) + +## Releasing a new app version + +To release a new caddy-ingress-controller image, you need to create a new semver tag. +It will build and push an image to https://hub.docker.com/r/caddy/ingress. diff --git a/serveis/altres/caddy/ingress/Dockerfile b/serveis/altres/caddy/ingress/Dockerfile new file mode 100644 index 0000000..cbd4ce5 --- /dev/null +++ b/serveis/altres/caddy/ingress/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:latest AS certs +RUN apk --update add ca-certificates + +FROM alpine:latest + +EXPOSE 80 443 +ENTRYPOINT ["/ingress-controller"] + +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY ingress-controller / diff --git a/serveis/altres/caddy/ingress/Dockerfile.dev b/serveis/altres/caddy/ingress/Dockerfile.dev new file mode 100644 index 0000000..f646297 --- /dev/null +++ b/serveis/altres/caddy/ingress/Dockerfile.dev @@ -0,0 +1,19 @@ +FROM golang:1.22 as build + +ENV CGO_ENABLED=0 + +WORKDIR /go/ingress-controller + +COPY go.mod go.sum /go/ingress-controller/ +RUN go mod download + +COPY . . +RUN go build -o ./bin/ingress-controller ./cmd/caddy + +FROM alpine:3.18 + +EXPOSE 80 443 + +COPY --from=build /go/ingress-controller/bin/ingress-controller /ingress-controller + +ENTRYPOINT ["/ingress-controller"] diff --git a/serveis/altres/caddy/ingress/LICENSE.txt b/serveis/altres/caddy/ingress/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/serveis/altres/caddy/ingress/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/serveis/altres/caddy/ingress/Makefile b/serveis/altres/caddy/ingress/Makefile new file mode 100644 index 0000000..0f72d66 --- /dev/null +++ b/serveis/altres/caddy/ingress/Makefile @@ -0,0 +1,6 @@ +build: + @mkdir -p bin + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ingress-controller ./cmd/caddy + +dev: + skaffold dev --port-forward diff --git a/serveis/altres/caddy/ingress/README.md b/serveis/altres/caddy/ingress/README.md new file mode 100644 index 0000000..62264af --- /dev/null +++ b/serveis/altres/caddy/ingress/README.md @@ -0,0 +1,153 @@ +# Caddy Ingress Controller + +This is the Kubernetes Ingress Controller for Caddy. It includes functionality +for monitoring `Ingress` resources on a Kubernetes cluster and includes support +for providing automatic HTTPS certificates for all hostnames defined in the +ingress resources that it is managing. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.19+ + +## Setup + +In the `charts` folder, a Helm Chart is provided to make installing the Caddy +Ingress Controller on a Kubernetes cluster straightforward. To install the +Caddy Ingress Controller adhere to the following steps: + +1. Create a new namespace in your cluster to isolate all Caddy resources. + +```sh +kubectl create namespace caddy-system +``` + +2. Install the Helm Chart. + +```sh +helm install \ + --namespace=caddy-system \ + --repo https://caddyserver.github.io/ingress/ \ + --atomic \ + mycaddy \ + caddy-ingress-controller +``` + +Or + +2. Generate kubernetes yaml file. +```sh +git clone https://github.com/caddyserver/ingress.git +cd ingress + +# generate the yaml file +helm template mycaddy ./charts/caddy-ingress-controller \ + --namespace=caddy-system \ + > mycaddy.yaml + +# apply the file +kubectl apply -f mycaddy.yaml +``` + +This will create a service of type `LoadBalancer` in the `caddy-system` +namespace on your cluster. You'll want to set any DNS records for accessing this +cluster to the external IP address of this `LoadBalancer` when the external IP +is provisioned by your cloud provider. + +You can get the external IP address with `kubectl get svc -n caddy-system` + +3. Alternate installation method: Glasskube + +To install the Caddy ingress controller using [Glasskube](https://glasskube.dev/), you can select "caddy-ingress-controller" from the "ClusterPackages" tab in the Glasskube GUI then click "install" or you can run the following command: +```console +glasskube install caddy-ingress-controller +``` +Add an email address in the package configuration section in the UI to enable automatic HTTPS, or run: +``` +glasskube install caddy-ingress-controller --value "automaticHTTPS=your@email.com" +``` + +## Debugging + +To view any logs generated by Caddy or the Ingress Controller you can view the +pod logs of the Caddy Ingress Controller. + +Get the pod name with: + +```sh +kubectl get pods -n caddy-system +``` + +View the pod logs: + +```sh +kubectl logs -n caddy-system +``` + +## Automatic HTTPS + +To have automatic HTTPS (not to be confused with `On-demand TLS`), you simply have +to specify your email in the config map. When using Helm chart, you can add +`--set ingressController.config.email=your@email.com` when installing. + +## On-Demand TLS + +[On-demand TLS](https://caddyserver.com/docs/automatic-https#on-demand-tls) can generate SSL certs on the fly +and can be enabled in this controller by setting the `onDemandTLS` config to `true`: + +```sh +helm install ...\ + --set ingressController.config.onDemandTLS=true +``` + +> You can also specify options +> for the on-demand config: `onDemandAsk` + + +## Bringing Your Own Certificates + +If you would like to disable automatic HTTPS for a specific host and use your +own certificates you can create a new TLS secret in Kubernetes and define what +certificates to use when serving your application on the ingress resource. + +Example: + +Create TLS secret `mycerts`, where `./tls.key` and `./tls.crt` are valid +certificates for `test.com`. + +``` +kubectl create secret tls mycerts --key ./tls.key --cert ./tls.crt +``` + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example + annotations: + kubernetes.io/ingress.class: caddy +spec: + rules: + - host: test.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: test + port: + number: 8080 + tls: + - secretName: mycerts # use mycerts for host test.com + hosts: + - test.com +``` + +### Contribution + +Learn how to start contributing on the [Contributing Guidline](CONTRIBUTING.md). + +## License + +[Apache License 2.0](LICENSE.txt) diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/.helmignore b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/.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/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml new file mode 100644 index 0000000..807f8fc --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: caddy-ingress-controller +home: https://github.com/caddyserver/ingress +description: A helm chart for the Caddy Kubernetes ingress controller +icon: https://caddyserver.com/resources/images/caddy-circle-lock.svg +type: application +version: 1.3.0 +appVersion: "v0.2.1" +keywords: + - ingress-controller + - caddyserver +sources: + - https://github.com/caddyserver/ingress +maintainers: + - name: mavimo + url: https://github.com/mavimo + - name: embraser01 + url: https://github.com/embraser01 diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md new file mode 100644 index 0000000..0050370 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md @@ -0,0 +1,84 @@ +# caddy-ingress-controller + +A helm chart for the Caddy Kubernetes ingress controller + +## TL;DR: + +```bash +helm install my-release caddy-ingress-controller\ + --repo https://caddyserver.github.io/ingress/ \ + --namespace=caddy-system +``` + +## Introduction + +This chart bootstraps a caddy-ingress-deployment deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.19+ + +## Installing the Chart + +```bash +helm repo add caddyserver https://caddyserver.github.io/ingress/ +helm install my-release caddyserver/caddy-ingress-controller --namespace=caddy-system +``` + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + +## Troubleshooting + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"caddy/ingress"` | | +| image.tag | string | `"latest"` | | +| imagePullSecrets | list | `[]` | | +| ingressController.config.acmeCA | string | `""` | | +| ingressController.config.acmeEABKeyId | string | `""` | | +| ingressController.config.acmeEABMacKey | string | `""` | | +| ingressController.config.debug | bool | `false` | | +| ingressController.config.email | string | `""` | | +| ingressController.config.metrics | bool | `true` | | +| ingressController.config.onDemandTLS | bool | `false` | | +| ingressController.config.proxyProtocol | bool | `false` | | +| ingressController.rbac.create | bool | `true` | | +| ingressController.verbose | bool | `false` | | +| ingressController.leaseId | string | `""` | | +| ingressController.watchNamespace | string | `""` | | +| minikube | bool | `false` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podDisruptionBudget.maxUnavailable | string | `nil` | | +| podDisruptionBudget.minAvailable | int | `1` | | +| podSecurityContext | object | `{}` | | +| replicaCount | int | `2` | | +| resources | object | `{}` | | +| securityContext.allowPrivilegeEscalation | bool | `true` | | +| securityContext.capabilities.add[0] | string | `"NET_BIND_SERVICE"` | | +| securityContext.capabilities.drop[0] | string | `"ALL"` | | +| securityContext.runAsGroup | int | `0` | | +| securityContext.runAsUser | int | `0` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `"caddy-ingress-controller"` | | +| tolerations | list | `[]` | | \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl new file mode 100644 index 0000000..d9c5d85 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl @@ -0,0 +1,48 @@ +{{ template "chart.header" . }} + +{{ template "chart.description" . }} + +## TL;DR: + +```bash +helm install my-release caddy-ingress-controller\ + --repo https://caddyserver.github.io/ingress/ \ + --namespace=caddy-system +``` + +## Introduction + +This chart bootstraps a caddy-ingress-deployment deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.14+ + +## Installing the Chart + +```bash +helm repo add caddyserver https://caddyserver.github.io/ingress/ +helm install my-release caddyserver/caddy-ingress-controller --namespace=caddy-system +``` + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + + +## Troubleshooting + + + +{{ template "chart.valuesSection" . }} \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml new file mode 100644 index 0000000..c937b7e --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml @@ -0,0 +1,5 @@ +image: + tag: test-image + +ingressController: + verbose: true diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl new file mode 100644 index 0000000..ba92739 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "caddy-ingress-controller.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 "caddy-ingress-controller.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 "caddy-ingress-controller.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "caddy-ingress-controller.labels" -}} +helm.sh/chart: {{ include "caddy-ingress-controller.chart" . }} +{{ include "caddy-ingress-controller.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "caddy-ingress-controller.selectorLabels" -}} +app.kubernetes.io/name: {{ include "caddy-ingress-controller.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "caddy-ingress-controller.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "caddy-ingress-controller.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml new file mode 100644 index 0000000..3bbb678 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml @@ -0,0 +1,31 @@ +{{- if .Values.ingressController.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-role + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - "" + - "networking.k8s.io" + - "coordination.k8s.io" + resources: + - ingresses + - ingresses/status + - secrets + - leases + verbs: ["*"] + - apiGroups: + - "" + resources: + - services + - pods + - nodes + - routes + - extensions + - configmaps + verbs: + - list + - get + - watch +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..4c9eb87 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml @@ -0,0 +1,15 @@ +{{- if .Values.ingressController.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-role-binding + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "caddy-ingress-controller.name" . }}-role + apiGroup: rbac.authorization.k8s.io +subjects: +- kind: ServiceAccount + name: {{ include "caddy-ingress-controller.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml new file mode 100644 index 0000000..795928a --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-configmap + namespace: {{ .Release.Namespace }} +data: +{{- range keys .Values.ingressController.config | sortAlpha }} + {{ . }}: {{ get $.Values.ingressController.config . | quote }} +{{- end }} + diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml new file mode 100644 index 0000000..c9e45e9 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml @@ -0,0 +1,105 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "caddy-ingress-controller.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 80 # optional, required if running in minikube + {{- end }} + - name: https + containerPort: 443 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 443 # optional, required if running in minikube + {{- end }} + - name: metrics + containerPort: 9765 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 9765 # optional, required if running in minikube + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: tmp + mountPath: /tmp + args: + - -config-map={{ include "caddy-ingress-controller.name" . }}-configmap + {{- if .Values.ingressController.watchNamespace }} + - -namespace={{ .Values.ingressController.watchNamespace }} + {{- end }} + {{- if .Values.ingressController.leaseId }} + - -lease-id={{ .Values.ingressController.leaseId }} + {{- end }} + {{- if .Values.ingressController.verbose }} + - -verbose + {{- end }} + {{- if .Values.ingressController.className }} + - -class-name={{ .Values.ingressController.className }} + {{- end }} + {{- if .Values.ingressController.classNameRequired }} + - -class-name-required={{ .Values.ingressController.classNameRequired }} + {{- end }} + readinessProbe: + initialDelaySeconds: 3 + periodSeconds: 10 + httpGet: + port: 9765 + path: /healthz + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: tmp + emptyDir: {} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml new file mode 100644 index 0000000..10eecc9 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml @@ -0,0 +1,33 @@ +{{- if .Values.loadBalancer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.loadBalancer.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} + {{- with .Values.loadBalancer.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: "LoadBalancer" + {{- if (semverCompare "<= 1.24.0" .Capabilities.KubeVersion.Version) }} + loadBalancerIP: {{ .Values.loadBalancer.loadBalancerIP }} #Deprecated in Kubernetes v1.24 + {{- end }} + externalTrafficPolicy: {{ .Values.loadBalancer.externalTrafficPolicy }} + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: https + port: 443 + protocol: TCP + targetPort: https + selector: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..52542f1 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml @@ -0,0 +1,19 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: +{{- with .Values.podDisruptionBudget }} + {{- if .minAvailable }} + minAvailable: {{ .minAvailable }} + {{- end }} + {{- if .maxUnavailable }} + maxUnavailable: {{ .maxUnavailable }} + {{- end }} +{{- end }} + selector: + matchLabels: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 6 }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml new file mode 100644 index 0000000..28a15e5 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml @@ -0,0 +1,35 @@ +{{- if not .Values.loadBalancer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} +{{- if .Values.service.ipDualStack.enabled }} + ipFamilies: {{ toYaml .Values.service.ipDualStack.ipFamilies | nindent 4 }} + ipFamilyPolicy: {{ .Values.service.ipDualStack.ipFamilyPolicy }} +{{- end }} + internalTrafficPolicy: {{ .Values.service.internalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} +{{- if and (eq .Values.service.type "ClusterIP") .Values.service.clusterIP }} + clusterIP: "{{ .Values.service.clusterIP }}" +{{- end }} + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: https + port: 443 + protocol: TCP + targetPort: https + selector: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml new file mode 100644 index 0000000..0188784 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "caddy-ingress-controller.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.schema.json b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.schema.json new file mode 100644 index 0000000..69becbe --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.schema.json @@ -0,0 +1,324 @@ +{ + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "replicaCount", + "minikube", + "image", + "imagePullSecrets", + "nameOverride", + "fullnameOverride", + "ingressController", + "serviceAccount", + "podAnnotations", + "podSecurityContext", + "securityContext", + "resources", + "nodeSelector", + "tolerations", + "affinity" + ], + "properties": { + "replicaCount": { + "$id": "#/properties/replicaCount", + "type": "number" + }, + "minikube": { + "$id": "#/properties/minikube", + "type": "boolean" + }, + "image": { + "$id": "#/properties/image", + "type": "object", + "required": [ + "repository", + "tag", + "pullPolicy" + ], + "properties": { + "repository": { + "$id": "#/properties/image/properties/repository", + "type": "string" + }, + "tag": { + "$id": "#/properties/image/properties/tag", + "type": "string" + }, + "pullPolicy": { + "$id": "#/properties/image/properties/pullPolicy", + "type": "string", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + } + } + }, + "imagePullSecrets": { + "$id": "#/properties/imagePullSecrets", + "type": "array" + }, + "nameOverride": { + "$id": "#/properties/nameOverride", + "type": "string" + }, + "fullnameOverride": { + "$id": "#/properties/fullnameOverride", + "type": "string" + }, + "loadBalancer": { + "$id": "#/properties/loadBalancer", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$id": "#/properties/loadBalancer/properties/enabled", + "type": "boolean" + }, + "annotations": { + "$id": "#/properties/loadBalancer/properties/annotations", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "labels": { + "$id": "#/properties/loadBalancer/properties/labels", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "ingressController": { + "$id": "#/properties/ingressController", + "type": "object", + "required": [ + "rbac", + "config", + "watchNamespace" + ], + "properties": { + "rbac": { + "$id": "#/properties/ingressController/properties/rbac", + "type": "object", + "required": [ + "create" + ], + "properties": { + "create": { + "$id": "#/properties/ingressController/properties/rbac/properties/create", + "type": "boolean" + } + } + }, + "leaseId": { + "$id": "#/properties/ingressController/properties/leaseId", + "type": "string" + }, + "config": { + "$id": "#/properties/ingressController/properties/config", + "type": "object", + "properties": { + "acmeCA": { + "$id": "#/properties/ingressController/properties/config/properties/acmeCA", + "type": "string", + "oneOf": [ + { + "format": "uri" + }, + { + "maxLength": 0 + } + ] + }, + "acmeEABKeyId": { + "$id": "#/properties/ingressController/properties/config/properties/acmeEABKeyId", + "type": "string" + }, + "acmeEABMacKey": { + "$id": "#/properties/ingressController/properties/config/properties/acmeEABMacKey", + "type": "string" + }, + "debug": { + "$id": "#/properties/ingressController/properties/config/properties/debug", + "type": "boolean" + }, + "email": { + "$id": "#/properties/ingressController/properties/config/properties/email", + "type": "string", + "oneOf": [ + { + "format": "email" + }, + { + "maxLength": 0 + } + ] + }, + "experimentalSmartSort": { + "$id": "#/properties/ingressController/properties/config/properties/experimentalSmartSort", + "type": "boolean" + }, + "metrics": { + "$id": "#/properties/ingressController/properties/config/properties/metrics", + "type": "boolean" + }, + "proxyProtocol": { + "$id": "#/properties/ingressController/properties/config/properties/proxyProtocol", + "type": "boolean" + }, + "onDemandTLS": { + "$id": "#/properties/ingressController/properties/config/properties/onDemandTLS", + "type": "boolean" + }, + "onDemandAsk": { + "$id": "#/properties/ingressController/properties/config/properties/onDemandAsk", + "type": "string" + } + } + }, + "verbose": { + "$id": "#/properties/ingressController/properties/verbose", + "type": "boolean" + }, + "watchNamespace": { + "$id": "#/properties/ingressController/properties/watchNamespace", + "type": "string" + } + } + }, + "service": { + "$id": "#/properties/service", + "type": "object", + "required": [ + "type", + "ipDualStack" + ], + "properties": { + "internalTrafficPolicy": { + "$id": "#/properties/service/properties/internalTrafficPolicy", + "type": "string", + "enum": [ + "Cluster", + "Local" + ] + }, + "externalTrafficPolicy": { + "$id": "#/properties/service/properties/externalTrafficPolicy", + "type": "string", + "enum": [ + "Cluster", + "Local" + ] + }, + "annotations": { + "$id": "#/properties/service/properties/annotations", + "type": "object" + }, + "type": { + "$id": "#/properties/service/properties/type", + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer", + "ExternalName" + ] + }, + "clusterIP": { + "$id": "#/properties/service/properties/clusterIP", + "type": "string" + }, + "ipDualStack": { + "$id": "#/properties/service/properties/name", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$id": "#/properties/service/properties/ipDualStack/properties/enabled", + "type": "boolean" + }, + "ipFamilies": { + "$id": "#/properties/service/properties/ipDualStack/properties/ipFamilies", + "type": "array", + "items": { + "type": "string", + "enum": [ + "IPv4", + "IPv6" + ] + } + }, + "ipFamilyPolicy": { + "$id": "#/properties/service/properties/ipDualStack/properties/ipFamilyPolicy", + "type": "string", + "enum": [ + "SingleStack", + "PreferDualStack", + "RequireDualStack" + ] + } + } + } + } + }, + "serviceAccount": { + "$id": "#/properties/serviceAccount", + "type": "object", + "required": [ + "create", + "name" + ], + "properties": { + "create": { + "$id": "#/properties/serviceAccount/properties/create", + "type": "boolean" + }, + "name": { + "$id": "#/properties/serviceAccount/properties/name", + "type": "string" + }, + "annotations": { + "$id": "#/properties/serviceAccount/properties/annotations", + "type": "object" + } + } + }, + "podAnnotations": { + "$id": "#/properties/podAnnotations", + "type": "object" + }, + "podSecurityContext": { + "$id": "#/properties/podSecurityContext", + "type": "object" + }, + "securityContext": { + "$id": "#/properties/securityContext", + "type": "object" + }, + "resources": { + "$id": "#/properties/resources", + "type": "object" + }, + "nodeSelector": { + "$id": "#/properties/nodeSelector", + "type": "object" + }, + "tolerations": { + "$id": "#/properties/tolerations", + "type": "array" + }, + "affinity": { + "$id": "#/properties/affinity", + "type": "object" + } + } +} diff --git a/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.yaml b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.yaml new file mode 100644 index 0000000..cb87436 --- /dev/null +++ b/serveis/altres/caddy/ingress/charts/caddy-ingress-controller/values.yaml @@ -0,0 +1,120 @@ +# Default values for caddy-ingress-controller. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 2 + +# Use to test in minikube context +minikube: false + +image: + repository: caddy/ingress + pullPolicy: IfNotPresent + tag: "v0.2.1" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# Default values for the caddy ingress controller. +ingressController: + watchNamespace: "" + verbose: false + rbac: + create: true + className: "caddy" + classNameRequired: false + leaseId: "" + config: + acmeEABKeyId: "" + acmeEABMacKey: "" + # -- Acme Server URL + acmeCA: "" + debug: false + email: "" + metrics: true + proxyProtocol: false + experimentalSmartSort: false + onDemandTLS: false + # onDemandAsk: + +loadBalancer: + enabled: true + # Deprecated in Kubernetes v1.24 + loadBalancerIP: + # Set to 'Local' to maintain the client's IP on inbound connections + externalTrafficPolicy: + annotations: {} + # service.beta.kubernetes.io/aws-load-balancer-type: + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: + # service.beta.kubernetes.io/aws-load-balancer-scheme: + # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: + # service.beta.kubernetes.io/aws-load-balancer-subnets: + labels: {} + +service: + # Set to 'Local' to maintain the client's IP on inbound connections + externalTrafficPolicy: Cluster + # Enable internal traffic policy for the service + internalTrafficPolicy: Cluster + annotations: {} + # service.beta.kubernetes.io/aws-load-balancer-type: + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: + # service.beta.kubernetes.io/aws-load-balancer-scheme: + # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: + # service.beta.kubernetes.io/aws-load-balancer-subnets: + ## Service type + type: ClusterIP + ## IP address for type ClusterIP + clusterIP: "" + ipDualStack: + enabled: false + ipFamilies: ["IPv6", "IPv4"] + ipFamilyPolicy: "PreferDualStack" + +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: "caddy-ingress-controller" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +podDisruptionBudget: + minAvailable: 1 + maxUnavailable: + +securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + runAsUser: 0 + runAsGroup: 0 + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/serveis/altres/caddy/ingress/cmd/caddy/flag.go b/serveis/altres/caddy/ingress/cmd/caddy/flag.go new file mode 100644 index 0000000..3301744 --- /dev/null +++ b/serveis/altres/caddy/ingress/cmd/caddy/flag.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "strings" + + "github.com/caddyserver/ingress/pkg/store" +) + +func parseFlags() store.Options { + var namespace string + flag.StringVar(&namespace, "namespace", "", "the namespace that you would like to observe kubernetes ingress resources in.") + + var className string + flag.StringVar(&className, "class-name", "caddy", "class name of the ingress controller") + + var classNameRequired bool + flag.BoolVar(&classNameRequired, "class-name-required", false, "only allow ingress resources with a matching ingress class name") + + var configMapName string + flag.StringVar(&configMapName, "config-map", "", "defines the config map name from where to load global options") + + var leaseId string + flag.StringVar(&leaseId, "lease-id", "", "defines the id of this instance for certmagic lock") + + var verbose bool + flag.BoolVar(&verbose, "verbose", false, "set the log level to debug") + + var pluginsOrder string + flag.StringVar(&pluginsOrder, "plugins-order", "", "defines the order plugins should be used") + + flag.Parse() + + return store.Options{ + WatchNamespace: namespace, + ClassName: className, + ClassNameRequired: classNameRequired, + ConfigMapName: configMapName, + Verbose: verbose, + LeaseId: leaseId, + PluginsOrder: strings.Split(pluginsOrder, ","), + } +} diff --git a/serveis/altres/caddy/ingress/cmd/caddy/main.go b/serveis/altres/caddy/ingress/cmd/caddy/main.go new file mode 100644 index 0000000..463a953 --- /dev/null +++ b/serveis/altres/caddy/ingress/cmd/caddy/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + "time" + + "github.com/caddyserver/ingress/internal/caddy" + "github.com/caddyserver/ingress/internal/controller" + "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + // high enough QPS to fit all expected use cases. + highQPS = 1e6 + + // high enough Burst to fit all expected use cases. + highBurst = 1e6 +) + +func createLogger(verbose bool) *zap.SugaredLogger { + prodCfg := zap.NewProductionConfig() + + if verbose { + prodCfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + } + logger, _ := prodCfg.Build() + + return logger.Sugar() +} + +func main() { + // parse any flags required to configure the caddy ingress controller + cfg := parseFlags() + + logger := createLogger(cfg.Verbose) + + if cfg.WatchNamespace == "" { + cfg.WatchNamespace = v1.NamespaceAll + logger.Warn("-namespace flag is unset, caddy ingress controller will monitor ingress resources in all namespaces.") + } + + // get client to access the kubernetes service api + kubeClient, _, err := createApiserverClient(logger) + if err != nil { + logger.Fatalf("Could not establish a connection to the Kubernetes API Server. %v", err) + } + + stopCh := make(chan struct{}, 1) + + c := controller.NewCaddyController(logger, kubeClient, cfg, caddy.Converter{}, stopCh) + + // start the ingress controller + logger.Info("Starting the caddy ingress controller") + go c.Run() + + // Listen for SIGINT and SIGTERM signals + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + <-sigs + close(stopCh) + + // Let controller exit the process + select {} +} + +// createApiserverClient creates a new Kubernetes REST client. We assume the +// controller runs inside Kubernetes and use the in-cluster config. +func createApiserverClient(logger *zap.SugaredLogger) (*kubernetes.Clientset, *version.Info, error) { + cfg, err := clientcmd.BuildConfigFromFlags("", "") + if err != nil { + return nil, nil, err + } + + logger.Infof("Creating API client for %s", cfg.Host) + + cfg.QPS = highQPS + cfg.Burst = highBurst + cfg.ContentType = "application/vnd.kubernetes.protobuf" + + client, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, nil, err + } + + // The client may fail to connect to the API server on the first request + defaultRetry := wait.Backoff{ + Steps: 10, + Duration: 1 * time.Second, + Factor: 1.5, + Jitter: 0.1, + } + + var v *version.Info + var retries int + var lastErr error + + err = wait.ExponentialBackoff(defaultRetry, func() (bool, error) { + v, err = client.Discovery().ServerVersion() + if err == nil { + return true, nil + } + + lastErr = err + logger.Infof("Unexpected error discovering Kubernetes version (attempt %v): %v", retries, err) + retries++ + return false, nil + }) + + // err is returned in case of timeout in the exponential backoff (ErrWaitTimeout) + if err != nil { + return nil, nil, lastErr + } + + if retries > 0 { + logger.Warnf("Initial connection to the Kubernetes API server was retried %d times.", retries) + } + + return client, v, nil +} diff --git a/serveis/altres/caddy/ingress/ct.yaml b/serveis/altres/caddy/ingress/ct.yaml new file mode 100644 index 0000000..d6bd5c0 --- /dev/null +++ b/serveis/altres/caddy/ingress/ct.yaml @@ -0,0 +1,3 @@ +# See https://github.com/helm/chart-testing#configuration +all: true +helm-extra-args: --timeout 300s diff --git a/serveis/altres/caddy/ingress/go.mod b/serveis/altres/caddy/ingress/go.mod new file mode 100644 index 0000000..4510f56 --- /dev/null +++ b/serveis/altres/caddy/ingress/go.mod @@ -0,0 +1,152 @@ +module github.com/caddyserver/ingress + +go 1.24.0 +require ( + github.com/caddyserver/caddy/v2 v2.9.1 + github.com/caddyserver/certmagic v0.22.2 + github.com/google/uuid v1.6.0 + github.com/mholt/acmez/v3 v3.1.1 + github.com/mitchellh/mapstructure v1.5.0 + github.com/pires/go-proxyproto v0.8.0 + github.com/stretchr/testify v1.10.0 + go.uber.org/zap v1.27.0 + gopkg.in/go-playground/pool.v3 v3.1.1 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.4 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/cel-go v0.21.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/libdns/libdns v0.2.3 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.21.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.48.2 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/slackhq/nebula v1.7.2 // indirect + github.com/smallstep/certificates v0.26.1 // indirect + github.com/smallstep/nosql v0.6.1 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/smallstep/truststore v0.13.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect + github.com/urfave/cli v1.22.14 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 // indirect + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/mock v0.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/serveis/altres/caddy/ingress/go.sum b/serveis/altres/caddy/ingress/go.sum new file mode 100644 index 0000000..05c4276 --- /dev/null +++ b/serveis/altres/caddy/ingress/go.sum @@ -0,0 +1,806 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.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.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +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/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/caddyserver/caddy/v2 v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY= +github.com/caddyserver/caddy/v2 v2.9.1/go.mod h1:ImUELya2el1FDVp3ahnSO2iH1or1aHxlQEQxd/spP68= +github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk= +github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +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/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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/gofuzz v1.0.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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/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.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8= +github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ= +github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0= +github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw= +github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +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/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20181029174526-d69651ed3497/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-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-20190316082340-a2f829d7f35f/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-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +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.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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-20181030000716-a0a13e073c7b/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-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-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +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.3.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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +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.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/pool.v3 v3.1.1 h1:4Qcj91IsYTpIeRhe/eo6Fz+w6uKWPEghx8vHFTYMfhw= +gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/serveis/altres/caddy/ingress/internal/caddy/convert.go b/serveis/altres/caddy/ingress/internal/caddy/convert.go new file mode 100644 index 0000000..c504c46 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/convert.go @@ -0,0 +1,26 @@ +package caddy + +import ( + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + + // Load default plugins + _ "github.com/caddyserver/ingress/internal/caddy/global" + _ "github.com/caddyserver/ingress/internal/caddy/ingress" +) + +type Converter struct{} + +func (c Converter) ConvertToCaddyConfig(store *store.Store) (interface{}, error) { + cfg := converter.NewConfig() + + for _, p := range converter.Plugins(store.Options.PluginsOrder) { + if m, ok := p.(converter.GlobalMiddleware); ok { + err := m.GlobalHandler(cfg, store) + if err != nil { + return cfg, err + } + } + } + return cfg, nil +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/convert_test.go b/serveis/altres/caddy/ingress/internal/caddy/convert_test.go new file mode 100644 index 0000000..02bef5e --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/convert_test.go @@ -0,0 +1,36 @@ +package caddy + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/ingress/pkg/store" + "github.com/stretchr/testify/require" +) + +func TestConvertToCaddyConfig(t *testing.T) { + tests := []struct { + name string + expectedConfigPath string + }{ + { + name: "default", + expectedConfigPath: "./test_data/default.json", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg, err := Converter{}.ConvertToCaddyConfig(store.NewStore(store.Options{}, &store.PodInfo{})) + require.NoError(t, err) + + cfgJson, err := json.Marshal(cfg) + require.NoError(t, err) + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + require.NoError(t, err) + + require.JSONEq(t, string(expectedCfg), string(cfgJson)) + }) + } +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/configmap.go b/serveis/altres/caddy/ingress/internal/caddy/global/configmap.go new file mode 100644 index 0000000..ad9fa7e --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/configmap.go @@ -0,0 +1,88 @@ +package global + +import ( + "encoding/json" + + caddy2 "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddytls" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + "github.com/mholt/acmez/v3/acme" +) + +type ConfigMapPlugin struct{} + +func init() { + converter.RegisterPlugin(ConfigMapPlugin{}) +} + +func (p ConfigMapPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "configmap", + New: func() converter.Plugin { return new(ConfigMapPlugin) }, + } +} + +func (p ConfigMapPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + cfgMap := store.ConfigMap + + tlsApp := config.GetTLSApp() + httpServer := config.GetHTTPServer() + + if cfgMap.Debug { + config.Logging.Logs = map[string]*caddy2.CustomLog{"default": {BaseLog: caddy2.BaseLog{Level: "DEBUG"}}} + } + + if cfgMap.AcmeCA != "" || cfgMap.Email != "" { + acmeIssuer := caddytls.ACMEIssuer{} + + if cfgMap.AcmeCA != "" { + acmeIssuer.CA = cfgMap.AcmeCA + } + + if cfgMap.AcmeEABKeyId != "" && cfgMap.AcmeEABMacKey != "" { + acmeIssuer.ExternalAccount = &acme.EAB{ + KeyID: cfgMap.AcmeEABKeyId, + MACKey: cfgMap.AcmeEABMacKey, + } + } + + if cfgMap.Email != "" { + acmeIssuer.Email = cfgMap.Email + } + + var onDemandConfig *caddytls.OnDemandConfig + if cfgMap.OnDemandTLS { + onDemandConfig = &caddytls.OnDemandConfig{ + Ask: cfgMap.OnDemandAsk, + } + } + + tlsApp.Automation = &caddytls.AutomationConfig{ + OnDemand: onDemandConfig, + OCSPCheckInterval: cfgMap.OCSPCheckInterval, + Policies: []*caddytls.AutomationPolicy{ + { + IssuersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(acmeIssuer, "module", "acme", nil), + }, + OnDemand: cfgMap.OnDemandTLS, + }, + }, + } + } + + if cfgMap.ProxyProtocol { + httpServer.ListenerWrappersRaw = []json.RawMessage{ + json.RawMessage(`{"wrapper":"proxy_protocol"}`), + json.RawMessage(`{"wrapper":"tls"}`), + } + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(ConfigMapPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/healthz.go b/serveis/altres/caddy/ingress/internal/caddy/global/healthz.go new file mode 100644 index 0000000..318a704 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/healthz.go @@ -0,0 +1,48 @@ +package global + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type HealthzPlugin struct{} + +func (p HealthzPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "healthz", + Priority: -20, + New: func() converter.Plugin { return new(HealthzPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(HealthzPlugin{}) +} + +func (p HealthzPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + healthzHandler := caddyhttp.StaticResponse{StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusOK))} + + healthzRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(healthzHandler, "handler", healthzHandler.CaddyModule().ID.Name(), nil), + }, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/healthz"}, nil), + }}, + } + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, healthzRoute) + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(HealthzPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/ingress.go b/serveis/altres/caddy/ingress/internal/caddy/global/ingress.go new file mode 100644 index 0000000..0ebb9b2 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/ingress.go @@ -0,0 +1,70 @@ +package global + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type IngressPlugin struct{} + +func (p IngressPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress", + New: func() converter.Plugin { return new(IngressPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(IngressPlugin{}) +} + +func (p IngressPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + ingressHandlers := make([]converter.IngressMiddleware, 0) + for _, plugin := range converter.Plugins(store.Options.PluginsOrder) { + if m, ok := plugin.(converter.IngressMiddleware); ok { + ingressHandlers = append(ingressHandlers, m) + } + } + + // create a server route for each ingress route + var routes caddyhttp.RouteList + for _, ing := range store.Ingresses { + for _, rule := range ing.Spec.Rules { + for _, path := range rule.HTTP.Paths { + r := &caddyhttp.Route{ + HandlersRaw: []json.RawMessage{}, + MatcherSetsRaw: []caddy.ModuleMap{}, + } + + for _, middleware := range ingressHandlers { + newRoute, err := middleware.IngressHandler(converter.IngressMiddlewareInput{ + Config: config, + Store: store, + Ingress: ing, + Rule: rule, + Path: path, + Route: r, + }) + if err != nil { + return err + } + r = newRoute + } + + routes = append(routes, *r) + } + } + } + + config.GetHTTPServer().Routes = routes + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(IngressPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort.go b/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort.go new file mode 100644 index 0000000..97569a5 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort.go @@ -0,0 +1,77 @@ +package global + +import ( + "encoding/json" + "sort" + "strings" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type IngressSortPlugin struct{} + +func (p IngressSortPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress_sort", + // Must go after ingress are configured + Priority: -2, + New: func() converter.Plugin { return new(IngressSortPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(IngressSortPlugin{}) +} + +func getFirstItemFromJSON(data json.RawMessage) string { + var arr []string + err := json.Unmarshal(data, &arr) + if err != nil { + return "" + } + return arr[0] +} + +func sortRoutes(routes caddyhttp.RouteList) { + sort.SliceStable(routes, func(i, j int) bool { + iPath := getFirstItemFromJSON(routes[i].MatcherSetsRaw[0]["path"]) + jPath := getFirstItemFromJSON(routes[j].MatcherSetsRaw[0]["path"]) + iPrefixed := strings.HasSuffix(iPath, "*") + jPrefixed := strings.HasSuffix(jPath, "*") + + // If both same type check by length + if iPrefixed == jPrefixed { + return len(jPath) < len(iPath) + } + // Empty path will be moved last + if jPath == "" || iPath == "" { + return jPath == "" + } + // j path is exact so should go first + return jPrefixed + }) +} + +// GlobalHandler in IngressSortPlugin tries to sort routes to have the less conflict. +// +// It only supports basic conflicts for now. It doesn't support multiple matchers in the same route +// nor multiple path/host in the matcher. It shouldn't be an issue with the ingress.matcher plugin. +// Sort will prioritize exact paths then prefix paths and finally empty paths. +// When 2 exacts paths or 2 prefixed paths are on the same host, we choose the longer first. +func (p IngressSortPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + if !store.ConfigMap.ExperimentalSmartSort { + return nil + } + + routes := config.GetHTTPServer().Routes + + sortRoutes(routes) + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(IngressSortPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort_test.go b/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort_test.go new file mode 100644 index 0000000..deb9fd8 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/ingress_sort_test.go @@ -0,0 +1,109 @@ +package global + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestIngressSort(t *testing.T) { + tests := []struct { + name string + routes []struct { + id int + path string + } + expect []int + }{ + + { + name: "multiple exact paths", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/a"}, + {id: 1, path: "/path/"}, + {id: 2, path: "/other"}, + }, + expect: []int{0, 1, 2}, + }, + { + name: "multiple prefix paths", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: "/path/auth/*"}, + {id: 2, path: "/other/*"}, + {id: 3, path: "/login/*"}, + }, + expect: []int{1, 2, 3, 0}, + }, + { + name: "mixed exact and prefixed", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: "/path/auth/"}, + {id: 2, path: "/path/v2/*"}, + {id: 3, path: "/path/new"}, + }, + expect: []int{1, 3, 2, 0}, + }, + { + name: "mixed exact, prefix and empty", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: ""}, + {id: 2, path: "/path/v2/*"}, + {id: 3, path: "/path/new"}, + {id: 4, path: ""}, + }, + expect: []int{3, 2, 0, 1, 4}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + routes := []caddyhttp.Route{} + + for _, route := range test.routes { + match := caddy.ModuleMap{} + match["id"] = caddyconfig.JSON(route.id, nil) + + if route.path != "" { + match["path"] = caddyconfig.JSON(caddyhttp.MatchPath{route.path}, nil) + } + + r := caddyhttp.Route{MatcherSetsRaw: []caddy.ModuleMap{match}} + routes = append(routes, r) + } + + sortRoutes(routes) + + var got []int + for i := range test.expect { + var currentId int + err := json.Unmarshal(routes[i].MatcherSetsRaw[0]["id"], ¤tId) + if err != nil { + t.Fatalf("error unmarshaling id for i %v, %v", i, err) + } + got = append(got, currentId) + } + + if !reflect.DeepEqual(test.expect, got) { + t.Errorf("expected order to match: got %v, expected %v, %s", got, test.expect, routes[1].MatcherSetsRaw) + } + }) + } +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/metrics.go b/serveis/altres/caddy/ingress/internal/caddy/global/metrics.go new file mode 100644 index 0000000..c8c576a --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/metrics.go @@ -0,0 +1,43 @@ +package global + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type MetricsPlugin struct{} + +func (p MetricsPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "metrics", + New: func() converter.Plugin { return new(MetricsPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(MetricsPlugin{}) +} + +func (p MetricsPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + if store.ConfigMap.Metrics { + metricsRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{json.RawMessage(`{ "handler": "metrics" }`)}, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/metrics"}, nil), + }}, + } + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, metricsRoute) + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(MetricsPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/secrets_store.go b/serveis/altres/caddy/ingress/internal/caddy/global/secrets_store.go new file mode 100644 index 0000000..3430240 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/secrets_store.go @@ -0,0 +1,36 @@ +package global + +import ( + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type SecretsStorePlugin struct{} + +func (p SecretsStorePlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "secrets_store", + New: func() converter.Plugin { return new(SecretsStorePlugin) }, + } +} + +func init() { + converter.RegisterPlugin(SecretsStorePlugin{}) +} + +func (p SecretsStorePlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + config.Storage = converter.Storage{ + System: "secret_store", + StorageValues: converter.StorageValues{ + Namespace: store.CurrentPod.Namespace, + LeaseId: store.Options.LeaseId, + }, + } + + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(SecretsStorePlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/tls.go b/serveis/altres/caddy/ingress/internal/caddy/global/tls.go new file mode 100644 index 0000000..97e8f47 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/tls.go @@ -0,0 +1,53 @@ +package global + +import ( + "encoding/json" + "slices" + + "github.com/caddyserver/ingress/internal/controller" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type TLSPlugin struct{} + +func (p TLSPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "tls", + New: func() converter.Plugin { return new(TLSPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(TLSPlugin{}) +} + +func (p TLSPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + tlsApp := config.GetTLSApp() + httpServer := config.GetHTTPServer() + + var hosts []string + + // Get all Hosts subject to custom TLS certs + for _, ing := range store.Ingresses { + for _, tlsRule := range ing.Spec.TLS { + for _, h := range tlsRule.Hosts { + if !slices.Contains(hosts, h) { + hosts = append(hosts, h) + } + } + } + } + + if len(hosts) > 0 { + tlsApp.CertificatesRaw["load_folders"] = json.RawMessage(`["` + controller.CertFolder + `"]`) + // do not manage certificates for those hosts + httpServer.AutoHTTPS.SkipCerts = hosts + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(TLSPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/global/tls_test.go b/serveis/altres/caddy/ingress/internal/caddy/global/tls_test.go new file mode 100644 index 0000000..a88e0b9 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/global/tls_test.go @@ -0,0 +1,211 @@ +package global + +import ( + "testing" + + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + + "github.com/stretchr/testify/assert" +) + +func TestIngressTlsSkipCertificates(t *testing.T) { + testCases := []struct { + desc string + skippedCertsDomains []string + ingresses []*networkingv1.Ingress + }{ + { + desc: "No ingress registered", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{}, + }, + { + desc: "One ingress registered with certificate with one domain", + skippedCertsDomains: []string{"domain1.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + }, + }, + { + desc: "One ingress registered with certificate with multiple domains", + skippedCertsDomains: []string{"domain1.tld", "domain2.tld", "domain3.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2.tld", "domain3.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate one domain each", + skippedCertsDomains: []string{"domain1.tld", "domain2.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain2.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate the same domain", + skippedCertsDomains: []string{"domain1.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate with multiple domains each", + skippedCertsDomains: []string{"domain1a.tld", "domain1b.tld", "domain2a.tld", "domain2b.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1a.tld", "domain1b.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain2a.tld", "domain2b.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate with multiple domains each and partial domain overlap", + skippedCertsDomains: []string{"domain1.tld", "domain2a.tld", "domain2b.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2a.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2b.tld"}, + }}, + }, + }, + }, + }, + { + desc: "One ingress registered without certificate", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{}, + }, + }, + }, + { + desc: "Two ingresses registered without certificate", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{}, + }, + }, + }, + } + + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + tp := TLSPlugin{} + c := converter.NewConfig() + s := store.NewStore(store.Options{}, &store.PodInfo{}) + + for _, ing := range tC.ingresses { + s.AddIngress(ing) + } + + tp.GlobalHandler(c, s) + + toSkip := c.GetHTTPServer().AutoHTTPS.SkipCerts + assert.ElementsMatch(t, toSkip, tC.skippedCertsDomains, "List of certificate to skip don't match expectation") + }) + } +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/annotations.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/annotations.go new file mode 100644 index 0000000..c711cdb --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/annotations.go @@ -0,0 +1,28 @@ +package ingress + +import v1 "k8s.io/api/networking/v1" + +const ( + annotationPrefix = "caddy.ingress.kubernetes.io" + rewriteToAnnotation = "rewrite-to" + rewriteStripPrefixAnnotation = "rewrite-strip-prefix" + disableSSLRedirect = "disable-ssl-redirect" + backendProtocol = "backend-protocol" + insecureSkipVerify = "insecure-skip-verify" + permanentRedirectAnnotation = "permanent-redirect" + permanentRedirectCodeAnnotation = "permanent-redirect-code" + temporaryRedirectAnnotation = "temporal-redirect" + trustedProxies = "trusted-proxies" +) + +func getAnnotation(ing *v1.Ingress, rule string) string { + return ing.Annotations[annotationPrefix+"/"+rule] +} + +func getAnnotationBool(ing *v1.Ingress, rule string, def bool) bool { + val := getAnnotation(ing, rule) + if val == "" { + return def + } + return val == "true" +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/matcher.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/matcher.go new file mode 100644 index 0000000..e78631c --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/matcher.go @@ -0,0 +1,52 @@ +package ingress + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + v1 "k8s.io/api/networking/v1" +) + +type MatcherPlugin struct{} + +func (p MatcherPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.matcher", + New: func() converter.Plugin { return new(MatcherPlugin) }, + } +} + +// IngressHandler Generate matchers for the route. +func (p MatcherPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + match := caddy.ModuleMap{} + + if getAnnotation(input.Ingress, disableSSLRedirect) != "true" { + match["protocol"] = caddyconfig.JSON(caddyhttp.MatchProtocol("https"), nil) + } + + if input.Rule.Host != "" { + match["host"] = caddyconfig.JSON(caddyhttp.MatchHost{input.Rule.Host}, nil) + } + + if input.Path.Path != "" { + p := input.Path.Path + + if *input.Path.PathType == v1.PathTypePrefix { + p += "*" + } + match["path"] = caddyconfig.JSON(caddyhttp.MatchPath{p}, nil) + } + + input.Route.MatcherSetsRaw = append(input.Route.MatcherSetsRaw, match) + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(MatcherPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(MatcherPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect.go new file mode 100644 index 0000000..2ed21c9 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect.go @@ -0,0 +1,82 @@ +package ingress + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" +) + +type RedirectPlugin struct{} + +func (p RedirectPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.redirect", + Priority: 10, + New: func() converter.Plugin { return new(RedirectPlugin) }, + } +} + +// IngressHandler Converts redirect annotations to static_response handler +func (p RedirectPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + ing := input.Ingress + + permanentRedir := getAnnotation(ing, permanentRedirectAnnotation) + temporaryRedir := getAnnotation(ing, temporaryRedirectAnnotation) + + var code string = "301" + var redirectTo string = "" + + // Don't allow both redirect annotations to be set + if permanentRedir != "" && temporaryRedir != "" { + return nil, fmt.Errorf("cannot use permanent-redirect annotation with temporal-redirect") + } + + if permanentRedir != "" { + redirectCode := getAnnotation(ing, permanentRedirectCodeAnnotation) + if redirectCode != "" { + codeInt, err := strconv.Atoi(redirectCode) + if err != nil { + return nil, fmt.Errorf("not a supported redirection code type or not a valid integer: '%s'", redirectCode) + } + + if codeInt < 300 || (codeInt > 399 && codeInt != 401) { + return nil, fmt.Errorf("redirection code not in the 3xx range or 401: '%v'", codeInt) + } + + code = redirectCode + } + redirectTo = permanentRedir + } + + if temporaryRedir != "" { + code = "302" + redirectTo = temporaryRedir + } + + if redirectTo != "" { + handler := caddyconfig.JSONModuleObject( + caddyhttp.StaticResponse{ + StatusCode: caddyhttp.WeakString(code), + Headers: http.Header{"Location": []string{redirectTo}}, + }, + "handler", "static_response", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(RedirectPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(RedirectPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect_test.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect_test.go new file mode 100644 index 0000000..2ec5f42 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/redirect_test.go @@ -0,0 +1,135 @@ +package ingress + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/stretchr/testify/assert" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestRedirectConvertToCaddyConfig(t *testing.T) { + rp := RedirectPlugin{} + + tests := []struct { + name string + expectedConfigPath string + expectedError string + annotations map[string]string + }{ + { + name: "Check permanent redirect without any specific redirect code", + expectedConfigPath: "test_data/redirect_default.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + }, + }, + { + name: "Check permanent redirect with custom redirect code", + expectedConfigPath: "test_data/redirect_custom_code.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "308", + }, + }, + { + name: "Check permanent redirect with 401 as redirect code", + expectedConfigPath: "test_data/redirect_401.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "401", + }, + }, + { + name: "Check temporary redirect", + expectedConfigPath: "test_data/redirect_temporary.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/temporal-redirect": "http://example.com", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rp.IngressHandler(input) + assert.NoError(t, err, "failed to generate ingress route") + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + assert.NoError(t, err, "failed to find config file for comparison") + + cfgJson, err := json.Marshal(&route) + assert.NoError(t, err, "failed to marshal route to JSON") + + assert.JSONEq(t, string(cfgJson), string(expectedCfg)) + }) + } +} + +func TestMisconfiguredRedirectConvertToCaddyConfig(t *testing.T) { + rp := RedirectPlugin{} + + tests := []struct { + name string + expectedError string + annotations map[string]string + }{ + { + name: "Check permanent redirect with invalid custom redirect code", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "502", + }, + expectedError: "redirection code not in the 3xx range or 401: '502'", + }, + { + name: "Check permanent redirect with invalid custom redirect code string", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "randomstring", + }, + expectedError: "not a supported redirection code type or not a valid integer: 'randomstring'", + }, + { + name: "Check if both permanent and temporary redirection annotations are set", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/temporal-redirect": "http://example2.com", + }, + expectedError: "cannot use permanent-redirect annotation with temporal-redirect", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rp.IngressHandler(input) + if assert.Error(t, err, "expected an error while generating the ingress route") { + assert.EqualError(t, err, test.expectedError) + } + + cfgJson, err := json.Marshal(&route) + assert.NoError(t, err, "failed to marshal route to JSON") + + assert.JSONEq(t, string(cfgJson), "null") + }) + } +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy.go new file mode 100644 index 0000000..3e5a41c --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy.go @@ -0,0 +1,103 @@ +package ingress + +import ( + "fmt" + "net/netip" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + "github.com/caddyserver/ingress/pkg/converter" +) + +type ReverseProxyPlugin struct{} + +func (p ReverseProxyPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.reverseproxy", + // Should always go last by default + Priority: -10, + New: func() converter.Plugin { return new(ReverseProxyPlugin) }, + } +} + +// IngressHandler Add a reverse proxy handler to the route +func (p ReverseProxyPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + path := input.Path + ing := input.Ingress + backendProtocol := strings.ToLower(getAnnotation(ing, backendProtocol)) + trustedProxiesAnnotation := strings.ToLower(getAnnotation(ing, trustedProxies)) + + // TODO :- + // when setting the upstream url we should bypass kube-dns and get the ip address of + // the pod for the deployment we are proxying to so that we can proxy to that ip address port. + // this is good for session affinity and increases performance. + clusterHostName := fmt.Sprintf("%v.%v.svc.cluster.local:%d", path.Backend.Service.Name, ing.Namespace, path.Backend.Service.Port.Number) + + transport := &reverseproxy.HTTPTransport{} + + if backendProtocol == "https" { + transport.TLS = &reverseproxy.TLSConfig{ + InsecureSkipVerify: getAnnotationBool(ing, insecureSkipVerify, true), + } + } + + var err error + var parsedProxies []string + if trustedProxiesAnnotation != "" { + trustedProxies := strings.Split(trustedProxiesAnnotation, ",") + parsedProxies, err = parseTrustedProxies(trustedProxies) + if err != nil { + return nil, err + } + } + + handler := reverseproxy.Handler{ + TransportRaw: caddyconfig.JSONModuleObject(transport, "protocol", "http", nil), + Upstreams: reverseproxy.UpstreamPool{ + {Dial: clusterHostName}, + }, + TrustedProxies: parsedProxies, + } + + handlerModule := caddyconfig.JSONModuleObject( + handler, + "handler", + "reverse_proxy", + nil, + ) + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handlerModule) + return input.Route, nil +} + +// Copied from https://github.com/caddyserver/caddy/blob/21af88fefc9a8239a024f635f1c6fdd9defd7eb7/modules/caddyhttp/reverseproxy/reverseproxy.go#L270-L286 +func parseTrustedProxies(trustedProxies []string) (parsedProxies []string, err error) { + for _, trustedProxy := range trustedProxies { + trustedProxy = strings.TrimSpace(trustedProxy) + if strings.Contains(trustedProxy, "/") { + ipNet, err := netip.ParsePrefix(trustedProxy) + if err != nil { + return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy) + } + parsedProxies = append(parsedProxies, ipNet.String()) + } else { + ipAddr, err := netip.ParseAddr(trustedProxy) + if err != nil { + return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy) + } + ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + parsedProxies = append(parsedProxies, ipNew.String()) + } + } + return parsedProxies, nil +} + +func init() { + converter.RegisterPlugin(ReverseProxyPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(ReverseProxyPlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go new file mode 100644 index 0000000..e9f9d6c --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go @@ -0,0 +1,168 @@ +package ingress + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/stretchr/testify/require" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTrustedProxesConvertToCaddyConfig(t *testing.T) { + rpp := ReverseProxyPlugin{} + + tests := []struct { + name string + annotations map[string]string + expectedConfigPath string + }{ + { + name: "ipv4 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0, 10.0.0.1", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4.json", + }, + { + name: "ipv4 trusted proxies wit subnet", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0/16,10.0.0.1/8", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4_subnet.json", + }, + { + name: "ipv6 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1, 2001:db8::5", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6.json", + }, + { + name: "ipv6 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1/36,2001:db8::5/60", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6_subnet.json", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + Namespace: "namespace", + }, + }, + Path: networkingv1.HTTPIngressPath{ + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "svcName", + Port: networkingv1.ServiceBackendPort{Number: 80}, + }, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rpp.IngressHandler(input) + require.NoError(t, err) + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + require.NoError(t, err) + + cfgJson, err := json.Marshal(&route) + require.NoError(t, err) + + require.JSONEq(t, string(expectedCfg), string(cfgJson)) + }) + } +} + +func TestMisconfiguredTrustedProxiesConvertToCaddyConfig(t *testing.T) { + rpp := ReverseProxyPlugin{} + + tests := []struct { + name string + annotations map[string]string + expectedError string + }{ + { + name: "invalid ipv4 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999", + }, + expectedError: `failed to parse IP: "999.999.999.999"`, + }, + { + name: "invalid ipv4 with subnet trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999/32", + }, + expectedError: `failed to parse IP: "999.999.999.999/32"`, + }, + { + name: "invalid subnet for ipv4 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "10.0.0.0/100", + }, + expectedError: `failed to parse IP: "10.0.0.0/100"`, + }, + { + name: "invalid ipv6 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g", + }, + expectedError: `failed to parse IP: "2001:db8::g"`, + }, + { + name: "invalid ipv6 with subnet trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g/128", + }, + expectedError: `failed to parse IP: "2001:db8::g/128"`, + }, + { + name: "invalid subnet for ipv6 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::/200", + }, + expectedError: `failed to parse IP: "2001:db8::/200"`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + Namespace: "namespace", + }, + }, + Path: networkingv1.HTTPIngressPath{ + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "svcName", + Port: networkingv1.ServiceBackendPort{Number: 80}, + }, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rpp.IngressHandler(input) + require.EqualError(t, err, test.expectedError) + + cfgJson, err := json.Marshal(&route) + require.NoError(t, err) + + require.JSONEq(t, string(cfgJson), "null") + }) + } +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/rewrite.go b/serveis/altres/caddy/ingress/internal/caddy/ingress/rewrite.go new file mode 100644 index 0000000..6a856e0 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/rewrite.go @@ -0,0 +1,53 @@ +package ingress + +import ( + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" + "github.com/caddyserver/ingress/pkg/converter" +) + +type RewritePlugin struct{} + +func (p RewritePlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.rewrite", + Priority: 10, + New: func() converter.Plugin { return new(RewritePlugin) }, + } +} + +// IngressHandler Converts rewrite annotations to rewrite handler +func (p RewritePlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + ing := input.Ingress + + rewriteTo := getAnnotation(ing, rewriteToAnnotation) + if rewriteTo != "" { + handler := caddyconfig.JSONModuleObject( + rewrite.Rewrite{URI: rewriteTo}, + "handler", "rewrite", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + + rewriteStripPrefix := getAnnotation(ing, rewriteStripPrefixAnnotation) + if rewriteStripPrefix != "" { + handler := caddyconfig.JSONModuleObject( + rewrite.Rewrite{StripPathPrefix: rewriteStripPrefix}, + "handler", "rewrite", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(RewritePlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(RewritePlugin{}) +) diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json new file mode 100644 index 0000000..c1539a9 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 401 + } + ] +} \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json new file mode 100644 index 0000000..78aaa93 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 308 + } + ] +} \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json new file mode 100644 index 0000000..1502b63 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 301 + } + ] +} \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json new file mode 100644 index 0000000..67e83c3 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 302 + } + ] +} \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json new file mode 100644 index 0000000..7ccbe8a --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "192.168.1.0/32", + "10.0.0.1/32" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json new file mode 100644 index 0000000..9b7a776 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "192.168.1.0/16", + "10.0.0.1/8" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json new file mode 100644 index 0000000..112bd0e --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "2001:db8::1/128", + "2001:db8::5/128" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json new file mode 100644 index 0000000..4c0e759 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "2001:db8::1/36", + "2001:db8::5/60" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/altres/caddy/ingress/internal/caddy/test_data/default.json b/serveis/altres/caddy/ingress/internal/caddy/test_data/default.json new file mode 100644 index 0000000..1156fa5 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/caddy/test_data/default.json @@ -0,0 +1,27 @@ +{ + "admin": {}, + "storage": { "module": "secret_store", "namespace": "", "leaseId": "" }, + "apps": { + "http": { + "servers": { + "ingress_server": { + "listen": [":80", ":443"], + "tls_connection_policies": [{}], + "automatic_https": {} + }, + "metrics_server": { + "listen": [":9765"], + "routes": [ + { + "match": [{ "path": ["/healthz"] }], + "handle": [{ "handler": "static_response", "status_code": 200 }] + } + ], + "automatic_https": { "disable": true } + } + } + }, + "tls": {} + }, + "logging": {} +} diff --git a/serveis/altres/caddy/ingress/internal/controller/action_configmap.go b/serveis/altres/caddy/ingress/internal/controller/action_configmap.go new file mode 100644 index 0000000..99f8c38 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/controller/action_configmap.go @@ -0,0 +1,71 @@ +package controller + +import ( + "github.com/caddyserver/ingress/pkg/store" + v1 "k8s.io/api/core/v1" +) + +// ConfigMapAddedAction provides an implementation of the action interface. +type ConfigMapAddedAction struct { + resource *v1.ConfigMap +} + +// ConfigMapUpdatedAction provides an implementation of the action interface. +type ConfigMapUpdatedAction struct { + resource *v1.ConfigMap + oldResource *v1.ConfigMap +} + +// ConfigMapDeletedAction provides an implementation of the action interface. +type ConfigMapDeletedAction struct { + resource *v1.ConfigMap +} + +// onConfigMapAdded runs when a configmap is added to the namespace. +func (c *CaddyController) onConfigMapAdded(obj *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapAddedAction{ + resource: obj, + }) +} + +// onConfigMapUpdated is run when a configmap is updated in the namespace. +func (c *CaddyController) onConfigMapUpdated(old *v1.ConfigMap, new *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapUpdatedAction{ + resource: new, + oldResource: old, + }) +} + +// onConfigMapDeleted is run when a configmap is deleted from the namespace. +func (c *CaddyController) onConfigMapDeleted(obj *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapDeletedAction{ + resource: obj, + }) +} + +func (r ConfigMapAddedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap created (%s/%s)", r.resource.Namespace, r.resource.Name) + + cfg, err := store.ParseConfigMap(r.resource) + if err == nil { + c.resourceStore.ConfigMap = cfg + } + return err +} + +func (r ConfigMapUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap updated (%s/%s)", r.resource.Namespace, r.resource.Name) + + cfg, err := store.ParseConfigMap(r.resource) + if err == nil { + c.resourceStore.ConfigMap = cfg + } + return err +} + +func (r ConfigMapDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + + c.resourceStore.ConfigMap = nil + return nil +} diff --git a/serveis/altres/caddy/ingress/internal/controller/action_ingress.go b/serveis/altres/caddy/ingress/internal/controller/action_ingress.go new file mode 100644 index 0000000..0e55c62 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/controller/action_ingress.go @@ -0,0 +1,70 @@ +package controller + +import ( + v1 "k8s.io/api/networking/v1" +) + +// IngressAddedAction provides an implementation of the action interface. +type IngressAddedAction struct { + resource *v1.Ingress +} + +// IngressUpdatedAction provides an implementation of the action interface. +type IngressUpdatedAction struct { + resource *v1.Ingress + oldResource *v1.Ingress +} + +// IngressDeletedAction provides an implementation of the action interface. +type IngressDeletedAction struct { + resource *v1.Ingress +} + +// onIngressAdded runs when an ingress resource is added to the cluster. +func (c *CaddyController) onIngressAdded(obj *v1.Ingress) { + c.syncQueue.Add(IngressAddedAction{ + resource: obj, + }) +} + +// onIngressUpdated is run when an ingress resource is updated in the cluster. +func (c *CaddyController) onIngressUpdated(old *v1.Ingress, new *v1.Ingress) { + c.syncQueue.Add(IngressUpdatedAction{ + resource: new, + oldResource: old, + }) +} + +// onIngressDeleted is run when an ingress resource is deleted from the cluster. +func (c *CaddyController) onIngressDeleted(obj *v1.Ingress) { + c.syncQueue.Add(IngressDeletedAction{ + resource: obj, + }) +} + +func (r IngressAddedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress created (%s/%s)", r.resource.Namespace, r.resource.Name) + // add this ingress to the internal store + c.resourceStore.AddIngress(r.resource) + + // Ingress may now have a TLS config + return c.watchTLSSecrets() +} + +func (r IngressUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress updated (%s/%s)", r.resource.Namespace, r.resource.Name) + + // add or update this ingress in the internal store + c.resourceStore.AddIngress(r.resource) + + // Ingress may now have a TLS config + return c.watchTLSSecrets() +} + +func (r IngressDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + + // delete all resources from caddy config that are associated with this resource + c.resourceStore.PluckIngress(r.resource) + return nil +} diff --git a/serveis/altres/caddy/ingress/internal/controller/action_status.go b/serveis/altres/caddy/ingress/internal/controller/action_status.go new file mode 100644 index 0000000..7758d46 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/controller/action_status.go @@ -0,0 +1,139 @@ +package controller + +import ( + "net" + "sort" + "strings" + + "github.com/caddyserver/ingress/internal/k8s" + "go.uber.org/zap" + "gopkg.in/go-playground/pool.v3" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/client-go/kubernetes" +) + +// dispatchSync is run every syncInterval duration to sync ingress source address fields. +func (c *CaddyController) dispatchSync() { + c.syncQueue.Add(SyncStatusAction{}) +} + +// SyncStatusAction provides an implementation of the action interface. +type SyncStatusAction struct { +} + +// handle is run when a syncStatusAction appears in the queue. +func (r SyncStatusAction) handle(c *CaddyController) error { + return c.syncStatus(c.resourceStore.Ingresses) +} + +// syncStatus ensures that the ingress source address points to this ingress controller's IP address. +func (c *CaddyController) syncStatus(ings []*networkingv1.Ingress) error { + addrs, err := k8s.GetAddresses(c.resourceStore.CurrentPod, c.kubeClient) + if err != nil { + return err + } + + c.logger.Debugf("Syncing %d Ingress resources source addresses", len(ings)) + c.updateIngStatuses(sliceToLoadBalancerIngress(addrs), ings) + + return nil +} + +// updateIngStatuses starts a queue and adds all monitored ingresses to update their status source address to the on +// that the ingress controller is running on. This is called by the syncStatus queue. +func (c *CaddyController) updateIngStatuses(controllerAddresses []networkingv1.IngressLoadBalancerIngress, ings []*networkingv1.Ingress) { + p := pool.NewLimited(10) + defer p.Close() + + batch := p.Batch() + sort.SliceStable(controllerAddresses, lessLoadBalancerIngress(controllerAddresses)) + + for _, ing := range ings { + curIPs := ing.Status.LoadBalancer.Ingress + sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs)) + + // check to see if ingresses source address does not match the ingress controller's. + if ingressSliceEqual(curIPs, controllerAddresses) { + c.logger.Debugf("skipping update of Ingress %v/%v (no change)", ing.Namespace, ing.Name) + continue + } + + batch.Queue(runUpdate(c.logger, ing, controllerAddresses, c.kubeClient)) + } + + batch.QueueComplete() + batch.WaitAll() +} + +// runUpdate updates the ingress status field. +func runUpdate(logger *zap.SugaredLogger, ing *networkingv1.Ingress, status []networkingv1.IngressLoadBalancerIngress, client *kubernetes.Clientset) pool.WorkFunc { + return func(wu pool.WorkUnit) (interface{}, error) { + if wu.IsCancelled() { + return nil, nil + } + + updated, err := k8s.UpdateIngressStatus(client, ing, status) + if err != nil { + logger.Warnf("error updating ingress rule: %v", err) + } else { + logger.Debugf( + "updating Ingress %v/%v status from %v to %v", + ing.Namespace, + ing.Name, + ing.Status.LoadBalancer.Ingress, + updated.Status.LoadBalancer.Ingress, + ) + } + + return true, nil + } +} + +// ingressSliceEqual determines if the ingress source matches the ingress controller's. +func ingressSliceEqual(lhs, rhs []networkingv1.IngressLoadBalancerIngress) bool { + if len(lhs) != len(rhs) { + return false + } + + for i := range lhs { + if lhs[i].IP != rhs[i].IP { + return false + } + if lhs[i].Hostname != rhs[i].Hostname { + return false + } + } + + return true +} + +// lessLoadBalancerIngress is a sorting function for ingress hostnames. +func lessLoadBalancerIngress(addrs []networkingv1.IngressLoadBalancerIngress) func(int, int) bool { + return func(a, b int) bool { + switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) { + case -1: + return true + case 1: + return false + } + return addrs[a].IP < addrs[b].IP + } +} + +// sliceToLoadBalancerIngress converts a slice of IP and/or hostnames to LoadBalancerIngress +func sliceToLoadBalancerIngress(endpoints []string) []networkingv1.IngressLoadBalancerIngress { + lbi := []networkingv1.IngressLoadBalancerIngress{} + for _, ep := range endpoints { + if net.ParseIP(ep) == nil { + lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{Hostname: ep}) + } else { + lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{IP: ep}) + } + } + + sort.SliceStable(lbi, func(a, b int) bool { + return lbi[a].IP < lbi[b].IP + }) + + return lbi +} diff --git a/serveis/altres/caddy/ingress/internal/controller/action_tls.go b/serveis/altres/caddy/ingress/internal/controller/action_tls.go new file mode 100644 index 0000000..dc674d3 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/controller/action_tls.go @@ -0,0 +1,124 @@ +package controller + +import ( + "os" + "path/filepath" + + "github.com/caddyserver/ingress/internal/k8s" + apiv1 "k8s.io/api/core/v1" +) + +var CertFolder = filepath.FromSlash("/etc/caddy/certs") + +// SecretAddedAction provides an implementation of the action interface. +type SecretAddedAction struct { + resource *apiv1.Secret +} + +// SecretUpdatedAction provides an implementation of the action interface. +type SecretUpdatedAction struct { + resource *apiv1.Secret + oldResource *apiv1.Secret +} + +// SecretDeletedAction provides an implementation of the action interface. +type SecretDeletedAction struct { + resource *apiv1.Secret +} + +// onSecretAdded runs when a TLS secret resource is added to the cluster. +func (c *CaddyController) onSecretAdded(obj *apiv1.Secret) { + if k8s.IsManagedTLSSecret(obj, c.resourceStore.Ingresses) { + c.syncQueue.Add(SecretAddedAction{ + resource: obj, + }) + } +} + +// onSecretUpdated is run when a TLS secret resource is updated in the cluster. +func (c *CaddyController) onSecretUpdated(old *apiv1.Secret, new *apiv1.Secret) { + if k8s.IsManagedTLSSecret(new, c.resourceStore.Ingresses) { + c.syncQueue.Add(SecretUpdatedAction{ + resource: new, + oldResource: old, + }) + } +} + +// onSecretDeleted is run when a TLS secret resource is deleted from the cluster. +func (c *CaddyController) onSecretDeleted(obj *apiv1.Secret) { + c.syncQueue.Add(SecretDeletedAction{ + resource: obj, + }) +} + +// writeFile writes a secret to a .pem file on disk. +func writeFile(s *apiv1.Secret) error { + content := make([]byte, 0) + + for _, cert := range s.Data { + content = append(content, cert...) + } + + err := os.WriteFile(filepath.Join(CertFolder, s.Name+".pem"), content, 0644) + if err != nil { + return err + } + + return nil +} + +func (r SecretAddedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret created (%s/%s)", r.resource.Namespace, r.resource.Name) + return writeFile(r.resource) +} + +func (r SecretUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret updated (%s/%s)", r.resource.Namespace, r.resource.Name) + return writeFile(r.resource) +} + +func (r SecretDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + return os.Remove(filepath.Join(CertFolder, r.resource.Name+".pem")) +} + +// watchTLSSecrets Start listening to TLS secrets if at least one ingress needs it. +// It will sync the CertFolder with TLS secrets +func (c *CaddyController) watchTLSSecrets() error { + if c.informers.TLSSecret == nil && c.resourceStore.HasManagedTLS() { + // Init informers + params := k8s.TLSSecretParams{ + InformerFactory: c.factories.WatchedNamespace, + } + c.informers.TLSSecret = k8s.WatchTLSSecrets(params, k8s.TLSSecretHandlers{ + AddFunc: c.onSecretAdded, + UpdateFunc: c.onSecretUpdated, + DeleteFunc: c.onSecretDeleted, + }) + + // Run it + go c.informers.TLSSecret.Run(c.stopChan) + + // Sync secrets + secrets, err := k8s.ListTLSSecrets(params, c.resourceStore.Ingresses) + if err != nil { + return err + } + + if _, err := os.Stat(CertFolder); os.IsNotExist(err) { + err = os.MkdirAll(CertFolder, 0755) + if err != nil { + return err + } + } + + for _, secret := range secrets { + if err := writeFile(secret); err != nil { + return err + } + } + } + + return nil +} diff --git a/serveis/altres/caddy/ingress/internal/controller/controller.go b/serveis/altres/caddy/ingress/internal/controller/controller.go new file mode 100644 index 0000000..3379c15 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/controller/controller.go @@ -0,0 +1,272 @@ +package controller + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/certmagic" + "github.com/caddyserver/ingress/internal/k8s" + "github.com/caddyserver/ingress/pkg/store" + "go.uber.org/zap" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + // load required caddy plugins + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + _ "github.com/caddyserver/caddy/v2/modules/caddytls" + _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek" + _ "github.com/caddyserver/caddy/v2/modules/metrics" + _ "github.com/caddyserver/ingress/pkg/storage" +) + +const ( + // how often we should attempt to keep ingress resource's source address in sync + syncInterval = time.Second * 30 + + // how often we resync informers resources (besides receiving updates) + resourcesSyncInterval = time.Hour * 1 +) + +// Action is an interface for ingress actions. +type Action interface { + handle(c *CaddyController) error +} + +// Informer defines the required SharedIndexInformers that interact with the API server. +type Informer struct { + Ingress cache.SharedIndexInformer + ConfigMap cache.SharedIndexInformer + TLSSecret cache.SharedIndexInformer +} + +// InformerFactory contains shared informer factory +// We need to type of factory: +// - One used to watch resources in the Pod namespaces (caddy config, secrets...) +// - Another one for Ingress resources in the selected namespace +type InformerFactory struct { + PodNamespace informers.SharedInformerFactory + WatchedNamespace informers.SharedInformerFactory +} + +type Converter interface { + ConvertToCaddyConfig(store *store.Store) (interface{}, error) +} + +// CaddyController represents a caddy ingress controller. +type CaddyController struct { + resourceStore *store.Store + + kubeClient *kubernetes.Clientset + + logger *zap.SugaredLogger + + // main queue syncing ingresses, configmaps, ... with caddy + syncQueue workqueue.RateLimitingInterface + + // informer factories + factories *InformerFactory + + // informer contains the cache Informers + informers *Informer + + // save last applied caddy config + lastAppliedConfig []byte + + converter Converter + + stopChan chan struct{} +} + +func NewCaddyController( + logger *zap.SugaredLogger, + kubeClient *kubernetes.Clientset, + opts store.Options, + converter Converter, + stopChan chan struct{}, +) *CaddyController { + controller := &CaddyController{ + logger: logger, + kubeClient: kubeClient, + converter: converter, + stopChan: stopChan, + syncQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), + informers: &Informer{}, + factories: &InformerFactory{}, + } + + podInfo, err := k8s.GetPodDetails(kubeClient) + if err != nil { + logger.Fatalf("Unexpected error obtaining pod information: %v", err) + } + + // Create informer factories + controller.factories.PodNamespace = informers.NewSharedInformerFactoryWithOptions( + kubeClient, + resourcesSyncInterval, + informers.WithNamespace(podInfo.Namespace), + ) + controller.factories.WatchedNamespace = informers.NewSharedInformerFactoryWithOptions( + kubeClient, + resourcesSyncInterval, + informers.WithNamespace(opts.WatchNamespace), + ) + + // Watch ingress resources in selected namespaces + ingressParams := k8s.IngressParams{ + InformerFactory: controller.factories.WatchedNamespace, + ClassName: opts.ClassName, + ClassNameRequired: opts.ClassNameRequired, + } + controller.informers.Ingress = k8s.WatchIngresses(ingressParams, k8s.IngressHandlers{ + AddFunc: controller.onIngressAdded, + UpdateFunc: controller.onIngressUpdated, + DeleteFunc: controller.onIngressDeleted, + }) + + // Watch Configmap in the pod's namespace for global options + cmOptionsParams := k8s.ConfigMapParams{ + Namespace: podInfo.Namespace, + InformerFactory: controller.factories.PodNamespace, + ConfigMapName: opts.ConfigMapName, + } + controller.informers.ConfigMap = k8s.WatchConfigMaps(cmOptionsParams, k8s.ConfigMapHandlers{ + AddFunc: controller.onConfigMapAdded, + UpdateFunc: controller.onConfigMapUpdated, + DeleteFunc: controller.onConfigMapDeleted, + }) + + // Create resource store + controller.resourceStore = store.NewStore(opts, podInfo) + + return controller +} + +// Shutdown stops the caddy controller. +func (c *CaddyController) Shutdown() error { + // remove this ingress controller's ip from ingress resources. + c.updateIngStatuses([]networkingv1.IngressLoadBalancerIngress{{}}, c.resourceStore.Ingresses) + + if err := caddy.Stop(); err != nil { + c.logger.Error("failed to stop caddy server", zap.Error(err)) + return err + } + certmagic.CleanUpOwnLocks(context.TODO(), c.logger.Desugar()) + return nil +} + +// Run method starts the ingress controller. +func (c *CaddyController) Run() { + defer runtime.HandleCrash() + defer c.syncQueue.ShutDown() + + // start informers where we listen to new / updated resources + go c.informers.ConfigMap.Run(c.stopChan) + go c.informers.Ingress.Run(c.stopChan) + + // wait for all involved caches to be synced before processing items + // from the queue + if !cache.WaitForCacheSync(c.stopChan, + c.informers.ConfigMap.HasSynced, + c.informers.Ingress.HasSynced, + ) { + runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) + } + + // start processing events for syncing ingress resources + go wait.Until(c.runWorker, time.Second, c.stopChan) + + // start ingress status syncher and run every syncInterval + go wait.Until(c.dispatchSync, syncInterval, c.stopChan) + + // wait for SIGTERM + <-c.stopChan + c.logger.Info("stopping ingress controller") + + var exitCode int + err := c.Shutdown() + if err != nil { + c.logger.Error("could not shutdown ingress controller properly, " + err.Error()) + exitCode = 1 + } + + os.Exit(exitCode) +} + +// runWorker processes items in the event queue. +func (c *CaddyController) runWorker() { + for c.processNextItem() { + } +} + +// processNextItem determines if there is an ingress item in the event queue and processes it. +func (c *CaddyController) processNextItem() bool { + // Wait until there is a new item in the working queue + action, quit := c.syncQueue.Get() + if quit { + return false + } + + // Tell the queue that we are done with processing this key. This unblocks the key for other workers + // This allows safe parallel processing because two ingresses with the same key are never processed in + // parallel. + defer c.syncQueue.Done(action) + + // Invoke the method containing the business logic + err := action.(Action).handle(c) + if err != nil { + c.handleErr(err, action) + return true + } + + err = c.reloadCaddy() + if err != nil { + c.logger.Error("could not reload caddy: " + err.Error()) + return true + } + + return true +} + +// handleErrs reports errors received from queue actions. +// +//goland:noinspection GoUnusedParameter +func (c *CaddyController) handleErr(err error, action interface{}) { + c.logger.Error(err.Error()) +} + +// reloadCaddy generate a caddy config from controller's store +func (c *CaddyController) reloadCaddy() error { + config, err := c.converter.ConvertToCaddyConfig(c.resourceStore) + if err != nil { + return err + } + + j, err := json.Marshal(config) + if err != nil { + return err + } + + if bytes.Equal(c.lastAppliedConfig, j) { + c.logger.Debug("caddy config did not change, skipping reload") + return nil + } + + c.logger.Debug("reloading caddy with config", string(j)) + err = caddy.Load(j, false) + if err != nil { + return fmt.Errorf("could not reload caddy config %v", err.Error()) + } + c.lastAppliedConfig = j + return nil +} diff --git a/serveis/altres/caddy/ingress/internal/k8s/configmap.go b/serveis/altres/caddy/ingress/internal/k8s/configmap.go new file mode 100644 index 0000000..3f47dea --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/k8s/configmap.go @@ -0,0 +1,54 @@ +package k8s + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +type ConfigMapHandlers struct { + AddFunc func(obj *v1.ConfigMap) + UpdateFunc func(oldObj, newObj *v1.ConfigMap) + DeleteFunc func(obj *v1.ConfigMap) +} + +type ConfigMapParams struct { + Namespace string + InformerFactory informers.SharedInformerFactory + ConfigMapName string +} + +func isControllerConfigMap(cm *v1.ConfigMap, name string) bool { + return cm.GetName() == name +} + +func WatchConfigMaps(options ConfigMapParams, funcs ConfigMapHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Core().V1().ConfigMaps().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + cm, ok := obj.(*v1.ConfigMap) + + if ok && isControllerConfigMap(cm, options.ConfigMapName) { + funcs.AddFunc(cm) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldCM, ok1 := oldObj.(*v1.ConfigMap) + newCM, ok2 := newObj.(*v1.ConfigMap) + + if ok1 && ok2 && isControllerConfigMap(newCM, options.ConfigMapName) { + funcs.UpdateFunc(oldCM, newCM) + } + }, + DeleteFunc: func(obj interface{}) { + cm, ok := obj.(*v1.ConfigMap) + + if ok && isControllerConfigMap(cm, options.ConfigMapName) { + funcs.DeleteFunc(cm) + } + }, + }) + + return informer +} diff --git a/serveis/altres/caddy/ingress/internal/k8s/ingress.go b/serveis/altres/caddy/ingress/internal/k8s/ingress.go new file mode 100644 index 0000000..1b871c5 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/k8s/ingress.go @@ -0,0 +1,82 @@ +package k8s + +import ( + "context" + "fmt" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +type IngressHandlers struct { + AddFunc func(obj *networkingv1.Ingress) + UpdateFunc func(oldObj, newObj *networkingv1.Ingress) + DeleteFunc func(obj *networkingv1.Ingress) +} + +type IngressParams struct { + InformerFactory informers.SharedInformerFactory + ClassName string + ClassNameRequired bool +} + +func WatchIngresses(options IngressParams, funcs IngressHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Networking().V1().Ingresses().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + ingress, ok := obj.(*networkingv1.Ingress) + + if ok && isControllerIngress(options, ingress) { + funcs.AddFunc(ingress) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldIng, ok1 := oldObj.(*networkingv1.Ingress) + newIng, ok2 := newObj.(*networkingv1.Ingress) + + if ok1 && ok2 && isControllerIngress(options, newIng) { + funcs.UpdateFunc(oldIng, newIng) + } + }, + DeleteFunc: func(obj interface{}) { + ingress, ok := obj.(*networkingv1.Ingress) + + if ok && isControllerIngress(options, ingress) { + funcs.DeleteFunc(ingress) + } + }, + }) + + return informer +} + +// isControllerIngress check if the ingress object can be controlled by us +func isControllerIngress(options IngressParams, ingress *networkingv1.Ingress) bool { + ingressClass := ingress.Annotations["kubernetes.io/ingress.class"] + if ingressClass == "" && ingress.Spec.IngressClassName != nil { + ingressClass = *ingress.Spec.IngressClassName + } + + if !options.ClassNameRequired && ingressClass == "" { + return true + } + + return ingressClass == options.ClassName +} + +func UpdateIngressStatus(kubeClient *kubernetes.Clientset, ing *networkingv1.Ingress, status []networkingv1.IngressLoadBalancerIngress) (*networkingv1.Ingress, error) { + ingClient := kubeClient.NetworkingV1().Ingresses(ing.Namespace) + + currIng, err := ingClient.Get(context.TODO(), ing.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("unexpected error searching Ingress %v/%v: %w", ing.Namespace, ing.Name, err) + } + + currIng.Status.LoadBalancer.Ingress = status + + return ingClient.UpdateStatus(context.TODO(), currIng, metav1.UpdateOptions{}) +} diff --git a/serveis/altres/caddy/ingress/internal/k8s/pod.go b/serveis/altres/caddy/ingress/internal/k8s/pod.go new file mode 100644 index 0000000..3246d61 --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/k8s/pod.go @@ -0,0 +1,99 @@ +package k8s + +import ( + "context" + "fmt" + "os" + + "github.com/caddyserver/ingress/pkg/store" + + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" +) + +// GetAddresses gets the ip address or name of the node in the cluster that the +// ingress controller is running on. +func GetAddresses(p *store.PodInfo, kubeClient *kubernetes.Clientset) ([]string, error) { + var addrs []string + + // Get services that may select this pod + svcs, err := kubeClient.CoreV1().Services(p.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + for _, svc := range svcs.Items { + if isSubset(svc.Spec.Selector, p.Labels) { + addr := GetAddressFromService(&svc) + if addr != "" { + addrs = append(addrs, addr) + } + } + } + + return addrs, nil +} + +// Copied from https://github.com/kubernetes/kubernetes/pull/95179 +func isSubset(subSet, superSet labels.Set) bool { + if len(superSet) == 0 { + return true + } + + for k, v := range subSet { + value, ok := superSet[k] + if !ok { + return false + } + if value != v { + return false + } + } + return true +} + +// GetAddressFromService returns the IP address or the name of a node in the cluster +func GetAddressFromService(service *apiv1.Service) string { + switch service.Spec.Type { + case apiv1.ServiceTypeNodePort: + case apiv1.ServiceTypeClusterIP: + if service.Spec.ClusterIP != apiv1.ClusterIPNone { + return service.Spec.ClusterIP + } + case apiv1.ServiceTypeExternalName: + return service.Spec.ExternalName + case apiv1.ServiceTypeLoadBalancer: + if len(service.Status.LoadBalancer.Ingress) > 0 { + ingress := service.Status.LoadBalancer.Ingress[0] + if ingress.Hostname != "" { + return ingress.Hostname + } + return ingress.IP + } + } + return "" +} + +// GetPodDetails returns runtime information about the pod: +// name, namespace and IP of the node where it is running +func GetPodDetails(kubeClient *kubernetes.Clientset) (*store.PodInfo, error) { + podName := os.Getenv("POD_NAME") + podNs := os.Getenv("POD_NAMESPACE") + + if podName == "" || podNs == "" { + return nil, fmt.Errorf("unable to get POD information (missing POD_NAME or POD_NAMESPACE environment variable") + } + + pod, _ := kubeClient.CoreV1().Pods(podNs).Get(context.TODO(), podName, metav1.GetOptions{}) + if pod == nil { + return nil, fmt.Errorf("unable to get POD information") + } + + return &store.PodInfo{ + Name: podName, + Namespace: podNs, + Labels: pod.GetLabels(), + }, nil +} diff --git a/serveis/altres/caddy/ingress/internal/k8s/tls_secret.go b/serveis/altres/caddy/ingress/internal/k8s/tls_secret.go new file mode 100644 index 0000000..b1323ce --- /dev/null +++ b/serveis/altres/caddy/ingress/internal/k8s/tls_secret.go @@ -0,0 +1,76 @@ +package k8s + +import ( + v12 "k8s.io/api/core/v1" + v1 "k8s.io/api/networking/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +type TLSSecretHandlers struct { + AddFunc func(obj *v12.Secret) + UpdateFunc func(oldObj, newObj *v12.Secret) + DeleteFunc func(obj *v12.Secret) +} + +type TLSSecretParams struct { + InformerFactory informers.SharedInformerFactory +} + +func WatchTLSSecrets(options TLSSecretParams, funcs TLSSecretHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Core().V1().Secrets().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + secret, ok := obj.(*v12.Secret) + + if ok && secret.Type == v12.SecretTypeTLS { + funcs.AddFunc(secret) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldSecret, ok1 := oldObj.(*v12.Secret) + newSecret, ok2 := newObj.(*v12.Secret) + + if ok1 && ok2 && newSecret.Type == v12.SecretTypeTLS { + funcs.UpdateFunc(oldSecret, newSecret) + } + }, + DeleteFunc: func(obj interface{}) { + secret, ok := obj.(*v12.Secret) + + if ok && secret.Type == v12.SecretTypeTLS { + funcs.DeleteFunc(secret) + } + }, + }) + + return informer +} + +func ListTLSSecrets(options TLSSecretParams, ings []*v1.Ingress) ([]*v12.Secret, error) { + lister := options.InformerFactory.Core().V1().Secrets().Lister() + + tlsSecrets := []*v12.Secret{} + for _, ing := range ings { + for _, tlsRule := range ing.Spec.TLS { + secret, err := lister.Secrets(ing.Namespace).Get(tlsRule.SecretName) + // TODO Handle errors + if err == nil { + tlsSecrets = append(tlsSecrets, secret) + } + } + } + return tlsSecrets, nil +} + +func IsManagedTLSSecret(secret *v12.Secret, ings []*v1.Ingress) bool { + for _, ing := range ings { + for _, tlsRule := range ing.Spec.TLS { + if tlsRule.SecretName == secret.Name && ing.Namespace == secret.Namespace { + return true + } + } + } + return false +} diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-configmap.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-configmap.yaml new file mode 100644 index 0000000..8bf139d --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-global-options + namespace: caddy-system +data: + acmeCA: internal + # email: test@example.com + # debug: "false" diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment1.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment1.yaml new file mode 100644 index 0000000..0be7ff0 --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment1.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example1 + labels: + app: example1 +spec: + replicas: 1 + selector: + matchLabels: + app: example1 + template: + metadata: + labels: + app: example1 + spec: + containers: + - name: httpecho + image: hashicorp/http-echo + args: + - "-listen=:8080" + - '-text="hello world 1"' + ports: + - containerPort: 8080 diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment2.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment2.yaml new file mode 100644 index 0000000..d7438cc --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-deployment2.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example2 + labels: + app: example2 +spec: + replicas: 1 + selector: + matchLabels: + app: example2 + template: + metadata: + labels: + app: example2 + spec: + containers: + - name: httpecho + image: hashicorp/http-echo + args: + - "-listen=:8080" + - '-text="hello world 2"' + ports: + - containerPort: 8080 \ No newline at end of file diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-ingress.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-ingress.yaml new file mode 100644 index 0000000..66dec2c --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-ingress.yaml @@ -0,0 +1,46 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example + annotations: + kubernetes.io/ingress.class: caddy +spec: + rules: + - host: example1.kubernetes.localhost + http: + paths: + - path: /hello1 + pathType: Prefix + backend: + service: + name: example1 + port: + number: 8080 + - path: /hello2 + pathType: Prefix + backend: + service: + name: example2 + port: + number: 8080 + - host: example2.kubernetes.localhost + http: + paths: + - path: /hello1 + pathType: Prefix + backend: + service: + name: example1 + port: + number: 8080 + - path: /hello2 + pathType: Prefix + backend: + service: + name: example2 + port: + number: 8080 +# tls: +# - secretName: ssl-example2.kubernetes.localhost +# hosts: +# - example2.caddy.dev diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-service1.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-service1.yaml new file mode 100644 index 0000000..4d59b75 --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-service1.yaml @@ -0,0 +1,13 @@ +kind: Service +apiVersion: v1 +metadata: + name: example1 +spec: + type: ClusterIP + selector: + app: example1 + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/serveis/altres/caddy/ingress/kubernetes/sample/example-service2.yaml b/serveis/altres/caddy/ingress/kubernetes/sample/example-service2.yaml new file mode 100644 index 0000000..3247795 --- /dev/null +++ b/serveis/altres/caddy/ingress/kubernetes/sample/example-service2.yaml @@ -0,0 +1,13 @@ +kind: Service +apiVersion: v1 +metadata: + name: example2 +spec: + type: ClusterIP + selector: + app: example2 + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/serveis/altres/caddy/ingress/pkg/converter/config.go b/serveis/altres/caddy/ingress/pkg/converter/config.go new file mode 100644 index 0000000..ffb7805 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/converter/config.go @@ -0,0 +1,65 @@ +package converter + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +// StorageValues represents the config for certmagic storage providers. +type StorageValues struct { + Namespace string `json:"namespace"` + LeaseId string `json:"leaseId"` +} + +// Storage represents the certmagic storage configuration. +type Storage struct { + System string `json:"module"` + StorageValues +} + +// Config represents a caddy2 config file. +type Config struct { + Admin caddy.AdminConfig `json:"admin,omitempty"` + Storage Storage `json:"storage"` + Apps map[string]interface{} `json:"apps"` + Logging caddy.Logging `json:"logging"` +} + +func (c Config) GetHTTPServer() *caddyhttp.Server { + return c.Apps["http"].(*caddyhttp.App).Servers[HttpServer] +} + +func (c Config) GetMetricsServer() *caddyhttp.Server { + return c.Apps["http"].(*caddyhttp.App).Servers[MetricsServer] +} + +func (c Config) GetTLSApp() *caddytls.TLS { + return c.Apps["tls"].(*caddytls.TLS) +} + +func NewConfig() *Config { + return &Config{ + Logging: caddy.Logging{}, + Apps: map[string]interface{}{ + "tls": &caddytls.TLS{CertificatesRaw: caddy.ModuleMap{}}, + "http": &caddyhttp.App{ + Servers: map[string]*caddyhttp.Server{ + HttpServer: { + AutoHTTPS: &caddyhttp.AutoHTTPSConfig{}, + // Listen to both :80 and :443 ports in order + // to use the same listener wrappers (PROXY protocol use it) + Listen: []string{":80", ":443"}, + TLSConnPolicies: caddytls.ConnectionPolicies{ + &caddytls.ConnectionPolicy{}, + }, + }, + MetricsServer: { + Listen: []string{":9765"}, + AutoHTTPS: &caddyhttp.AutoHTTPSConfig{Disabled: true}, + }, + }, + }, + }, + } +} diff --git a/serveis/altres/caddy/ingress/pkg/converter/converter.go b/serveis/altres/caddy/ingress/pkg/converter/converter.go new file mode 100644 index 0000000..a020757 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/converter/converter.go @@ -0,0 +1,113 @@ +package converter + +import ( + "fmt" + "sort" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/store" + v1 "k8s.io/api/networking/v1" +) + +const ( + HttpServer = "ingress_server" + MetricsServer = "metrics_server" +) + +// GlobalMiddleware is called with a default caddy config +// already configured with: +// - Secret storage store +// - A TLS App (https://caddyserver.com/docs/json/apps/tls/) +// - A HTTP App with an HTTP server listening to 80 443 ports (https://caddyserver.com/docs/json/apps/http/) +type GlobalMiddleware interface { + GlobalHandler(config *Config, store *store.Store) error +} + +type IngressMiddlewareInput struct { + Config *Config + Store *store.Store + Ingress *v1.Ingress + Rule v1.IngressRule + Path v1.HTTPIngressPath + Route *caddyhttp.Route +} + +// IngressMiddleware is called for each Caddy route that is generated for a specific +// ingress. It allows anyone to manipulate caddy routes before sending it to caddy. +type IngressMiddleware interface { + IngressHandler(input IngressMiddlewareInput) (*caddyhttp.Route, error) +} + +type Plugin interface { + IngressPlugin() PluginInfo +} + +type PluginInfo struct { + Name string + Priority int + New func() Plugin +} + +func RegisterPlugin(m Plugin) { + plugin := m.IngressPlugin() + + if _, ok := plugins[plugin.Name]; ok { + panic(fmt.Sprintf("plugin already registered: %s", plugin.Name)) + } + plugins[plugin.Name] = plugin + pluginInstances[plugin.Name] = plugin.New() +} + +func getOrderIndex(order []string, plugin string) int { + for idx, o := range order { + if plugin == o { + return idx + } + } + return -1 +} + +func sortPlugins(plugins []PluginInfo, order []string) []PluginInfo { + sort.SliceStable(plugins, func(i, j int) bool { + iPlugin, jPlugin := plugins[i], plugins[j] + + iSortedIdx := getOrderIndex(order, iPlugin.Name) + jSortedIdx := getOrderIndex(order, jPlugin.Name) + + if iSortedIdx != jSortedIdx { + return iSortedIdx > jSortedIdx + } + + if iPlugin.Priority != jPlugin.Priority { + return iPlugin.Priority > jPlugin.Priority + } + return iPlugin.Name < jPlugin.Name + + }) + return plugins +} + +// Plugins return a sorted array of plugin instances. +// Sort is made following these rules: +// - Plugins specified in the order slice will always go first (in the order specified in the slice) +// - A Plugin with higher priority will go before a plugin with lower priority +// - If 2 plugins have the same priority (and not in order slice), they will be sorted by plugin name +func Plugins(order []string) []Plugin { + sortedPlugins := make([]PluginInfo, 0, len(plugins)) + for _, p := range plugins { + sortedPlugins = append(sortedPlugins, p) + } + + sortPlugins(sortedPlugins, order) + + pluginArr := make([]Plugin, 0, len(plugins)) + for _, p := range sortedPlugins { + pluginArr = append(pluginArr, pluginInstances[p.Name]) + } + return pluginArr +} + +var ( + plugins = make(map[string]PluginInfo) + pluginInstances = make(map[string]Plugin) +) diff --git a/serveis/altres/caddy/ingress/pkg/converter/converter_test.go b/serveis/altres/caddy/ingress/pkg/converter/converter_test.go new file mode 100644 index 0000000..d215562 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/converter/converter_test.go @@ -0,0 +1,53 @@ +package converter + +import "testing" + +func TestSortPlugins(t *testing.T) { + tests := []struct { + name string + order []string + plugins []PluginInfo + expect []string + }{ + { + name: "default to alpha sort", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "c"}, {Name: "a"}}, + expect: []string{"a", "b", "c"}, + }, + { + name: "use priority when specified", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "a", Priority: 20}, {Name: "c", Priority: 10}}, + expect: []string{"a", "c", "b"}, + }, + { + name: "fallback to alpha when no priority", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "a"}, {Name: "c", Priority: 20}}, + expect: []string{"c", "a", "b"}, + }, + { + name: "specify order", + order: []string{"c"}, + plugins: []PluginInfo{{Name: "b"}, {Name: "a"}, {Name: "c"}}, + expect: []string{"c", "a", "b"}, + }, + { + name: "order overrides other settings", + order: []string{"c"}, + plugins: []PluginInfo{{Name: "b", Priority: 10}, {Name: "a"}, {Name: "c"}}, + expect: []string{"c", "b", "a"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sortPlugins(test.plugins, test.order) + for i, plugin := range test.plugins { + if test.expect[i] != plugin.Name { + t.Errorf("expected order to match %v: got %v, expected %v", test.expect, plugin.Name, test.expect[i]) + } + } + }) + } +} diff --git a/serveis/altres/caddy/ingress/pkg/proxy/proxy.go b/serveis/altres/caddy/ingress/pkg/proxy/proxy.go new file mode 100644 index 0000000..9b64149 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/proxy/proxy.go @@ -0,0 +1,46 @@ +package proxy + +import ( + "net" + + "github.com/caddyserver/caddy/v2" + "github.com/pires/go-proxyproto" +) + +var ( + _ = caddy.Provisioner(&Wrapper{}) + _ = caddy.Module(&Wrapper{}) + _ = caddy.ListenerWrapper(&Wrapper{}) +) + +func init() { + caddy.RegisterModule(Wrapper{}) +} + +// Wrapper provides PROXY protocol support to Caddy by implementing the caddy.ListenerWrapper interface. +// It must be loaded before the `tls` listener. +// +// Deprecated: This caddy module should be replaced by the included proxy_protocol listener in Caddy. +type Wrapper struct { + policy proxyproto.PolicyFunc +} + +func (Wrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.proxy_protocol", + New: func() caddy.Module { return new(Wrapper) }, + } +} + +func (pp *Wrapper) Provision(ctx caddy.Context) error { + pp.policy = func(upstream net.Addr) (proxyproto.Policy, error) { + return proxyproto.REQUIRE, nil + } + return nil +} + +func (pp *Wrapper) WrapListener(l net.Listener) net.Listener { + pL := &proxyproto.Listener{Listener: l, Policy: pp.policy} + + return pL +} diff --git a/serveis/altres/caddy/ingress/pkg/storage/storage.go b/serveis/altres/caddy/ingress/pkg/storage/storage.go new file mode 100644 index 0000000..f13f833 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/storage/storage.go @@ -0,0 +1,290 @@ +package storage + +import ( + "context" + "fmt" + "io/fs" + "regexp" + "strings" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/certmagic" + "github.com/google/uuid" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/leaderelection/resourcelock" +) + +const ( + leaseDuration = 5 * time.Second + leaseRenewInterval = 2 * time.Second + leasePollInterval = 5 * time.Second + leasePrefix = "caddy-lock-" + + keyPrefix = "caddy.ingress--" +) + +// matchLabels are attached to each resource so that they can be found in the future. +var matchLabels = map[string]string{ + "manager": "caddy", +} + +// specialChars is a regex that matches all special characters except '-'. +var specialChars = regexp.MustCompile("[^\\da-zA-Z-]+") + +// cleanKey strips all special characters that are not supported by kubernetes names and converts them to a '.'. +// sequences like '.*.' are also converted to a single '.'. +func cleanKey(key string, prefix string) string { + return prefix + specialChars.ReplaceAllString(key, ".") +} + +// SecretStorage facilitates storing certificates retrieved by certmagic in kubernetes secrets. +type SecretStorage struct { + Namespace string + LeaseId string + + kubeClient *kubernetes.Clientset + logger *zap.Logger +} + +func init() { + caddy.RegisterModule(SecretStorage{}) +} + +func (SecretStorage) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.storage.secret_store", + New: func() caddy.Module { return new(SecretStorage) }, + } +} + +// Provisions the SecretStorage instance. +func (s *SecretStorage) Provision(ctx caddy.Context) error { + config, _ := clientcmd.BuildConfigFromFlags("", "") + // creates the clientset + clientset, _ := kubernetes.NewForConfig(config) + + s.logger = ctx.Logger(s) + s.kubeClient = clientset + if s.LeaseId == "" { + s.LeaseId = uuid.New().String() + } + return nil +} + +// CertMagicStorage returns a certmagic storage type to be used by caddy. +func (s *SecretStorage) CertMagicStorage() (certmagic.Storage, error) { + return s, nil +} + +// Exists returns true if key exists in fs. +func (s *SecretStorage) Exists(ctx context.Context, key string) bool { + s.logger.Debug("finding secret", zap.String("name", key)) + secrets, err := s.kubeClient.CoreV1().Secrets(s.Namespace).List(context.TODO(), metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%v", cleanKey(key, keyPrefix)), + }) + + if err != nil { + return false + } + + var found bool + for _, i := range secrets.Items { + if i.ObjectMeta.Name == cleanKey(key, keyPrefix) { + found = true + break + } + } + + return found +} + +// Store saves value at key. More than certs and keys are stored by certmagic in secrets. +func (s *SecretStorage) Store(ctx context.Context, key string, value []byte) error { + se := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cleanKey(key, keyPrefix), + Labels: matchLabels, + }, + Data: map[string][]byte{ + "value": value, + }, + } + + var err error + if s.Exists(ctx, key) { + s.logger.Debug("creating secret", zap.String("name", key)) + _, err = s.kubeClient.CoreV1().Secrets(s.Namespace).Update(context.TODO(), &se, metav1.UpdateOptions{}) + } else { + s.logger.Debug("updating secret", zap.String("name", key)) + _, err = s.kubeClient.CoreV1().Secrets(s.Namespace).Create(context.TODO(), &se, metav1.CreateOptions{}) + } + + if err != nil { + return err + } + + return nil +} + +// Load retrieves the value at the given key. +func (s *SecretStorage) Load(ctx context.Context, key string) ([]byte, error) { + secret, err := s.kubeClient.CoreV1().Secrets(s.Namespace).Get(context.TODO(), cleanKey(key, keyPrefix), metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil, fs.ErrNotExist + } + return nil, err + } + + s.logger.Debug("loading secret", zap.String("name", key)) + return secret.Data["value"], nil +} + +// Delete deletes the value at the given key. +func (s *SecretStorage) Delete(ctx context.Context, key string) error { + err := s.kubeClient.CoreV1().Secrets(s.Namespace).Delete(context.TODO(), cleanKey(key, keyPrefix), metav1.DeleteOptions{}) + if err != nil { + return err + } + + s.logger.Debug("deleting secret", zap.String("name", key)) + return nil +} + +// List returns all keys that match prefix. +func (s *SecretStorage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) { + var keys []string + + s.logger.Debug("listing secrets", zap.String("name", prefix)) + secrets, err := s.kubeClient.CoreV1().Secrets(s.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(matchLabels).String(), + }) + if err != nil { + return keys, err + } + + // TODO :- do we need to handle the recursive flag? + for _, secret := range secrets.Items { + key := secret.ObjectMeta.Name + if strings.HasPrefix(key, cleanKey(prefix, keyPrefix)) { + keys = append(keys, strings.TrimPrefix(key, keyPrefix)) + } + } + + return keys, err +} + +// Stat returns information about key. +func (s *SecretStorage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error) { + secret, err := s.kubeClient.CoreV1().Secrets(s.Namespace).Get(context.TODO(), cleanKey(key, keyPrefix), metav1.GetOptions{}) + if err != nil { + return certmagic.KeyInfo{}, err + } + + s.logger.Debug("stats secret", zap.String("name", key)) + + return certmagic.KeyInfo{ + Key: key, + Modified: secret.GetCreationTimestamp().UTC(), + Size: int64(len(secret.Data["value"])), + IsTerminal: false, + }, nil +} + +func (s *SecretStorage) Lock(ctx context.Context, key string) error { + for { + _, err := s.tryAcquireOrRenew(ctx, cleanKey(key, leasePrefix), false) + if err == nil { + go s.keepLockUpdated(ctx, cleanKey(key, leasePrefix)) + return nil + } + + select { + case <-time.After(leasePollInterval): + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (s *SecretStorage) keepLockUpdated(ctx context.Context, key string) { + for { + time.Sleep(leaseRenewInterval) + done, err := s.tryAcquireOrRenew(ctx, key, true) + if err != nil { + return + } + if done { + return + } + } +} + +func (s *SecretStorage) tryAcquireOrRenew(ctx context.Context, key string, shouldExist bool) (bool, error) { + now := metav1.Now() + lock := resourcelock.LeaseLock{ + LeaseMeta: metav1.ObjectMeta{ + Name: key, + Namespace: s.Namespace, + }, + Client: s.kubeClient.CoordinationV1(), + LockConfig: resourcelock.ResourceLockConfig{ + Identity: s.LeaseId, + }, + } + + ler := resourcelock.LeaderElectionRecord{ + HolderIdentity: lock.Identity(), + LeaseDurationSeconds: 5, + AcquireTime: now, + RenewTime: now, + } + + currLer, _, err := lock.Get(ctx) + + // 1. obtain or create the ElectionRecord + if err != nil { + if !errors.IsNotFound(err) { + return true, err + } + if shouldExist { + return true, nil // Lock has been released + } + if err = lock.Create(ctx, ler); err != nil { + return true, err + } + return false, nil + } + + // 2. Record obtained, check the Identity & Time + if currLer.HolderIdentity != "" && + currLer.RenewTime.Add(leaseDuration).After(now.Time) && + currLer.HolderIdentity != lock.Identity() { + return true, fmt.Errorf("lock is held by %v and has not yet expired", currLer.HolderIdentity) + } + + // 3. We're going to try to update the existing one + if currLer.HolderIdentity == lock.Identity() { + ler.AcquireTime = currLer.AcquireTime + ler.LeaderTransitions = currLer.LeaderTransitions + } else { + ler.LeaderTransitions = currLer.LeaderTransitions + 1 + } + + if err = lock.Update(ctx, ler); err != nil { + return true, fmt.Errorf("failed to update lock: %v", err) + } + return false, nil +} + +func (s *SecretStorage) Unlock(ctx context.Context, key string) error { + err := s.kubeClient.CoordinationV1().Leases(s.Namespace).Delete(context.TODO(), cleanKey(key, leasePrefix), metav1.DeleteOptions{}) + return err +} diff --git a/serveis/altres/caddy/ingress/pkg/store/configmap_parser.go b/serveis/altres/caddy/ingress/pkg/store/configmap_parser.go new file mode 100644 index 0000000..7d4b46c --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/store/configmap_parser.go @@ -0,0 +1,63 @@ +package store + +import ( + "fmt" + "reflect" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/mitchellh/mapstructure" + apiv1 "k8s.io/api/core/v1" +) + +// ConfigMapOptions represents global options set through a configmap +type ConfigMapOptions struct { + Debug bool `json:"debug,omitempty"` + AcmeCA string `json:"acmeCA,omitempty"` + AcmeEABKeyId string `json:"acmeEABKeyId,omitempty"` + AcmeEABMacKey string `json:"acmeEABMacKey,omitempty"` + Email string `json:"email,omitempty"` + ExperimentalSmartSort bool `json:"experimentalSmartSort,omitempty"` + ProxyProtocol bool `json:"proxyProtocol,omitempty"` + Metrics bool `json:"metrics,omitempty"` + OnDemandTLS bool `json:"onDemandTLS,omitempty"` + OnDemandAsk string `json:"onDemandAsk,omitempty"` + OCSPCheckInterval caddy.Duration `json:"ocspCheckInterval,omitempty"` +} + +func stringToCaddyDurationHookFunc() mapstructure.DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(caddy.Duration(time.Second)) { + return data, nil + } + return caddy.ParseDuration(data.(string)) + } +} + +func ParseConfigMap(cm *apiv1.ConfigMap) (*ConfigMapOptions, error) { + // parse configmap + cfgMap := ConfigMapOptions{} + config := &mapstructure.DecoderConfig{ + Metadata: nil, + WeaklyTypedInput: true, + Result: &cfgMap, + TagName: "json", + DecodeHook: mapstructure.ComposeDecodeHookFunc( + stringToCaddyDurationHookFunc(), + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, fmt.Errorf("unexpected error creating decoder: %w", err) + } + err = decoder.Decode(cm.Data) + if err != nil { + return nil, fmt.Errorf("unexpected error parsing configmap: %w", err) + } + + return &cfgMap, nil +} diff --git a/serveis/altres/caddy/ingress/pkg/store/options.go b/serveis/altres/caddy/ingress/pkg/store/options.go new file mode 100644 index 0000000..d389f3d --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/store/options.go @@ -0,0 +1,12 @@ +package store + +// Options represents ingress controller config received through cli arguments. +type Options struct { + WatchNamespace string + ConfigMapName string + ClassName string + ClassNameRequired bool + Verbose bool + LeaseId string + PluginsOrder []string +} diff --git a/serveis/altres/caddy/ingress/pkg/store/pod.go b/serveis/altres/caddy/ingress/pkg/store/pod.go new file mode 100644 index 0000000..b6756c9 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/store/pod.go @@ -0,0 +1,10 @@ +package store + +// PodInfo contains runtime information about the pod running the Ingress controller +type PodInfo struct { + Name string + Namespace string + // Labels selectors of the running pod + // This is used to search for other Ingress controller pods + Labels map[string]string +} diff --git a/serveis/altres/caddy/ingress/pkg/store/store.go b/serveis/altres/caddy/ingress/pkg/store/store.go new file mode 100644 index 0000000..ff3e738 --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/store/store.go @@ -0,0 +1,72 @@ +package store + +import ( + v1 "k8s.io/api/networking/v1" +) + +// Store contains resources used to generate Caddy config +type Store struct { + Options *Options + ConfigMap *ConfigMapOptions + Ingresses []*v1.Ingress + CurrentPod *PodInfo +} + +// NewStore returns a new store that keeps track of K8S resources needed by the controller. +func NewStore(opts Options, podInfo *PodInfo) *Store { + s := &Store{ + Options: &opts, + Ingresses: []*v1.Ingress{}, + ConfigMap: &ConfigMapOptions{}, + CurrentPod: podInfo, + } + return s +} + +// AddIngress adds an ingress to the store. It updates the element at the given index if it is unique. +func (s *Store) AddIngress(ing *v1.Ingress) { + isUniq := true + + for i := range s.Ingresses { + in := s.Ingresses[i] + if in.GetUID() == ing.GetUID() { + isUniq = false + s.Ingresses[i] = ing + } + } + + if isUniq { + s.Ingresses = append(s.Ingresses, ing) + } +} + +// PluckIngress removes the ingress passed in as an argument from the stores list of ingresses. +func (s *Store) PluckIngress(ing *v1.Ingress) { + id := ing.GetUID() + + var index int + var hasMatch bool + for i := range s.Ingresses { + if s.Ingresses[i].GetUID() == id { + index = i + hasMatch = true + break + } + } + + // since order is not important we can swap the element to delete with the one at the end of the slice + // and then set ingresses to the n-1 first elements + if hasMatch { + s.Ingresses[len(s.Ingresses)-1], s.Ingresses[index] = s.Ingresses[index], s.Ingresses[len(s.Ingresses)-1] + s.Ingresses = s.Ingresses[:len(s.Ingresses)-1] + } +} + +func (s *Store) HasManagedTLS() bool { + for _, ing := range s.Ingresses { + if len(ing.Spec.TLS) > 0 { + return true + } + } + return false +} diff --git a/serveis/altres/caddy/ingress/pkg/store/store_test.go b/serveis/altres/caddy/ingress/pkg/store/store_test.go new file mode 100644 index 0000000..36eb05b --- /dev/null +++ b/serveis/altres/caddy/ingress/pkg/store/store_test.go @@ -0,0 +1,200 @@ +package store + +import ( + "testing" + + v1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + typev1 "k8s.io/apimachinery/pkg/types" +) + +func TestStoreIngresses(t *testing.T) { + tests := []struct { + name string + addIngresses []string + removeIngresses []string + expectCount int + }{ + { + name: "No ingress added nor removed", + addIngresses: []string{}, + removeIngresses: []string{}, + expectCount: 0, + }, + { + name: "One ingress added, no ingress removed", + addIngresses: []string{"first"}, + removeIngresses: []string{}, + expectCount: 1, + }, + { + name: "Two ingresses added, no ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{}, + expectCount: 2, + }, + { + name: "Two ingresses added, first ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"first"}, + expectCount: 1, + }, + { + name: "Two ingresses added, second ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second"}, + expectCount: 1, + }, + { + name: "Two ingresses added, both ingresses removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "first"}, + expectCount: 0, + }, + { + name: "Two ingresses added, one existing and one non existing ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "third"}, + expectCount: 1, + }, + { + name: "Remove non existing ingresses", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"third", "forth"}, + expectCount: 2, + }, + { + name: "Remove non existing ingresses from an empty list", + addIngresses: []string{}, + removeIngresses: []string{"first", "second"}, + expectCount: 0, + }, + { + name: "Add the same ingress multiple time", + addIngresses: []string{"first", "first", "first"}, + removeIngresses: []string{}, + expectCount: 1, + }, + { + name: "Remove the same ingress multiple time", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "second", "second"}, + expectCount: 1, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := NewStore(Options{}, &PodInfo{}) + for _, uid := range test.addIngresses { + i := createIngress(uid) + s.AddIngress(&i) + } + + for _, uid := range test.removeIngresses { + i := createIngress(uid) + s.PluckIngress(&i) + } + + if test.expectCount != len(s.Ingresses) { + t.Errorf("Number of ingresses do not match expectation in %s: got %v, expected %v", test.name, len(s.Ingresses), test.expectCount) + } + }) + } +} + +func TestStoreReturnIfHasManagedTLS(t *testing.T) { + tests := []struct { + name string + ingresses []v1.Ingress + expect bool + }{ + { + name: "No ingress", + ingresses: []v1.Ingress{}, + expect: false, + }, + { + name: "One ingress without certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + }, + expect: false, + }, + { + name: "One ingress with certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1"}, "mysecret1"), + }, + expect: true, + }, + { + name: "Multiple ingresses without certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + createIngress("second"), + createIngress("third"), + }, + expect: false, + }, + { + name: "Three ingresses mixed with and without certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + createIngress("second"), + createIngressTLS("third", []string{"host3"}, "mysecret3"), + }, + expect: true, + }, + { + name: "Ingress replaced without a certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + createIngress("first"), + }, + expect: false, + }, + { + name: "Ingress replaced with a certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + }, + expect: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := NewStore(Options{}, &PodInfo{}) + for _, i := range test.ingresses { + s.AddIngress(&i) + } + + if test.expect != s.HasManagedTLS() { + t.Errorf("managed TLS do not match expectation in %s: got %v, expected %v", test.name, s.HasManagedTLS(), test.expect) + } + }) + } +} + +func createIngressTLS(uid string, hosts []string, secret string) v1.Ingress { + i := createIngress(uid) + + i.Spec = v1.IngressSpec{ + TLS: []v1.IngressTLS{ + { + Hosts: hosts, + SecretName: secret, + }, + }, + } + + return i +} + +func createIngress(uid string) v1.Ingress { + return v1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + UID: typev1.UID(uid), + }, + } +} diff --git a/serveis/altres/caddy/ingress/skaffold.yaml b/serveis/altres/caddy/ingress/skaffold.yaml new file mode 100644 index 0000000..8363935 --- /dev/null +++ b/serveis/altres/caddy/ingress/skaffold.yaml @@ -0,0 +1,37 @@ +apiVersion: skaffold/v4beta7 +kind: Config +metadata: + name: caddy-ingress-controller +build: + artifacts: + - image: caddy-ingress + context: . + docker: + dockerfile: Dockerfile.dev +deploy: + helm: + releases: + - name: caddy-ingress-development + namespace: caddy-system + chartPath: charts/caddy-ingress-controller + createNamespace: true + setValueTemplates: + image: + repository: "{{ .IMAGE_REPO_NO_DOMAIN_caddy_ingress }}" + tag: "{{.IMAGE_TAG_caddy_ingress}}@{{.IMAGE_DIGEST_caddy_ingress}}" +manifests: + rawYaml: + - ./kubernetes/sample/*.yaml +portForward: + - resourceType: service + resourceName: caddy-ingress-development-caddy-ingress-controller + namespace: caddy-system + address: 0.0.0.0 + port: 80 + localPort: 8080 + - resourceType: service + resourceName: caddy-ingress-development-caddy-ingress-controller + namespace: caddy-system + address: 0.0.0.0 + port: 443 + localPort: 8443 \ No newline at end of file diff --git a/serveis/altres/crypad/cryptpad-k8s/cryptpad.yml b/serveis/altres/crypad/cryptpad-k8s/cryptpad.yml new file mode 100644 index 0000000..5385151 --- /dev/null +++ b/serveis/altres/crypad/cryptpad-k8s/cryptpad.yml @@ -0,0 +1,174 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: cryptpad +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cryptpad + namespace: cryptpad +spec: + selector: + matchLabels: + app: cryptpad + template: + metadata: + labels: + app: cryptpad + spec: + volumes: + - name: config + configMap: + name: config + - name: cryptpad + persistentVolumeClaim: + claimName: cryptpad + containers: + - name: cryptpad + image: quay.io/ffddorf/cryptpad:4.8.0 + resources: + limits: + memory: "512Mi" + cpu: "500m" + ports: + - containerPort: 3000 + volumeMounts: + - name: config + mountPath: /cryptpad/config + - name: cryptpad + mountPath: /cryptpad/data +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config + namespace: cryptpad +data: + config.js: | + module.exports = { + adminKeys: [ + "[nomaster@pad.freifunk-duesseldorf.de/WUdnwywXbKnT6QsT6OuZXQqJOQCZwiZDz2y3492oGpw=]", + ], + adminEmail: 'kontakt@freifunk-duesseldorf.de', + allowSubscriptions: false, + archivePath: './data/archive', + blobPath: './data/blob', + blobStagingPath: './data/blobstage', + blockPath: './data/block', + filePath: './data/store', + httpAddress: '::', + httpPort: 3000, + httpSafeOrigin: 'https://cryptpad.freifunk-duesseldorf.de/', + httpUnsafeOrigin: 'https://pad.freifunk-duesseldorf.de/', + logFeedback: false, + logLevel: 'info', + logToStdout: true, + noSubscriptionButton: true, + pinPath: './data/pins', + removeDonateButton: true, + supportMailboxPublicKey: 'bLZQjf8j/kQnV3LLT64ROORvJjzJzz7FQRLWh1DV6B4=', + taskPath: './data/tasks', + verbose: false, + }; +--- +apiVersion: v1 +kind: Service +metadata: + name: cryptpad + namespace: cryptpad +spec: + selector: + app: cryptpad + ports: + - port: 3000 + targetPort: 3000 +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: security + namespace: cryptpad +spec: + headers: + stsSeconds: 63072000 + customResponseHeaders: + cross-origin-resource-policy: cross-origin + cross-origin-embedder-policy: require-corp +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: cryptpad + namespace: cryptpad + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + kubernetes.io/ingress.class: traefik + kubernetes.io/tls-acme: "true" + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + traefik.ingress.kubernetes.io/router.middlewares: cryptpad-security@kubernetescrd +spec: + tls: + - hosts: + - cryptpad.freifunk-duesseldorf.de + - pad.freifunk-duesseldorf.de + secretName: cryptpad-tls-prod + rules: + - host: cryptpad.freifunk-duesseldorf.de + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: cryptpad + port: + number: 3000 + - host: pad.freifunk-duesseldorf.de + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: cryptpad + port: + number: 3000 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cryptpad + namespace: cryptpad +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 3Gi + volumeName: cryptpad +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: cryptpad +spec: + capacity: + storage: 4Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-path + local: + path: /data/cryptpad/cryptpad + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - k3s1 diff --git a/serveis/altres/crypad/cryptpad-k8s/pvc-cryptpad.yml b/serveis/altres/crypad/cryptpad-k8s/pvc-cryptpad.yml new file mode 100644 index 0000000..b8675f6 --- /dev/null +++ b/serveis/altres/crypad/cryptpad-k8s/pvc-cryptpad.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cryptpad + namespace: cryptpad +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: local-path diff --git a/serveis/altres/crypad/cryptpad-k8s/renovate.json b/serveis/altres/crypad/cryptpad-k8s/renovate.json new file mode 100644 index 0000000..ab917d5 --- /dev/null +++ b/serveis/altres/crypad/cryptpad-k8s/renovate.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "regexManagers": [ + { + "fileMatch": ["^version\\.txt$"], + "matchStrings": ["^(?\\d+\\.\\d+\\.\\d+)"], + "depNameTemplate": "xwiki-labs/cryptpad-docker", + "datasourceTemplate": "github-tags" + }, + { + "fileMatch": ["^cryptpad\\.yml$"], + "matchStrings": [ + "^\\s+image:\\s?quay\\.io/ffddorf/cryptpad:(?\\d+\\.\\d+\\.\\d+)$" + ], + "depNameTemplate": "xwiki-labs/cryptpad-docker", + "datasourceTemplate": "github-tags" + } + ], + "packageRules": [ + { + "matchPackageNames": ["xwiki-labs/cryptpad"], + "groupName": "cryptpad" + } + ] +} diff --git a/serveis/altres/crypad/cryptpad-k8s/version.txt b/serveis/altres/crypad/cryptpad-k8s/version.txt new file mode 100644 index 0000000..0062ac9 --- /dev/null +++ b/serveis/altres/crypad/cryptpad-k8s/version.txt @@ -0,0 +1 @@ +5.0.0 diff --git a/serveis/altres/etherpad/estructura b/serveis/altres/etherpad/estructura new file mode 100644 index 0000000..4227936 --- /dev/null +++ b/serveis/altres/etherpad/estructura @@ -0,0 +1,256 @@ +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ tree +. +├── etherpad-lite-k8s +│   ├── configmap.yaml +│   ├── deployment.yaml +│   ├── kustomization.yaml +│   └── service.yaml +├── etherpad-lite-k8s-kubedb-mysql +│   ├── configmap.yaml +│   ├── deployment.yaml +│   ├── kustomization.yaml +│   └── name-prefix-transformer-config.yaml +├── kubedb-mysql-etherpad-lite +│   ├── etherpad-mysql.yaml +│   ├── kustomization.yaml +│   ├── README.md +│   └── transformer-config-kubedb.yaml +├── kubedb-mysql-etherpad-lite-with-init-script +│   ├── etherpad-mysql-init-configmap.yaml +│   ├── etherpad-mysql-with-init-script.yaml +│   └── kustomization.yaml +└── test-etherpad-lite-mysql-with-namePrefix + └── kustomization.yaml + +6 directories, 16 files +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes" + } +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + replicas: 1 + selector: + matchLabels: + app: etherpad + template: + metadata: + labels: + app: etherpad + spec: + containers: + - name: etherpad + image: etherpad/etherpad:1.7.5 + ports: + - containerPort: 9001 + name: web + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad/settings.json" + subPath: "settings.json" + volumes: + - name: config + configMap: + name: etherpad +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- configmap.yaml +- deployment.yaml +- service.yaml +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: etherpad +spec: + selector: + app: etherpad + ports: + - name: web + port: 80 + targetPort: web +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s-kubedb-mysql/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes w/ MySQL", + "dbType": "${ETHERPAD_DB_TYPE:mysql}", + "dbSettings": { + "database": "${ETHERPAD_DB_DATABASE}", + "host": "${ETHERPAD_DB_HOST}", + "password": "${ETHERPAD_DB_PASSWORD}", + "user": "${ETHERPAD_DB_USER}" + } + } +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s-kubedb-mysql/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + template: + spec: + containers: + - name: etherpad + env: + - name: ETHERPAD_DB_TYPE + value: mysql + - name: ETHERPAD_DB_HOST + value: $(MYSQL_SERVICE) + - name: ETHERPAD_DB_DATABASE + value: etherpad_lite_db + - name: ETHERPAD_DB_USER + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: username + - name: ETHERPAD_DB_PASSWORD + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: password + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad-lite/settings.json" + subPath: "settings.json" +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s-kubedb-mysql/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +bases: +- ../kubedb-mysql-etherpad-lite-with-init-script +- ../etherpad-lite-k8s +patchesStrategicMerge: +- configmap.yaml +- deployment.yaml +images: +- name: etherpad/etherpad + # This is required until etherpad-lite 1.8 comes out to be able to use env vars in settings.json + newTag: latest +configurations: +- name-prefix-transformer-config.yaml +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml +namePrefix: +- apiVersion: apps/v1 + kind: Deployment + path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite/etherpad-mysql.yaml +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + version: "5.7.25" + storageType: Durable + terminationPolicy: WipeOut + storage: + storageClassName: "default" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- etherpad-mysql.yaml +vars: +- name: MYSQL_SERVICE + objref: + apiVersion: kubedb.com/v1alpha1 + kind: MySQL + name: etherpad-mysql + fieldref: + fieldpath: metadata.name +configurations: +- transformer-config-kubedb.yaml +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite/README.md +# kubedb-mysql-etherpad-lite + +This is *just* the kubedb MySQL resource for etherpad-lite. Compose it with something like ../etherpad-lite-k8s to get a full setup. +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- etherpad-mysql.yaml +vars: +- name: MYSQL_SERVICE + objref: + apiVersion: kubedb.com/v1alpha1 + kind: MySQL + name: etherpad-mysql + fieldref: + fieldpath: metadata.name +configurations: +- transformer-config-kubedb.yaml +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml +namePrefix: +- apiVersion: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource/configMap/name + +nameReference: +- version: v1 + kind: ConfigMap + fieldSpecs: + - version: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad-mysql-init +data: + init.sql: | + create database `etherpad_lite_db`; + use `etherpad_lite_db`; + + CREATE TABLE `store` ( + `key` varchar(100) COLLATE utf8mb4_bin NOT NULL DEFAULT '', + `value` longtext COLLATE utf8mb4_bin NOT NULL, + PRIMARY KEY (`key`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + init: + scriptSource: + configMap: + name: etherpad-mysql-init +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +bases: +- ../kubedb-mysql-etherpad-lite +resources: +- etherpad-mysql-init-configmap.yaml +patchesStrategicMerge: +- etherpad-mysql-with-init-script.yaml +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ cat test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml +bases: +- ../etherpad-lite-k8s-kubedb-mysql +namePrefix: test-namePrefix- +usuari@CASCA:~/Nextcloud/EC/Documents/fct/k9/serveis/etherpad/etherpad-lite/lib$ diff --git a/serveis/altres/etherpad/etherpad-lite/README.md b/serveis/altres/etherpad/etherpad-lite/README.md new file mode 100755 index 0000000..44fd17c --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/README.md @@ -0,0 +1,40 @@ +# etherpad-lite + +Configuration for running [Etherpad Lite](https://github.com/ether/etherpad-lite) on kubernetes. + +`./lib/` contains several directories that configure Etherpad Lite in different ways, from the very simplest (but using ephemeral storage) to one that stores ether pads in MySQL provisioned by kubedb. + +## Usage + +Use `kubectl kustomize ` to render kubernetes manifests, e.g. + +``` +kubectl kustomize lib/etherpad-lite-k8s +``` + +or + +``` +kubectl kustomize lib/etherpad-lite-k8s-kubedb-mysql/ +``` + +or (via URL, no git clone required!) + +``` +kubectl kustomize github.com/gobengo/etherpad-lite.git/lib/etherpad-lite-k8s +``` + +## Install on Kubernetes + +Assuming you have created a namespace named `my-etherpad-namespace` with something like `kubectl create ns my-etherpad-namespace` + +``` +kubectl kustomize github.com/gobengo/etherpad-lite.git/lib/etherpad-lite-k8s | kubectl apply -n my-etherpad-namespace -f - +``` + +If you don't have unix pipes: + +``` +kubectl -n my-etherpad-namespace apply -k github.com/gobengo/etherpad-lite.git/lib/etherpad-lite-k8s +``` + diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/configmap.yaml new file mode 100755 index 0000000..b5e525c --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes w/ MySQL", + "dbType": "${ETHERPAD_DB_TYPE:mysql}", + "dbSettings": { + "database": "${ETHERPAD_DB_DATABASE}", + "host": "${ETHERPAD_DB_HOST}", + "password": "${ETHERPAD_DB_PASSWORD}", + "user": "${ETHERPAD_DB_USER}" + } + } diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/deployment.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/deployment.yaml new file mode 100755 index 0000000..a50aa18 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + template: + spec: + containers: + - name: etherpad + env: + - name: ETHERPAD_DB_TYPE + value: mysql + - name: ETHERPAD_DB_HOST + value: $(MYSQL_SERVICE) + - name: ETHERPAD_DB_DATABASE + value: etherpad_lite_db + - name: ETHERPAD_DB_USER + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: username + - name: ETHERPAD_DB_PASSWORD + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: password + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad-lite/settings.json" + subPath: "settings.json" diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml new file mode 100755 index 0000000..4a0b5ec --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ../kubedb-mysql-etherpad-lite-with-init-script + - ../etherpad-lite-k8s +patches: + - configmap.yaml + - deployment.yaml +images: + - name: etherpad/etherpad + newTag: latest +configurations: + - name-prefix-transformer-config.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml new file mode 100755 index 0000000..31022f8 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml @@ -0,0 +1,4 @@ +namePrefix: +- apiVersion: apps/v1 + kind: Deployment + path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/configmap.yaml new file mode 100755 index 0000000..a166406 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes" + } diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/deployment.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/deployment.yaml new file mode 100755 index 0000000..efbd49b --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + replicas: 1 + selector: + matchLabels: + app: etherpad + template: + metadata: + labels: + app: etherpad + spec: + containers: + - name: etherpad + image: etherpad/etherpad:1.7.5 + ports: + - containerPort: 9001 + name: web + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad/settings.json" + subPath: "settings.json" + volumes: + - name: config + configMap: + name: etherpad diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/kustomization.yaml new file mode 100755 index 0000000..a6d1ae5 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- configmap.yaml +- deployment.yaml +- service.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/service.yaml b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/service.yaml new file mode 100755 index 0000000..eb0d024 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/etherpad-lite-k8s/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: etherpad +spec: + selector: + app: etherpad + ports: + - name: web + port: 80 + targetPort: web diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml new file mode 100755 index 0000000..51f9206 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad-mysql-init +data: + init.sql: | + create database `etherpad_lite_db`; + use `etherpad_lite_db`; + + CREATE TABLE `store` ( + `key` varchar(100) COLLATE utf8mb4_bin NOT NULL DEFAULT '', + `value` longtext COLLATE utf8mb4_bin NOT NULL, + PRIMARY KEY (`key`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml new file mode 100755 index 0000000..f0395dd --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml @@ -0,0 +1,9 @@ +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + init: + scriptSource: + configMap: + name: etherpad-mysql-init diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml new file mode 100755 index 0000000..2aa311b --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1 +kind: Kustomization +bases: +- ../kubedb-mysql-etherpad-lite +resources: +- etherpad-mysql-init-configmap.yaml +patchesStrategicMerge: +- etherpad-mysql-with-init-script.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/README.md b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/README.md new file mode 100755 index 0000000..a894a2e --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/README.md @@ -0,0 +1,3 @@ +# kubedb-mysql-etherpad-lite + +This is *just* the kubedb MySQL resource for etherpad-lite. Compose it with something like ../etherpad-lite-k8s to get a full setup. diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml new file mode 100755 index 0000000..2fb6236 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + version: "5.7.25" + storageType: Durable + terminationPolicy: WipeOut + storage: + storageClassName: "default" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/kustomization.yaml new file mode 100755 index 0000000..2969e65 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1 +kind: Kustomization +resources: +- etherpad-mysql.yaml +vars: +- name: MYSQL_SERVICE + objref: + apiVersion: kubedb.com/v1alpha1 + kind: MySQL + name: etherpad-mysql + fieldref: + fieldpath: metadata.name +configurations: +- transformer-config-kubedb.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml new file mode 100755 index 0000000..b206765 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml @@ -0,0 +1,12 @@ +namePrefix: +- apiVersion: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource/configMap/name + +nameReference: +- version: v1 + kind: ConfigMap + fieldSpecs: + - version: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource diff --git a/serveis/altres/etherpad/etherpad-lite/lib/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml new file mode 100644 index 0000000..c1753b9 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml @@ -0,0 +1,3 @@ +bases: +- ../etherpad-lite-k8s-kubedb-mysql +namePrefix: test-namePrefix- diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/configmap.yaml new file mode 100755 index 0000000..b5e525c --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes w/ MySQL", + "dbType": "${ETHERPAD_DB_TYPE:mysql}", + "dbSettings": { + "database": "${ETHERPAD_DB_DATABASE}", + "host": "${ETHERPAD_DB_HOST}", + "password": "${ETHERPAD_DB_PASSWORD}", + "user": "${ETHERPAD_DB_USER}" + } + } diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/deployment.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/deployment.yaml new file mode 100755 index 0000000..a50aa18 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + template: + spec: + containers: + - name: etherpad + env: + - name: ETHERPAD_DB_TYPE + value: mysql + - name: ETHERPAD_DB_HOST + value: $(MYSQL_SERVICE) + - name: ETHERPAD_DB_DATABASE + value: etherpad_lite_db + - name: ETHERPAD_DB_USER + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: username + - name: ETHERPAD_DB_PASSWORD + valueFrom: + secretKeyRef: + name: etherpad-mysql-auth + key: password + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad-lite/settings.json" + subPath: "settings.json" diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml new file mode 100755 index 0000000..cc19af6 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +bases: +- ../kubedb-mysql-etherpad-lite-with-init-script +- ../etherpad-lite-k8s +patchesStrategicMerge: +- configmap.yaml +- deployment.yaml +images: +- name: etherpad/etherpad + # This is required until etherpad-lite 1.8 comes out to be able to use env vars in settings.json + newTag: latest +configurations: +- name-prefix-transformer-config.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml new file mode 100755 index 0000000..31022f8 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s-kubedb-mysql/name-prefix-transformer-config.yaml @@ -0,0 +1,4 @@ +namePrefix: +- apiVersion: apps/v1 + kind: Deployment + path: spec/template/spec/containers/env/valueFrom/secretKeyRef/name diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/configmap.yaml new file mode 100755 index 0000000..a166406 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad +data: + settings.json: | + { + "skinName":"colibris", + "title":"Etherpad on Kubernetes" + } diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/deployment.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/deployment.yaml new file mode 100755 index 0000000..efbd49b --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: etherpad +spec: + replicas: 1 + selector: + matchLabels: + app: etherpad + template: + metadata: + labels: + app: etherpad + spec: + containers: + - name: etherpad + image: etherpad/etherpad:1.7.5 + ports: + - containerPort: 9001 + name: web + volumeMounts: + - name: "config" + mountPath: "/opt/etherpad/settings.json" + subPath: "settings.json" + volumes: + - name: config + configMap: + name: etherpad diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/kustomization.yaml new file mode 100755 index 0000000..a6d1ae5 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- configmap.yaml +- deployment.yaml +- service.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/service.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/service.yaml new file mode 100755 index 0000000..eb0d024 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/etherpad-lite-k8s/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: etherpad +spec: + selector: + app: etherpad + ports: + - name: web + port: 80 + targetPort: web diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml new file mode 100755 index 0000000..51f9206 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-init-configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: etherpad-mysql-init +data: + init.sql: | + create database `etherpad_lite_db`; + use `etherpad_lite_db`; + + CREATE TABLE `store` ( + `key` varchar(100) COLLATE utf8mb4_bin NOT NULL DEFAULT '', + `value` longtext COLLATE utf8mb4_bin NOT NULL, + PRIMARY KEY (`key`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml new file mode 100755 index 0000000..f0395dd --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/etherpad-mysql-with-init-script.yaml @@ -0,0 +1,9 @@ +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + init: + scriptSource: + configMap: + name: etherpad-mysql-init diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml new file mode 100755 index 0000000..ba70a17 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite-with-init-script/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +bases: +- ../kubedb-mysql-etherpad-lite +resources: +- etherpad-mysql-init-configmap.yaml +patchesStrategicMerge: +- etherpad-mysql-with-init-script.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/README.md b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/README.md new file mode 100755 index 0000000..a894a2e --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/README.md @@ -0,0 +1,3 @@ +# kubedb-mysql-etherpad-lite + +This is *just* the kubedb MySQL resource for etherpad-lite. Compose it with something like ../etherpad-lite-k8s to get a full setup. diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml new file mode 100755 index 0000000..2fb6236 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/etherpad-mysql.yaml @@ -0,0 +1,16 @@ +apiVersion: kubedb.com/v1alpha1 +kind: MySQL +metadata: + name: etherpad-mysql +spec: + version: "5.7.25" + storageType: Durable + terminationPolicy: WipeOut + storage: + storageClassName: "default" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/kustomization.yaml new file mode 100755 index 0000000..1914ecf --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- etherpad-mysql.yaml +vars: +- name: MYSQL_SERVICE + objref: + apiVersion: kubedb.com/v1alpha1 + kind: MySQL + name: etherpad-mysql + fieldref: + fieldpath: metadata.name +configurations: +- transformer-config-kubedb.yaml diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml new file mode 100755 index 0000000..b206765 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/kubedb-mysql-etherpad-lite/transformer-config-kubedb.yaml @@ -0,0 +1,12 @@ +namePrefix: +- apiVersion: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource/configMap/name + +nameReference: +- version: v1 + kind: ConfigMap + fieldSpecs: + - version: kubedb.com/v1alpha1 + kind: MySQL + path: spec/init/scriptSource diff --git a/serveis/altres/etherpad/etherpad-lite/lib2/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml b/serveis/altres/etherpad/etherpad-lite/lib2/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml new file mode 100644 index 0000000..c1753b9 --- /dev/null +++ b/serveis/altres/etherpad/etherpad-lite/lib2/test-etherpad-lite-mysql-with-namePrefix/kustomization.yaml @@ -0,0 +1,3 @@ +bases: +- ../etherpad-lite-k8s-kubedb-mysql +namePrefix: test-namePrefix- diff --git a/serveis/altres/example/deployment.yml b/serveis/altres/example/deployment.yml new file mode 100644 index 0000000..ad875ee --- /dev/null +++ b/serveis/altres/example/deployment.yml @@ -0,0 +1,20 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + replicas: 3 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 diff --git a/serveis/altres/example/service.yml b/serveis/altres/example/service.yml new file mode 100644 index 0000000..a309465 --- /dev/null +++ b/serveis/altres/example/service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx + ports: + - port: 80 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/altres/nextcloud/nextcloud_cron.yml b/serveis/altres/nextcloud/nextcloud_cron.yml new file mode 100644 index 0000000..c64b963 --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_cron.yml @@ -0,0 +1,21 @@ +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: nextcloud-cron + namespace: nextcloud +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: nextcloud + image: nextcloud:25.0.3-apache + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - curl https://your.nextcloud.domain/cron.php + restartPolicy: OnFailure diff --git a/serveis/altres/nextcloud/nextcloud_deployment.yml b/serveis/altres/nextcloud/nextcloud_deployment.yml new file mode 100644 index 0000000..9bef235 --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_deployment.yml @@ -0,0 +1,117 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nextcloud + namespace: nextcloud + labels: + app: nextcloud +spec: + replicas: 1 + selector: + matchLabels: + app: nextcloud + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: nextcloud + spec: + containers: + - image: nextcloud:25.0.3-apache + name: nextcloud + ports: + - containerPort: 80 + protocol: TCP + env: + - name: REDIS_HOST + value: redis + - name: POSTGRES_HOST + value: postgresql + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + key: POSTGRES_DB + name: nextcloud-secrets + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + key: POSTGRES_USER + name: nextcloud-secrets + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + key: POSTGRES_PASSWORD + name: nextcloud-secrets + - name: NEXTCLOUD_ADMIN_USER + valueFrom: + secretKeyRef: + key: NEXTCLOUD_ADMIN_USER + name: nextcloud-secrets + - name: NEXTCLOUD_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + key: NEXTCLOUD_ADMIN_PASSWORD + name: nextcloud-secrets + - name: NEXTCLOUD_TRUSTED_DOMAINS + value: your.nextcloud.domain + - name: NEXTCLOUD_DATA_DIR + value: /mnt/data + # - name: OBJECTSTORE_S3_HOST + # value: your.s3.host + # - name: OBJECTSTORE_S3_REGION + # value: gso-rack-1 + # - name: OBJECTSTORE_S3_BUCKET + # value: nextcloud + # - name: OBJECTSTORE_S3_PORT + # value: "9000" + # - name: OBJECTSTORE_S3_SSL + # value: "true" + # - name: OBJECTSTORE_S3_USEPATH_STYLE + # value: "true" + # - name: OBJECTSTORE_S3_KEY + # valueFrom: + # secretKeyRef: + # key: OBJECTSTORE_S3_KEY + # name: nextcloud-secrets + # - name: OBJECTSTORE_S3_SECRET + # valueFrom: + # secretKeyRef: + # key: OBJECTSTORE_S3_SECRET + # name: nextcloud-secrets + - name: TRUSTED_PROXIES + value: 192.168.4.0/24 10.0.0.0/16 # This includes my router IP address and the CIDR range of the cluster + - name: APACHE_DISABLE_REWRITE_IP + value: "1" + - name: OVERWRITEHOST + value: your.nextcloud.domain + - name: OVERWRITEPROTOCOL + value: https + - name: OVERWRITECLIURL + value: https://your.nextcloud.domain + - name: OVERWRITEWEBROOT + value: "/" + - name: PHP_MEMORY_LIMIT + value: 4G + - name: PHP_UPLOAD_LIMIT + value: 1G + - name: TZ + value: America/New_York + volumeMounts: + - mountPath: /var/www/html + name: nextcloud-storage + readOnly: false + - mountPath: /mnt/data + name: nextcloud-storage-nfs + readOnly: false + volumes: + - name: nextcloud-storage + persistentVolumeClaim: + claimName: nextcloud-pvc + - name: nextcloud-storage-nfs + persistentVolumeClaim: + claimName: nextcloud-pvc-nfs diff --git a/serveis/altres/nextcloud/nextcloud_headers.yml b/serveis/altres/nextcloud/nextcloud_headers.yml new file mode 100644 index 0000000..a440ef1 --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_headers.yml @@ -0,0 +1,26 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: headers + namespace: nextcloud +spec: + headers: + frameDeny: true + browserXssFilter: true + customResponseHeaders: + Strict-Transport-Security: "15552000" + X-Frame-Options: SAMEORIGIN +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: redirects + namespace: nextcloud +spec: + redirectScheme: + permanent: true + scheme: https + redirectRegex: + regex: https://(.*)/.well-known/(card|cal)dav + replacement: https://$1/remote.php/dav/ diff --git a/serveis/altres/nextcloud/nextcloud_ingress.yml b/serveis/altres/nextcloud/nextcloud_ingress.yml new file mode 100644 index 0000000..b60eedc --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_ingress.yml @@ -0,0 +1,26 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: nextcloud-ingress + namespace: nextcloud + annotations: + traefik.ingress.kubernetes.io/router.middlewares: nextcloud-headers@kubernetescrd,nextcloud-redirects@kubernetescrd + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure + cert-manager.io/cluster-issuer: letsencrypt-aws +spec: + rules: + - host: your.nextcloud.domain + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nextcloud + port: + number: 80 + tls: + - secretName: ssl-cert + hosts: + - your.nextcloud.domain diff --git a/serveis/altres/nextcloud/nextcloud_pvc.yml b/serveis/altres/nextcloud/nextcloud_pvc.yml new file mode 100644 index 0000000..d31a607 --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_pvc.yml @@ -0,0 +1,26 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextcloud-pvc + namespace: nextcloud +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nextcloud-pvc-nfs + namespace: nextcloud +spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-client + resources: + requests: + storage: 100Gi diff --git a/serveis/altres/nextcloud/nextcloud_service.yml b/serveis/altres/nextcloud/nextcloud_service.yml new file mode 100644 index 0000000..ffc0a8a --- /dev/null +++ b/serveis/altres/nextcloud/nextcloud_service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nextcloud + namespace: nextcloud + labels: + app: nextcloud +spec: + ports: + - port: 80 + selector: + app: nextcloud diff --git a/serveis/altres/nextcloud/postgresql_deployment.yml b/serveis/altres/nextcloud/postgresql_deployment.yml new file mode 100644 index 0000000..606104d --- /dev/null +++ b/serveis/altres/nextcloud/postgresql_deployment.yml @@ -0,0 +1,50 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgresql + namespace: nextcloud + labels: + app: postgresql +spec: + replicas: 1 + selector: + matchLabels: + app: postgresql + template: + metadata: + labels: + app: postgresql + spec: + containers: + - name: postgresql + image: postgres:15 + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + key: POSTGRES_DB + name: nextcloud-secrets + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + key: POSTGRES_USER + name: nextcloud-secrets + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + key: POSTGRES_PASSWORD + name: nextcloud-secrets + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + - name: TZ + value: America/New_York + volumeMounts: + - name: postgresql-data + mountPath: /var/lib/postgresql/data + volumes: + - name: postgresql-data + persistentVolumeClaim: + claimName: postgresql-pvc diff --git a/serveis/altres/nextcloud/postgresql_pvc.yml b/serveis/altres/nextcloud/postgresql_pvc.yml new file mode 100644 index 0000000..5d4131c --- /dev/null +++ b/serveis/altres/nextcloud/postgresql_pvc.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgresql-pvc + namespace: nextcloud +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: 2Gi diff --git a/serveis/altres/nextcloud/postgresql_service.yml b/serveis/altres/nextcloud/postgresql_service.yml new file mode 100644 index 0000000..8673be8 --- /dev/null +++ b/serveis/altres/nextcloud/postgresql_service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: postgresql + namespace: nextcloud + labels: + app: postgresql +spec: + ports: + - port: 5432 + selector: + app: postgresql diff --git a/serveis/altres/nextcloud/redis_deployment.yml b/serveis/altres/nextcloud/redis_deployment.yml new file mode 100644 index 0000000..e65ac55 --- /dev/null +++ b/serveis/altres/nextcloud/redis_deployment.yml @@ -0,0 +1,27 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: nextcloud + labels: + app: redis +spec: + selector: + matchLabels: + app: redis + replicas: 1 + template: + metadata: + labels: + app: redis + spec: + containers: + - image: redis:alpine + name: redis + ports: + - containerPort: 6379 + env: + - name: TZ + value: America/New_York + restartPolicy: Always diff --git a/serveis/altres/nextcloud/redis_service.yml b/serveis/altres/nextcloud/redis_service.yml new file mode 100644 index 0000000..1938749 --- /dev/null +++ b/serveis/altres/nextcloud/redis_service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: nextcloud + labels: + app: redis +spec: + ports: + - port: 6379 + selector: + app: redis diff --git a/serveis/altres/nextcloud/secrets.yml b/serveis/altres/nextcloud/secrets.yml new file mode 100644 index 0000000..2272496 --- /dev/null +++ b/serveis/altres/nextcloud/secrets.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: nextcloud-secrets + namespace: nextcloud +type: Opaque +stringData: + POSTGRES_DB: $DB + POSTGRES_USER: $DB_USER + POSTGRES_PASSWORD: $DB_NEXTCLOUD_PASSWORD + NEXTCLOUD_ADMIN_USER: $NEXTCLOUD_ADMIN_USER + NEXTCLOUD_ADMIN_PASSWORD: $NEXTCLOUD_ADMIN_PASSWORD diff --git a/serveis/altres/nginx/deployment.yml b/serveis/altres/nginx/deployment.yml new file mode 100644 index 0000000..ad875ee --- /dev/null +++ b/serveis/altres/nginx/deployment.yml @@ -0,0 +1,20 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + replicas: 3 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 diff --git a/serveis/altres/nginx/ingress.yaml b/serveis/altres/nginx/ingress.yaml new file mode 100644 index 0000000..2c51d73 --- /dev/null +++ b/serveis/altres/nginx/ingress.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: clustergv-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx # Usa "traefik" si es el controlador por defecto + rules: + - host: radicale.clustergv.fai.st + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: radicale + port: + number: 80 + - host: wordpress.clustergv.fai.st + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: wordpress + port: + number: 80 + - host: nginx.clustergv.fai.st + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nginx + port: + number: 80 diff --git a/serveis/altres/nginx/service.yml b/serveis/altres/nginx/service.yml new file mode 100644 index 0000000..a309465 --- /dev/null +++ b/serveis/altres/nginx/service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx + ports: + - port: 80 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/altres/wordpress/kustomization.yaml b/serveis/altres/wordpress/kustomization.yaml new file mode 100644 index 0000000..55be286 --- /dev/null +++ b/serveis/altres/wordpress/kustomization.yaml @@ -0,0 +1,8 @@ +secretGenerator: + - name: mysql-pass + literals: + - password=YOUR_PASSWORD + +resources: + - mysql-deployment.yaml + - wordpress-deployment.yaml diff --git a/serveis/altres/wordpress/mysql-deployment.yaml b/serveis/altres/wordpress/mysql-deployment.yaml new file mode 100644 index 0000000..3a2332e --- /dev/null +++ b/serveis/altres/wordpress/mysql-deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wordpress-mysql + labels: + app: wordpress +spec: + selector: + matchLabels: + app: wordpress + tier: mysql + strategy: + type: Recreate + template: + metadata: + labels: + app: wordpress + tier: mysql + spec: + containers: + - image: mysql:5.7 # Usar una versión más antigua de MySQL + name: mysql + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-pass + key: password + - name: MYSQL_DATABASE + value: wordpress + - name: MYSQL_USER + value: wordpress + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-pass + key: password + ports: + - containerPort: 3306 + name: mysql + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql + volumes: + - name: mysql-persistent-storage + persistentVolumeClaim: + claimName: mysql-pv-claim diff --git a/serveis/altres/wordpress/mysql-pv.yaml b/serveis/altres/wordpress/mysql-pv.yaml new file mode 100644 index 0000000..7e7a338 --- /dev/null +++ b/serveis/altres/wordpress/mysql-pv.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: mysql-pv +spec: + capacity: + storage: 20Gi # Ajusta el tamaño si es necesario + volumeMode: Filesystem + accessModes: + - ReadWriteOnce # Permite acceso de lectura/escritura por un solo pod + persistentVolumeReclaimPolicy: Retain # El volumen no se eliminará cuando se libere + storageClassName: local-path # Usamos 'local-path' ya que es un almacenamiento local en el nodo + hostPath: + path: /mnt/data/mysql # Ruta en el nodo donde se almacenarán los datos + type: DirectoryOrCreate # Crea el directorio si no existe diff --git a/serveis/altres/wordpress/wordpress-deployment.yaml b/serveis/altres/wordpress/wordpress-deployment.yaml new file mode 100644 index 0000000..43d9525 --- /dev/null +++ b/serveis/altres/wordpress/wordpress-deployment.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Service +metadata: + name: wordpress + labels: + app: wordpress +spec: + ports: + - port: 80 + selector: + app: wordpress + tier: frontend + type: LoadBalancer +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: wp-pv-claim + labels: + app: wordpress +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wordpress + labels: + app: wordpress +spec: + selector: + matchLabels: + app: wordpress + tier: frontend + strategy: + type: Recreate + template: + metadata: + labels: + app: wordpress + tier: frontend + spec: + containers: + - image: wordpress:6.2.1-apache + name: wordpress + env: + - name: WORDPRESS_DB_HOST + value: wordpress-mysql + - name: WORDPRESS_DB_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-pass + key: password + - name: WORDPRESS_DB_USER + value: wordpress + ports: + - containerPort: 80 + name: wordpress + volumeMounts: + - name: wordpress-persistent-storage + mountPath: /var/www/html + volumes: + - name: wordpress-persistent-storage + persistentVolumeClaim: + claimName: wp-pv-claim diff --git a/serveis/altres/wordpress/wp-pv.yaml b/serveis/altres/wordpress/wp-pv.yaml new file mode 100644 index 0000000..715e959 --- /dev/null +++ b/serveis/altres/wordpress/wp-pv.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: wp-pv +spec: + capacity: + storage: 20Gi # Ajusta el tamaño si es necesario + volumeMode: Filesystem + accessModes: + - ReadWriteOnce # Permite acceso de lectura/escritura por un solo pod + persistentVolumeReclaimPolicy: Retain # El volumen no se eliminará cuando se libere + storageClassName: local-path # Usamos 'local-path' para almacenamiento local en el nodo + hostPath: + path: /mnt/data/wordpress # Ruta en el nodo donde se almacenarán los datos + type: DirectoryOrCreate # Crea el directorio si no existe diff --git a/serveis/caddy/caddy/font b/serveis/caddy/caddy/font new file mode 100644 index 0000000..15c5ac3 --- /dev/null +++ b/serveis/caddy/caddy/font @@ -0,0 +1 @@ +https://github.com/caddyserver/ingress diff --git a/serveis/caddy/caddy/ingress/.dockerignore b/serveis/caddy/caddy/ingress/.dockerignore new file mode 100644 index 0000000..c267599 --- /dev/null +++ b/serveis/caddy/caddy/ingress/.dockerignore @@ -0,0 +1,13 @@ +/.github +/bin +/charts +/kubernetes + +/.gitignore +/.goreleaser.yaml +/CONTRIBUTING.md +/ct.yaml +/skaffold.yaml +/README.md +/Makefile +/LICENSE.txt \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/.gitignore b/serveis/caddy/caddy/ingress/.gitignore new file mode 100644 index 0000000..241d36d --- /dev/null +++ b/serveis/caddy/caddy/ingress/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +bin +vendor +.idea/ +dist/ +ingress-controller diff --git a/serveis/caddy/caddy/ingress/.goreleaser.yaml b/serveis/caddy/caddy/ingress/.goreleaser.yaml new file mode 100644 index 0000000..328f079 --- /dev/null +++ b/serveis/caddy/caddy/ingress/.goreleaser.yaml @@ -0,0 +1,112 @@ +project_name: ingress +before: + hooks: + - go mod tidy +builds: + - main: ./cmd/caddy + binary: ingress-controller + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm64 + - s390x + mod_timestamp: "{{ .CommitTimestamp }}" + +dockers: + - use: buildx + goos: linux + goarch: amd64 + dockerfile: ./Dockerfile + skip_push: "true" + image_templates: + - "caddy/{{ .ProjectName }}:test-image" + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: amd64 + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-amd64" + build_flag_templates: + - "--platform=linux/amd64" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: arm64 + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8" + build_flag_templates: + - "--platform=linux/arm64/v8" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + + - use: buildx + goos: linux + goarch: s390x + dockerfile: ./Dockerfile + image_templates: + - "caddy/{{ .ProjectName }}:{{ .Tag }}-s390x" + build_flag_templates: + - "--platform=linux/s390x" + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + +docker_manifests: + # https://goreleaser.com/customization/docker_manifest/ + - name_template: caddy/{{ .ProjectName }}:latest + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:v{{ .Major }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + - name_template: caddy/{{ .ProjectName }}:{{ .Tag }} + image_templates: + - caddy/{{ .ProjectName }}:{{ .Tag }}-amd64 + - caddy/{{ .ProjectName }}:{{ .Tag }}-arm64v8 + - caddy/{{ .ProjectName }}:{{ .Tag }}-s390x + +release: + disable: true + +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^ci:" diff --git a/serveis/caddy/caddy/ingress/CONTRIBUTING.md b/serveis/caddy/caddy/ingress/CONTRIBUTING.md new file mode 100644 index 0000000..0646565 --- /dev/null +++ b/serveis/caddy/caddy/ingress/CONTRIBUTING.md @@ -0,0 +1,54 @@ +## Requirements + + - A running kubernetes cluster (if you don't have one see *Setup a local cluster* section) + - [skaffold](https://skaffold.dev/) installed on your machine + - [helm 3](https://helm.sh/) installed on your machine + - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl/) installed on your machine + +### Setup a local cluster + + - You need a machine with [docker](https://docker.io) up & running + - If you are using Docker Desktop enable the kubernetes cluster from the config (see [docs](https://docs.docker.com/desktop/kubernetes/) here) + +Otherwise you can decide to use [kind]() or [minukube]() + +## Setup development env + +Start `skaffold` using the command: +``` +make dev +``` + +this will automatically: + + - build your docker image every time you change some code + - update the helm release every time you change the helm chart + - expose the caddy ingress controller (port 8080 and 8443) + +You can test that all work as expected with: +``` +curl -D - -s -k https://example1.kubernetes.localhost:8443/hello1 --resolve example1.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example1.kubernetes.localhost:8443/hello2 --resolve example1.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example2.kubernetes.localhost:8443/hello1 --resolve example2.kubernetes.localhost:8443:127.0.0.1 +curl -D - -s -k https://example2.kubernetes.localhost:8443/hello2 --resolve example2.kubernetes.localhost:8443:127.0.0.1 +``` + +You can change domains defined in `kuberentes/sample` folder with some domain that are risolved on your local machine or you can add them in the `/etc/host` file to be automatically resolved as localhost. + +## Notes + + - You can change local port forwarded by skaffold by changing the port values in the `skaffold.yaml` file on section `portForward` `localPort`. Remind that you can forward only port greater than 1024 if you execute it as non-root user + - We use an internal CA for test to simplify the installation, but then you can decide to use external CA for tests, see the `values.yaml` in the helm chart for the info on how to configure it. + +## Releasing new helm chart version + +If you want to release a new version of the `caddy-ingress-controller` chart, you'll need +to create a new PR with: +- The new chart's `version` in `Chart.yaml` +- The new `image.tag` in `values.yaml` (if you want to update the default image used in the chart) +- The new `appVersion` in `Chart.yaml` (if you did the previous line) + +## Releasing a new app version + +To release a new caddy-ingress-controller image, you need to create a new semver tag. +It will build and push an image to https://hub.docker.com/r/caddy/ingress. diff --git a/serveis/caddy/caddy/ingress/Dockerfile b/serveis/caddy/caddy/ingress/Dockerfile new file mode 100644 index 0000000..cbd4ce5 --- /dev/null +++ b/serveis/caddy/caddy/ingress/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:latest AS certs +RUN apk --update add ca-certificates + +FROM alpine:latest + +EXPOSE 80 443 +ENTRYPOINT ["/ingress-controller"] + +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY ingress-controller / diff --git a/serveis/caddy/caddy/ingress/Dockerfile.dev b/serveis/caddy/caddy/ingress/Dockerfile.dev new file mode 100644 index 0000000..f646297 --- /dev/null +++ b/serveis/caddy/caddy/ingress/Dockerfile.dev @@ -0,0 +1,19 @@ +FROM golang:1.22 as build + +ENV CGO_ENABLED=0 + +WORKDIR /go/ingress-controller + +COPY go.mod go.sum /go/ingress-controller/ +RUN go mod download + +COPY . . +RUN go build -o ./bin/ingress-controller ./cmd/caddy + +FROM alpine:3.18 + +EXPOSE 80 443 + +COPY --from=build /go/ingress-controller/bin/ingress-controller /ingress-controller + +ENTRYPOINT ["/ingress-controller"] diff --git a/serveis/caddy/caddy/ingress/LICENSE.txt b/serveis/caddy/caddy/ingress/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/serveis/caddy/caddy/ingress/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/serveis/caddy/caddy/ingress/Makefile b/serveis/caddy/caddy/ingress/Makefile new file mode 100644 index 0000000..0f72d66 --- /dev/null +++ b/serveis/caddy/caddy/ingress/Makefile @@ -0,0 +1,6 @@ +build: + @mkdir -p bin + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ingress-controller ./cmd/caddy + +dev: + skaffold dev --port-forward diff --git a/serveis/caddy/caddy/ingress/README.md b/serveis/caddy/caddy/ingress/README.md new file mode 100644 index 0000000..62264af --- /dev/null +++ b/serveis/caddy/caddy/ingress/README.md @@ -0,0 +1,153 @@ +# Caddy Ingress Controller + +This is the Kubernetes Ingress Controller for Caddy. It includes functionality +for monitoring `Ingress` resources on a Kubernetes cluster and includes support +for providing automatic HTTPS certificates for all hostnames defined in the +ingress resources that it is managing. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.19+ + +## Setup + +In the `charts` folder, a Helm Chart is provided to make installing the Caddy +Ingress Controller on a Kubernetes cluster straightforward. To install the +Caddy Ingress Controller adhere to the following steps: + +1. Create a new namespace in your cluster to isolate all Caddy resources. + +```sh +kubectl create namespace caddy-system +``` + +2. Install the Helm Chart. + +```sh +helm install \ + --namespace=caddy-system \ + --repo https://caddyserver.github.io/ingress/ \ + --atomic \ + mycaddy \ + caddy-ingress-controller +``` + +Or + +2. Generate kubernetes yaml file. +```sh +git clone https://github.com/caddyserver/ingress.git +cd ingress + +# generate the yaml file +helm template mycaddy ./charts/caddy-ingress-controller \ + --namespace=caddy-system \ + > mycaddy.yaml + +# apply the file +kubectl apply -f mycaddy.yaml +``` + +This will create a service of type `LoadBalancer` in the `caddy-system` +namespace on your cluster. You'll want to set any DNS records for accessing this +cluster to the external IP address of this `LoadBalancer` when the external IP +is provisioned by your cloud provider. + +You can get the external IP address with `kubectl get svc -n caddy-system` + +3. Alternate installation method: Glasskube + +To install the Caddy ingress controller using [Glasskube](https://glasskube.dev/), you can select "caddy-ingress-controller" from the "ClusterPackages" tab in the Glasskube GUI then click "install" or you can run the following command: +```console +glasskube install caddy-ingress-controller +``` +Add an email address in the package configuration section in the UI to enable automatic HTTPS, or run: +``` +glasskube install caddy-ingress-controller --value "automaticHTTPS=your@email.com" +``` + +## Debugging + +To view any logs generated by Caddy or the Ingress Controller you can view the +pod logs of the Caddy Ingress Controller. + +Get the pod name with: + +```sh +kubectl get pods -n caddy-system +``` + +View the pod logs: + +```sh +kubectl logs -n caddy-system +``` + +## Automatic HTTPS + +To have automatic HTTPS (not to be confused with `On-demand TLS`), you simply have +to specify your email in the config map. When using Helm chart, you can add +`--set ingressController.config.email=your@email.com` when installing. + +## On-Demand TLS + +[On-demand TLS](https://caddyserver.com/docs/automatic-https#on-demand-tls) can generate SSL certs on the fly +and can be enabled in this controller by setting the `onDemandTLS` config to `true`: + +```sh +helm install ...\ + --set ingressController.config.onDemandTLS=true +``` + +> You can also specify options +> for the on-demand config: `onDemandAsk` + + +## Bringing Your Own Certificates + +If you would like to disable automatic HTTPS for a specific host and use your +own certificates you can create a new TLS secret in Kubernetes and define what +certificates to use when serving your application on the ingress resource. + +Example: + +Create TLS secret `mycerts`, where `./tls.key` and `./tls.crt` are valid +certificates for `test.com`. + +``` +kubectl create secret tls mycerts --key ./tls.key --cert ./tls.crt +``` + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example + annotations: + kubernetes.io/ingress.class: caddy +spec: + rules: + - host: test.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: test + port: + number: 8080 + tls: + - secretName: mycerts # use mycerts for host test.com + hosts: + - test.com +``` + +### Contribution + +Learn how to start contributing on the [Contributing Guidline](CONTRIBUTING.md). + +## License + +[Apache License 2.0](LICENSE.txt) diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/.helmignore b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/.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/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml new file mode 100644 index 0000000..807f8fc --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: caddy-ingress-controller +home: https://github.com/caddyserver/ingress +description: A helm chart for the Caddy Kubernetes ingress controller +icon: https://caddyserver.com/resources/images/caddy-circle-lock.svg +type: application +version: 1.3.0 +appVersion: "v0.2.1" +keywords: + - ingress-controller + - caddyserver +sources: + - https://github.com/caddyserver/ingress +maintainers: + - name: mavimo + url: https://github.com/mavimo + - name: embraser01 + url: https://github.com/embraser01 diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md new file mode 100644 index 0000000..0050370 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md @@ -0,0 +1,84 @@ +# caddy-ingress-controller + +A helm chart for the Caddy Kubernetes ingress controller + +## TL;DR: + +```bash +helm install my-release caddy-ingress-controller\ + --repo https://caddyserver.github.io/ingress/ \ + --namespace=caddy-system +``` + +## Introduction + +This chart bootstraps a caddy-ingress-deployment deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.19+ + +## Installing the Chart + +```bash +helm repo add caddyserver https://caddyserver.github.io/ingress/ +helm install my-release caddyserver/caddy-ingress-controller --namespace=caddy-system +``` + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + +## Troubleshooting + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"caddy/ingress"` | | +| image.tag | string | `"latest"` | | +| imagePullSecrets | list | `[]` | | +| ingressController.config.acmeCA | string | `""` | | +| ingressController.config.acmeEABKeyId | string | `""` | | +| ingressController.config.acmeEABMacKey | string | `""` | | +| ingressController.config.debug | bool | `false` | | +| ingressController.config.email | string | `""` | | +| ingressController.config.metrics | bool | `true` | | +| ingressController.config.onDemandTLS | bool | `false` | | +| ingressController.config.proxyProtocol | bool | `false` | | +| ingressController.rbac.create | bool | `true` | | +| ingressController.verbose | bool | `false` | | +| ingressController.leaseId | string | `""` | | +| ingressController.watchNamespace | string | `""` | | +| minikube | bool | `false` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podDisruptionBudget.maxUnavailable | string | `nil` | | +| podDisruptionBudget.minAvailable | int | `1` | | +| podSecurityContext | object | `{}` | | +| replicaCount | int | `2` | | +| resources | object | `{}` | | +| securityContext.allowPrivilegeEscalation | bool | `true` | | +| securityContext.capabilities.add[0] | string | `"NET_BIND_SERVICE"` | | +| securityContext.capabilities.drop[0] | string | `"ALL"` | | +| securityContext.runAsGroup | int | `0` | | +| securityContext.runAsUser | int | `0` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `"caddy-ingress-controller"` | | +| tolerations | list | `[]` | | \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl new file mode 100644 index 0000000..d9c5d85 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/README.md.gotmpl @@ -0,0 +1,48 @@ +{{ template "chart.header" . }} + +{{ template "chart.description" . }} + +## TL;DR: + +```bash +helm install my-release caddy-ingress-controller\ + --repo https://caddyserver.github.io/ingress/ \ + --namespace=caddy-system +``` + +## Introduction + +This chart bootstraps a caddy-ingress-deployment deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.14+ + +## Installing the Chart + +```bash +helm repo add caddyserver https://caddyserver.github.io/ingress/ +helm install my-release caddyserver/caddy-ingress-controller --namespace=caddy-system +``` + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + + +## Troubleshooting + + + +{{ template "chart.valuesSection" . }} \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml new file mode 100644 index 0000000..c937b7e --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/ci/test-values.yaml @@ -0,0 +1,5 @@ +image: + tag: test-image + +ingressController: + verbose: true diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl new file mode 100644 index 0000000..ba92739 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "caddy-ingress-controller.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 "caddy-ingress-controller.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 "caddy-ingress-controller.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "caddy-ingress-controller.labels" -}} +helm.sh/chart: {{ include "caddy-ingress-controller.chart" . }} +{{ include "caddy-ingress-controller.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "caddy-ingress-controller.selectorLabels" -}} +app.kubernetes.io/name: {{ include "caddy-ingress-controller.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "caddy-ingress-controller.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "caddy-ingress-controller.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml new file mode 100644 index 0000000..3bbb678 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrole.yaml @@ -0,0 +1,31 @@ +{{- if .Values.ingressController.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-role + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - "" + - "networking.k8s.io" + - "coordination.k8s.io" + resources: + - ingresses + - ingresses/status + - secrets + - leases + verbs: ["*"] + - apiGroups: + - "" + resources: + - services + - pods + - nodes + - routes + - extensions + - configmaps + verbs: + - list + - get + - watch +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..4c9eb87 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/clusterrolebinding.yaml @@ -0,0 +1,15 @@ +{{- if .Values.ingressController.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-role-binding + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "caddy-ingress-controller.name" . }}-role + apiGroup: rbac.authorization.k8s.io +subjects: +- kind: ServiceAccount + name: {{ include "caddy-ingress-controller.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml new file mode 100644 index 0000000..795928a --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "caddy-ingress-controller.name" . }}-configmap + namespace: {{ .Release.Namespace }} +data: +{{- range keys .Values.ingressController.config | sortAlpha }} + {{ . }}: {{ get $.Values.ingressController.config . | quote }} +{{- end }} + diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml new file mode 100644 index 0000000..c9e45e9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/deployment.yaml @@ -0,0 +1,105 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "caddy-ingress-controller.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 80 # optional, required if running in minikube + {{- end }} + - name: https + containerPort: 443 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 443 # optional, required if running in minikube + {{- end }} + - name: metrics + containerPort: 9765 + protocol: TCP + {{- if .Values.minikube }} + hostPort: 9765 # optional, required if running in minikube + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: tmp + mountPath: /tmp + args: + - -config-map={{ include "caddy-ingress-controller.name" . }}-configmap + {{- if .Values.ingressController.watchNamespace }} + - -namespace={{ .Values.ingressController.watchNamespace }} + {{- end }} + {{- if .Values.ingressController.leaseId }} + - -lease-id={{ .Values.ingressController.leaseId }} + {{- end }} + {{- if .Values.ingressController.verbose }} + - -verbose + {{- end }} + {{- if .Values.ingressController.className }} + - -class-name={{ .Values.ingressController.className }} + {{- end }} + {{- if .Values.ingressController.classNameRequired }} + - -class-name-required={{ .Values.ingressController.classNameRequired }} + {{- end }} + readinessProbe: + initialDelaySeconds: 3 + periodSeconds: 10 + httpGet: + port: 9765 + path: /healthz + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: tmp + emptyDir: {} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml new file mode 100644 index 0000000..10eecc9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/loadbalancer.yaml @@ -0,0 +1,33 @@ +{{- if .Values.loadBalancer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.loadBalancer.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} + {{- with .Values.loadBalancer.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: "LoadBalancer" + {{- if (semverCompare "<= 1.24.0" .Capabilities.KubeVersion.Version) }} + loadBalancerIP: {{ .Values.loadBalancer.loadBalancerIP }} #Deprecated in Kubernetes v1.24 + {{- end }} + externalTrafficPolicy: {{ .Values.loadBalancer.externalTrafficPolicy }} + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: https + port: 443 + protocol: TCP + targetPort: https + selector: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..52542f1 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/poddisruptionbudget.yaml @@ -0,0 +1,19 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: +{{- with .Values.podDisruptionBudget }} + {{- if .minAvailable }} + minAvailable: {{ .minAvailable }} + {{- end }} + {{- if .maxUnavailable }} + maxUnavailable: {{ .maxUnavailable }} + {{- end }} +{{- end }} + selector: + matchLabels: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 6 }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml new file mode 100644 index 0000000..28a15e5 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/service.yaml @@ -0,0 +1,35 @@ +{{- if not .Values.loadBalancer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "caddy-ingress-controller.fullname" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} +{{- if .Values.service.ipDualStack.enabled }} + ipFamilies: {{ toYaml .Values.service.ipDualStack.ipFamilies | nindent 4 }} + ipFamilyPolicy: {{ .Values.service.ipDualStack.ipFamilyPolicy }} +{{- end }} + internalTrafficPolicy: {{ .Values.service.internalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} +{{- if and (eq .Values.service.type "ClusterIP") .Values.service.clusterIP }} + clusterIP: "{{ .Values.service.clusterIP }}" +{{- end }} + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: https + port: 443 + protocol: TCP + targetPort: https + selector: + {{- include "caddy-ingress-controller.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml new file mode 100644 index 0000000..0188784 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "caddy-ingress-controller.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "caddy-ingress-controller.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.schema.json b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.schema.json new file mode 100644 index 0000000..69becbe --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.schema.json @@ -0,0 +1,324 @@ +{ + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "replicaCount", + "minikube", + "image", + "imagePullSecrets", + "nameOverride", + "fullnameOverride", + "ingressController", + "serviceAccount", + "podAnnotations", + "podSecurityContext", + "securityContext", + "resources", + "nodeSelector", + "tolerations", + "affinity" + ], + "properties": { + "replicaCount": { + "$id": "#/properties/replicaCount", + "type": "number" + }, + "minikube": { + "$id": "#/properties/minikube", + "type": "boolean" + }, + "image": { + "$id": "#/properties/image", + "type": "object", + "required": [ + "repository", + "tag", + "pullPolicy" + ], + "properties": { + "repository": { + "$id": "#/properties/image/properties/repository", + "type": "string" + }, + "tag": { + "$id": "#/properties/image/properties/tag", + "type": "string" + }, + "pullPolicy": { + "$id": "#/properties/image/properties/pullPolicy", + "type": "string", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + } + } + }, + "imagePullSecrets": { + "$id": "#/properties/imagePullSecrets", + "type": "array" + }, + "nameOverride": { + "$id": "#/properties/nameOverride", + "type": "string" + }, + "fullnameOverride": { + "$id": "#/properties/fullnameOverride", + "type": "string" + }, + "loadBalancer": { + "$id": "#/properties/loadBalancer", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$id": "#/properties/loadBalancer/properties/enabled", + "type": "boolean" + }, + "annotations": { + "$id": "#/properties/loadBalancer/properties/annotations", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "labels": { + "$id": "#/properties/loadBalancer/properties/labels", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "ingressController": { + "$id": "#/properties/ingressController", + "type": "object", + "required": [ + "rbac", + "config", + "watchNamespace" + ], + "properties": { + "rbac": { + "$id": "#/properties/ingressController/properties/rbac", + "type": "object", + "required": [ + "create" + ], + "properties": { + "create": { + "$id": "#/properties/ingressController/properties/rbac/properties/create", + "type": "boolean" + } + } + }, + "leaseId": { + "$id": "#/properties/ingressController/properties/leaseId", + "type": "string" + }, + "config": { + "$id": "#/properties/ingressController/properties/config", + "type": "object", + "properties": { + "acmeCA": { + "$id": "#/properties/ingressController/properties/config/properties/acmeCA", + "type": "string", + "oneOf": [ + { + "format": "uri" + }, + { + "maxLength": 0 + } + ] + }, + "acmeEABKeyId": { + "$id": "#/properties/ingressController/properties/config/properties/acmeEABKeyId", + "type": "string" + }, + "acmeEABMacKey": { + "$id": "#/properties/ingressController/properties/config/properties/acmeEABMacKey", + "type": "string" + }, + "debug": { + "$id": "#/properties/ingressController/properties/config/properties/debug", + "type": "boolean" + }, + "email": { + "$id": "#/properties/ingressController/properties/config/properties/email", + "type": "string", + "oneOf": [ + { + "format": "email" + }, + { + "maxLength": 0 + } + ] + }, + "experimentalSmartSort": { + "$id": "#/properties/ingressController/properties/config/properties/experimentalSmartSort", + "type": "boolean" + }, + "metrics": { + "$id": "#/properties/ingressController/properties/config/properties/metrics", + "type": "boolean" + }, + "proxyProtocol": { + "$id": "#/properties/ingressController/properties/config/properties/proxyProtocol", + "type": "boolean" + }, + "onDemandTLS": { + "$id": "#/properties/ingressController/properties/config/properties/onDemandTLS", + "type": "boolean" + }, + "onDemandAsk": { + "$id": "#/properties/ingressController/properties/config/properties/onDemandAsk", + "type": "string" + } + } + }, + "verbose": { + "$id": "#/properties/ingressController/properties/verbose", + "type": "boolean" + }, + "watchNamespace": { + "$id": "#/properties/ingressController/properties/watchNamespace", + "type": "string" + } + } + }, + "service": { + "$id": "#/properties/service", + "type": "object", + "required": [ + "type", + "ipDualStack" + ], + "properties": { + "internalTrafficPolicy": { + "$id": "#/properties/service/properties/internalTrafficPolicy", + "type": "string", + "enum": [ + "Cluster", + "Local" + ] + }, + "externalTrafficPolicy": { + "$id": "#/properties/service/properties/externalTrafficPolicy", + "type": "string", + "enum": [ + "Cluster", + "Local" + ] + }, + "annotations": { + "$id": "#/properties/service/properties/annotations", + "type": "object" + }, + "type": { + "$id": "#/properties/service/properties/type", + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer", + "ExternalName" + ] + }, + "clusterIP": { + "$id": "#/properties/service/properties/clusterIP", + "type": "string" + }, + "ipDualStack": { + "$id": "#/properties/service/properties/name", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$id": "#/properties/service/properties/ipDualStack/properties/enabled", + "type": "boolean" + }, + "ipFamilies": { + "$id": "#/properties/service/properties/ipDualStack/properties/ipFamilies", + "type": "array", + "items": { + "type": "string", + "enum": [ + "IPv4", + "IPv6" + ] + } + }, + "ipFamilyPolicy": { + "$id": "#/properties/service/properties/ipDualStack/properties/ipFamilyPolicy", + "type": "string", + "enum": [ + "SingleStack", + "PreferDualStack", + "RequireDualStack" + ] + } + } + } + } + }, + "serviceAccount": { + "$id": "#/properties/serviceAccount", + "type": "object", + "required": [ + "create", + "name" + ], + "properties": { + "create": { + "$id": "#/properties/serviceAccount/properties/create", + "type": "boolean" + }, + "name": { + "$id": "#/properties/serviceAccount/properties/name", + "type": "string" + }, + "annotations": { + "$id": "#/properties/serviceAccount/properties/annotations", + "type": "object" + } + } + }, + "podAnnotations": { + "$id": "#/properties/podAnnotations", + "type": "object" + }, + "podSecurityContext": { + "$id": "#/properties/podSecurityContext", + "type": "object" + }, + "securityContext": { + "$id": "#/properties/securityContext", + "type": "object" + }, + "resources": { + "$id": "#/properties/resources", + "type": "object" + }, + "nodeSelector": { + "$id": "#/properties/nodeSelector", + "type": "object" + }, + "tolerations": { + "$id": "#/properties/tolerations", + "type": "array" + }, + "affinity": { + "$id": "#/properties/affinity", + "type": "object" + } + } +} diff --git a/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.yaml b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.yaml new file mode 100644 index 0000000..cb87436 --- /dev/null +++ b/serveis/caddy/caddy/ingress/charts/caddy-ingress-controller/values.yaml @@ -0,0 +1,120 @@ +# Default values for caddy-ingress-controller. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 2 + +# Use to test in minikube context +minikube: false + +image: + repository: caddy/ingress + pullPolicy: IfNotPresent + tag: "v0.2.1" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# Default values for the caddy ingress controller. +ingressController: + watchNamespace: "" + verbose: false + rbac: + create: true + className: "caddy" + classNameRequired: false + leaseId: "" + config: + acmeEABKeyId: "" + acmeEABMacKey: "" + # -- Acme Server URL + acmeCA: "" + debug: false + email: "" + metrics: true + proxyProtocol: false + experimentalSmartSort: false + onDemandTLS: false + # onDemandAsk: + +loadBalancer: + enabled: true + # Deprecated in Kubernetes v1.24 + loadBalancerIP: + # Set to 'Local' to maintain the client's IP on inbound connections + externalTrafficPolicy: + annotations: {} + # service.beta.kubernetes.io/aws-load-balancer-type: + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: + # service.beta.kubernetes.io/aws-load-balancer-scheme: + # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: + # service.beta.kubernetes.io/aws-load-balancer-subnets: + labels: {} + +service: + # Set to 'Local' to maintain the client's IP on inbound connections + externalTrafficPolicy: Cluster + # Enable internal traffic policy for the service + internalTrafficPolicy: Cluster + annotations: {} + # service.beta.kubernetes.io/aws-load-balancer-type: + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: + # service.beta.kubernetes.io/aws-load-balancer-scheme: + # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: + # service.beta.kubernetes.io/aws-load-balancer-subnets: + ## Service type + type: ClusterIP + ## IP address for type ClusterIP + clusterIP: "" + ipDualStack: + enabled: false + ipFamilies: ["IPv6", "IPv4"] + ipFamilyPolicy: "PreferDualStack" + +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: "caddy-ingress-controller" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +podDisruptionBudget: + minAvailable: 1 + maxUnavailable: + +securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + runAsUser: 0 + runAsGroup: 0 + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/serveis/caddy/caddy/ingress/cmd/caddy/flag.go b/serveis/caddy/caddy/ingress/cmd/caddy/flag.go new file mode 100644 index 0000000..3301744 --- /dev/null +++ b/serveis/caddy/caddy/ingress/cmd/caddy/flag.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "strings" + + "github.com/caddyserver/ingress/pkg/store" +) + +func parseFlags() store.Options { + var namespace string + flag.StringVar(&namespace, "namespace", "", "the namespace that you would like to observe kubernetes ingress resources in.") + + var className string + flag.StringVar(&className, "class-name", "caddy", "class name of the ingress controller") + + var classNameRequired bool + flag.BoolVar(&classNameRequired, "class-name-required", false, "only allow ingress resources with a matching ingress class name") + + var configMapName string + flag.StringVar(&configMapName, "config-map", "", "defines the config map name from where to load global options") + + var leaseId string + flag.StringVar(&leaseId, "lease-id", "", "defines the id of this instance for certmagic lock") + + var verbose bool + flag.BoolVar(&verbose, "verbose", false, "set the log level to debug") + + var pluginsOrder string + flag.StringVar(&pluginsOrder, "plugins-order", "", "defines the order plugins should be used") + + flag.Parse() + + return store.Options{ + WatchNamespace: namespace, + ClassName: className, + ClassNameRequired: classNameRequired, + ConfigMapName: configMapName, + Verbose: verbose, + LeaseId: leaseId, + PluginsOrder: strings.Split(pluginsOrder, ","), + } +} diff --git a/serveis/caddy/caddy/ingress/cmd/caddy/main.go b/serveis/caddy/caddy/ingress/cmd/caddy/main.go new file mode 100644 index 0000000..463a953 --- /dev/null +++ b/serveis/caddy/caddy/ingress/cmd/caddy/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + "time" + + "github.com/caddyserver/ingress/internal/caddy" + "github.com/caddyserver/ingress/internal/controller" + "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + // high enough QPS to fit all expected use cases. + highQPS = 1e6 + + // high enough Burst to fit all expected use cases. + highBurst = 1e6 +) + +func createLogger(verbose bool) *zap.SugaredLogger { + prodCfg := zap.NewProductionConfig() + + if verbose { + prodCfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + } + logger, _ := prodCfg.Build() + + return logger.Sugar() +} + +func main() { + // parse any flags required to configure the caddy ingress controller + cfg := parseFlags() + + logger := createLogger(cfg.Verbose) + + if cfg.WatchNamespace == "" { + cfg.WatchNamespace = v1.NamespaceAll + logger.Warn("-namespace flag is unset, caddy ingress controller will monitor ingress resources in all namespaces.") + } + + // get client to access the kubernetes service api + kubeClient, _, err := createApiserverClient(logger) + if err != nil { + logger.Fatalf("Could not establish a connection to the Kubernetes API Server. %v", err) + } + + stopCh := make(chan struct{}, 1) + + c := controller.NewCaddyController(logger, kubeClient, cfg, caddy.Converter{}, stopCh) + + // start the ingress controller + logger.Info("Starting the caddy ingress controller") + go c.Run() + + // Listen for SIGINT and SIGTERM signals + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + <-sigs + close(stopCh) + + // Let controller exit the process + select {} +} + +// createApiserverClient creates a new Kubernetes REST client. We assume the +// controller runs inside Kubernetes and use the in-cluster config. +func createApiserverClient(logger *zap.SugaredLogger) (*kubernetes.Clientset, *version.Info, error) { + cfg, err := clientcmd.BuildConfigFromFlags("", "") + if err != nil { + return nil, nil, err + } + + logger.Infof("Creating API client for %s", cfg.Host) + + cfg.QPS = highQPS + cfg.Burst = highBurst + cfg.ContentType = "application/vnd.kubernetes.protobuf" + + client, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, nil, err + } + + // The client may fail to connect to the API server on the first request + defaultRetry := wait.Backoff{ + Steps: 10, + Duration: 1 * time.Second, + Factor: 1.5, + Jitter: 0.1, + } + + var v *version.Info + var retries int + var lastErr error + + err = wait.ExponentialBackoff(defaultRetry, func() (bool, error) { + v, err = client.Discovery().ServerVersion() + if err == nil { + return true, nil + } + + lastErr = err + logger.Infof("Unexpected error discovering Kubernetes version (attempt %v): %v", retries, err) + retries++ + return false, nil + }) + + // err is returned in case of timeout in the exponential backoff (ErrWaitTimeout) + if err != nil { + return nil, nil, lastErr + } + + if retries > 0 { + logger.Warnf("Initial connection to the Kubernetes API server was retried %d times.", retries) + } + + return client, v, nil +} diff --git a/serveis/caddy/caddy/ingress/ct.yaml b/serveis/caddy/caddy/ingress/ct.yaml new file mode 100644 index 0000000..d6bd5c0 --- /dev/null +++ b/serveis/caddy/caddy/ingress/ct.yaml @@ -0,0 +1,3 @@ +# See https://github.com/helm/chart-testing#configuration +all: true +helm-extra-args: --timeout 300s diff --git a/serveis/caddy/caddy/ingress/go.mod b/serveis/caddy/caddy/ingress/go.mod new file mode 100644 index 0000000..4510f56 --- /dev/null +++ b/serveis/caddy/caddy/ingress/go.mod @@ -0,0 +1,152 @@ +module github.com/caddyserver/ingress + +go 1.24.0 +require ( + github.com/caddyserver/caddy/v2 v2.9.1 + github.com/caddyserver/certmagic v0.22.2 + github.com/google/uuid v1.6.0 + github.com/mholt/acmez/v3 v3.1.1 + github.com/mitchellh/mapstructure v1.5.0 + github.com/pires/go-proxyproto v0.8.0 + github.com/stretchr/testify v1.10.0 + go.uber.org/zap v1.27.0 + gopkg.in/go-playground/pool.v3 v3.1.1 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.4 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/cel-go v0.21.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/libdns/libdns v0.2.3 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.21.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.48.2 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/slackhq/nebula v1.7.2 // indirect + github.com/smallstep/certificates v0.26.1 // indirect + github.com/smallstep/nosql v0.6.1 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/smallstep/truststore v0.13.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect + github.com/urfave/cli v1.22.14 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 // indirect + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/mock v0.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/serveis/caddy/caddy/ingress/go.sum b/serveis/caddy/caddy/ingress/go.sum new file mode 100644 index 0000000..05c4276 --- /dev/null +++ b/serveis/caddy/caddy/ingress/go.sum @@ -0,0 +1,806 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.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.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +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/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/caddyserver/caddy/v2 v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY= +github.com/caddyserver/caddy/v2 v2.9.1/go.mod h1:ImUELya2el1FDVp3ahnSO2iH1or1aHxlQEQxd/spP68= +github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk= +github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +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/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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/gofuzz v1.0.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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/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.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +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/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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8= +github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ= +github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0= +github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw= +github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +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/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20181029174526-d69651ed3497/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-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-20190316082340-a2f829d7f35f/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-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +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.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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-20181030000716-a0a13e073c7b/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-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-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +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.3.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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +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.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/pool.v3 v3.1.1 h1:4Qcj91IsYTpIeRhe/eo6Fz+w6uKWPEghx8vHFTYMfhw= +gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/serveis/caddy/caddy/ingress/internal/caddy/convert.go b/serveis/caddy/caddy/ingress/internal/caddy/convert.go new file mode 100644 index 0000000..c504c46 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/convert.go @@ -0,0 +1,26 @@ +package caddy + +import ( + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + + // Load default plugins + _ "github.com/caddyserver/ingress/internal/caddy/global" + _ "github.com/caddyserver/ingress/internal/caddy/ingress" +) + +type Converter struct{} + +func (c Converter) ConvertToCaddyConfig(store *store.Store) (interface{}, error) { + cfg := converter.NewConfig() + + for _, p := range converter.Plugins(store.Options.PluginsOrder) { + if m, ok := p.(converter.GlobalMiddleware); ok { + err := m.GlobalHandler(cfg, store) + if err != nil { + return cfg, err + } + } + } + return cfg, nil +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/convert_test.go b/serveis/caddy/caddy/ingress/internal/caddy/convert_test.go new file mode 100644 index 0000000..02bef5e --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/convert_test.go @@ -0,0 +1,36 @@ +package caddy + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/ingress/pkg/store" + "github.com/stretchr/testify/require" +) + +func TestConvertToCaddyConfig(t *testing.T) { + tests := []struct { + name string + expectedConfigPath string + }{ + { + name: "default", + expectedConfigPath: "./test_data/default.json", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg, err := Converter{}.ConvertToCaddyConfig(store.NewStore(store.Options{}, &store.PodInfo{})) + require.NoError(t, err) + + cfgJson, err := json.Marshal(cfg) + require.NoError(t, err) + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + require.NoError(t, err) + + require.JSONEq(t, string(expectedCfg), string(cfgJson)) + }) + } +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/configmap.go b/serveis/caddy/caddy/ingress/internal/caddy/global/configmap.go new file mode 100644 index 0000000..ad9fa7e --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/configmap.go @@ -0,0 +1,88 @@ +package global + +import ( + "encoding/json" + + caddy2 "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddytls" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + "github.com/mholt/acmez/v3/acme" +) + +type ConfigMapPlugin struct{} + +func init() { + converter.RegisterPlugin(ConfigMapPlugin{}) +} + +func (p ConfigMapPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "configmap", + New: func() converter.Plugin { return new(ConfigMapPlugin) }, + } +} + +func (p ConfigMapPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + cfgMap := store.ConfigMap + + tlsApp := config.GetTLSApp() + httpServer := config.GetHTTPServer() + + if cfgMap.Debug { + config.Logging.Logs = map[string]*caddy2.CustomLog{"default": {BaseLog: caddy2.BaseLog{Level: "DEBUG"}}} + } + + if cfgMap.AcmeCA != "" || cfgMap.Email != "" { + acmeIssuer := caddytls.ACMEIssuer{} + + if cfgMap.AcmeCA != "" { + acmeIssuer.CA = cfgMap.AcmeCA + } + + if cfgMap.AcmeEABKeyId != "" && cfgMap.AcmeEABMacKey != "" { + acmeIssuer.ExternalAccount = &acme.EAB{ + KeyID: cfgMap.AcmeEABKeyId, + MACKey: cfgMap.AcmeEABMacKey, + } + } + + if cfgMap.Email != "" { + acmeIssuer.Email = cfgMap.Email + } + + var onDemandConfig *caddytls.OnDemandConfig + if cfgMap.OnDemandTLS { + onDemandConfig = &caddytls.OnDemandConfig{ + Ask: cfgMap.OnDemandAsk, + } + } + + tlsApp.Automation = &caddytls.AutomationConfig{ + OnDemand: onDemandConfig, + OCSPCheckInterval: cfgMap.OCSPCheckInterval, + Policies: []*caddytls.AutomationPolicy{ + { + IssuersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(acmeIssuer, "module", "acme", nil), + }, + OnDemand: cfgMap.OnDemandTLS, + }, + }, + } + } + + if cfgMap.ProxyProtocol { + httpServer.ListenerWrappersRaw = []json.RawMessage{ + json.RawMessage(`{"wrapper":"proxy_protocol"}`), + json.RawMessage(`{"wrapper":"tls"}`), + } + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(ConfigMapPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/healthz.go b/serveis/caddy/caddy/ingress/internal/caddy/global/healthz.go new file mode 100644 index 0000000..318a704 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/healthz.go @@ -0,0 +1,48 @@ +package global + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type HealthzPlugin struct{} + +func (p HealthzPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "healthz", + Priority: -20, + New: func() converter.Plugin { return new(HealthzPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(HealthzPlugin{}) +} + +func (p HealthzPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + healthzHandler := caddyhttp.StaticResponse{StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusOK))} + + healthzRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(healthzHandler, "handler", healthzHandler.CaddyModule().ID.Name(), nil), + }, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/healthz"}, nil), + }}, + } + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, healthzRoute) + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(HealthzPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/ingress.go b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress.go new file mode 100644 index 0000000..0ebb9b2 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress.go @@ -0,0 +1,70 @@ +package global + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type IngressPlugin struct{} + +func (p IngressPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress", + New: func() converter.Plugin { return new(IngressPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(IngressPlugin{}) +} + +func (p IngressPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + ingressHandlers := make([]converter.IngressMiddleware, 0) + for _, plugin := range converter.Plugins(store.Options.PluginsOrder) { + if m, ok := plugin.(converter.IngressMiddleware); ok { + ingressHandlers = append(ingressHandlers, m) + } + } + + // create a server route for each ingress route + var routes caddyhttp.RouteList + for _, ing := range store.Ingresses { + for _, rule := range ing.Spec.Rules { + for _, path := range rule.HTTP.Paths { + r := &caddyhttp.Route{ + HandlersRaw: []json.RawMessage{}, + MatcherSetsRaw: []caddy.ModuleMap{}, + } + + for _, middleware := range ingressHandlers { + newRoute, err := middleware.IngressHandler(converter.IngressMiddlewareInput{ + Config: config, + Store: store, + Ingress: ing, + Rule: rule, + Path: path, + Route: r, + }) + if err != nil { + return err + } + r = newRoute + } + + routes = append(routes, *r) + } + } + } + + config.GetHTTPServer().Routes = routes + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(IngressPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort.go b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort.go new file mode 100644 index 0000000..97569a5 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort.go @@ -0,0 +1,77 @@ +package global + +import ( + "encoding/json" + "sort" + "strings" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type IngressSortPlugin struct{} + +func (p IngressSortPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress_sort", + // Must go after ingress are configured + Priority: -2, + New: func() converter.Plugin { return new(IngressSortPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(IngressSortPlugin{}) +} + +func getFirstItemFromJSON(data json.RawMessage) string { + var arr []string + err := json.Unmarshal(data, &arr) + if err != nil { + return "" + } + return arr[0] +} + +func sortRoutes(routes caddyhttp.RouteList) { + sort.SliceStable(routes, func(i, j int) bool { + iPath := getFirstItemFromJSON(routes[i].MatcherSetsRaw[0]["path"]) + jPath := getFirstItemFromJSON(routes[j].MatcherSetsRaw[0]["path"]) + iPrefixed := strings.HasSuffix(iPath, "*") + jPrefixed := strings.HasSuffix(jPath, "*") + + // If both same type check by length + if iPrefixed == jPrefixed { + return len(jPath) < len(iPath) + } + // Empty path will be moved last + if jPath == "" || iPath == "" { + return jPath == "" + } + // j path is exact so should go first + return jPrefixed + }) +} + +// GlobalHandler in IngressSortPlugin tries to sort routes to have the less conflict. +// +// It only supports basic conflicts for now. It doesn't support multiple matchers in the same route +// nor multiple path/host in the matcher. It shouldn't be an issue with the ingress.matcher plugin. +// Sort will prioritize exact paths then prefix paths and finally empty paths. +// When 2 exacts paths or 2 prefixed paths are on the same host, we choose the longer first. +func (p IngressSortPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + if !store.ConfigMap.ExperimentalSmartSort { + return nil + } + + routes := config.GetHTTPServer().Routes + + sortRoutes(routes) + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(IngressSortPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort_test.go b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort_test.go new file mode 100644 index 0000000..deb9fd8 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/ingress_sort_test.go @@ -0,0 +1,109 @@ +package global + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestIngressSort(t *testing.T) { + tests := []struct { + name string + routes []struct { + id int + path string + } + expect []int + }{ + + { + name: "multiple exact paths", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/a"}, + {id: 1, path: "/path/"}, + {id: 2, path: "/other"}, + }, + expect: []int{0, 1, 2}, + }, + { + name: "multiple prefix paths", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: "/path/auth/*"}, + {id: 2, path: "/other/*"}, + {id: 3, path: "/login/*"}, + }, + expect: []int{1, 2, 3, 0}, + }, + { + name: "mixed exact and prefixed", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: "/path/auth/"}, + {id: 2, path: "/path/v2/*"}, + {id: 3, path: "/path/new"}, + }, + expect: []int{1, 3, 2, 0}, + }, + { + name: "mixed exact, prefix and empty", + routes: []struct { + id int + path string + }{ + {id: 0, path: "/path/*"}, + {id: 1, path: ""}, + {id: 2, path: "/path/v2/*"}, + {id: 3, path: "/path/new"}, + {id: 4, path: ""}, + }, + expect: []int{3, 2, 0, 1, 4}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + routes := []caddyhttp.Route{} + + for _, route := range test.routes { + match := caddy.ModuleMap{} + match["id"] = caddyconfig.JSON(route.id, nil) + + if route.path != "" { + match["path"] = caddyconfig.JSON(caddyhttp.MatchPath{route.path}, nil) + } + + r := caddyhttp.Route{MatcherSetsRaw: []caddy.ModuleMap{match}} + routes = append(routes, r) + } + + sortRoutes(routes) + + var got []int + for i := range test.expect { + var currentId int + err := json.Unmarshal(routes[i].MatcherSetsRaw[0]["id"], ¤tId) + if err != nil { + t.Fatalf("error unmarshaling id for i %v, %v", i, err) + } + got = append(got, currentId) + } + + if !reflect.DeepEqual(test.expect, got) { + t.Errorf("expected order to match: got %v, expected %v, %s", got, test.expect, routes[1].MatcherSetsRaw) + } + }) + } +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/metrics.go b/serveis/caddy/caddy/ingress/internal/caddy/global/metrics.go new file mode 100644 index 0000000..c8c576a --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/metrics.go @@ -0,0 +1,43 @@ +package global + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type MetricsPlugin struct{} + +func (p MetricsPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "metrics", + New: func() converter.Plugin { return new(MetricsPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(MetricsPlugin{}) +} + +func (p MetricsPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + if store.ConfigMap.Metrics { + metricsRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{json.RawMessage(`{ "handler": "metrics" }`)}, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/metrics"}, nil), + }}, + } + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, metricsRoute) + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(MetricsPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/secrets_store.go b/serveis/caddy/caddy/ingress/internal/caddy/global/secrets_store.go new file mode 100644 index 0000000..3430240 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/secrets_store.go @@ -0,0 +1,36 @@ +package global + +import ( + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type SecretsStorePlugin struct{} + +func (p SecretsStorePlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "secrets_store", + New: func() converter.Plugin { return new(SecretsStorePlugin) }, + } +} + +func init() { + converter.RegisterPlugin(SecretsStorePlugin{}) +} + +func (p SecretsStorePlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + config.Storage = converter.Storage{ + System: "secret_store", + StorageValues: converter.StorageValues{ + Namespace: store.CurrentPod.Namespace, + LeaseId: store.Options.LeaseId, + }, + } + + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(SecretsStorePlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/tls.go b/serveis/caddy/caddy/ingress/internal/caddy/global/tls.go new file mode 100644 index 0000000..97e8f47 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/tls.go @@ -0,0 +1,53 @@ +package global + +import ( + "encoding/json" + "slices" + + "github.com/caddyserver/ingress/internal/controller" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type TLSPlugin struct{} + +func (p TLSPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "tls", + New: func() converter.Plugin { return new(TLSPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(TLSPlugin{}) +} + +func (p TLSPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + tlsApp := config.GetTLSApp() + httpServer := config.GetHTTPServer() + + var hosts []string + + // Get all Hosts subject to custom TLS certs + for _, ing := range store.Ingresses { + for _, tlsRule := range ing.Spec.TLS { + for _, h := range tlsRule.Hosts { + if !slices.Contains(hosts, h) { + hosts = append(hosts, h) + } + } + } + } + + if len(hosts) > 0 { + tlsApp.CertificatesRaw["load_folders"] = json.RawMessage(`["` + controller.CertFolder + `"]`) + // do not manage certificates for those hosts + httpServer.AutoHTTPS.SkipCerts = hosts + } + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(TLSPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/global/tls_test.go b/serveis/caddy/caddy/ingress/internal/caddy/global/tls_test.go new file mode 100644 index 0000000..a88e0b9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/global/tls_test.go @@ -0,0 +1,211 @@ +package global + +import ( + "testing" + + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + + "github.com/stretchr/testify/assert" +) + +func TestIngressTlsSkipCertificates(t *testing.T) { + testCases := []struct { + desc string + skippedCertsDomains []string + ingresses []*networkingv1.Ingress + }{ + { + desc: "No ingress registered", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{}, + }, + { + desc: "One ingress registered with certificate with one domain", + skippedCertsDomains: []string{"domain1.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + }, + }, + { + desc: "One ingress registered with certificate with multiple domains", + skippedCertsDomains: []string{"domain1.tld", "domain2.tld", "domain3.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2.tld", "domain3.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate one domain each", + skippedCertsDomains: []string{"domain1.tld", "domain2.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain2.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate the same domain", + skippedCertsDomains: []string{"domain1.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate with multiple domains each", + skippedCertsDomains: []string{"domain1a.tld", "domain1b.tld", "domain2a.tld", "domain2b.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1a.tld", "domain1b.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain2a.tld", "domain2b.tld"}, + }}, + }, + }, + }, + }, + { + desc: "Two ingress registered with certificate with multiple domains each and partial domain overlap", + skippedCertsDomains: []string{"domain1.tld", "domain2a.tld", "domain2b.tld"}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2a.tld"}, + }}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{{ + Hosts: []string{"domain1.tld", "domain2b.tld"}, + }}, + }, + }, + }, + }, + { + desc: "One ingress registered without certificate", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{}, + }, + }, + }, + { + desc: "Two ingresses registered without certificate", + skippedCertsDomains: []string{}, + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("first"), + }, + Spec: networkingv1.IngressSpec{}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("second"), + }, + Spec: networkingv1.IngressSpec{}, + }, + }, + }, + } + + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + tp := TLSPlugin{} + c := converter.NewConfig() + s := store.NewStore(store.Options{}, &store.PodInfo{}) + + for _, ing := range tC.ingresses { + s.AddIngress(ing) + } + + tp.GlobalHandler(c, s) + + toSkip := c.GetHTTPServer().AutoHTTPS.SkipCerts + assert.ElementsMatch(t, toSkip, tC.skippedCertsDomains, "List of certificate to skip don't match expectation") + }) + } +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/annotations.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/annotations.go new file mode 100644 index 0000000..c711cdb --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/annotations.go @@ -0,0 +1,28 @@ +package ingress + +import v1 "k8s.io/api/networking/v1" + +const ( + annotationPrefix = "caddy.ingress.kubernetes.io" + rewriteToAnnotation = "rewrite-to" + rewriteStripPrefixAnnotation = "rewrite-strip-prefix" + disableSSLRedirect = "disable-ssl-redirect" + backendProtocol = "backend-protocol" + insecureSkipVerify = "insecure-skip-verify" + permanentRedirectAnnotation = "permanent-redirect" + permanentRedirectCodeAnnotation = "permanent-redirect-code" + temporaryRedirectAnnotation = "temporal-redirect" + trustedProxies = "trusted-proxies" +) + +func getAnnotation(ing *v1.Ingress, rule string) string { + return ing.Annotations[annotationPrefix+"/"+rule] +} + +func getAnnotationBool(ing *v1.Ingress, rule string, def bool) bool { + val := getAnnotation(ing, rule) + if val == "" { + return def + } + return val == "true" +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/matcher.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/matcher.go new file mode 100644 index 0000000..e78631c --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/matcher.go @@ -0,0 +1,52 @@ +package ingress + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + v1 "k8s.io/api/networking/v1" +) + +type MatcherPlugin struct{} + +func (p MatcherPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.matcher", + New: func() converter.Plugin { return new(MatcherPlugin) }, + } +} + +// IngressHandler Generate matchers for the route. +func (p MatcherPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + match := caddy.ModuleMap{} + + if getAnnotation(input.Ingress, disableSSLRedirect) != "true" { + match["protocol"] = caddyconfig.JSON(caddyhttp.MatchProtocol("https"), nil) + } + + if input.Rule.Host != "" { + match["host"] = caddyconfig.JSON(caddyhttp.MatchHost{input.Rule.Host}, nil) + } + + if input.Path.Path != "" { + p := input.Path.Path + + if *input.Path.PathType == v1.PathTypePrefix { + p += "*" + } + match["path"] = caddyconfig.JSON(caddyhttp.MatchPath{p}, nil) + } + + input.Route.MatcherSetsRaw = append(input.Route.MatcherSetsRaw, match) + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(MatcherPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(MatcherPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect.go new file mode 100644 index 0000000..2ed21c9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect.go @@ -0,0 +1,82 @@ +package ingress + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" +) + +type RedirectPlugin struct{} + +func (p RedirectPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.redirect", + Priority: 10, + New: func() converter.Plugin { return new(RedirectPlugin) }, + } +} + +// IngressHandler Converts redirect annotations to static_response handler +func (p RedirectPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + ing := input.Ingress + + permanentRedir := getAnnotation(ing, permanentRedirectAnnotation) + temporaryRedir := getAnnotation(ing, temporaryRedirectAnnotation) + + var code string = "301" + var redirectTo string = "" + + // Don't allow both redirect annotations to be set + if permanentRedir != "" && temporaryRedir != "" { + return nil, fmt.Errorf("cannot use permanent-redirect annotation with temporal-redirect") + } + + if permanentRedir != "" { + redirectCode := getAnnotation(ing, permanentRedirectCodeAnnotation) + if redirectCode != "" { + codeInt, err := strconv.Atoi(redirectCode) + if err != nil { + return nil, fmt.Errorf("not a supported redirection code type or not a valid integer: '%s'", redirectCode) + } + + if codeInt < 300 || (codeInt > 399 && codeInt != 401) { + return nil, fmt.Errorf("redirection code not in the 3xx range or 401: '%v'", codeInt) + } + + code = redirectCode + } + redirectTo = permanentRedir + } + + if temporaryRedir != "" { + code = "302" + redirectTo = temporaryRedir + } + + if redirectTo != "" { + handler := caddyconfig.JSONModuleObject( + caddyhttp.StaticResponse{ + StatusCode: caddyhttp.WeakString(code), + Headers: http.Header{"Location": []string{redirectTo}}, + }, + "handler", "static_response", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(RedirectPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(RedirectPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect_test.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect_test.go new file mode 100644 index 0000000..2ec5f42 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/redirect_test.go @@ -0,0 +1,135 @@ +package ingress + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/stretchr/testify/assert" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestRedirectConvertToCaddyConfig(t *testing.T) { + rp := RedirectPlugin{} + + tests := []struct { + name string + expectedConfigPath string + expectedError string + annotations map[string]string + }{ + { + name: "Check permanent redirect without any specific redirect code", + expectedConfigPath: "test_data/redirect_default.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + }, + }, + { + name: "Check permanent redirect with custom redirect code", + expectedConfigPath: "test_data/redirect_custom_code.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "308", + }, + }, + { + name: "Check permanent redirect with 401 as redirect code", + expectedConfigPath: "test_data/redirect_401.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "401", + }, + }, + { + name: "Check temporary redirect", + expectedConfigPath: "test_data/redirect_temporary.json", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/temporal-redirect": "http://example.com", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rp.IngressHandler(input) + assert.NoError(t, err, "failed to generate ingress route") + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + assert.NoError(t, err, "failed to find config file for comparison") + + cfgJson, err := json.Marshal(&route) + assert.NoError(t, err, "failed to marshal route to JSON") + + assert.JSONEq(t, string(cfgJson), string(expectedCfg)) + }) + } +} + +func TestMisconfiguredRedirectConvertToCaddyConfig(t *testing.T) { + rp := RedirectPlugin{} + + tests := []struct { + name string + expectedError string + annotations map[string]string + }{ + { + name: "Check permanent redirect with invalid custom redirect code", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "502", + }, + expectedError: "redirection code not in the 3xx range or 401: '502'", + }, + { + name: "Check permanent redirect with invalid custom redirect code string", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/permanent-redirect-code": "randomstring", + }, + expectedError: "not a supported redirection code type or not a valid integer: 'randomstring'", + }, + { + name: "Check if both permanent and temporary redirection annotations are set", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/permanent-redirect": "http://example.com", + "caddy.ingress.kubernetes.io/temporal-redirect": "http://example2.com", + }, + expectedError: "cannot use permanent-redirect annotation with temporal-redirect", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rp.IngressHandler(input) + if assert.Error(t, err, "expected an error while generating the ingress route") { + assert.EqualError(t, err, test.expectedError) + } + + cfgJson, err := json.Marshal(&route) + assert.NoError(t, err, "failed to marshal route to JSON") + + assert.JSONEq(t, string(cfgJson), "null") + }) + } +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy.go new file mode 100644 index 0000000..3e5a41c --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy.go @@ -0,0 +1,103 @@ +package ingress + +import ( + "fmt" + "net/netip" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + "github.com/caddyserver/ingress/pkg/converter" +) + +type ReverseProxyPlugin struct{} + +func (p ReverseProxyPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.reverseproxy", + // Should always go last by default + Priority: -10, + New: func() converter.Plugin { return new(ReverseProxyPlugin) }, + } +} + +// IngressHandler Add a reverse proxy handler to the route +func (p ReverseProxyPlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + path := input.Path + ing := input.Ingress + backendProtocol := strings.ToLower(getAnnotation(ing, backendProtocol)) + trustedProxiesAnnotation := strings.ToLower(getAnnotation(ing, trustedProxies)) + + // TODO :- + // when setting the upstream url we should bypass kube-dns and get the ip address of + // the pod for the deployment we are proxying to so that we can proxy to that ip address port. + // this is good for session affinity and increases performance. + clusterHostName := fmt.Sprintf("%v.%v.svc.cluster.local:%d", path.Backend.Service.Name, ing.Namespace, path.Backend.Service.Port.Number) + + transport := &reverseproxy.HTTPTransport{} + + if backendProtocol == "https" { + transport.TLS = &reverseproxy.TLSConfig{ + InsecureSkipVerify: getAnnotationBool(ing, insecureSkipVerify, true), + } + } + + var err error + var parsedProxies []string + if trustedProxiesAnnotation != "" { + trustedProxies := strings.Split(trustedProxiesAnnotation, ",") + parsedProxies, err = parseTrustedProxies(trustedProxies) + if err != nil { + return nil, err + } + } + + handler := reverseproxy.Handler{ + TransportRaw: caddyconfig.JSONModuleObject(transport, "protocol", "http", nil), + Upstreams: reverseproxy.UpstreamPool{ + {Dial: clusterHostName}, + }, + TrustedProxies: parsedProxies, + } + + handlerModule := caddyconfig.JSONModuleObject( + handler, + "handler", + "reverse_proxy", + nil, + ) + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handlerModule) + return input.Route, nil +} + +// Copied from https://github.com/caddyserver/caddy/blob/21af88fefc9a8239a024f635f1c6fdd9defd7eb7/modules/caddyhttp/reverseproxy/reverseproxy.go#L270-L286 +func parseTrustedProxies(trustedProxies []string) (parsedProxies []string, err error) { + for _, trustedProxy := range trustedProxies { + trustedProxy = strings.TrimSpace(trustedProxy) + if strings.Contains(trustedProxy, "/") { + ipNet, err := netip.ParsePrefix(trustedProxy) + if err != nil { + return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy) + } + parsedProxies = append(parsedProxies, ipNet.String()) + } else { + ipAddr, err := netip.ParseAddr(trustedProxy) + if err != nil { + return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy) + } + ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + parsedProxies = append(parsedProxies, ipNew.String()) + } + } + return parsedProxies, nil +} + +func init() { + converter.RegisterPlugin(ReverseProxyPlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(ReverseProxyPlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go new file mode 100644 index 0000000..e9f9d6c --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/reverseproxy_test.go @@ -0,0 +1,168 @@ +package ingress + +import ( + "encoding/json" + "os" + "testing" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/stretchr/testify/require" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTrustedProxesConvertToCaddyConfig(t *testing.T) { + rpp := ReverseProxyPlugin{} + + tests := []struct { + name string + annotations map[string]string + expectedConfigPath string + }{ + { + name: "ipv4 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0, 10.0.0.1", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4.json", + }, + { + name: "ipv4 trusted proxies wit subnet", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0/16,10.0.0.1/8", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4_subnet.json", + }, + { + name: "ipv6 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1, 2001:db8::5", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6.json", + }, + { + name: "ipv6 trusted proxies", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1/36,2001:db8::5/60", + }, + expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6_subnet.json", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + Namespace: "namespace", + }, + }, + Path: networkingv1.HTTPIngressPath{ + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "svcName", + Port: networkingv1.ServiceBackendPort{Number: 80}, + }, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rpp.IngressHandler(input) + require.NoError(t, err) + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + require.NoError(t, err) + + cfgJson, err := json.Marshal(&route) + require.NoError(t, err) + + require.JSONEq(t, string(expectedCfg), string(cfgJson)) + }) + } +} + +func TestMisconfiguredTrustedProxiesConvertToCaddyConfig(t *testing.T) { + rpp := ReverseProxyPlugin{} + + tests := []struct { + name string + annotations map[string]string + expectedError string + }{ + { + name: "invalid ipv4 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999", + }, + expectedError: `failed to parse IP: "999.999.999.999"`, + }, + { + name: "invalid ipv4 with subnet trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999/32", + }, + expectedError: `failed to parse IP: "999.999.999.999/32"`, + }, + { + name: "invalid subnet for ipv4 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "10.0.0.0/100", + }, + expectedError: `failed to parse IP: "10.0.0.0/100"`, + }, + { + name: "invalid ipv6 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g", + }, + expectedError: `failed to parse IP: "2001:db8::g"`, + }, + { + name: "invalid ipv6 with subnet trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g/128", + }, + expectedError: `failed to parse IP: "2001:db8::g/128"`, + }, + { + name: "invalid subnet for ipv6 trusted proxy", + annotations: map[string]string{ + "caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::/200", + }, + expectedError: `failed to parse IP: "2001:db8::/200"`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := converter.IngressMiddlewareInput{ + Ingress: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + Namespace: "namespace", + }, + }, + Path: networkingv1.HTTPIngressPath{ + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "svcName", + Port: networkingv1.ServiceBackendPort{Number: 80}, + }, + }, + }, + Route: &caddyhttp.Route{}, + } + + route, err := rpp.IngressHandler(input) + require.EqualError(t, err, test.expectedError) + + cfgJson, err := json.Marshal(&route) + require.NoError(t, err) + + require.JSONEq(t, string(cfgJson), "null") + }) + } +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/rewrite.go b/serveis/caddy/caddy/ingress/internal/caddy/ingress/rewrite.go new file mode 100644 index 0000000..6a856e0 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/rewrite.go @@ -0,0 +1,53 @@ +package ingress + +import ( + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" + "github.com/caddyserver/ingress/pkg/converter" +) + +type RewritePlugin struct{} + +func (p RewritePlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "ingress.rewrite", + Priority: 10, + New: func() converter.Plugin { return new(RewritePlugin) }, + } +} + +// IngressHandler Converts rewrite annotations to rewrite handler +func (p RewritePlugin) IngressHandler(input converter.IngressMiddlewareInput) (*caddyhttp.Route, error) { + ing := input.Ingress + + rewriteTo := getAnnotation(ing, rewriteToAnnotation) + if rewriteTo != "" { + handler := caddyconfig.JSONModuleObject( + rewrite.Rewrite{URI: rewriteTo}, + "handler", "rewrite", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + + rewriteStripPrefix := getAnnotation(ing, rewriteStripPrefixAnnotation) + if rewriteStripPrefix != "" { + handler := caddyconfig.JSONModuleObject( + rewrite.Rewrite{StripPathPrefix: rewriteStripPrefix}, + "handler", "rewrite", nil, + ) + + input.Route.HandlersRaw = append(input.Route.HandlersRaw, handler) + } + return input.Route, nil +} + +func init() { + converter.RegisterPlugin(RewritePlugin{}) +} + +// Interface guards +var ( + _ = converter.IngressMiddleware(RewritePlugin{}) +) diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json new file mode 100644 index 0000000..c1539a9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_401.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 401 + } + ] +} \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json new file mode 100644 index 0000000..78aaa93 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_custom_code.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 308 + } + ] +} \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json new file mode 100644 index 0000000..1502b63 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_default.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 301 + } + ] +} \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json new file mode 100644 index 0000000..67e83c3 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/redirect_temporary.json @@ -0,0 +1,13 @@ +{ + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "http://example.com" + ] + }, + "status_code": 302 + } + ] +} \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json new file mode 100644 index 0000000..7ccbe8a --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "192.168.1.0/32", + "10.0.0.1/32" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json new file mode 100644 index 0000000..9b7a776 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv4_subnet.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "192.168.1.0/16", + "10.0.0.1/8" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json new file mode 100644 index 0000000..112bd0e --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "2001:db8::1/128", + "2001:db8::5/128" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json new file mode 100644 index 0000000..4c0e759 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/ingress/test_data/reverseproxy_trusted_proxies_ipv6_subnet.json @@ -0,0 +1,19 @@ +{ + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http" + }, + "trusted_proxies": [ + "2001:db8::1/36", + "2001:db8::5/60" + ], + "upstreams": [ + { + "dial": "svcName.namespace.svc.cluster.local:80" + } + ] + } + ] +} diff --git a/serveis/caddy/caddy/ingress/internal/caddy/test_data/default.json b/serveis/caddy/caddy/ingress/internal/caddy/test_data/default.json new file mode 100644 index 0000000..1156fa5 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/caddy/test_data/default.json @@ -0,0 +1,27 @@ +{ + "admin": {}, + "storage": { "module": "secret_store", "namespace": "", "leaseId": "" }, + "apps": { + "http": { + "servers": { + "ingress_server": { + "listen": [":80", ":443"], + "tls_connection_policies": [{}], + "automatic_https": {} + }, + "metrics_server": { + "listen": [":9765"], + "routes": [ + { + "match": [{ "path": ["/healthz"] }], + "handle": [{ "handler": "static_response", "status_code": 200 }] + } + ], + "automatic_https": { "disable": true } + } + } + }, + "tls": {} + }, + "logging": {} +} diff --git a/serveis/caddy/caddy/ingress/internal/controller/action_configmap.go b/serveis/caddy/caddy/ingress/internal/controller/action_configmap.go new file mode 100644 index 0000000..99f8c38 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/controller/action_configmap.go @@ -0,0 +1,71 @@ +package controller + +import ( + "github.com/caddyserver/ingress/pkg/store" + v1 "k8s.io/api/core/v1" +) + +// ConfigMapAddedAction provides an implementation of the action interface. +type ConfigMapAddedAction struct { + resource *v1.ConfigMap +} + +// ConfigMapUpdatedAction provides an implementation of the action interface. +type ConfigMapUpdatedAction struct { + resource *v1.ConfigMap + oldResource *v1.ConfigMap +} + +// ConfigMapDeletedAction provides an implementation of the action interface. +type ConfigMapDeletedAction struct { + resource *v1.ConfigMap +} + +// onConfigMapAdded runs when a configmap is added to the namespace. +func (c *CaddyController) onConfigMapAdded(obj *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapAddedAction{ + resource: obj, + }) +} + +// onConfigMapUpdated is run when a configmap is updated in the namespace. +func (c *CaddyController) onConfigMapUpdated(old *v1.ConfigMap, new *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapUpdatedAction{ + resource: new, + oldResource: old, + }) +} + +// onConfigMapDeleted is run when a configmap is deleted from the namespace. +func (c *CaddyController) onConfigMapDeleted(obj *v1.ConfigMap) { + c.syncQueue.Add(ConfigMapDeletedAction{ + resource: obj, + }) +} + +func (r ConfigMapAddedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap created (%s/%s)", r.resource.Namespace, r.resource.Name) + + cfg, err := store.ParseConfigMap(r.resource) + if err == nil { + c.resourceStore.ConfigMap = cfg + } + return err +} + +func (r ConfigMapUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap updated (%s/%s)", r.resource.Namespace, r.resource.Name) + + cfg, err := store.ParseConfigMap(r.resource) + if err == nil { + c.resourceStore.ConfigMap = cfg + } + return err +} + +func (r ConfigMapDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("ConfigMap deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + + c.resourceStore.ConfigMap = nil + return nil +} diff --git a/serveis/caddy/caddy/ingress/internal/controller/action_ingress.go b/serveis/caddy/caddy/ingress/internal/controller/action_ingress.go new file mode 100644 index 0000000..0e55c62 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/controller/action_ingress.go @@ -0,0 +1,70 @@ +package controller + +import ( + v1 "k8s.io/api/networking/v1" +) + +// IngressAddedAction provides an implementation of the action interface. +type IngressAddedAction struct { + resource *v1.Ingress +} + +// IngressUpdatedAction provides an implementation of the action interface. +type IngressUpdatedAction struct { + resource *v1.Ingress + oldResource *v1.Ingress +} + +// IngressDeletedAction provides an implementation of the action interface. +type IngressDeletedAction struct { + resource *v1.Ingress +} + +// onIngressAdded runs when an ingress resource is added to the cluster. +func (c *CaddyController) onIngressAdded(obj *v1.Ingress) { + c.syncQueue.Add(IngressAddedAction{ + resource: obj, + }) +} + +// onIngressUpdated is run when an ingress resource is updated in the cluster. +func (c *CaddyController) onIngressUpdated(old *v1.Ingress, new *v1.Ingress) { + c.syncQueue.Add(IngressUpdatedAction{ + resource: new, + oldResource: old, + }) +} + +// onIngressDeleted is run when an ingress resource is deleted from the cluster. +func (c *CaddyController) onIngressDeleted(obj *v1.Ingress) { + c.syncQueue.Add(IngressDeletedAction{ + resource: obj, + }) +} + +func (r IngressAddedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress created (%s/%s)", r.resource.Namespace, r.resource.Name) + // add this ingress to the internal store + c.resourceStore.AddIngress(r.resource) + + // Ingress may now have a TLS config + return c.watchTLSSecrets() +} + +func (r IngressUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress updated (%s/%s)", r.resource.Namespace, r.resource.Name) + + // add or update this ingress in the internal store + c.resourceStore.AddIngress(r.resource) + + // Ingress may now have a TLS config + return c.watchTLSSecrets() +} + +func (r IngressDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("Ingress deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + + // delete all resources from caddy config that are associated with this resource + c.resourceStore.PluckIngress(r.resource) + return nil +} diff --git a/serveis/caddy/caddy/ingress/internal/controller/action_status.go b/serveis/caddy/caddy/ingress/internal/controller/action_status.go new file mode 100644 index 0000000..7758d46 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/controller/action_status.go @@ -0,0 +1,139 @@ +package controller + +import ( + "net" + "sort" + "strings" + + "github.com/caddyserver/ingress/internal/k8s" + "go.uber.org/zap" + "gopkg.in/go-playground/pool.v3" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/client-go/kubernetes" +) + +// dispatchSync is run every syncInterval duration to sync ingress source address fields. +func (c *CaddyController) dispatchSync() { + c.syncQueue.Add(SyncStatusAction{}) +} + +// SyncStatusAction provides an implementation of the action interface. +type SyncStatusAction struct { +} + +// handle is run when a syncStatusAction appears in the queue. +func (r SyncStatusAction) handle(c *CaddyController) error { + return c.syncStatus(c.resourceStore.Ingresses) +} + +// syncStatus ensures that the ingress source address points to this ingress controller's IP address. +func (c *CaddyController) syncStatus(ings []*networkingv1.Ingress) error { + addrs, err := k8s.GetAddresses(c.resourceStore.CurrentPod, c.kubeClient) + if err != nil { + return err + } + + c.logger.Debugf("Syncing %d Ingress resources source addresses", len(ings)) + c.updateIngStatuses(sliceToLoadBalancerIngress(addrs), ings) + + return nil +} + +// updateIngStatuses starts a queue and adds all monitored ingresses to update their status source address to the on +// that the ingress controller is running on. This is called by the syncStatus queue. +func (c *CaddyController) updateIngStatuses(controllerAddresses []networkingv1.IngressLoadBalancerIngress, ings []*networkingv1.Ingress) { + p := pool.NewLimited(10) + defer p.Close() + + batch := p.Batch() + sort.SliceStable(controllerAddresses, lessLoadBalancerIngress(controllerAddresses)) + + for _, ing := range ings { + curIPs := ing.Status.LoadBalancer.Ingress + sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs)) + + // check to see if ingresses source address does not match the ingress controller's. + if ingressSliceEqual(curIPs, controllerAddresses) { + c.logger.Debugf("skipping update of Ingress %v/%v (no change)", ing.Namespace, ing.Name) + continue + } + + batch.Queue(runUpdate(c.logger, ing, controllerAddresses, c.kubeClient)) + } + + batch.QueueComplete() + batch.WaitAll() +} + +// runUpdate updates the ingress status field. +func runUpdate(logger *zap.SugaredLogger, ing *networkingv1.Ingress, status []networkingv1.IngressLoadBalancerIngress, client *kubernetes.Clientset) pool.WorkFunc { + return func(wu pool.WorkUnit) (interface{}, error) { + if wu.IsCancelled() { + return nil, nil + } + + updated, err := k8s.UpdateIngressStatus(client, ing, status) + if err != nil { + logger.Warnf("error updating ingress rule: %v", err) + } else { + logger.Debugf( + "updating Ingress %v/%v status from %v to %v", + ing.Namespace, + ing.Name, + ing.Status.LoadBalancer.Ingress, + updated.Status.LoadBalancer.Ingress, + ) + } + + return true, nil + } +} + +// ingressSliceEqual determines if the ingress source matches the ingress controller's. +func ingressSliceEqual(lhs, rhs []networkingv1.IngressLoadBalancerIngress) bool { + if len(lhs) != len(rhs) { + return false + } + + for i := range lhs { + if lhs[i].IP != rhs[i].IP { + return false + } + if lhs[i].Hostname != rhs[i].Hostname { + return false + } + } + + return true +} + +// lessLoadBalancerIngress is a sorting function for ingress hostnames. +func lessLoadBalancerIngress(addrs []networkingv1.IngressLoadBalancerIngress) func(int, int) bool { + return func(a, b int) bool { + switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) { + case -1: + return true + case 1: + return false + } + return addrs[a].IP < addrs[b].IP + } +} + +// sliceToLoadBalancerIngress converts a slice of IP and/or hostnames to LoadBalancerIngress +func sliceToLoadBalancerIngress(endpoints []string) []networkingv1.IngressLoadBalancerIngress { + lbi := []networkingv1.IngressLoadBalancerIngress{} + for _, ep := range endpoints { + if net.ParseIP(ep) == nil { + lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{Hostname: ep}) + } else { + lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{IP: ep}) + } + } + + sort.SliceStable(lbi, func(a, b int) bool { + return lbi[a].IP < lbi[b].IP + }) + + return lbi +} diff --git a/serveis/caddy/caddy/ingress/internal/controller/action_tls.go b/serveis/caddy/caddy/ingress/internal/controller/action_tls.go new file mode 100644 index 0000000..dc674d3 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/controller/action_tls.go @@ -0,0 +1,124 @@ +package controller + +import ( + "os" + "path/filepath" + + "github.com/caddyserver/ingress/internal/k8s" + apiv1 "k8s.io/api/core/v1" +) + +var CertFolder = filepath.FromSlash("/etc/caddy/certs") + +// SecretAddedAction provides an implementation of the action interface. +type SecretAddedAction struct { + resource *apiv1.Secret +} + +// SecretUpdatedAction provides an implementation of the action interface. +type SecretUpdatedAction struct { + resource *apiv1.Secret + oldResource *apiv1.Secret +} + +// SecretDeletedAction provides an implementation of the action interface. +type SecretDeletedAction struct { + resource *apiv1.Secret +} + +// onSecretAdded runs when a TLS secret resource is added to the cluster. +func (c *CaddyController) onSecretAdded(obj *apiv1.Secret) { + if k8s.IsManagedTLSSecret(obj, c.resourceStore.Ingresses) { + c.syncQueue.Add(SecretAddedAction{ + resource: obj, + }) + } +} + +// onSecretUpdated is run when a TLS secret resource is updated in the cluster. +func (c *CaddyController) onSecretUpdated(old *apiv1.Secret, new *apiv1.Secret) { + if k8s.IsManagedTLSSecret(new, c.resourceStore.Ingresses) { + c.syncQueue.Add(SecretUpdatedAction{ + resource: new, + oldResource: old, + }) + } +} + +// onSecretDeleted is run when a TLS secret resource is deleted from the cluster. +func (c *CaddyController) onSecretDeleted(obj *apiv1.Secret) { + c.syncQueue.Add(SecretDeletedAction{ + resource: obj, + }) +} + +// writeFile writes a secret to a .pem file on disk. +func writeFile(s *apiv1.Secret) error { + content := make([]byte, 0) + + for _, cert := range s.Data { + content = append(content, cert...) + } + + err := os.WriteFile(filepath.Join(CertFolder, s.Name+".pem"), content, 0644) + if err != nil { + return err + } + + return nil +} + +func (r SecretAddedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret created (%s/%s)", r.resource.Namespace, r.resource.Name) + return writeFile(r.resource) +} + +func (r SecretUpdatedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret updated (%s/%s)", r.resource.Namespace, r.resource.Name) + return writeFile(r.resource) +} + +func (r SecretDeletedAction) handle(c *CaddyController) error { + c.logger.Infof("TLS secret deleted (%s/%s)", r.resource.Namespace, r.resource.Name) + return os.Remove(filepath.Join(CertFolder, r.resource.Name+".pem")) +} + +// watchTLSSecrets Start listening to TLS secrets if at least one ingress needs it. +// It will sync the CertFolder with TLS secrets +func (c *CaddyController) watchTLSSecrets() error { + if c.informers.TLSSecret == nil && c.resourceStore.HasManagedTLS() { + // Init informers + params := k8s.TLSSecretParams{ + InformerFactory: c.factories.WatchedNamespace, + } + c.informers.TLSSecret = k8s.WatchTLSSecrets(params, k8s.TLSSecretHandlers{ + AddFunc: c.onSecretAdded, + UpdateFunc: c.onSecretUpdated, + DeleteFunc: c.onSecretDeleted, + }) + + // Run it + go c.informers.TLSSecret.Run(c.stopChan) + + // Sync secrets + secrets, err := k8s.ListTLSSecrets(params, c.resourceStore.Ingresses) + if err != nil { + return err + } + + if _, err := os.Stat(CertFolder); os.IsNotExist(err) { + err = os.MkdirAll(CertFolder, 0755) + if err != nil { + return err + } + } + + for _, secret := range secrets { + if err := writeFile(secret); err != nil { + return err + } + } + } + + return nil +} diff --git a/serveis/caddy/caddy/ingress/internal/controller/controller.go b/serveis/caddy/caddy/ingress/internal/controller/controller.go new file mode 100644 index 0000000..3379c15 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/controller/controller.go @@ -0,0 +1,272 @@ +package controller + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/certmagic" + "github.com/caddyserver/ingress/internal/k8s" + "github.com/caddyserver/ingress/pkg/store" + "go.uber.org/zap" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + // load required caddy plugins + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + _ "github.com/caddyserver/caddy/v2/modules/caddytls" + _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek" + _ "github.com/caddyserver/caddy/v2/modules/metrics" + _ "github.com/caddyserver/ingress/pkg/storage" +) + +const ( + // how often we should attempt to keep ingress resource's source address in sync + syncInterval = time.Second * 30 + + // how often we resync informers resources (besides receiving updates) + resourcesSyncInterval = time.Hour * 1 +) + +// Action is an interface for ingress actions. +type Action interface { + handle(c *CaddyController) error +} + +// Informer defines the required SharedIndexInformers that interact with the API server. +type Informer struct { + Ingress cache.SharedIndexInformer + ConfigMap cache.SharedIndexInformer + TLSSecret cache.SharedIndexInformer +} + +// InformerFactory contains shared informer factory +// We need to type of factory: +// - One used to watch resources in the Pod namespaces (caddy config, secrets...) +// - Another one for Ingress resources in the selected namespace +type InformerFactory struct { + PodNamespace informers.SharedInformerFactory + WatchedNamespace informers.SharedInformerFactory +} + +type Converter interface { + ConvertToCaddyConfig(store *store.Store) (interface{}, error) +} + +// CaddyController represents a caddy ingress controller. +type CaddyController struct { + resourceStore *store.Store + + kubeClient *kubernetes.Clientset + + logger *zap.SugaredLogger + + // main queue syncing ingresses, configmaps, ... with caddy + syncQueue workqueue.RateLimitingInterface + + // informer factories + factories *InformerFactory + + // informer contains the cache Informers + informers *Informer + + // save last applied caddy config + lastAppliedConfig []byte + + converter Converter + + stopChan chan struct{} +} + +func NewCaddyController( + logger *zap.SugaredLogger, + kubeClient *kubernetes.Clientset, + opts store.Options, + converter Converter, + stopChan chan struct{}, +) *CaddyController { + controller := &CaddyController{ + logger: logger, + kubeClient: kubeClient, + converter: converter, + stopChan: stopChan, + syncQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), + informers: &Informer{}, + factories: &InformerFactory{}, + } + + podInfo, err := k8s.GetPodDetails(kubeClient) + if err != nil { + logger.Fatalf("Unexpected error obtaining pod information: %v", err) + } + + // Create informer factories + controller.factories.PodNamespace = informers.NewSharedInformerFactoryWithOptions( + kubeClient, + resourcesSyncInterval, + informers.WithNamespace(podInfo.Namespace), + ) + controller.factories.WatchedNamespace = informers.NewSharedInformerFactoryWithOptions( + kubeClient, + resourcesSyncInterval, + informers.WithNamespace(opts.WatchNamespace), + ) + + // Watch ingress resources in selected namespaces + ingressParams := k8s.IngressParams{ + InformerFactory: controller.factories.WatchedNamespace, + ClassName: opts.ClassName, + ClassNameRequired: opts.ClassNameRequired, + } + controller.informers.Ingress = k8s.WatchIngresses(ingressParams, k8s.IngressHandlers{ + AddFunc: controller.onIngressAdded, + UpdateFunc: controller.onIngressUpdated, + DeleteFunc: controller.onIngressDeleted, + }) + + // Watch Configmap in the pod's namespace for global options + cmOptionsParams := k8s.ConfigMapParams{ + Namespace: podInfo.Namespace, + InformerFactory: controller.factories.PodNamespace, + ConfigMapName: opts.ConfigMapName, + } + controller.informers.ConfigMap = k8s.WatchConfigMaps(cmOptionsParams, k8s.ConfigMapHandlers{ + AddFunc: controller.onConfigMapAdded, + UpdateFunc: controller.onConfigMapUpdated, + DeleteFunc: controller.onConfigMapDeleted, + }) + + // Create resource store + controller.resourceStore = store.NewStore(opts, podInfo) + + return controller +} + +// Shutdown stops the caddy controller. +func (c *CaddyController) Shutdown() error { + // remove this ingress controller's ip from ingress resources. + c.updateIngStatuses([]networkingv1.IngressLoadBalancerIngress{{}}, c.resourceStore.Ingresses) + + if err := caddy.Stop(); err != nil { + c.logger.Error("failed to stop caddy server", zap.Error(err)) + return err + } + certmagic.CleanUpOwnLocks(context.TODO(), c.logger.Desugar()) + return nil +} + +// Run method starts the ingress controller. +func (c *CaddyController) Run() { + defer runtime.HandleCrash() + defer c.syncQueue.ShutDown() + + // start informers where we listen to new / updated resources + go c.informers.ConfigMap.Run(c.stopChan) + go c.informers.Ingress.Run(c.stopChan) + + // wait for all involved caches to be synced before processing items + // from the queue + if !cache.WaitForCacheSync(c.stopChan, + c.informers.ConfigMap.HasSynced, + c.informers.Ingress.HasSynced, + ) { + runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) + } + + // start processing events for syncing ingress resources + go wait.Until(c.runWorker, time.Second, c.stopChan) + + // start ingress status syncher and run every syncInterval + go wait.Until(c.dispatchSync, syncInterval, c.stopChan) + + // wait for SIGTERM + <-c.stopChan + c.logger.Info("stopping ingress controller") + + var exitCode int + err := c.Shutdown() + if err != nil { + c.logger.Error("could not shutdown ingress controller properly, " + err.Error()) + exitCode = 1 + } + + os.Exit(exitCode) +} + +// runWorker processes items in the event queue. +func (c *CaddyController) runWorker() { + for c.processNextItem() { + } +} + +// processNextItem determines if there is an ingress item in the event queue and processes it. +func (c *CaddyController) processNextItem() bool { + // Wait until there is a new item in the working queue + action, quit := c.syncQueue.Get() + if quit { + return false + } + + // Tell the queue that we are done with processing this key. This unblocks the key for other workers + // This allows safe parallel processing because two ingresses with the same key are never processed in + // parallel. + defer c.syncQueue.Done(action) + + // Invoke the method containing the business logic + err := action.(Action).handle(c) + if err != nil { + c.handleErr(err, action) + return true + } + + err = c.reloadCaddy() + if err != nil { + c.logger.Error("could not reload caddy: " + err.Error()) + return true + } + + return true +} + +// handleErrs reports errors received from queue actions. +// +//goland:noinspection GoUnusedParameter +func (c *CaddyController) handleErr(err error, action interface{}) { + c.logger.Error(err.Error()) +} + +// reloadCaddy generate a caddy config from controller's store +func (c *CaddyController) reloadCaddy() error { + config, err := c.converter.ConvertToCaddyConfig(c.resourceStore) + if err != nil { + return err + } + + j, err := json.Marshal(config) + if err != nil { + return err + } + + if bytes.Equal(c.lastAppliedConfig, j) { + c.logger.Debug("caddy config did not change, skipping reload") + return nil + } + + c.logger.Debug("reloading caddy with config", string(j)) + err = caddy.Load(j, false) + if err != nil { + return fmt.Errorf("could not reload caddy config %v", err.Error()) + } + c.lastAppliedConfig = j + return nil +} diff --git a/serveis/caddy/caddy/ingress/internal/k8s/configmap.go b/serveis/caddy/caddy/ingress/internal/k8s/configmap.go new file mode 100644 index 0000000..3f47dea --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/k8s/configmap.go @@ -0,0 +1,54 @@ +package k8s + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +type ConfigMapHandlers struct { + AddFunc func(obj *v1.ConfigMap) + UpdateFunc func(oldObj, newObj *v1.ConfigMap) + DeleteFunc func(obj *v1.ConfigMap) +} + +type ConfigMapParams struct { + Namespace string + InformerFactory informers.SharedInformerFactory + ConfigMapName string +} + +func isControllerConfigMap(cm *v1.ConfigMap, name string) bool { + return cm.GetName() == name +} + +func WatchConfigMaps(options ConfigMapParams, funcs ConfigMapHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Core().V1().ConfigMaps().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + cm, ok := obj.(*v1.ConfigMap) + + if ok && isControllerConfigMap(cm, options.ConfigMapName) { + funcs.AddFunc(cm) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldCM, ok1 := oldObj.(*v1.ConfigMap) + newCM, ok2 := newObj.(*v1.ConfigMap) + + if ok1 && ok2 && isControllerConfigMap(newCM, options.ConfigMapName) { + funcs.UpdateFunc(oldCM, newCM) + } + }, + DeleteFunc: func(obj interface{}) { + cm, ok := obj.(*v1.ConfigMap) + + if ok && isControllerConfigMap(cm, options.ConfigMapName) { + funcs.DeleteFunc(cm) + } + }, + }) + + return informer +} diff --git a/serveis/caddy/caddy/ingress/internal/k8s/ingress.go b/serveis/caddy/caddy/ingress/internal/k8s/ingress.go new file mode 100644 index 0000000..1b871c5 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/k8s/ingress.go @@ -0,0 +1,82 @@ +package k8s + +import ( + "context" + "fmt" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +type IngressHandlers struct { + AddFunc func(obj *networkingv1.Ingress) + UpdateFunc func(oldObj, newObj *networkingv1.Ingress) + DeleteFunc func(obj *networkingv1.Ingress) +} + +type IngressParams struct { + InformerFactory informers.SharedInformerFactory + ClassName string + ClassNameRequired bool +} + +func WatchIngresses(options IngressParams, funcs IngressHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Networking().V1().Ingresses().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + ingress, ok := obj.(*networkingv1.Ingress) + + if ok && isControllerIngress(options, ingress) { + funcs.AddFunc(ingress) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldIng, ok1 := oldObj.(*networkingv1.Ingress) + newIng, ok2 := newObj.(*networkingv1.Ingress) + + if ok1 && ok2 && isControllerIngress(options, newIng) { + funcs.UpdateFunc(oldIng, newIng) + } + }, + DeleteFunc: func(obj interface{}) { + ingress, ok := obj.(*networkingv1.Ingress) + + if ok && isControllerIngress(options, ingress) { + funcs.DeleteFunc(ingress) + } + }, + }) + + return informer +} + +// isControllerIngress check if the ingress object can be controlled by us +func isControllerIngress(options IngressParams, ingress *networkingv1.Ingress) bool { + ingressClass := ingress.Annotations["kubernetes.io/ingress.class"] + if ingressClass == "" && ingress.Spec.IngressClassName != nil { + ingressClass = *ingress.Spec.IngressClassName + } + + if !options.ClassNameRequired && ingressClass == "" { + return true + } + + return ingressClass == options.ClassName +} + +func UpdateIngressStatus(kubeClient *kubernetes.Clientset, ing *networkingv1.Ingress, status []networkingv1.IngressLoadBalancerIngress) (*networkingv1.Ingress, error) { + ingClient := kubeClient.NetworkingV1().Ingresses(ing.Namespace) + + currIng, err := ingClient.Get(context.TODO(), ing.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("unexpected error searching Ingress %v/%v: %w", ing.Namespace, ing.Name, err) + } + + currIng.Status.LoadBalancer.Ingress = status + + return ingClient.UpdateStatus(context.TODO(), currIng, metav1.UpdateOptions{}) +} diff --git a/serveis/caddy/caddy/ingress/internal/k8s/pod.go b/serveis/caddy/caddy/ingress/internal/k8s/pod.go new file mode 100644 index 0000000..3246d61 --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/k8s/pod.go @@ -0,0 +1,99 @@ +package k8s + +import ( + "context" + "fmt" + "os" + + "github.com/caddyserver/ingress/pkg/store" + + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" +) + +// GetAddresses gets the ip address or name of the node in the cluster that the +// ingress controller is running on. +func GetAddresses(p *store.PodInfo, kubeClient *kubernetes.Clientset) ([]string, error) { + var addrs []string + + // Get services that may select this pod + svcs, err := kubeClient.CoreV1().Services(p.Namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + for _, svc := range svcs.Items { + if isSubset(svc.Spec.Selector, p.Labels) { + addr := GetAddressFromService(&svc) + if addr != "" { + addrs = append(addrs, addr) + } + } + } + + return addrs, nil +} + +// Copied from https://github.com/kubernetes/kubernetes/pull/95179 +func isSubset(subSet, superSet labels.Set) bool { + if len(superSet) == 0 { + return true + } + + for k, v := range subSet { + value, ok := superSet[k] + if !ok { + return false + } + if value != v { + return false + } + } + return true +} + +// GetAddressFromService returns the IP address or the name of a node in the cluster +func GetAddressFromService(service *apiv1.Service) string { + switch service.Spec.Type { + case apiv1.ServiceTypeNodePort: + case apiv1.ServiceTypeClusterIP: + if service.Spec.ClusterIP != apiv1.ClusterIPNone { + return service.Spec.ClusterIP + } + case apiv1.ServiceTypeExternalName: + return service.Spec.ExternalName + case apiv1.ServiceTypeLoadBalancer: + if len(service.Status.LoadBalancer.Ingress) > 0 { + ingress := service.Status.LoadBalancer.Ingress[0] + if ingress.Hostname != "" { + return ingress.Hostname + } + return ingress.IP + } + } + return "" +} + +// GetPodDetails returns runtime information about the pod: +// name, namespace and IP of the node where it is running +func GetPodDetails(kubeClient *kubernetes.Clientset) (*store.PodInfo, error) { + podName := os.Getenv("POD_NAME") + podNs := os.Getenv("POD_NAMESPACE") + + if podName == "" || podNs == "" { + return nil, fmt.Errorf("unable to get POD information (missing POD_NAME or POD_NAMESPACE environment variable") + } + + pod, _ := kubeClient.CoreV1().Pods(podNs).Get(context.TODO(), podName, metav1.GetOptions{}) + if pod == nil { + return nil, fmt.Errorf("unable to get POD information") + } + + return &store.PodInfo{ + Name: podName, + Namespace: podNs, + Labels: pod.GetLabels(), + }, nil +} diff --git a/serveis/caddy/caddy/ingress/internal/k8s/tls_secret.go b/serveis/caddy/caddy/ingress/internal/k8s/tls_secret.go new file mode 100644 index 0000000..b1323ce --- /dev/null +++ b/serveis/caddy/caddy/ingress/internal/k8s/tls_secret.go @@ -0,0 +1,76 @@ +package k8s + +import ( + v12 "k8s.io/api/core/v1" + v1 "k8s.io/api/networking/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +type TLSSecretHandlers struct { + AddFunc func(obj *v12.Secret) + UpdateFunc func(oldObj, newObj *v12.Secret) + DeleteFunc func(obj *v12.Secret) +} + +type TLSSecretParams struct { + InformerFactory informers.SharedInformerFactory +} + +func WatchTLSSecrets(options TLSSecretParams, funcs TLSSecretHandlers) cache.SharedIndexInformer { + informer := options.InformerFactory.Core().V1().Secrets().Informer() + + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + secret, ok := obj.(*v12.Secret) + + if ok && secret.Type == v12.SecretTypeTLS { + funcs.AddFunc(secret) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldSecret, ok1 := oldObj.(*v12.Secret) + newSecret, ok2 := newObj.(*v12.Secret) + + if ok1 && ok2 && newSecret.Type == v12.SecretTypeTLS { + funcs.UpdateFunc(oldSecret, newSecret) + } + }, + DeleteFunc: func(obj interface{}) { + secret, ok := obj.(*v12.Secret) + + if ok && secret.Type == v12.SecretTypeTLS { + funcs.DeleteFunc(secret) + } + }, + }) + + return informer +} + +func ListTLSSecrets(options TLSSecretParams, ings []*v1.Ingress) ([]*v12.Secret, error) { + lister := options.InformerFactory.Core().V1().Secrets().Lister() + + tlsSecrets := []*v12.Secret{} + for _, ing := range ings { + for _, tlsRule := range ing.Spec.TLS { + secret, err := lister.Secrets(ing.Namespace).Get(tlsRule.SecretName) + // TODO Handle errors + if err == nil { + tlsSecrets = append(tlsSecrets, secret) + } + } + } + return tlsSecrets, nil +} + +func IsManagedTLSSecret(secret *v12.Secret, ings []*v1.Ingress) bool { + for _, ing := range ings { + for _, tlsRule := range ing.Spec.TLS { + if tlsRule.SecretName == secret.Name && ing.Namespace == secret.Namespace { + return true + } + } + } + return false +} diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-configmap.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-configmap.yaml new file mode 100644 index 0000000..8bf139d --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-global-options + namespace: caddy-system +data: + acmeCA: internal + # email: test@example.com + # debug: "false" diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment1.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment1.yaml new file mode 100644 index 0000000..0be7ff0 --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment1.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example1 + labels: + app: example1 +spec: + replicas: 1 + selector: + matchLabels: + app: example1 + template: + metadata: + labels: + app: example1 + spec: + containers: + - name: httpecho + image: hashicorp/http-echo + args: + - "-listen=:8080" + - '-text="hello world 1"' + ports: + - containerPort: 8080 diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment2.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment2.yaml new file mode 100644 index 0000000..d7438cc --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-deployment2.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example2 + labels: + app: example2 +spec: + replicas: 1 + selector: + matchLabels: + app: example2 + template: + metadata: + labels: + app: example2 + spec: + containers: + - name: httpecho + image: hashicorp/http-echo + args: + - "-listen=:8080" + - '-text="hello world 2"' + ports: + - containerPort: 8080 \ No newline at end of file diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-ingress.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-ingress.yaml new file mode 100644 index 0000000..66dec2c --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-ingress.yaml @@ -0,0 +1,46 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example + annotations: + kubernetes.io/ingress.class: caddy +spec: + rules: + - host: example1.kubernetes.localhost + http: + paths: + - path: /hello1 + pathType: Prefix + backend: + service: + name: example1 + port: + number: 8080 + - path: /hello2 + pathType: Prefix + backend: + service: + name: example2 + port: + number: 8080 + - host: example2.kubernetes.localhost + http: + paths: + - path: /hello1 + pathType: Prefix + backend: + service: + name: example1 + port: + number: 8080 + - path: /hello2 + pathType: Prefix + backend: + service: + name: example2 + port: + number: 8080 +# tls: +# - secretName: ssl-example2.kubernetes.localhost +# hosts: +# - example2.caddy.dev diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-service1.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-service1.yaml new file mode 100644 index 0000000..4d59b75 --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-service1.yaml @@ -0,0 +1,13 @@ +kind: Service +apiVersion: v1 +metadata: + name: example1 +spec: + type: ClusterIP + selector: + app: example1 + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/serveis/caddy/caddy/ingress/kubernetes/sample/example-service2.yaml b/serveis/caddy/caddy/ingress/kubernetes/sample/example-service2.yaml new file mode 100644 index 0000000..3247795 --- /dev/null +++ b/serveis/caddy/caddy/ingress/kubernetes/sample/example-service2.yaml @@ -0,0 +1,13 @@ +kind: Service +apiVersion: v1 +metadata: + name: example2 +spec: + type: ClusterIP + selector: + app: example2 + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/serveis/caddy/caddy/ingress/pkg/converter/config.go b/serveis/caddy/caddy/ingress/pkg/converter/config.go new file mode 100644 index 0000000..ffb7805 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/converter/config.go @@ -0,0 +1,65 @@ +package converter + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +// StorageValues represents the config for certmagic storage providers. +type StorageValues struct { + Namespace string `json:"namespace"` + LeaseId string `json:"leaseId"` +} + +// Storage represents the certmagic storage configuration. +type Storage struct { + System string `json:"module"` + StorageValues +} + +// Config represents a caddy2 config file. +type Config struct { + Admin caddy.AdminConfig `json:"admin,omitempty"` + Storage Storage `json:"storage"` + Apps map[string]interface{} `json:"apps"` + Logging caddy.Logging `json:"logging"` +} + +func (c Config) GetHTTPServer() *caddyhttp.Server { + return c.Apps["http"].(*caddyhttp.App).Servers[HttpServer] +} + +func (c Config) GetMetricsServer() *caddyhttp.Server { + return c.Apps["http"].(*caddyhttp.App).Servers[MetricsServer] +} + +func (c Config) GetTLSApp() *caddytls.TLS { + return c.Apps["tls"].(*caddytls.TLS) +} + +func NewConfig() *Config { + return &Config{ + Logging: caddy.Logging{}, + Apps: map[string]interface{}{ + "tls": &caddytls.TLS{CertificatesRaw: caddy.ModuleMap{}}, + "http": &caddyhttp.App{ + Servers: map[string]*caddyhttp.Server{ + HttpServer: { + AutoHTTPS: &caddyhttp.AutoHTTPSConfig{}, + // Listen to both :80 and :443 ports in order + // to use the same listener wrappers (PROXY protocol use it) + Listen: []string{":80", ":443"}, + TLSConnPolicies: caddytls.ConnectionPolicies{ + &caddytls.ConnectionPolicy{}, + }, + }, + MetricsServer: { + Listen: []string{":9765"}, + AutoHTTPS: &caddyhttp.AutoHTTPSConfig{Disabled: true}, + }, + }, + }, + }, + } +} diff --git a/serveis/caddy/caddy/ingress/pkg/converter/converter.go b/serveis/caddy/caddy/ingress/pkg/converter/converter.go new file mode 100644 index 0000000..a020757 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/converter/converter.go @@ -0,0 +1,113 @@ +package converter + +import ( + "fmt" + "sort" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/store" + v1 "k8s.io/api/networking/v1" +) + +const ( + HttpServer = "ingress_server" + MetricsServer = "metrics_server" +) + +// GlobalMiddleware is called with a default caddy config +// already configured with: +// - Secret storage store +// - A TLS App (https://caddyserver.com/docs/json/apps/tls/) +// - A HTTP App with an HTTP server listening to 80 443 ports (https://caddyserver.com/docs/json/apps/http/) +type GlobalMiddleware interface { + GlobalHandler(config *Config, store *store.Store) error +} + +type IngressMiddlewareInput struct { + Config *Config + Store *store.Store + Ingress *v1.Ingress + Rule v1.IngressRule + Path v1.HTTPIngressPath + Route *caddyhttp.Route +} + +// IngressMiddleware is called for each Caddy route that is generated for a specific +// ingress. It allows anyone to manipulate caddy routes before sending it to caddy. +type IngressMiddleware interface { + IngressHandler(input IngressMiddlewareInput) (*caddyhttp.Route, error) +} + +type Plugin interface { + IngressPlugin() PluginInfo +} + +type PluginInfo struct { + Name string + Priority int + New func() Plugin +} + +func RegisterPlugin(m Plugin) { + plugin := m.IngressPlugin() + + if _, ok := plugins[plugin.Name]; ok { + panic(fmt.Sprintf("plugin already registered: %s", plugin.Name)) + } + plugins[plugin.Name] = plugin + pluginInstances[plugin.Name] = plugin.New() +} + +func getOrderIndex(order []string, plugin string) int { + for idx, o := range order { + if plugin == o { + return idx + } + } + return -1 +} + +func sortPlugins(plugins []PluginInfo, order []string) []PluginInfo { + sort.SliceStable(plugins, func(i, j int) bool { + iPlugin, jPlugin := plugins[i], plugins[j] + + iSortedIdx := getOrderIndex(order, iPlugin.Name) + jSortedIdx := getOrderIndex(order, jPlugin.Name) + + if iSortedIdx != jSortedIdx { + return iSortedIdx > jSortedIdx + } + + if iPlugin.Priority != jPlugin.Priority { + return iPlugin.Priority > jPlugin.Priority + } + return iPlugin.Name < jPlugin.Name + + }) + return plugins +} + +// Plugins return a sorted array of plugin instances. +// Sort is made following these rules: +// - Plugins specified in the order slice will always go first (in the order specified in the slice) +// - A Plugin with higher priority will go before a plugin with lower priority +// - If 2 plugins have the same priority (and not in order slice), they will be sorted by plugin name +func Plugins(order []string) []Plugin { + sortedPlugins := make([]PluginInfo, 0, len(plugins)) + for _, p := range plugins { + sortedPlugins = append(sortedPlugins, p) + } + + sortPlugins(sortedPlugins, order) + + pluginArr := make([]Plugin, 0, len(plugins)) + for _, p := range sortedPlugins { + pluginArr = append(pluginArr, pluginInstances[p.Name]) + } + return pluginArr +} + +var ( + plugins = make(map[string]PluginInfo) + pluginInstances = make(map[string]Plugin) +) diff --git a/serveis/caddy/caddy/ingress/pkg/converter/converter_test.go b/serveis/caddy/caddy/ingress/pkg/converter/converter_test.go new file mode 100644 index 0000000..d215562 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/converter/converter_test.go @@ -0,0 +1,53 @@ +package converter + +import "testing" + +func TestSortPlugins(t *testing.T) { + tests := []struct { + name string + order []string + plugins []PluginInfo + expect []string + }{ + { + name: "default to alpha sort", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "c"}, {Name: "a"}}, + expect: []string{"a", "b", "c"}, + }, + { + name: "use priority when specified", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "a", Priority: 20}, {Name: "c", Priority: 10}}, + expect: []string{"a", "c", "b"}, + }, + { + name: "fallback to alpha when no priority", + order: nil, + plugins: []PluginInfo{{Name: "b"}, {Name: "a"}, {Name: "c", Priority: 20}}, + expect: []string{"c", "a", "b"}, + }, + { + name: "specify order", + order: []string{"c"}, + plugins: []PluginInfo{{Name: "b"}, {Name: "a"}, {Name: "c"}}, + expect: []string{"c", "a", "b"}, + }, + { + name: "order overrides other settings", + order: []string{"c"}, + plugins: []PluginInfo{{Name: "b", Priority: 10}, {Name: "a"}, {Name: "c"}}, + expect: []string{"c", "b", "a"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sortPlugins(test.plugins, test.order) + for i, plugin := range test.plugins { + if test.expect[i] != plugin.Name { + t.Errorf("expected order to match %v: got %v, expected %v", test.expect, plugin.Name, test.expect[i]) + } + } + }) + } +} diff --git a/serveis/caddy/caddy/ingress/pkg/proxy/proxy.go b/serveis/caddy/caddy/ingress/pkg/proxy/proxy.go new file mode 100644 index 0000000..9b64149 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/proxy/proxy.go @@ -0,0 +1,46 @@ +package proxy + +import ( + "net" + + "github.com/caddyserver/caddy/v2" + "github.com/pires/go-proxyproto" +) + +var ( + _ = caddy.Provisioner(&Wrapper{}) + _ = caddy.Module(&Wrapper{}) + _ = caddy.ListenerWrapper(&Wrapper{}) +) + +func init() { + caddy.RegisterModule(Wrapper{}) +} + +// Wrapper provides PROXY protocol support to Caddy by implementing the caddy.ListenerWrapper interface. +// It must be loaded before the `tls` listener. +// +// Deprecated: This caddy module should be replaced by the included proxy_protocol listener in Caddy. +type Wrapper struct { + policy proxyproto.PolicyFunc +} + +func (Wrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.proxy_protocol", + New: func() caddy.Module { return new(Wrapper) }, + } +} + +func (pp *Wrapper) Provision(ctx caddy.Context) error { + pp.policy = func(upstream net.Addr) (proxyproto.Policy, error) { + return proxyproto.REQUIRE, nil + } + return nil +} + +func (pp *Wrapper) WrapListener(l net.Listener) net.Listener { + pL := &proxyproto.Listener{Listener: l, Policy: pp.policy} + + return pL +} diff --git a/serveis/caddy/caddy/ingress/pkg/storage/storage.go b/serveis/caddy/caddy/ingress/pkg/storage/storage.go new file mode 100644 index 0000000..f13f833 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/storage/storage.go @@ -0,0 +1,290 @@ +package storage + +import ( + "context" + "fmt" + "io/fs" + "regexp" + "strings" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/certmagic" + "github.com/google/uuid" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/leaderelection/resourcelock" +) + +const ( + leaseDuration = 5 * time.Second + leaseRenewInterval = 2 * time.Second + leasePollInterval = 5 * time.Second + leasePrefix = "caddy-lock-" + + keyPrefix = "caddy.ingress--" +) + +// matchLabels are attached to each resource so that they can be found in the future. +var matchLabels = map[string]string{ + "manager": "caddy", +} + +// specialChars is a regex that matches all special characters except '-'. +var specialChars = regexp.MustCompile("[^\\da-zA-Z-]+") + +// cleanKey strips all special characters that are not supported by kubernetes names and converts them to a '.'. +// sequences like '.*.' are also converted to a single '.'. +func cleanKey(key string, prefix string) string { + return prefix + specialChars.ReplaceAllString(key, ".") +} + +// SecretStorage facilitates storing certificates retrieved by certmagic in kubernetes secrets. +type SecretStorage struct { + Namespace string + LeaseId string + + kubeClient *kubernetes.Clientset + logger *zap.Logger +} + +func init() { + caddy.RegisterModule(SecretStorage{}) +} + +func (SecretStorage) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.storage.secret_store", + New: func() caddy.Module { return new(SecretStorage) }, + } +} + +// Provisions the SecretStorage instance. +func (s *SecretStorage) Provision(ctx caddy.Context) error { + config, _ := clientcmd.BuildConfigFromFlags("", "") + // creates the clientset + clientset, _ := kubernetes.NewForConfig(config) + + s.logger = ctx.Logger(s) + s.kubeClient = clientset + if s.LeaseId == "" { + s.LeaseId = uuid.New().String() + } + return nil +} + +// CertMagicStorage returns a certmagic storage type to be used by caddy. +func (s *SecretStorage) CertMagicStorage() (certmagic.Storage, error) { + return s, nil +} + +// Exists returns true if key exists in fs. +func (s *SecretStorage) Exists(ctx context.Context, key string) bool { + s.logger.Debug("finding secret", zap.String("name", key)) + secrets, err := s.kubeClient.CoreV1().Secrets(s.Namespace).List(context.TODO(), metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%v", cleanKey(key, keyPrefix)), + }) + + if err != nil { + return false + } + + var found bool + for _, i := range secrets.Items { + if i.ObjectMeta.Name == cleanKey(key, keyPrefix) { + found = true + break + } + } + + return found +} + +// Store saves value at key. More than certs and keys are stored by certmagic in secrets. +func (s *SecretStorage) Store(ctx context.Context, key string, value []byte) error { + se := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cleanKey(key, keyPrefix), + Labels: matchLabels, + }, + Data: map[string][]byte{ + "value": value, + }, + } + + var err error + if s.Exists(ctx, key) { + s.logger.Debug("creating secret", zap.String("name", key)) + _, err = s.kubeClient.CoreV1().Secrets(s.Namespace).Update(context.TODO(), &se, metav1.UpdateOptions{}) + } else { + s.logger.Debug("updating secret", zap.String("name", key)) + _, err = s.kubeClient.CoreV1().Secrets(s.Namespace).Create(context.TODO(), &se, metav1.CreateOptions{}) + } + + if err != nil { + return err + } + + return nil +} + +// Load retrieves the value at the given key. +func (s *SecretStorage) Load(ctx context.Context, key string) ([]byte, error) { + secret, err := s.kubeClient.CoreV1().Secrets(s.Namespace).Get(context.TODO(), cleanKey(key, keyPrefix), metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil, fs.ErrNotExist + } + return nil, err + } + + s.logger.Debug("loading secret", zap.String("name", key)) + return secret.Data["value"], nil +} + +// Delete deletes the value at the given key. +func (s *SecretStorage) Delete(ctx context.Context, key string) error { + err := s.kubeClient.CoreV1().Secrets(s.Namespace).Delete(context.TODO(), cleanKey(key, keyPrefix), metav1.DeleteOptions{}) + if err != nil { + return err + } + + s.logger.Debug("deleting secret", zap.String("name", key)) + return nil +} + +// List returns all keys that match prefix. +func (s *SecretStorage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) { + var keys []string + + s.logger.Debug("listing secrets", zap.String("name", prefix)) + secrets, err := s.kubeClient.CoreV1().Secrets(s.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(matchLabels).String(), + }) + if err != nil { + return keys, err + } + + // TODO :- do we need to handle the recursive flag? + for _, secret := range secrets.Items { + key := secret.ObjectMeta.Name + if strings.HasPrefix(key, cleanKey(prefix, keyPrefix)) { + keys = append(keys, strings.TrimPrefix(key, keyPrefix)) + } + } + + return keys, err +} + +// Stat returns information about key. +func (s *SecretStorage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error) { + secret, err := s.kubeClient.CoreV1().Secrets(s.Namespace).Get(context.TODO(), cleanKey(key, keyPrefix), metav1.GetOptions{}) + if err != nil { + return certmagic.KeyInfo{}, err + } + + s.logger.Debug("stats secret", zap.String("name", key)) + + return certmagic.KeyInfo{ + Key: key, + Modified: secret.GetCreationTimestamp().UTC(), + Size: int64(len(secret.Data["value"])), + IsTerminal: false, + }, nil +} + +func (s *SecretStorage) Lock(ctx context.Context, key string) error { + for { + _, err := s.tryAcquireOrRenew(ctx, cleanKey(key, leasePrefix), false) + if err == nil { + go s.keepLockUpdated(ctx, cleanKey(key, leasePrefix)) + return nil + } + + select { + case <-time.After(leasePollInterval): + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (s *SecretStorage) keepLockUpdated(ctx context.Context, key string) { + for { + time.Sleep(leaseRenewInterval) + done, err := s.tryAcquireOrRenew(ctx, key, true) + if err != nil { + return + } + if done { + return + } + } +} + +func (s *SecretStorage) tryAcquireOrRenew(ctx context.Context, key string, shouldExist bool) (bool, error) { + now := metav1.Now() + lock := resourcelock.LeaseLock{ + LeaseMeta: metav1.ObjectMeta{ + Name: key, + Namespace: s.Namespace, + }, + Client: s.kubeClient.CoordinationV1(), + LockConfig: resourcelock.ResourceLockConfig{ + Identity: s.LeaseId, + }, + } + + ler := resourcelock.LeaderElectionRecord{ + HolderIdentity: lock.Identity(), + LeaseDurationSeconds: 5, + AcquireTime: now, + RenewTime: now, + } + + currLer, _, err := lock.Get(ctx) + + // 1. obtain or create the ElectionRecord + if err != nil { + if !errors.IsNotFound(err) { + return true, err + } + if shouldExist { + return true, nil // Lock has been released + } + if err = lock.Create(ctx, ler); err != nil { + return true, err + } + return false, nil + } + + // 2. Record obtained, check the Identity & Time + if currLer.HolderIdentity != "" && + currLer.RenewTime.Add(leaseDuration).After(now.Time) && + currLer.HolderIdentity != lock.Identity() { + return true, fmt.Errorf("lock is held by %v and has not yet expired", currLer.HolderIdentity) + } + + // 3. We're going to try to update the existing one + if currLer.HolderIdentity == lock.Identity() { + ler.AcquireTime = currLer.AcquireTime + ler.LeaderTransitions = currLer.LeaderTransitions + } else { + ler.LeaderTransitions = currLer.LeaderTransitions + 1 + } + + if err = lock.Update(ctx, ler); err != nil { + return true, fmt.Errorf("failed to update lock: %v", err) + } + return false, nil +} + +func (s *SecretStorage) Unlock(ctx context.Context, key string) error { + err := s.kubeClient.CoordinationV1().Leases(s.Namespace).Delete(context.TODO(), cleanKey(key, leasePrefix), metav1.DeleteOptions{}) + return err +} diff --git a/serveis/caddy/caddy/ingress/pkg/store/configmap_parser.go b/serveis/caddy/caddy/ingress/pkg/store/configmap_parser.go new file mode 100644 index 0000000..7d4b46c --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/store/configmap_parser.go @@ -0,0 +1,63 @@ +package store + +import ( + "fmt" + "reflect" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/mitchellh/mapstructure" + apiv1 "k8s.io/api/core/v1" +) + +// ConfigMapOptions represents global options set through a configmap +type ConfigMapOptions struct { + Debug bool `json:"debug,omitempty"` + AcmeCA string `json:"acmeCA,omitempty"` + AcmeEABKeyId string `json:"acmeEABKeyId,omitempty"` + AcmeEABMacKey string `json:"acmeEABMacKey,omitempty"` + Email string `json:"email,omitempty"` + ExperimentalSmartSort bool `json:"experimentalSmartSort,omitempty"` + ProxyProtocol bool `json:"proxyProtocol,omitempty"` + Metrics bool `json:"metrics,omitempty"` + OnDemandTLS bool `json:"onDemandTLS,omitempty"` + OnDemandAsk string `json:"onDemandAsk,omitempty"` + OCSPCheckInterval caddy.Duration `json:"ocspCheckInterval,omitempty"` +} + +func stringToCaddyDurationHookFunc() mapstructure.DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(caddy.Duration(time.Second)) { + return data, nil + } + return caddy.ParseDuration(data.(string)) + } +} + +func ParseConfigMap(cm *apiv1.ConfigMap) (*ConfigMapOptions, error) { + // parse configmap + cfgMap := ConfigMapOptions{} + config := &mapstructure.DecoderConfig{ + Metadata: nil, + WeaklyTypedInput: true, + Result: &cfgMap, + TagName: "json", + DecodeHook: mapstructure.ComposeDecodeHookFunc( + stringToCaddyDurationHookFunc(), + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, fmt.Errorf("unexpected error creating decoder: %w", err) + } + err = decoder.Decode(cm.Data) + if err != nil { + return nil, fmt.Errorf("unexpected error parsing configmap: %w", err) + } + + return &cfgMap, nil +} diff --git a/serveis/caddy/caddy/ingress/pkg/store/options.go b/serveis/caddy/caddy/ingress/pkg/store/options.go new file mode 100644 index 0000000..d389f3d --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/store/options.go @@ -0,0 +1,12 @@ +package store + +// Options represents ingress controller config received through cli arguments. +type Options struct { + WatchNamespace string + ConfigMapName string + ClassName string + ClassNameRequired bool + Verbose bool + LeaseId string + PluginsOrder []string +} diff --git a/serveis/caddy/caddy/ingress/pkg/store/pod.go b/serveis/caddy/caddy/ingress/pkg/store/pod.go new file mode 100644 index 0000000..b6756c9 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/store/pod.go @@ -0,0 +1,10 @@ +package store + +// PodInfo contains runtime information about the pod running the Ingress controller +type PodInfo struct { + Name string + Namespace string + // Labels selectors of the running pod + // This is used to search for other Ingress controller pods + Labels map[string]string +} diff --git a/serveis/caddy/caddy/ingress/pkg/store/store.go b/serveis/caddy/caddy/ingress/pkg/store/store.go new file mode 100644 index 0000000..ff3e738 --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/store/store.go @@ -0,0 +1,72 @@ +package store + +import ( + v1 "k8s.io/api/networking/v1" +) + +// Store contains resources used to generate Caddy config +type Store struct { + Options *Options + ConfigMap *ConfigMapOptions + Ingresses []*v1.Ingress + CurrentPod *PodInfo +} + +// NewStore returns a new store that keeps track of K8S resources needed by the controller. +func NewStore(opts Options, podInfo *PodInfo) *Store { + s := &Store{ + Options: &opts, + Ingresses: []*v1.Ingress{}, + ConfigMap: &ConfigMapOptions{}, + CurrentPod: podInfo, + } + return s +} + +// AddIngress adds an ingress to the store. It updates the element at the given index if it is unique. +func (s *Store) AddIngress(ing *v1.Ingress) { + isUniq := true + + for i := range s.Ingresses { + in := s.Ingresses[i] + if in.GetUID() == ing.GetUID() { + isUniq = false + s.Ingresses[i] = ing + } + } + + if isUniq { + s.Ingresses = append(s.Ingresses, ing) + } +} + +// PluckIngress removes the ingress passed in as an argument from the stores list of ingresses. +func (s *Store) PluckIngress(ing *v1.Ingress) { + id := ing.GetUID() + + var index int + var hasMatch bool + for i := range s.Ingresses { + if s.Ingresses[i].GetUID() == id { + index = i + hasMatch = true + break + } + } + + // since order is not important we can swap the element to delete with the one at the end of the slice + // and then set ingresses to the n-1 first elements + if hasMatch { + s.Ingresses[len(s.Ingresses)-1], s.Ingresses[index] = s.Ingresses[index], s.Ingresses[len(s.Ingresses)-1] + s.Ingresses = s.Ingresses[:len(s.Ingresses)-1] + } +} + +func (s *Store) HasManagedTLS() bool { + for _, ing := range s.Ingresses { + if len(ing.Spec.TLS) > 0 { + return true + } + } + return false +} diff --git a/serveis/caddy/caddy/ingress/pkg/store/store_test.go b/serveis/caddy/caddy/ingress/pkg/store/store_test.go new file mode 100644 index 0000000..36eb05b --- /dev/null +++ b/serveis/caddy/caddy/ingress/pkg/store/store_test.go @@ -0,0 +1,200 @@ +package store + +import ( + "testing" + + v1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + typev1 "k8s.io/apimachinery/pkg/types" +) + +func TestStoreIngresses(t *testing.T) { + tests := []struct { + name string + addIngresses []string + removeIngresses []string + expectCount int + }{ + { + name: "No ingress added nor removed", + addIngresses: []string{}, + removeIngresses: []string{}, + expectCount: 0, + }, + { + name: "One ingress added, no ingress removed", + addIngresses: []string{"first"}, + removeIngresses: []string{}, + expectCount: 1, + }, + { + name: "Two ingresses added, no ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{}, + expectCount: 2, + }, + { + name: "Two ingresses added, first ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"first"}, + expectCount: 1, + }, + { + name: "Two ingresses added, second ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second"}, + expectCount: 1, + }, + { + name: "Two ingresses added, both ingresses removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "first"}, + expectCount: 0, + }, + { + name: "Two ingresses added, one existing and one non existing ingress removed", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "third"}, + expectCount: 1, + }, + { + name: "Remove non existing ingresses", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"third", "forth"}, + expectCount: 2, + }, + { + name: "Remove non existing ingresses from an empty list", + addIngresses: []string{}, + removeIngresses: []string{"first", "second"}, + expectCount: 0, + }, + { + name: "Add the same ingress multiple time", + addIngresses: []string{"first", "first", "first"}, + removeIngresses: []string{}, + expectCount: 1, + }, + { + name: "Remove the same ingress multiple time", + addIngresses: []string{"first", "second"}, + removeIngresses: []string{"second", "second", "second"}, + expectCount: 1, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := NewStore(Options{}, &PodInfo{}) + for _, uid := range test.addIngresses { + i := createIngress(uid) + s.AddIngress(&i) + } + + for _, uid := range test.removeIngresses { + i := createIngress(uid) + s.PluckIngress(&i) + } + + if test.expectCount != len(s.Ingresses) { + t.Errorf("Number of ingresses do not match expectation in %s: got %v, expected %v", test.name, len(s.Ingresses), test.expectCount) + } + }) + } +} + +func TestStoreReturnIfHasManagedTLS(t *testing.T) { + tests := []struct { + name string + ingresses []v1.Ingress + expect bool + }{ + { + name: "No ingress", + ingresses: []v1.Ingress{}, + expect: false, + }, + { + name: "One ingress without certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + }, + expect: false, + }, + { + name: "One ingress with certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1"}, "mysecret1"), + }, + expect: true, + }, + { + name: "Multiple ingresses without certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + createIngress("second"), + createIngress("third"), + }, + expect: false, + }, + { + name: "Three ingresses mixed with and without certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + createIngress("second"), + createIngressTLS("third", []string{"host3"}, "mysecret3"), + }, + expect: true, + }, + { + name: "Ingress replaced without a certificate", + ingresses: []v1.Ingress{ + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + createIngress("first"), + }, + expect: false, + }, + { + name: "Ingress replaced with a certificate", + ingresses: []v1.Ingress{ + createIngress("first"), + createIngressTLS("first", []string{"host1a", "host1b"}, "mysecret1"), + }, + expect: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := NewStore(Options{}, &PodInfo{}) + for _, i := range test.ingresses { + s.AddIngress(&i) + } + + if test.expect != s.HasManagedTLS() { + t.Errorf("managed TLS do not match expectation in %s: got %v, expected %v", test.name, s.HasManagedTLS(), test.expect) + } + }) + } +} + +func createIngressTLS(uid string, hosts []string, secret string) v1.Ingress { + i := createIngress(uid) + + i.Spec = v1.IngressSpec{ + TLS: []v1.IngressTLS{ + { + Hosts: hosts, + SecretName: secret, + }, + }, + } + + return i +} + +func createIngress(uid string) v1.Ingress { + return v1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + UID: typev1.UID(uid), + }, + } +} diff --git a/serveis/caddy/caddy/ingress/skaffold.yaml b/serveis/caddy/caddy/ingress/skaffold.yaml new file mode 100644 index 0000000..8363935 --- /dev/null +++ b/serveis/caddy/caddy/ingress/skaffold.yaml @@ -0,0 +1,37 @@ +apiVersion: skaffold/v4beta7 +kind: Config +metadata: + name: caddy-ingress-controller +build: + artifacts: + - image: caddy-ingress + context: . + docker: + dockerfile: Dockerfile.dev +deploy: + helm: + releases: + - name: caddy-ingress-development + namespace: caddy-system + chartPath: charts/caddy-ingress-controller + createNamespace: true + setValueTemplates: + image: + repository: "{{ .IMAGE_REPO_NO_DOMAIN_caddy_ingress }}" + tag: "{{.IMAGE_TAG_caddy_ingress}}@{{.IMAGE_DIGEST_caddy_ingress}}" +manifests: + rawYaml: + - ./kubernetes/sample/*.yaml +portForward: + - resourceType: service + resourceName: caddy-ingress-development-caddy-ingress-controller + namespace: caddy-system + address: 0.0.0.0 + port: 80 + localPort: 8080 + - resourceType: service + resourceName: caddy-ingress-development-caddy-ingress-controller + namespace: caddy-system + address: 0.0.0.0 + port: 443 + localPort: 8443 \ No newline at end of file diff --git a/serveis/caddy/caddy2/configmap.yaml b/serveis/caddy/caddy2/configmap.yaml new file mode 100644 index 0000000..f10a789 --- /dev/null +++ b/serveis/caddy/caddy2/configmap.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: default +data: + Caddyfile: | + { + auto_https off + } + + :11110 { + route { + host example.com { + reverse_proxy localhost:8080 + } + host sub.example.com { + reverse_proxy localhost:8081 + } + host another.example.com { + reverse_proxy localhost:8082 + } + } + } diff --git a/serveis/caddy/caddy2/deployment.yaml b/serveis/caddy/caddy2/deployment.yaml new file mode 100644 index 0000000..a17e964 --- /dev/null +++ b/serveis/caddy/caddy2/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 11110 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy/Caddyfile + subPath: Caddyfile + volumes: + - name: caddy-config + configMap: + name: caddy-config diff --git a/serveis/caddy/caddy2/service.yaml b/serveis/caddy/caddy2/service.yaml new file mode 100644 index 0000000..1433999 --- /dev/null +++ b/serveis/caddy/caddy2/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy-service + namespace: default +spec: + type: LoadBalancer + ports: + - port: 11110 + targetPort: 11110 + selector: + app: caddy diff --git a/serveis/caddy/caddy3/configmap.yaml b/serveis/caddy/caddy3/configmap.yaml new file mode 100644 index 0000000..adb3654 --- /dev/null +++ b/serveis/caddy/caddy3/configmap.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: default +data: + Caddyfile: | + { + auto_https off + } + + :11110 { + + n1.ents.space { + reverse_proxy nginx1-service:11111 + } + n2.ents.space { + reverse_proxy nginx2-service:11112 + } + wordpress.ents.space { + reverse_proxy nginx3-service:11113 + } + cryptpad.ents.space { + reverse_proxy nginx4-service:11114 + } diff --git a/serveis/caddy/caddy3/deployment.yaml b/serveis/caddy/caddy3/deployment.yaml new file mode 100644 index 0000000..a17e964 --- /dev/null +++ b/serveis/caddy/caddy3/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 11110 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy/Caddyfile + subPath: Caddyfile + volumes: + - name: caddy-config + configMap: + name: caddy-config diff --git a/serveis/caddy/caddy3/service.yaml b/serveis/caddy/caddy3/service.yaml new file mode 100644 index 0000000..1433999 --- /dev/null +++ b/serveis/caddy/caddy3/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy-service + namespace: default +spec: + type: LoadBalancer + ports: + - port: 11110 + targetPort: 11110 + selector: + app: caddy diff --git a/serveis/caddy/caddy4/configmap.yaml b/serveis/caddy/caddy4/configmap.yaml new file mode 100644 index 0000000..bd9b1c9 --- /dev/null +++ b/serveis/caddy/caddy4/configmap.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config +data: + Caddyfile: | + { + email tu-email@ejemplo.com + } + + nginx1.ejemplo.com:11110 { + reverse_proxy nginx1-service:11111 + } + + nginx2.ejemplo.com:11110 { + reverse_proxy nginx2-service:11112 + } + + nginx3.ejemplo.com:11110 { + reverse_proxy nginx3-service:11113 + } + + nginx4.ejemplo.com:11110 { + reverse_proxy nginx4-service:11114 + } diff --git a/serveis/caddy/caddy4/deployment.yaml b/serveis/caddy/caddy4/deployment.yaml new file mode 100644 index 0000000..729efd4 --- /dev/null +++ b/serveis/caddy/caddy4/deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + labels: + app: caddy +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 11110 # Cambiado a 11110 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy/Caddyfile + subPath: Caddyfile + volumes: + - name: caddy-config + configMap: + name: caddy-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: caddy +spec: + type: LoadBalancer + ports: + - port: 11110 # Cambiado a 11110 + targetPort: 11110 + selector: + app: caddy diff --git a/serveis/caddy/caddy4/service.yaml b/serveis/caddy/caddy4/service.yaml new file mode 100644 index 0000000..9858504 --- /dev/null +++ b/serveis/caddy/caddy4/service.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + labels: + app: caddy +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 11110 # Cambiado a 11110 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy/Caddyfile + subPath: Caddyfile + volumes: + - name: caddy-config + configMap: + name: caddy-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: caddy +spec: + type: LoadBalancer + ports: + - port: 11110 # Cambiado a 11110 + targetPort: 11110 # Asegúrate de que coincida con el puerto del contenedor + selector: + app: caddy diff --git a/serveis/caddy/caddy5/caddy-deployment.yaml b/serveis/caddy/caddy5/caddy-deployment.yaml new file mode 100644 index 0000000..e75ae3d --- /dev/null +++ b/serveis/caddy/caddy5/caddy-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: caddy-namespace +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 12080 + - containerPort: 12443 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy + volumes: + - name: caddy-config + configMap: + name: caddy-config + items: + - key: Caddyfile + path: Caddyfile + - name: caddy-data + emptyDir: {} diff --git a/serveis/caddy/caddy5/caddy-service.yaml b/serveis/caddy/caddy5/caddy-service.yaml new file mode 100644 index 0000000..33c8407 --- /dev/null +++ b/serveis/caddy/caddy5/caddy-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy + namespace: caddy-namespace +spec: + selector: + app: caddy + ports: + - name: http + protocol: TCP + port: 12080 + targetPort: 12080 + - name: https + protocol: TCP + port: 12443 + targetPort: 12443 + type: LoadBalancer diff --git a/serveis/caddy/caddy5/configmap.yaml b/serveis/caddy/caddy5/configmap.yaml new file mode 100644 index 0000000..771061a --- /dev/null +++ b/serveis/caddy/caddy5/configmap.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: caddy-namespace +data: + Caddyfile: | + { + email emancipaciocomunitaria@riseup.net + } + + :12080 { + respond "Caddy is running!" + } + + :12443 { + reverse_proxy nginx1-service:11111 + } diff --git a/serveis/caddy/caddy6/caddy-deployment.yaml b/serveis/caddy/caddy6/caddy-deployment.yaml new file mode 100644 index 0000000..fdae978 --- /dev/null +++ b/serveis/caddy/caddy6/caddy-deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: caddy-namespace +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:latest + ports: + - containerPort: 12080 + - containerPort: 12443 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy + - name: caddy-data + mountPath: /data/caddy + volumes: + - name: caddy-config + configMap: + name: caddy-config + items: + - key: Caddyfile + path: Caddyfile + - name: caddy-data + persistentVolumeClaim: + claimName: caddy-data-pvc diff --git a/serveis/caddy/caddy6/caddy-service.yaml b/serveis/caddy/caddy6/caddy-service.yaml new file mode 100644 index 0000000..33c8407 --- /dev/null +++ b/serveis/caddy/caddy6/caddy-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy + namespace: caddy-namespace +spec: + selector: + app: caddy + ports: + - name: http + protocol: TCP + port: 12080 + targetPort: 12080 + - name: https + protocol: TCP + port: 12443 + targetPort: 12443 + type: LoadBalancer diff --git a/serveis/caddy/caddy6/configmap.yaml b/serveis/caddy/caddy6/configmap.yaml new file mode 100644 index 0000000..89b5acf --- /dev/null +++ b/serveis/caddy/caddy6/configmap.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: caddy-namespace +data: + Caddyfile: | + { + email emancipaciocomunitaria@riseup.net + acme_ca https://acme-v02.api.letsencrypt.org/directory + } + + :12080 { + respond "Caddy is running!" + } + + :12443 { + reverse_proxy nginx1-service:11111 + } diff --git a/serveis/caddy/caddy6/pv.yaml b/serveis/caddy/caddy6/pv.yaml new file mode 100644 index 0000000..94085b3 --- /dev/null +++ b/serveis/caddy/caddy6/pv.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: caddy-data-pv + namespace: caddy-namespace +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/caddy" diff --git a/serveis/caddy/caddy6/pvc.yaml b/serveis/caddy/caddy6/pvc.yaml new file mode 100644 index 0000000..729c85d --- /dev/null +++ b/serveis/caddy/caddy6/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: caddy-data-pvc + namespace: caddy-namespace +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/serveis/caddy/caddy7/caddy-deployment.yaml b/serveis/caddy/caddy7/caddy-deployment.yaml new file mode 100644 index 0000000..19e21ea --- /dev/null +++ b/serveis/caddy/caddy7/caddy-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: caddy-namespace +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:2.6.2 + ports: + - containerPort: 12080 + - containerPort: 12443 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy + - name: caddy-data + mountPath: /data/caddy + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "128Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: / + port: 12080 + initialDelaySeconds: 10 + periodSeconds: 5 + readinessProbe: + httpGet: + path: / + port: 12080 + initialDelaySeconds: 10 + periodSeconds: 5 + volumes: + - name: caddy-config + configMap: + name: caddy-config + items: + - key: Caddyfile + path: Caddyfile + - name: caddy-data + persistentVolumeClaim: + claimName: caddy-data-pvc diff --git a/serveis/caddy/caddy7/caddy-service.yaml b/serveis/caddy/caddy7/caddy-service.yaml new file mode 100644 index 0000000..33c8407 --- /dev/null +++ b/serveis/caddy/caddy7/caddy-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy + namespace: caddy-namespace +spec: + selector: + app: caddy + ports: + - name: http + protocol: TCP + port: 12080 + targetPort: 12080 + - name: https + protocol: TCP + port: 12443 + targetPort: 12443 + type: LoadBalancer diff --git a/serveis/caddy/caddy7/configmap.yaml b/serveis/caddy/caddy7/configmap.yaml new file mode 100644 index 0000000..37db2f2 --- /dev/null +++ b/serveis/caddy/caddy7/configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: caddy-namespace +data: + Caddyfile: | + { + email emancipaciocomunitaria@riseup.net + acme_ca https://acme-v02.api.letsencrypt.org/directory + } + + # Redirigir HTTP → HTTPS (opcional) + http://ents.space { + redir https://ents.space{uri} permanent + } + + # Configuración HTTPS con proxy inverso + https://ents.space { + reverse_proxy nginx1-service.default.svc.cluster.local:11111 + } diff --git a/serveis/caddy/caddy7/pv.yaml b/serveis/caddy/caddy7/pv.yaml new file mode 100644 index 0000000..94085b3 --- /dev/null +++ b/serveis/caddy/caddy7/pv.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: caddy-data-pv + namespace: caddy-namespace +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/caddy" diff --git a/serveis/caddy/caddy7/pvc.yaml b/serveis/caddy/caddy7/pvc.yaml new file mode 100644 index 0000000..729c85d --- /dev/null +++ b/serveis/caddy/caddy7/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: caddy-data-pvc + namespace: caddy-namespace +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/serveis/caddy/caddy8/caddy-deployment.yaml b/serveis/caddy/caddy8/caddy-deployment.yaml new file mode 100644 index 0000000..19e21ea --- /dev/null +++ b/serveis/caddy/caddy8/caddy-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + namespace: caddy-namespace +spec: + replicas: 1 + selector: + matchLabels: + app: caddy + template: + metadata: + labels: + app: caddy + spec: + containers: + - name: caddy + image: caddy:2.6.2 + ports: + - containerPort: 12080 + - containerPort: 12443 + volumeMounts: + - name: caddy-config + mountPath: /etc/caddy + - name: caddy-data + mountPath: /data/caddy + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "128Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: / + port: 12080 + initialDelaySeconds: 10 + periodSeconds: 5 + readinessProbe: + httpGet: + path: / + port: 12080 + initialDelaySeconds: 10 + periodSeconds: 5 + volumes: + - name: caddy-config + configMap: + name: caddy-config + items: + - key: Caddyfile + path: Caddyfile + - name: caddy-data + persistentVolumeClaim: + claimName: caddy-data-pvc diff --git a/serveis/caddy/caddy8/caddy-service.yaml b/serveis/caddy/caddy8/caddy-service.yaml new file mode 100644 index 0000000..33c8407 --- /dev/null +++ b/serveis/caddy/caddy8/caddy-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: caddy + namespace: caddy-namespace +spec: + selector: + app: caddy + ports: + - name: http + protocol: TCP + port: 12080 + targetPort: 12080 + - name: https + protocol: TCP + port: 12443 + targetPort: 12443 + type: LoadBalancer diff --git a/serveis/caddy/caddy8/configmap.yaml b/serveis/caddy/caddy8/configmap.yaml new file mode 100644 index 0000000..da7fee2 --- /dev/null +++ b/serveis/caddy/caddy8/configmap.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: caddy-config + namespace: caddy-namespace +data: + Caddyfile: | + # MODO PRUEBAS (STAGING) - SIN LÍMITES + { + email emancipaciocomunitaria@riseup.net + acme_ca https://acme-staging-v02.api.letsencrypt.org/directory # Servidor de pruebas + debug # Logs detallados + } + + # Configuración mínima para testear + http://ents.space:12080 { + respond "Caddy is running!" + } + + https://ents.space:12443 { + respond "¡Funciona! Certificado Staging OK" + } diff --git a/serveis/caddy/caddy8/pv.yaml b/serveis/caddy/caddy8/pv.yaml new file mode 100644 index 0000000..94085b3 --- /dev/null +++ b/serveis/caddy/caddy8/pv.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: caddy-data-pv + namespace: caddy-namespace +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/caddy" diff --git a/serveis/caddy/caddy8/pvc.yaml b/serveis/caddy/caddy8/pvc.yaml new file mode 100644 index 0000000..729c85d --- /dev/null +++ b/serveis/caddy/caddy8/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: caddy-data-pvc + namespace: caddy-namespace +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/serveis/nginx1/deployment.yml b/serveis/nginx1/deployment.yml new file mode 100644 index 0000000..506826a --- /dev/null +++ b/serveis/nginx1/deployment.yml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx1 +spec: + selector: + matchLabels: + app: nginx1 + replicas: 3 + template: + metadata: + labels: + app: nginx1 + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-html-volume + mountPath: /usr/share/nginx/html + volumes: + - name: nginx-html-volume + configMap: + name: nginx-html-config1 + items: + - key: index.html + path: index.html + diff --git a/serveis/nginx1/index.html b/serveis/nginx1/index.html new file mode 100644 index 0000000..56e69c3 --- /dev/null +++ b/serveis/nginx1/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + + diff --git a/serveis/nginx1/nginx-html-configmap.yaml b/serveis/nginx1/nginx-html-configmap.yaml new file mode 100644 index 0000000..17a01e4 --- /dev/null +++ b/serveis/nginx1/nginx-html-configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-html-config1 +data: + index.html: | + + + + Welcome to nginx 1! + + + +

nginx 1

+

sin comentarios

+ + diff --git a/serveis/nginx1/service.yml b/serveis/nginx1/service.yml new file mode 100644 index 0000000..de8662c --- /dev/null +++ b/serveis/nginx1/service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx1-service +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx1 + ports: + - port: 11111 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/nginx2/deployment.yml b/serveis/nginx2/deployment.yml new file mode 100644 index 0000000..e284b73 --- /dev/null +++ b/serveis/nginx2/deployment.yml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx2 +spec: + selector: + matchLabels: + app: nginx2 + replicas: 3 + template: + metadata: + labels: + app: nginx2 + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-html-volume + mountPath: /usr/share/nginx/html + volumes: + - name: nginx-html-volume + configMap: + name: nginx-html-config2 + items: + - key: index.html + path: index.html + diff --git a/serveis/nginx2/index.html b/serveis/nginx2/index.html new file mode 100644 index 0000000..56e69c3 --- /dev/null +++ b/serveis/nginx2/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + + diff --git a/serveis/nginx2/nginx-html-configmap.yaml b/serveis/nginx2/nginx-html-configmap.yaml new file mode 100644 index 0000000..7a9bf82 --- /dev/null +++ b/serveis/nginx2/nginx-html-configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-html-config2 +data: + index.html: | + + + + Welcome to nginx 2! + + + +

nginx 2

+

sin comentarios

+ + diff --git a/serveis/nginx2/service.yml b/serveis/nginx2/service.yml new file mode 100644 index 0000000..78803c1 --- /dev/null +++ b/serveis/nginx2/service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx2-service +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx2 + ports: + - port: 11112 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/nginx3/deployment.yml b/serveis/nginx3/deployment.yml new file mode 100644 index 0000000..980e305 --- /dev/null +++ b/serveis/nginx3/deployment.yml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx3 +spec: + selector: + matchLabels: + app: nginx3 + replicas: 3 + template: + metadata: + labels: + app: nginx3 + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-html-volume + mountPath: /usr/share/nginx/html + volumes: + - name: nginx-html-volume + configMap: + name: nginx-html-config3 + items: + - key: index.html + path: index.html + diff --git a/serveis/nginx3/index.html b/serveis/nginx3/index.html new file mode 100644 index 0000000..56e69c3 --- /dev/null +++ b/serveis/nginx3/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + + diff --git a/serveis/nginx3/nginx-html-configmap.yaml b/serveis/nginx3/nginx-html-configmap.yaml new file mode 100644 index 0000000..6cfa1be --- /dev/null +++ b/serveis/nginx3/nginx-html-configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-html-config3 +data: + index.html: | + + + + Welcome to nginx 3! + + + +

nginx 3

+

sin comentarios

+ + diff --git a/serveis/nginx3/service.yml b/serveis/nginx3/service.yml new file mode 100644 index 0000000..06af698 --- /dev/null +++ b/serveis/nginx3/service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx3-service +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx3 + ports: + - port: 11113 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/nginx4/deployment.yml b/serveis/nginx4/deployment.yml new file mode 100644 index 0000000..775da71 --- /dev/null +++ b/serveis/nginx4/deployment.yml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx4 +spec: + selector: + matchLabels: + app: nginx4 + replicas: 3 + template: + metadata: + labels: + app: nginx4 + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-html-volume + mountPath: /usr/share/nginx/html + volumes: + - name: nginx-html-volume + configMap: + name: nginx-html-config4 + items: + - key: index.html + path: index.html + diff --git a/serveis/nginx4/index.html b/serveis/nginx4/index.html new file mode 100644 index 0000000..56e69c3 --- /dev/null +++ b/serveis/nginx4/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + + diff --git a/serveis/nginx4/nginx-html-configmap.yaml b/serveis/nginx4/nginx-html-configmap.yaml new file mode 100644 index 0000000..ce779d8 --- /dev/null +++ b/serveis/nginx4/nginx-html-configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-html-config4 +data: + index.html: | + + + + Welcome to nginx 4! + + + +

nginx 4

+

sin comentarios

+ + diff --git a/serveis/nginx4/service.yml b/serveis/nginx4/service.yml new file mode 100644 index 0000000..c4e423f --- /dev/null +++ b/serveis/nginx4/service.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx4-service +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx4 + ports: + - port: 11114 + targetPort: 80 + type: LoadBalancer diff --git a/serveis/nginxh/deployment.yml b/serveis/nginxh/deployment.yml new file mode 100644 index 0000000..1120793 --- /dev/null +++ b/serveis/nginxh/deployment.yml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + replicas: 3 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-html-volume + mountPath: /usr/share/nginx/html + volumes: + - name: nginx-html-volume + configMap: + name: nginx-html-config + items: + - key: index.html + path: index.html diff --git a/serveis/nginxh/index.html b/serveis/nginxh/index.html new file mode 100644 index 0000000..56e69c3 --- /dev/null +++ b/serveis/nginxh/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + + diff --git a/serveis/nginxh/nginx-html-configmap.yaml b/serveis/nginxh/nginx-html-configmap.yaml new file mode 100644 index 0000000..006ba41 --- /dev/null +++ b/serveis/nginxh/nginx-html-configmap.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-html-config +data: + index.html: | + + + + Welcome to nginx! + + + +

nginx 1

+

kubectl exec -it nginx-6f999cfffb-46rpl -- cat /usr/share/nginx/html/index.html

+

If you see this page, the nginx web server is successfully installed and + working. Further configuration is required.

+ +

For online documentation and support please refer to + nginx.org.
+ Commercial support is available at + nginx.com.

+ +

Thank you for using nginx.

+ + diff --git a/serveis/nginxh/service.yml b/serveis/nginxh/service.yml new file mode 100644 index 0000000..24559d0 --- /dev/null +++ b/serveis/nginxh/service.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + ipFamilyPolicy: PreferDualStack + selector: + app: nginx + ports: + - port: 11111 + targetPort: 80 + type: LoadBalancer