85 Commits

Author SHA1 Message Date
9b5ff9f4a9 Update ADOPTERS.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2023-11-02 15:52:47 +00:00
9ba4a73d5d Rename Makefile targets
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-10-23 11:29:55 +01:00
479285caf6 fixed adopters
Signed-off-by: Nitishkumar Singh <nitishkumarsingh71@gmail.com>
2023-10-20 14:00:53 +01:00
4cf5fb8369 Update contributing guide
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-10-04 15:32:30 +01:00
ed5bd7546e Rename to OpenFaaS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-10-04 15:07:01 +01:00
4f4e3d288a chore: remove refs to deprecated io/ioutil
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2023-09-14 10:09:23 +01:00
d0eec5fbbf Update Call ID Middleware
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-09-13 21:53:52 +01:00
25e44f0b57 Add additional adopter
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-09-12 14:39:55 +01:00
6a9ece3cc1 Update module to Go 1.20
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-08-22 18:38:00 +01:00
0036d6ac78 Update ADOPTERS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-08-22 18:36:37 +01:00
2a88b5d2f7 Remove ioutil usage
This has been deprecated in Go for some time, in favour of the
io package.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-08-22 18:29:28 +01:00
55776acc0d Add Altair
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-08-03 16:08:46 +01:00
c3800da6fa Add additional use-case from CDATA
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-08-03 15:55:28 +01:00
472291b40b Setup quotes
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-07-31 15:27:46 +01:00
128b450a88 Add new ADOPTER who contacted us via email
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-07-31 15:27:31 +01:00
5c851cdf31 docs: add missing namespace field to the scale request
The ScaleFunction request supports specifying the function namespace.
This is now included in the API spec.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
2023-07-19 09:18:42 +01:00
9e6f814f6f Increase go.mod version
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-07-07 09:53:01 +01:00
68ec0f59d6 Migrate to latest faas-provider version
Adds CRUD for namespaces and moves namespace for delete/
scale to the body from the query string.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-07-07 09:52:36 +01:00
c0d710c97f chore: add missing List Namespaces endpoint
Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
2023-06-28 08:33:03 +01:00
373a79256f Remove generated models
Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
2023-06-28 08:33:03 +01:00
06ade37420 feat: refactor api spec to use OpenAPI and add missing spec
Convert the existing swagger2.0 file to a moden OpenAPI file.
Add missing endpoitns and model definitions.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
2023-06-28 08:33:03 +01:00
910b8dae1b Update go.mod to 1.19 spec
This is not related to the Go version being used, but the min
version a developer can use to contribute.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-06-07 16:07:11 +01:00
00613347f8 Implement scaling ranges
Scaling ranges keep the OpenFaaS CE gateway's requests to
within the intended bounds already implemented in other
components.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-06-07 16:05:37 +01:00
e0144b0573 Add Valentin Nourdin as an adopter
Closes: #1793 by @vnourdin

Signed-off-by: Valentin Nourdin <valentin.nourdin@lilo.org>
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-05-31 15:14:17 +01:00
4315101191 Add Corva
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-05-21 10:21:08 +01:00
b4b7e2d450 Update OSS ADOPTERS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-05-03 15:42:19 +01:00
0972fa6093 update community guide
Signed-off-by: Nitishkumar Singh <nitishkumarsingh71@gmail.com>
2023-04-16 22:43:49 +02:00
e44448c5dc update community guide for latest blogs and post
Signed-off-by: Nitishkumar Singh <nitishkumarsingh71@gmail.com>
2023-03-17 10:01:58 +00:00
bf63bbf88f Added Blog Post and Videos
Signed-off-by: Nitishkumar Singh <nitishkumarsingh71@gmail.com>
2023-03-07 18:47:05 +00:00
a128df471f Function-based metrics for CE
OpenFaaS CE exposes metrics about function invocations and
about the gateway itself. OpenFaaS Pro has a richer set of
metrics including HTTP RED.

See also: https://docs.openfaas.com/architecture/metrics/

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-01-28 09:10:48 +00:00
c26ec5221e Updates for NATS Streaming support
NATS Streaming is deprecated and will be removed from OpenFaaS
CE in a future release for security reasons.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-01-17 21:55:48 +00:00
8e1c34e222 Set default max scale to 5 replicas and a 10% increment
Sets a new default maximum scale limit of 5 replicas out of
the box for CE users, CE meaning "Community" rather than
"Commercial".

The increment factor of 10 vs 25 should not make a difference
to genuine community and hobbyist users.

Tested and verified with unit tests and hey with a CE cluster
where the maximum limit was reached over several minutes,
finally going back to 1 replica.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-01-17 21:16:32 +00:00
21a8f0cec1 Update GHA for deprecations
The actions from Docker needed to be updated due to
deprecations.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-12 10:19:39 +00:00
8d38b4befe Update to EULA Jan 2023
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-12 10:18:55 +00:00
1fc7bbce4e Remove auth plugins
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-11 13:13:28 +00:00
231e3ed426 Remove builds for auth plugins
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-11 13:13:12 +00:00
fbc0ebdf4a Add comment to middleware
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-11 13:10:46 +00:00
4f9c61b5d2 Deprecate external auth plugins
There is no need for OpenFaaS CE to have external auth plugins
since this added extra overhead and was never used.

OpenFaaS Pro retains the option so it can use the OIDC
auth plugin.

It's still possible, as it ever was to put a proxy in front
of any HTTP server like the gateway.

Tested with a local KinD cluster, auth still worked for the
API and UI.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-11 12:22:12 +00:00
a7d486eee6 Make OpenFaaS CE use the provider for load-balancing
This change removes the direct functions option which was
used originally for Docker Swarm. The Community Edition will
rely on the faas provider - faas-netes / faasd for load-balancing
of requests.

Direct Functions is required in order to delegate load-balancing
to Istio, Linkerd or some other kind of service mesh.

Tested by deploying a modified gateway image to a KinD cluster,
deploying the env function, and scaling to two replicas. This
balanced the load between the two pods by printing out the names
and then I ran a test with hey which returned 200s for all the
requests.

The prober which was part of the Istio support is no longer
required in the CE gateway so is removed for simplicity.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2023-01-11 12:12:26 +00:00
b1ef4b49b7 fix: use io.ReadAll replace ioutil.ReadAll
ioutil.ReadAll will delete in future

Signed-off-by: 流雨声 <212724256@qq.com>
2022-12-14 17:18:08 +00:00
f9245ebbb3 Add write permissions to GHCR
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 12:12:31 +00:00
f3599f4699 Apply gofmt
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:40:43 +00:00
3bafff7e09 Fix issue with empty CI tag
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:33:42 +00:00
e3171b49b0 Remove old ROADMAP
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:32:20 +00:00
e1c62f4875 Update Go and alpine versions
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:30:41 +00:00
b31419c8de Fix CI for deprecated set_output
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:29:08 +00:00
004bbddadb Update queue code for legacy NATS Streaming
NATS Streaming is deprecated and will have no support from
early 2023 by Synadia. Upgrade to OpenFaaS Pro as soon as
possible.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:24:45 +00:00
88bedf78bd Update ADOPTER
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-12-14 11:24:45 +00:00
9d0436e511 Update README intro
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-12-06 21:12:58 +00:00
c07bebbbc9 fix: return provider response during fnc listing errors
Return the original upstream response body when the the list request
returns an error. In general, the provider is returning useful and
actionable error messages for the user, the previous code hid this in
the logs and this is easy for user to overlook.

Additionally, remove an early return from error case after fetching
metrics. This looked like a bug and could result in empty api responses
if there was a prometheus error.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
2022-10-24 18:23:49 +01:00
208b1b2235 Update ISSUE_TEMPLATE.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2022-10-24 11:47:46 +01:00
0255a9480b Add ADOPTER
Signed-off-by: Arne Diekmann <diekmann@neoskop.de>
2022-10-24 11:35:03 +01:00
f7f71f1497 Add Klar to ADOPTERS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-10-24 10:54:44 +01:00
03b6d6c01b Add another user to ADOPTERS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-10-20 13:27:28 +01:00
efffd83990 Add ADOPTER
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-10-20 09:30:46 +01:00
06433e11c0 Add ADOPTER
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-10-20 09:29:24 +01:00
806585b434 Update some missing ADOPTERS
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-10-20 09:27:26 +01:00
32b828b25e Update ADOPTERS.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2022-10-13 08:54:23 +01:00
bb163760ff HelloSafe
Signed-off-by: Simon Renault <94172348+SimonRenault86@users.noreply.github.com>
2022-10-11 20:42:14 +01:00
1a00a55c77 Use write interceptor from faas-provider
We now have two write interceptors, with one moved into
faas-provider. This commit makes the gateway use the new
external package and deletes its own.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-09-29 20:36:40 +01:00
bc2eeff467 Improve errors when backend doesn't return JSON
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-09-21 09:09:48 +01:00
887c804254 Improve error message when unable to list functions
Related to: #1022

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-09-21 08:53:04 +01:00
9da2ec244f Update ISSUE_TEMPLATE.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2022-09-13 09:02:04 +01:00
8e711b3a0c Use Desired Replicas when scaling from zero
During some exploratory testing, I ran into an issue where
the gateway would attempt to scale a deployment from zero
replicas to min, despite there already being min replicas.

Why?

The scaling logic was looking for Available replicas when
it should have looked for Desired replicas. So when a
deployment had zero ready replicas due to readiness checks
failing, the gateway was attempting to scale from zero
to min.

This logic has been corrected and separated from the
a holding pattern where the gateway waits for a ready
replica.

Tested with KinD and an edited function which had a
readiness probe, which was failing and no ready
replicas. As desired, the gateway did not scale to min.

However, when setting desired replicas to zero, the
gateway did scale up as expected.

This change also modifies all print statements for
"seconds" and makes them use 4 decimal places instead of
the default which was a longer, more verbose string for
the logs.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-09-08 11:21:29 +01:00
4604271076 Introduce welcome message and change default timeout
The welcome message shows the difference between
Pro and CE.

The timeout of 8 seconds was never going to be useful as
a default, so changing to 60 seconds.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-30 14:02:44 +01:00
9fccc67b9c Remove OpenFaaS Pro metrics from OpenFaaS CE
* Removes service min and target metrics from the CE gateway

OpenFaaS Pro metrics are no longer required in OpenFaaS CE
since there is an OpenFaaS Pro gateway available.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-30 11:30:58 +01:00
dc2a7a0c6e Update alpine version for basic-auth-plugin
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-24 18:31:14 +01:00
ce5ea178ec Upgrade x/sync, Prometheus client and faas-provider
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-24 18:12:23 +01:00
20b62e3cc9 Remove deprecated files
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-24 18:09:02 +01:00
2e8ec683eb Update README.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2022-08-24 11:46:39 +01:00
774c7bb133 Update README.md
Signed-off-by: Alex Ellis <alexellis2@gmail.com>
2022-08-24 11:45:07 +01:00
40bb3581b7 Remove sample-functions in favour of newer examples
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-24 11:44:05 +01:00
1ee7db994c Remove deprecated stack files
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-24 11:35:37 +01:00
2cdc7f3083 Update ADOPTERS.md
Signed-off-by: Derek Colley <derek@colley.cc>
2022-08-11 12:22:34 +01:00
b87b96ae45 Migrate to Go 1.18 and update dependencies
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-08-05 09:03:31 +01:00
2e14a34243 Update example for golang-http
Fixes: #1741

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-07-13 14:32:33 +01:00
88eea5f62e Feature for probing functions
Introduces a single-flight call to a function's health
endpoint to verify that it is registered with an Istio
sidecar (Envoy) before letting the invocation through.

Results are cached for 5 seconds, before a probe is
required again.

Tested without Istio, with probe_functions environment
variable set to true, I saw a probe execute in the logs.

Fixes: #1721 for Istio users.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-07-07 10:35:07 +01:00
01841f605c Use sync package from unofficial Go library
Uses the sync package from the unofficial Go library instead
of simpler solution.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-06-29 09:43:58 +01:00
6ed0ab71fb Move to single-flight for back-end queries
When querying for replicas during a scale up event, then the
gateway can overwhelm the provider with requests. This is
especially true under high concurrent load.

The changes in this PR limit the inflight requests.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-06-29 09:43:58 +01:00
8f8a93d43f Add Makefile for testing gateway builds
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-06-29 09:43:58 +01:00
08279fb79b Update a couple of adopters as disclosed outside of NDA
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2022-06-06 14:50:40 +01:00
df3d59c918 Removed newline and corrected markdown-table for Interviews and podcasts
👋 
There was an extra newline character after the first entry in the `Interviews and podcasts` sections breaking the markdown preview. Corrected it.

Signed-off-by: Andreas Mosti <andreas.mosti@gmail.com>
2022-06-04 08:37:20 +01:00
d1022c410a Add blog post on event-driven edge
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <han@openfaas.com>
2022-06-04 08:34:21 +01:00
5b77ad4af0 Add blog post on running faasd on azure arm vm
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <han@openfaas.com>
2022-06-02 08:56:07 +01:00
cc2f38938e Add HTTP status code to histogram
The histogram for gateway_functions_seconds excluded the status
code that gives important information for setting up SLOs.

Fixes: #1725

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alex@openfaas.com>
2022-06-01 10:14:18 +01:00
1643 changed files with 88716 additions and 225238 deletions

View File

@ -2,7 +2,7 @@ curators:
- alexellis
- LucasRoesler
- viveksyngh
- Waterdrips
- nitishkumar71
- rgee0
- welteki
@ -13,43 +13,3 @@ features:
- release_notes
contributing_url: https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md
required_in_issues:
- "## Why do you need this?"
- "## Expected Behaviour"
- "## Current Behaviour"
- "## Steps to Reproduce (for bugs)"
- "## Your Environment"
custom_messages:
messages:
- name: template
value: |
Your issue has been marked as *invalid* because you've deleted or removed questions from our issue template.
If you wish to participate in this community, then you need to follow guidelines set by the maintainers.
You will find the template in the .github folder, edit your issue and use the whole issue template, so that we can help you.
- name: propose
value: |
This project follows a contributing guide which states that all
changes must be proposed with an Issue before being worked on.
Please raise an Issue and update your Pull Request to include
the ID or link as part of the description.
Thank you for your contribution.
- name: test
value: |
This project follows a contributing guide which requires that
all changes are tested before being merged. You should include
worked examples that a maintainer can run to prove that the
changes are good.
Screenshots and command line output are also accepted, but
must show the positive, and negative cases, not just that
what was added worked as you expected.
Thank you for your contribution.

View File

@ -7,6 +7,9 @@
<!-- How is this affecting you? What task are you trying to accomplish? -->
## Why do you need this?
## Who is this for?
What company is this for? Are you listed in the [ADOPTERS.md](https://github.com/openfaas/faas/blob/master/ADOPTERS.md) file?
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behaviour
@ -20,7 +23,7 @@
## Are you a GitHub Sponsor (Yes/No?)
<!--- Given this request for help, how are you supporting the project? -->
<!-- Issues created by customers or monthly sponsors get priority -->
Check at: https://github.com/sponsors/openfaas
- [ ] Yes

View File

@ -9,71 +9,37 @@ on:
- '*'
jobs:
build-gateway:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.17.x]
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Get TAG
id: get_tag
run: echo ::set-output name=TAG::latest-dev
uses: docker/setup-buildx-action@v2
- name: Get git commit
id: get_git_commit
run: echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
- name: Get version
id: get_version
run: echo "VERSION=$(git describe --tags --dirty)" >> $GITHUB_ENV
- name: Get Repo Owner
id: get_repo_owner
run: echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')
run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" > $GITHUB_ENV
- name: Build ${{ matrix.svc }}
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: ./gateway
file: ./gateway/Dockerfile
outputs: "type=image,push=false"
platforms: linux/amd64,linux/arm/v7,linux/arm64
build-args: |
VERSION=${{ steps.get_tag.outputs.TAG }}
VERSION=${{ env.TAG }}
GIT_COMMIT=${{ github.sha }}
tags: |
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:${{ steps.get_tag.outputs.TAG }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:${{ github.sha }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:latest
build-auth-plugins:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.17.x]
svc: [
basic-auth
]
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Get TAG
id: get_tag
run: echo ::set-output name=TAG::latest-dev
- name: Get Repo Owner
id: get_repo_owner
run: echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')
- name: Build ${{ matrix.svc }}
uses: docker/build-push-action@v2
with:
context: ./auth/${{ matrix.svc }}
file: ./auth/${{ matrix.svc }}/Dockerfile
outputs: "type=image,push=false"
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: |
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:${{ steps.get_tag.outputs.TAG }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:${{ github.sha }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:latest
ghcr.io/${{ env.REPO_OWNER }}/gateway:${{ github.sha }}
ghcr.io/${{ env.REPO_OWNER }}/gateway:latest

View File

@ -6,82 +6,57 @@ on:
- '*'
jobs:
publish-gateway:
publish:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.17.x]
permissions:
actions: read
checks: write
issues: read
packages: write
pull-requests: read
repository-projects: read
statuses: read
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to Docker Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_PASSWORD }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
- name: Get TAG
id: get_tag
run: echo ::set-output name=TAG::${GITHUB_REF#refs/tags/}
run: echo TAG=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV
- name: Get git commit
id: get_git_commit
run: echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
- name: Get version
id: get_version
run: echo "VERSION=$(git describe --tags --dirty)" >> $GITHUB_ENV
- name: Get Repo Owner
id: get_repo_owner
run: echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')
run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" > $GITHUB_ENV
- name: Publish ${{ matrix.svc }}
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: ./gateway
file: ./gateway/Dockerfile
outputs: "type=registry,push=true"
platforms: linux/amd64,linux/arm/v7,linux/arm64
build-args: |
VERSION=${{ steps.get_tag.outputs.TAG }}
VERSION=${{ env.TAG }}
GIT_COMMIT=${{ github.sha }}
tags: |
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:${{ steps.get_tag.outputs.TAG }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:${{ github.sha }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/gateway:latest
publish-auth-plugins:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.17.x]
svc: [
basic-auth
]
steps:
- uses: actions/checkout@master
with:
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Get TAG
id: get_tag
run: echo ::set-output name=TAG::${GITHUB_REF#refs/tags/}
- name: Login to Docker Registry
uses: docker/login-action@v1
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_PASSWORD }}
registry: ghcr.io
- name: Get Repo Owner
id: get_repo_owner
run: echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')
- name: Publish ${{ matrix.svc }}
uses: docker/build-push-action@v2
with:
context: ./auth/${{ matrix.svc }}
file: ./auth/${{ matrix.svc }}/Dockerfile
outputs: "type=registry,push=true"
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: |
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:${{ steps.get_tag.outputs.TAG }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:${{ github.sha }}
ghcr.io/${{ steps.get_repo_owner.outputs.repo_owner }}/${{ matrix.svc }}:latest
ghcr.io/${{ env.REPO_OWNER }}/gateway:${{ github.sha }}
ghcr.io/${{ env.REPO_OWNER }}/gateway:${{ env.TAG }}
ghcr.io/${{ env.REPO_OWNER }}/gateway:latest

View File

@ -1,16 +1,29 @@
# Adopters
This list shows adopters of OpenFaaS. If you're using OpenFaaS in some way, then please get in touch.
This list shows adopters of OpenFaaS. If you're using OpenFaaS in some way, then please add your team and use-case to this file
## Further resources
## How else can you support this project?
Become a GitHub Sponsor - either as an individual practitioner using or introducing OpenFaaS, or as a company, or both.
### Individual or company sponsor
You can sponsor OpenFaaS on GitHub, most users do this using their personal accounts.
You'll show up as a sponsor on issues and PRs, which is going to make it more likely you'll get a timely response and help from the community.
* [Sponsor OpenFaaS on GitHub](https://github.com/openfaas/sponsors)
Support & OpenFaaS PRO
### Help yourself, with the manual for OpenFaaS
* Buy [OpenFaaS PRO or Enterprise Subscription](https://openfaas.com/support) from OpenFaaS Ltd
Help yourself learn and grow, whilst supporting us.
* [Serverless For Everyone Else](https://openfaas.gumroad.com/l/serverless-for-everyone-else) is the official manual for OpenFaaS, brimming with examples written in Node.js
* [Everyday Go](https://openfaas.gumroad.com/l/everyday-golang) is the official reference material from the OpenFaaS founder Alex Ellis for learning Go, and writing functions in Go.
### Running OpenFaaS in production?
See how Community Edition (CE) which you are using now, compares to OpenFaaS Pro, which is designed for commercial use:
[Overview and comparison of OpenFaaS Pro](https://docs.openfaas.com/openfaas-pro/introduction/)
Tell us more:
@ -21,20 +34,38 @@ Tell us more:
* [3fs](https://3fs.si) - 3fs is using OpenFaaS for automating repetitive development tasks like automatic rebasing, vendoring of dependencies on merge requests and many other things that make our developers lives easier
* [BT](https://www.bt.com) - BT are using OpenFaaS to enable collaboration between data-scientists and developers. The teams are going from 3-years to build and deliver a PoC, to 3 months. See: [KubeCon video](https://www.youtube.com/watch?v=y77HlN2Fa-w)
* [911 Security](https://www.911security.com/) - "We migrated our Python functions from AWS Lambda using automation, and now run them in airgapped environments for customers using OpenFaaS and arkade" - Scott Creager
* [Altair Engineering](https://altair.com/) - OpenFaaS powers the customer functions capability of the IoT SaaS platform, and separately each private on-prem installation of the product.
* [Axa France](https://www.axa.fr) - Axa uses OpenFaaS for inference and predictions at scale using ML models - Pierre-Henri Gache
* [Baidu](https://baidu.com) - A team within Baidu provides ML models to customers which are hosted on OpenFaaS - He Sun.
* [BCubed Engineering](https://bcubed-corp.com) - "We use OpenFaaS to provide a serverless platform for our customers to run their code on."
* [Black.ai](https://black.ai) - video encoding, transcoding - object detection for CCTV using AI.
* [Breu](https://breu.io) - Breu is using OpenFaaS to build an end user monitoring solution for hybrid cloud.
* [BT](https://www.bt.com) - BT are using OpenFaaS to enable collaboration between data-scientists and developers. The teams are going from 3-years to build and deliver a PoC, to 3 months. See: [KubeCon video](https://www.youtube.com/watch?v=y77HlN2Fa-w)
* [BulletProof](https://www.bulletproof.co.uk/) - Bulletproof are using OpenFaaS to build an on demand and scalable Vulnerability Scanning (VA) engine. Using OpenFaaS allows us to use compute resource efficiently yet maintain the ability to grow to meet customer scanning demands. We also like the ability to use pure docker containers to compose multiple scanning tools with different technologies into a single, coherent interface. This has reduced the time need to add new tools to the platform.
* [CDATA](https://cdata.com) - Used for background jobs and tasks such as backing up and exchanging data between systems.
* [Citrix](https://www.citrix.com/en-gb/) - Citrix built out a closed-source multi-tenant functions platform and UI using OpenFaaS. It is used for testing hardware devices and for automated QA testing.
* [Civo](https://www.civo.com) - Civo Cloud provide a 1-click Kubernetes marketplace application for OpenFaaS
* [Cloud Initiatives](https://cloudinitiatives.com) - Used for customer installations for custom functionality, and for main product providing educational course metrics.
* [Cognite](https://www.cognite.com) - Cognite targets heavy asset industries such as oil and gas, shipping and energy sector. They provide data integration tools that help you extract, import, and transform data from siloed source systems, and OpenFaaS is used to provide a cloud function service for heavy tasks.
* [Contiamo](https://www.contiamo.com) - data-science platform hosting jupyter notebooks and functions for multiple tenants.
* [Corva.ai](https://corva.ai) - "Corva is an information-sharing, collaboration-driving, and productivity-powering solution for your Drilling, Completions, Geoscience, and Sustainability teams."
* [DB2 Limited](https://db2.io) - mobile and web development company in Ukraine. Our internal projects using OpenFaaS functions to run customers code in Kubernetes cluster.
* [DigitalOcean](https://www.digitalocean.com) - DigitalOcean provide a one-click droplet and a 1-click Kubernetes marketplace application for OpenFaaS
@ -43,47 +74,87 @@ Tell us more:
* [Dragonchain](https://dragonchain.com/) - "At Dragonchain, we focus on creating a hybrid blockchain-as-a-service product, with integrations of OpenFaaS as our 'smart contract' platform, to be able to automatically run customer code based on interactions that occur on the blockchain. This allows us to be extremely flexible, as customers only have to create a docker container and give it to us in order to create a 'smart contract' which can have deep integrations with our blockchain itself.". Blog: [Dragonchain & OpenFaaS](https://dragonchain.com/blog/blockchain-as-a-service-at-scale-for-enterprise)
* [Edgedelta](https://www.edgedelta.com/) - "OpenFaaS powers parts of the "edge observability platform"
* [Edge Delta](https://www.edgedelta.com/) - "OpenFaaS powers parts of our "edge observability platform""
* [First Baptist Church Carrollton](https://www.fbcc.us) - "We use faasd as the backend for a Slack bot connected to our internal Slack workspace. The bot was initially created to facilitate remote question and answer sessions at our church by allowing viewers of our live stream to text or email questions in, have a staff member ask their question in the room, and then allow the staff member to send a response back to the sender. The texting is facilitated by Twilio while the email is done by interacting with a Gmail account via IMAP and SMTP."
* [Fonix Telematics](https://fonixtelematics.com/) - "We are using OpenFaaS to build our new generation of APIs."
* [FTI Consulting](https://www.fticonsulting.com/) - "We've built a cloud-based analytical framework using Netflix Conductor for the workflow engine and OpenFaaS for our serverless function implementation, where each function can be called from a workflow. We've currently deployed several dozen OpenFaaS functions to our on-premise Kubernetes clusters" - Jason Cullison
* [GalaxyCard](https://www.galaxycard.in/) - "GalaxyCard is a happy user of OpenFaaS"
* [GH Electronic GmbH](https://gselectronic.com/) - "We've been using OpenFaaS in production for over 5 years and have 30 C# functions which are used in our manufacturing process."
* [GMO Internet](https://www.gmo.jp/en/)
* [HelloSafe](https://hellosafe.ca/en/) - "HelloSafe is one of the leading website of financial products comparison in Canada. We're using OpenFaas on our production applications."
* [HM Planning Inspectorate](http://www.planninginspectorate.gov.uk) - HM Planning Inspectorate is the UK Government body responsible for dealing with planning appeals, national infrastructure planning applications, examinations of local plans and other specialist casework in England and Wales. OpenFaaS eased the communication between the new planning appeals website and the monolithic back-office application and allowed easy retries in the event of network failure.
* [HPE](https://www.hpe.com/) - HPE Ezmeral is a purpose-built, hybrid cloud platform for data science and analytics workloads.
* [Iconscout](https://iconscout.com) - e-commerce site for stock photography and icons. OpenFaaS is used to resize images and to bundle assets for customers.
* [Infotechpartners](www.infotechpartners.be)
* [Ingrooves](https://ingrooves.com) - Ingrooves is a global music distribution, tech & marketing company, and OpenFaaS is a key component in its finance system for report generation, event publishing, and data ingestion.
* [Infotechpartners](www.infotechpartners.be)
* [Intel.com](https://intel.com) - OpenFaaS is used within a commercial service and within the Open Source group for AI model serving.
* [Intraffic](https://www.intraffic.nl/) - "Using OpenFaaS for integration and callable AI/ML models for asset management."
* [Klar MX](https://klar.mx) - "Cuenta con Klar" - Klar provides access to credit cards in Mexico for those who have issues with credit history.
* [Kubiya.ai](https://kubiya.ai) - ChatGPT-like DevOps Virtual Assistant that runs OpenFaaS functions for custom infrastructure automation and management.
* [LivePerson](https://www.liveperson.com/) - LivePerson extended their chat platform by allowing customers to write functions to execute in client chat flows. See [KubeCon video](https://www.youtube.com/watch?v=bt06Z28uzPA)
* [Live Time Value (LTV) Co.](https://www.ltvco.com) - "Data is at the heart of what we do" - the data-science team at LTV use OpenFaaS to provide a scalable and cost-effective way to run their models in production.
* [Mercedes Benz Tech Innovation](https://mercedes-benz.com) - "We are currently using OpenFaaS as a communicative API between the vehicle app and the backend. In the future we plan to offer a service in the company, which every app developer can host an API on the OpenFaaS platform at short notice"
* [metaspan](https://metaspan.com) - "End-to-end blockchain solutions". metaspan ported all api endpoints from monolith express.js/sails.js to openfaas micro-functions.
* [MoneyLion](https://www.moneylion.com/)
* [Naamio](https://naamio.cloud/) - "Naamio are providing an event-based serverless API to developers to enable rapid development of decentralized applications on the cloud. By providing progressive enhancement within the developer tools, OpenFaaS has enabled Naamio to go from clustered Docker container deployments with REST APIs using Kubernetes, to load balanced deployable functions over an open event queue interface. It was key to enabling a standard multilingual development kit across cloud providers."
* [Nexylan](nexylan.com/) - "We are a French professional hoster that use OpenFaaS in dev and production inside our private extranet. We use OpenFaaS to split our historic monolith project and then simplify development/maintainability and speed up development times."
* [Neoskop](https://www.neoskop.de) - Neoskop is using OpenFaaS in production to provide our developers with a self-service platform for backend functionality and thereby our customers agile and rapid feature development.
* [Nexylan](nexylan.com/) - "We are a French professional host that use OpenFaaS in dev and production inside our private extranet. We use OpenFaaS to split our historic monolith project and then simplify development/maintainability and speed up development times."
* [NGC](https://www.ngcsoftware.com/)
* [Northwestern Mutual](https://www.northwesternmutual.com/) - "OpenFaaS is a great platform and Alex and team are a great resource. They will work very diligently with your team to help you get the most out of OpenFaaS, and he will always be able to provide valuable insight into issues that a team might face while developing software for the cloud." Kieran Gordon
* [Optiv](https://optiv.com) - Cyber Security Solutions
* [Outsystems](https://outsystems.com) - "In my team, we're using OpenFaaS to help the orchestration of our CD pipelines. From a high-level perspective, we have a NATS cluster and the OpenFaaS functions subscribing to NATS topics and reacting to them. Our functions are doing some work related to the pipeline, like saving data to the database, sending Slack messages, or just returning something from the database." (Marco Alves)
* [P. A. Media Group](https://pamediagroup.com/) - "We use OpenFaaS to orchestrate Terraform and Jenkins jobs for our internal infrastructure provisioning" - Rob Stonham
* [Patchworks Integration Limited](https://www.wearepatchworks.com) - Ecommerce integrations made easy - functions provide custom enrichment for data and integrations with third-party APIs. Customers can provide their own PHP code to execute in a sandboxed environment.
* [PathfinderZA](https://www.pathfinderza.com) - PathfinderZA is an IOT security firm selling underground sensors that transmits warnings to users if a person or vehicle goes past it. We're using OpenFaas, with Dockerised functions written in Java (Quarkus) and Rust (Actix/Rocket-RS).
* [Pentium Network](https://www.pentium.network/)
* [PiperCI](https://piperci.dreamer-labs.net) - PiperCI is a task management framework that provides users with a standard library of CI/CD-centric tasks and the [OpenFaas](https://www.openfaas.com/) and [Kubernetes](https://kubernetes.io/) based infrastructure required to run them. PiperCI can be used in conjunction with existing CI/CD orchestrators like GitlabCI, Jenkins, TravisCI, or others to create a more scalable, robust, and functional CI/CD system.
* [Politics Rewired](https://www.politicsrewired.com/) - Politics Rewired uses OpenFaaS to enable organisation of political campaigns and sending of SMS message at scale using functions.
* [Press Association](https://www.pressassociation.com/) - Press Association is using OpenFaaS in development and production as part of our deployment pipeline.
* [Pypestream](https://www.pypestream.com) - "We have just migrated 50 of our customers from Kubeless, which is now deprecated to OpenFaaS" - Antoine Hamon
* [Rapid Circle](https://www.rapidcircle.com) is using OpenFaaS within a Azure Kubernetes cluster to host a large amount of micro-services aiming at automating core activities of their Microsoft 365 Cloud Managed Services offering. Robustness, speed, scalable and simplicity have been major reasons to favor OpenFaaS over Azure Functions.
* [Ratehub](https://www.ratehub.ca) - Ratehub is Canada's leading personal finance comparison site. We're breaking apart our monolithic PHP and Java codebases into Node, PHP and Java OpenFaaS functions; there's not much that we don't plan on moving to FaaS!
* [Rapid Circle](https://www.rapidcircle.com) is using OpenFaaS within a Azure Kubernetes cluster to host a large amount of micro-services aiming at automating core activities of their Microsoft 365 Cloud Managed Services offering. Robustness, speed, scalable and simplicity have been major reasons to favor OpenFaaS over Azure Functions.
* [skyslope.com](https://skyslope.com) - "We process millions of documents per day and moved from AWS Lambda to Kubernetes. We estimate that OpenFaaS has saved us 60,000 USD each year over the past three years that we've been running it in our business" - Derrick Martinez
* [smashHit](https://smashhit.eu) - smashHit is a project funded by the European Union's Horizon 2020 research and innovation programme under grant agreement No. 871477. The objective of smashHit is to assure trusted and secure sharing of data streams from both personal and industrial platforms, needed to build sectorial and cross-sectorial services, by establishing a Framework for processing of data owner consent and legal rules (GDPR) and effective contracting, as well as joint security and privacy-preserving mechanisms. We are utilising OpenFaaS to support the need for scalable processing through the use of functions.
* [Sprucee](https://spruce.casa) - We use [faasd](https://github.com/openfaas/faasd) as part of our base Encryption as a Service platform which were manually managed docker containers. As NATS based platform we were able to scale to every size we want, but deployment takes many labor time as we need to deal with OS level and customer limitations. Now we can use "faas install/up" to accomplish 80% of deployment effort.
@ -91,6 +162,12 @@ Tell us more:
* [SURFsara IoT Platform for Sensemakers](https://github.com/sensemakersamsterdam/sensemakers-iot-platform) - The SURFsara IoT Platform for Sensemakers is a platform for storing, monitoring, visualising and analyzing sensor data. It is a collaboration platform designed to host multiple projects carried by the Sensemakers community. In addition, there is a project dedicated to experimentation, available for everyone to use. All data within the platform is shared. OpenFaaS serverless functions give access to the platform through an HTTP entry point, take care of the metadata extraction and enable custom event-driven actions.
* [Surge](https://www.workwithsurge.com) - Lending Platform and Salesforce integrations
* [TeamViewer.com](https://teamviewer.com) - "TeamViewer users OpenFaaS across several clusters"
* [T-Mobile](https://www.t-mobile.com/) - T-Mobile is a global mobile network that provides mobile data, voice and text services to consumers and businesses.
* [Transmute Industries](https://www.transmute.industries/) - "At Transmute we use OpenFaaS to develop identity and access integrations leveraging decentralized identities that integrate with legacy IAM systems. OpenFaaS helps Transmute and our customers avoid vendor lock in, encourages modularity, and helps us rapidly develop and release integrations for customers."
* [Traversals](https://traversals.com/) - At Traversals, we use OpenFaaS for processing of incoming data. We take benefit from various programming languages available in OpenFaaS.
@ -99,21 +176,40 @@ Tell us more:
* [Very Good Security](https://www.verygoodsecurity.com) - VGS uses OpenFaaS to build a solid foundation for the development, deployment, and execution of custom logic on customer payloads as part of their secure compute platform.
* [Vision Banco SAECA](https://www.visionbanco.com) - self-service home banking portal and asynchronous report/PDF generation. See: [KubeCon Video](https://www.youtube.com/watch?v=mPjI34qj5vU&t=1417s)
* [Virality](https://www.virality.de/)
* [Vision Banco SAECA](https://www.visionbanco.com) - self-service home banking portal and asynchronous report/PDF generation. See: [KubeCon Video](https://www.youtube.com/watch?v=mPjI34qj5vU&t=1417s)
* [VMware](https://vmware.com)
* Used in "veba" VMware Event Broker Appliance to extend vSphere by adding event functionality. OpenFaaS functions and the vcenter-connector are used as an appliance.
* CAS / vRA8 - The Cloud Automation Services product has an option to deploy "FaaS on-premises", this actually deploys OpenFaaS white-boxed / white-labelled. [CAS Write-up from Swisscom](https://ict.swisscom.ch/2019/08/cloud-automation-services-on-prem-faas-provider-for-vsphere/)
* OpenFaaS is also repackaged as "Automation Extensibility" in the ["vRO" product](https://vnuggets.com/2019/08/16/cloud-assembly-extensibility-with-abx-faas-part1/). [See an example](https://vnuggets.com/2019/08/19/cloud-assembly-extensibility-with-abx-faas-part3/)
* [VNourdin](https://www.vnourdin.dev) - I am a French web developer working with the Jamstack, using 11ty and faasd. I started with faasd as I wanted to make it work on my small VPS, but as I do more and more projects relying on OpenFaaS, I'll probably switch to a K8s cluster to gain scalability.
* [Waylay](https://www.waylay.io) - We use OpenFaaS to deploy small snippets of code that can be combined in a low-code manner by our clients to do data orchestration and automation. Users of the platform also are able to deploy their own plugins (written in multiple languages), which also get deployed on OpenFaas.
* [Wireline.io](https://wireline.io) - portable functions that can run on any hardware, indexed through blockchain.
* [WorldQuant](https://worldquant.com) - Using OpenFaaS as part of WorldQuant's solutions.
* [Yokogawa Electric](https://en.wikipedia.org/wiki/Yokogawa_Electric)
* [Ytel](https://www.ytel.com) - Ytel are a Google Cloud customer and deployed OpenFaaS vs. the vendor alternative due to its wide range of templates, Dockerfile support and easier access to services within the VPC. The Dockerfile template allowed for easy migration of existing code. The latency of transactions for customers during purchase process was reduced by offloading synchronous code to NATS which is built into OpenFaaS. OpenFaaS also allowed "hot path" code to be refactored from large services into multiple functions, to take advantage of horizontal scaling.
* [smashHit](https://smashhit.eu) - smashHit is a project funded by the European Union's Horizon 2020 research and innovation programme under grant agreement No. 871477. The objective of smashHit is to assure trusted and secure sharing of data streams from both personal and industrial platforms, needed to build sectorial and cross-sectorial services, by establishing a Framework for processing of data owner consent and legal rules (GDPR) and effective contracting, as well as joint security and privacy-preserving mechanisms. We are utilising OpenFaaS to support the need for scalable processing through the use of functions.
See the top of the file for how to participate.
## Appendix
### Sorting sections
Adopeters list should be sorted after it's been updated, here's how you can do that with bash.
```bash
cat | sort --ignore-case
# Copy / paste
# Control + D
```
Please note, for few vendors multiple use case has been listed under same vendor. Hence, the output need to be compared with content in document before updating the document.

1
CNAME
View File

@ -1 +0,0 @@
docs.get-faas.com

View File

@ -1,12 +1,14 @@
# Contributing
# Contributing guidelines
## Guidelines
These are the guidelines for contributing to OpenFaaS Community Edition (CE) and faasd components.
Guidelines for contributing.
OpenFaaS Standard and OpenFaaS For Enterprises are commercial software, and maintained solely by employees of OpenFaaS Ltd.
### First impressions - introducing yourself and your use-case
Customers can provide feedback via the [openfaas/customers](https://github.com/openfaas/customers) repository
One of the best ways to participate within a new open source communities is to introduce yourself and your use-case. This builds goodwill, but also means the community can start to understand your needs and how best to help you.
## First impressions - introducing yourself and your use-case
One of the best ways to participate within a new open source community is to introduce yourself and your use-case. This builds goodwill, but also means the community can start to understand your needs and how best to help you.
Given that the community is made up of volunteers, making a good first impression is important to getting their ear and attention.
@ -31,7 +33,7 @@ The primary ways to engage with the community are via GitHub Issues and [Enterpr
See also: [The no-excuses guide to introducing yourself to a new open source project](https://opensource.com/education/13/7/introduce-yourself-open-source-project)
### How can I get involved?
## How can I get involved?
There are a number of areas where contributions can be accepted:
@ -49,17 +51,19 @@ There are a number of areas where contributions can be accepted:
This is just a short list of ideas, if you have other ideas for contributing please make a suggestion.
### I want to contribute on GitHub
If you'd like help getting involved, [join our weekly community call on Zoom](https://docs.openfaas.com/community).
#### I've found a security issue
## I want to contribute on GitHub
Please follow [responsible disclosure practices](https://en.wikipedia.org/wiki/Responsible_disclosure) and send an email to support@openfaas.com. Bear in mind that instructions on how to reproduce the issue are key to proving an issue exists, and getting it resolved. Suggested solutions are also weclome.
### I've found a security issue
#### I've found a typo
Please follow [responsible disclosure practices](https://en.wikipedia.org/wiki/Responsible_disclosure) and send an email to support@openfaas.com. Bear in mind that instructions on how to reproduce the issue are key to proving an issue exists, and getting it resolved.
### I've found a typo
* A Pull Request is not necessary. Raise an [Issue](https://github.com/openfaas/faas/issues) and we'll fix it as soon as we can.
#### I have a (great) idea
### I have a (great) idea
The OpenFaaS maintainers would like to make OpenFaaS the best it can be and welcome new contributions that align with the project's goals. Our time is limited so we'd like to make sure we agree on the proposed work before you spend time doing it. Saying "no" is hard which is why we'd rather say "yes" ahead of time. You need to raise a proposal.
@ -84,7 +88,7 @@ If you are proposing a new tool or service please do due diligence. Does this to
Every effort will be made to work with contributors who do not follow the process. Your PR may be closed or marked as `invalid` if it is left inactive, or the proposal cannot move into a `design/approved` status.
#### Paperwork for Pull Requests
### Paperwork for Pull Requests
Please read this whole guide and make sure you agree to the Developer Certificate of Origin (DCO) agreement (included below):
@ -95,7 +99,7 @@ Please read this whole guide and make sure you agree to the Developer Certificat
* Always give instructions for testing
* Provide us CLI commands and output or screenshots where you can
##### Commit messages
#### Commit messages
The first line of the commit message is the *subject*, this should be followed by a blank line and then a message describing the intent and purpose of the commit. These guidelines are based upon a [post by Chris Beams](https://chris.beams.io/posts/git-commit/).
@ -178,47 +182,50 @@ defer goleak.VerifyNoLeaks(t)
at the very beginning of the test, and it will fail the test if it detects goroutines that were opened but never cleaned up at the end of the test.
#### I have a question, a suggestion or need help
#### I need to add a dependency
All projects use [Go modules](https://github.com/golang/go/wiki/Modules) and vendoring. The concept of `vendoring` is still broadly used in projects written in Go. This means that a copy of the source-code of dependencies is stored within each repository in the `vendor` folder. It allows for a repeatable build and isolates change.
Components must be licensed with an MIT, BSD, or Apache 2.0 license. We may ask you to write your own code when dependencies are trivial, or unmaintained by their authors.
### I have a question, a suggestion or need help
If you have a deeply technical request or need help debugging your application then you should prepare a simple, public GitHub repository with the minimum amount of code required to reproduce the issue.
If you feel there is an issue with OpenFaaS or were unable to get the help you needed from the GitHub, [then send us an email](https://openfaas.com/support/)
#### Setting expectations, support and SLAs
* What kind of support can I expect for free?
* What kind of support can I expect?
OpenFaaS is licensed in a way that enables you to use the source code in or with your project or product.
OpenFaaS Standard customers have self-service support, and can directly contact the OpenFaaS Ltd team via the [openfaas/customers](https://github.com/openfaas/customers) repository using Discussions.
If you are using one of the Open Source projects within the openfaas or openfaas-incubator repository, then help may be offered on a limited, good-will basis by volunteers, but if you are a commercial user, you will need to purchase support for timely help.
Please be respectful of any time given to you and your needs. The person you are requesting help from may not reside in your timezone and contacting them via direct message is inappropriate.
Support is only offered to free users to fix bugs and issues in the codebase, where the full Issue Template is filled out with sufficient instructions to reproduce the issue. We will not debug your application, or comment on your architecture on GitHub.
Enterprise support is the best place to ask questions, suggest features, and to get help. The GitHub issue tracker can be used for suspected issues with the codebase or deployment artifacts. The whole template must be filled out in detail.
* Can we talk to you in person?
There is a weekly Zoom call for any free user or customer to attend, topics are taken at the beginning of the call, and we will strive to give everyone time to talk.
* Doesn't Open Source mean that everything is free?
The OpenFaaS projects are licensed as MIT which means that you are free to use, modify and distribute the software within the terms of the license.
The OpenFaaS Community Edition (CE) projects are licensed as MIT which means that you are free to use, modify and distribute the software within the terms of the license.
Contributions, suggestions and feedback is welcomed in the appropriate channels as outlined in this guide. The MIT license does not cover support for PRs, Issues, Technical
Support questions, feature requests and technical support/professional services which you may require; the preceding are not free and have a cost to those providing the services. Where possible, this time may be volunteered for free, but it is not unlimited.
Contributions, suggestions and feedback is welcomed in the appropriate channels as outlined in this guide. The MIT license does not cover support for PRs, Issues, Technical Support questions, feature requests and technical support/professional services which you may require; the preceding are not free and have a cost to those providing the services.
* What is the SLA for my Issue?
Issues are examined, triaged and answered on a best effort basis by volunteers and community contributors. This means that you may receive an initial response within any time period such as: 1 minute, 1 hour, 1 day, or 1 week. There is no implicit meaning to the time between you raising an issue and it being answered or resolved.
If you see an issue which does not have a response or does not have a resolution, it does not mean that it is not important, or that it is being ignored. It simply means it has not been worked on by a volunteer yet.
If you see an issue which does not have a response or does not have a resolution, it does not mean that it is not important, or that it is being ignored. It simply means it has not been worked on yet, or may have been missed.
Please take responsibility for following up on your Issues if you feel further action is required.
If you are a business using OpenFaaS and need timely and attentive responses, then you should purchase Enterprise Support from OpenFaaS Ltd.
If you're an OpenFaaS customer, then you will have a direct line of communication with the OpenFaaS Ltd team, feel free to reach out for an update.
* What is the SLA for my Pull Request?
In a similar way to Issues, Pull Requests are triaged, reviewed, and considered by a team of volunteers - the Core Team, Members Team and the Project Lead. There are dozens of components that make up the OpenFaaS project and a limited amount of people. Sometimes PRs may become blocked or require further action.
Please take responsibility for following up on your Pull Requests if you feel further action is required.
* Why may your PR be delayed?
* The contributing guide was not followed in some way
@ -229,25 +236,19 @@ If you feel there is an issue with OpenFaaS or were unable to get the help you n
* Changes have been requested
More information, a use-case, or context may be required for the change to be accepted.
* The PR is low priority or low impact
In addition, more information, a use-case, or context may be required for the change to be accepted.
* What if I am a GitHub Sponsor?
If you [sponsor OpenFaaS on GitHub](https://github.com/sponsors/openfaas), then you will show up as a Sponsor on your issues and PRs which is one way to show your support for the community and project. Whilst the entry-level sponsorship is only 25 USD / mo, you will benefit from access to regular updates on project development via the [Treasure Trove portal](https://faasd.exit.openfaas.pro/function/trove/). Your company can also take up a GitHub Sponsorship using their GitHub organisation's existing billing relationship.
If you [sponsor OpenFaaS on GitHub](https://github.com/sponsors/openfaas), then you will show up as a Sponsor on your issues and PRs which is one way to show your support for the community and project. Thank you for your contribution.
Most sponsors are individuals, not corporations. But if your organisation can also take up a GitHub Sponsorship using their GitHub organisation's existing billing relationship.
* What if I need more than that?
* What if I need more?
If you're a company using any of these projects, you can get the following through an [Enterprise Support agreement with OpenFaaS Ltd](https://openfaas.com/support/) so that the time and resources required to support your business are paid for.
A support agreement can be tailored to your needs, you may benefit from support, if you need any of the following:
* security issues patched in a timely manner for all 40 +/- open source components
* priority responses to issues/PRs
* immediate help and access to experts
#### I need to add a dependency
All projects use [Go modules](https://github.com/golang/go/wiki/Modules) and vendoring. The concept of `vendoring` is still broadly used in projects written in Go. This means that a copy of the source-code of dependencies is stored within each repository in the `vendor` folder. It allows for a repeatable build and isolates change.
[Check out the options for self-service and enterprise support](https://openfaas.com/pricing/).
### How are releases made?
@ -302,8 +303,10 @@ Core Team attend all project meetings and calls. Allowances will be made for tim
The Core Team includes:
- Alex Ellis (@alexellis) - Lead
- Lucas Roesler (@LucasRoesler) - SME for logs, provider model and secrets
- Alex Ellis (@alexellis) - Founder, OpenFaaS Ltd
- Han Verstraete (@welteki) - Junior Software Developer, OpenFaaS Ltd
- Lucas Roesler (@LucasRoesler) - SME for logs, provider model and secrets. Lead Developer @ Contiamo
- Nitishkumar Singh (@nitishkumar71) - Senior Engineer, CTO.ai
#### Members Team
@ -385,17 +388,13 @@ The [community.md](https://github.com/openfaas/faas/blob/master/community.md) fi
### Roadmap
* See the [2019 Project Update](https://www.openfaas.com/blog/project-update/)
* Browse open issues in [openfaas/faas](https://github.com/openfaas/faas/issues)
* Join the [2020 Roadmap on Trello](https://trello.com/invite/b/5OpMyrBP/ade103a10ae1e38eb5d3eee7955260a9/2020-openfaas-roadmap)
For commercial users, please feel free to ask about support, backlog prioritisation and feature development. Email sales@openfaas.com.
See also: [OpenFaaS Pro](https://docs.openfaas.com/openfaas-pro/introduction/)
## License
This project is licensed under the MIT License.
OpenFaaS Community Edition (CE) is licensed under the MIT License.
OpenFaaS Standard and OpenFaaS for Enterprises are proprietary and binaries are licensed under the commercial [OpenFaaS Pro EULA](https://github.com/openfaas/faas/blob/master/pro/EULA.md).
### Copyright notice
@ -404,7 +403,7 @@ It is important to state that you retain copyright for your contributions, but a
Please add a Copyright notice to new files you add where this is not already present.
```
// Copyright (c) OpenFaaS Author(s) 2018. All rights reserved.
// Copyright (c) OpenFaaS Author(s) 2023. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
```

106
DEV.md
View File

@ -1,106 +0,0 @@
## Develop your own function
### Working on the API Gateway or Watchdog
To work on either of the FaaS Golang components checkout the "./build.sh" scripts and acompanying Dockerfiles.
* [Roadmap and Contributing](https://github.com/openfaas/faas/blob/master/ROADMAP.md)
### Creating a function
Functions run as Docker containers with the Watchdog component embedded to handle communication with the API Gateway.
You can find the [reference documentation for the Watchdog here](https://github.com/openfaas/faas/tree/master/watchdog).
**Markdown Parser**
This is the basis of a function which generates HTML from MarkDown:
```
FROM golang:1.9.7
RUN mkdir -p /go/src/app
COPY handler.go /go/src/app
WORKDIR /go/src/app
RUN go get github.com/microcosm-cc/bluemonday && \
go get github.com/russross/blackfriday
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
ADD https://github.com/openfaas/faas/releases/download/0.9.14/fwatchdog /usr/bin
RUN chmod +x /usr/bin/fwatchdog
ENV fprocess="/go/src/app/app"
CMD ["/usr/bin/fwatchdog"]
```
The base Docker container is not important, you just need to add the watchdog component and then set the fprocess to execute your binary at runtime.
Update the Docker stack with this:
```
markdown:
image: alexellis2/faas-markdownrender:latest
labels:
function: "true"
networks:
- functions
```
**Word counter with busybox**
```
FROM alpine:latest
ADD https://github.com/openfaas/faas/releases/download/0.9.14/fwatchdog /usr/bin
RUN chmod +x /usr/bin/fwatchdog
ENV fprocess="wc"
CMD ["fwatchdog"]
```
Update your Docker stack with this definition:
```
wordcount:
image: alexellis2/faas-alpinefunction:latest
labels:
function: "true"
networks:
- functions
environment:
fprocess: "wc"
```
**Tip:**
You can optimize Docker to cache getting the watchdog by using curl, instead of ADD.
To do so, replace the related lines with:
```
RUN apt-get update && apt-get install -y curl \
&& curl -sL https://github.com/openfaas/faas/releases/download/0.9.14/fwatchdog > /usr/bin/fwatchdog \
&& chmod +x /usr/bin/fwatchdog \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
```
or with the following for Alpine based images:
```
RUN apk --no-cache add curl \
&& curl -sL https://github.com/openfaas/faas/releases/download/0.9.14/fwatchdog > /usr/bin/fwatchdog \
&& chmod +x /usr/bin/fwatchdog
```
### Testing your function
You can test your function through a webbrowser against the UI portal on port 8080.
http://localhost:8080/
You can also invoke a function by name with curl:
```
curl --data-binary @README.md http://localhost:8080/function/func_wordcount
```

View File

@ -5,6 +5,12 @@ NS?=openfaas
build-gateway:
(cd gateway; docker buildx build --platform linux/amd64 -t ${NS}/gateway:latest-dev .)
# generate Go models from the OpenAPI spec using https://github.com/contiamo/openapi-generator-go
generate:
rm gateway/models/model_*.go || true
openapi-generator-go generate models -s api-docs/spec.openapi.yml -o gateway/models --package-name models
# .PHONY: test-ci
# test-ci:
# ./contrib/ci.sh

View File

@ -20,7 +20,7 @@ OpenFaaS&reg; makes it easy for developers to deploy event-driven functions and
* Portable: runs on existing hardware or public/private cloud by leveraging [Kubernetes](https://github.com/openfaas/faas-netes)
* [CLI](http://github.com/openfaas/faas-cli) available with YAML format for templating and defining functions
* Auto-scales as demand increases [including to zero](https://docs.openfaas.com/architecture/autoscaling/)
* [Commercially supported distribution by the team behind OpenFaaS](https://openfaas.com/support/)
* [Commercially supported Pro distribution by the team behind OpenFaaS](https://openfaas.com/pricing/)
**Want to dig deeper into OpenFaaS?**
@ -79,19 +79,21 @@ Official templates exist for many popular languages and are easily extensible wi
package function
import (
"log"
"fmt"
"net/http"
"github.com/openfaas-incubator/go-function-sdk"
handler "github.com/openfaas/templates-sdk/go-http"
)
// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
var err error
message := fmt.Sprintf("Body: %s", string(req.Body))
return handler.Response{
Body: []byte("Try us out today!"),
Header: map[string][]string{
"X-Served-By": []string{"openfaas.com"},
},
Body: []byte(message),
StatusCode: http.StatusOK,
}, err
}
```
@ -151,9 +153,9 @@ Have you written a blog about OpenFaaS? Do you have a speaking event? Send a Pul
* [Read blogs/articles and find events about OpenFaaS](https://github.com/openfaas/faas/blob/master/community.md)
### Roadmap and contributing
### Contributing
OpenFaaS is written in Golang and is MIT licensed - contributions are welcomed whether that means providing feedback, testing existing and new feature or hacking on the source.
OpenFaaS Community Edition is written in Golang and is MIT licensed. Various types of contributions are welcomed whether that means providing feedback, testing existing and new feature or hacking on the source code.
#### How do I become a contributor?

View File

@ -1,34 +0,0 @@
# Roadmap
## GitHub projects / source code
You can find a detailed breakdown of the [openfaas](https://github.com/openfaas/) and [openfaas-incubator](https://github.com/openfaas-incubator/) organisations and projects [in the docs](https://docs.openfaas.com/contributing/get-started/).
## Feature overview
For an overview see [the docs](https://docs.openfaas.com/) or see a [feature comparison between OpenFaaS and OpenFaaS Cloud](https://docs.openfaas.com/openfaas-cloud/intro/).
### OpenFaaS
OpenFaaS is a platform for building Serverless Functions and/or deploying existing microservices. Any programming language or binary is supported with a range of [templates](https://github.com/openfaas/templates) available to help you get started.
The core services which make up OpenFaaS need to run on a Linux master, but Windows worker nodes can be added to your cluster to run Windows binaries and functions.
Platforms: the x86_64 platform has first class support, with 32-bit arm and 64-bit arm provided on a best-effort basis.
Orchestrators: there is official support for Kubernetes & faasd (containerd) with the community providing support for AWS Fargate, Hashicorp Nomad and others.
### OpenFaaS Cloud
OpenFaaS Cloud is a multi-user distribution of OpenFaaS with a built-in CI/CD pipeline, OAuth delegation, a dashboard and a git-based workflow with public/private GitHub and self-hosted GitLab.
## What is coming next?
Proposals and feature requests are tracked [on the 2020 Roadmap on Trello](https://trello.com/invite/b/5OpMyrBP/ade103a10ae1e38eb5d3eee7955260a9/2020-openfaas-roadmap) and through the GitHub issue tracker of each project in the two organisations.
* [openfaas](https://github.com/openfaas/)
* [openfaas-incubator](https://github.com/openfaas-incubator/)
## Contributing
Please see [CONTRIBUTING.md](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md).

View File

@ -1 +1,2 @@
theme: jekyll-theme-cayman
theme: jekyll-theme-cayman

996
api-docs/spec.openapi.yml Normal file
View File

@ -0,0 +1,996 @@
openapi: 3.0.1
info:
title: OpenFaaS API Gateway
description: OpenFaaS API documentation
license:
name: MIT
version: 0.8.12
contact:
name: OpenFaaS Ltd
url: https://www.openfaas.com/support/
servers:
- url: "http://localhost:8080"
description: Local server
tags:
- name: internal
description: Internal use only
- name: system
description: System endpoints for managing functions and related objects
- name: function
description: Endpoints for invoking functions
paths:
"/healthz":
get:
summary: Healthcheck
operationId: healthcheck
description: Healthcheck for the gateway, indicates if the gateway is running and available
tags:
- internal
responses:
'200':
description: Healthy
'500':
description: Not healthy
"/metrics":
get:
summary: Prometheus metrics
operationId: metrics
description: Prometheus metrics for the gateway
tags:
- internal
responses:
'200':
description: Prometheus metrics in text format
"/system/info":
get:
operationId: GetSystemInfo
description: Get system provider information
summary: Get info such as provider version number and provider orchestrator
tags:
- system
responses:
'200':
description: Info result
content:
application/json:
schema:
"$ref": "#/components/schemas/GatewayInfo"
'500':
description: Internal Server Error
"/system/alert":
post:
operationId: ScaleAlert
description: Scale a function based on an alert
summary: |
Event-sink for AlertManager, for auto-scaling
Internal use for AlertManager, requires valid AlertManager alert
JSON
tags:
- internal
requestBody:
description: Incoming alert
content:
application/json:
schema:
$ref: '#/components/schemas/PrometheusAlert'
required: false
responses:
'200':
description: Alert handled successfully
'500':
description: Internal error with swarm or request JSON invalid
"/system/functions":
get:
operationId: GetFunctions
description: Get a list of deployed functions
summary: 'Get a list of deployed functions with: stats and image digest'
tags:
- system
responses:
'200':
description: List of deployed functions.
content:
application/json:
schema:
type: array
items:
"$ref": "#/components/schemas/FunctionStatus"
put:
operationId: UpdateFunction
description: update a function spec
summary: Update a function.
tags:
- system
requestBody:
description: Function to update
content:
application/json:
schema:
"$ref": "#/components/schemas/FunctionDeployment"
required: true
responses:
'200':
description: Accepted
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
post:
operationId: DeployFunction
description: Deploy a new function.
summary: Deploy a new function.
tags:
- system
requestBody:
description: Function to deploy
content:
application/json:
schema:
"$ref": "#/components/schemas/FunctionDeployment"
required: true
responses:
'202':
description: Accepted
'400':
description: Bad Request
'500':
description: Internal Server Error
delete:
operationId: DeleteFunction
description: Remove a deployed function.
summary: Remove a deployed function.
tags:
- system
requestBody:
description: Function to delete
content:
application/json:
schema:
"$ref": "#/components/schemas/DeleteFunctionRequest"
required: true
responses:
'200':
description: OK
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
"/system/scale-function/{functionName}":
post:
operationId: ScaleFunction
description: Scale a function
summary: Scale a function to a specific replica count
tags:
- system
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ScaleServiceRequest'
responses:
'200':
description: Scaling OK
'202':
description: Scaling OK
'404':
description: Function not found
'500':
description: Error scaling function
"/system/function/{functionName}":
get:
operationId: GetFunctionStatus
description: Get the status of a function by name
tags:
- system
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
- name: namespace
in: query
description: Namespace of the function
required: false
schema:
type: string
responses:
'200':
description: Function Summary
content:
"*/*":
schema:
"$ref": "#/components/schemas/FunctionStatus"
'404':
description: Not Found
'500':
description: Internal Server Error
"/system/secrets":
get:
operationId: ListSecrets
description: Get a list of secret names and metadata from the provider
summary: Get a list of secret names and metadata from the provider
tags:
- system
responses:
'200':
description: List of submitted secrets.
content:
application/json:
schema:
"$ref": "#/components/schemas/SecretDescription"
put:
operationId: UpdateSecret
description: Update a secret.
summary: Update a secret, the value is replaced.
tags:
- system
requestBody:
description: Secret to update
content:
application/json:
schema:
"$ref": "#/components/schemas/Secret"
required: true
responses:
'200':
description: Ok
'400':
description: Bad Request
'404':
description: Not Found
'405':
description: Method Not Allowed. Secret update is not allowed in faas-swarm.
'500':
description: Internal Server Error
post:
operationId: CreateSecret
description: Create a new secret.
tags:
- system
requestBody:
description: A new secret to create
content:
application/json:
schema:
"$ref": "#/components/schemas/Secret"
required: true
responses:
'201':
description: Created
'400':
description: Bad Request
'500':
description: Internal Server Error
delete:
operationId: DeleteSecret
description: Remove a secret.
tags:
- system
requestBody:
description: Secret to delete
content:
application/json:
schema:
"$ref": "#/components/schemas/SecretDescription"
required: true
responses:
'204':
description: OK
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
"/system/logs":
get:
operationId: GetFunctionLogs
description: Get a stream of the logs for a specific function
tags:
- system
parameters:
- name: name
in: query
description: Function name
required: true
schema:
type: string
- name: namespace
in: query
description: Namespace of the function
required: false
schema:
type: string
- name: instance
in: query
description: Instance of the function
required: false
schema:
type: string
- name: tail
in: query
description: Sets the maximum number of log messages to return, <=0 means
unlimited
schema:
type: integer
- name: follow
in: query
description: When true, the request will stream logs until the request timeout
schema:
type: boolean
- name: since
in: query
description: Only return logs after a specific date (RFC3339)
schema:
type: string
format: date-time
responses:
'200':
description: Newline delimited stream of log messages
content:
application/x-ndjson:
schema:
"$ref": "#/components/schemas/LogEntry"
'404':
description: Not Found
'500':
description: Internal Server Error
"/system/namespaces":
get:
operationId: ListNamespaces
description: Get a list of namespaces
tags:
- system
responses:
'200':
description: List of namespaces
content:
application/json:
schema:
$ref: '#/components/schemas/ListNamespaceResponse'
'500':
description: Internal Server Error
"/async-function/{functionName}":
post:
operationId: InvokeAsync
description: Invoke a function asynchronously
summary: |
Invoke a function asynchronously in the default OpenFaaS namespace
Any additional path segments and query parameters will be passed to the function as is.
See https://docs.openfaas.com/reference/async/.
tags:
- function
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
requestBody:
description: "(Optional) data to pass to function"
content:
"*/*":
schema:
type: string
format: binary
example: '{"hello": "world"}'
required: false
responses:
'202':
description: Request accepted and queued
'404':
description: Not Found
'500':
description: Internal Server Error
"/async-function/{functionName}.{namespace}":
post:
operationId: InvokeAsyncNamespaced
description: Invoke a function asynchronously in an OpenFaaS namespace.
summary: |
Invoke a function asynchronously in an OpenFaaS namespace.
Any additional path segments and query parameters will be passed to the function as is.
See https://docs.openfaas.com/reference/async/.
tags:
- function
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
- name: namespace
in: path
description: Namespace of the function
required: true
schema:
type: string
requestBody:
description: "(Optional) data to pass to function"
content:
"*/*":
schema:
type: string
format: binary
example: '{"hello": "world"}'
required: false
responses:
'202':
description: Request accepted and queued
'404':
description: Not Found
'500':
description: Internal Server Error
"/function/{functionName}":
post:
operationId: InvokeFunction
description: Invoke a function in the default OpenFaaS namespace.
summary: |
Synchronously invoke a function defined in te default OpenFaaS namespace.
Any additional path segments and query parameters will be passed to the function as is.
tags:
- function
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
requestBody:
description: "(Optional) data to pass to function"
content:
"*/*":
schema:
type: string
format: binary
example: '{"hello": "world"}'
required: false
responses:
'200':
description: Value returned from function
'404':
description: Not Found
'500':
description: Internal server error
"/function/{functionName}.{namespace}":
post:
operationId: InvokeFunctionNamespaced
description: Invoke a function in an OpenFaaS namespace.
summary: |
Synchronously invoke a function defined in the specified namespace.
Any additional path segments and query parameters will be passed to the function as is.
tags:
- function
parameters:
- name: functionName
in: path
description: Function name
required: true
schema:
type: string
- name: namespace
in: path
description: Namespace of the function
required: true
schema:
type: string
requestBody:
description: "(Optional) data to pass to function"
content:
"*/*":
schema:
type: string
format: binary
example: '{"hello": "world"}'
required: false
responses:
'200':
description: Value returned from function
'404':
description: Not Found
'500':
description: Internal server error
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
schemas:
GatewayInfo:
required:
- provider
- version
- arch
type: object
properties:
provider:
nullable: true
allOf:
- $ref: "#/components/schemas/ProviderInfo"
version:
nullable: true
description: version of the gateway
allOf:
- $ref: "#/components/schemas/VersionInfo"
arch:
type: string
description: Platform architecture
example: x86_64
VersionInfo:
type: object
required:
- sha
- release
properties:
commit_message:
type: string
example: Sample Message
sha:
type: string
example: 7108418d9dd6b329ddff40e7393b3166f8160a88
release:
type: string
format: semver
example: 0.8.9
ProviderInfo:
type: object
required:
- provider
- orchestration
- version
properties:
provider:
type: string
description: The orchestration provider / implementation
example: faas-netes
orchestration:
type: string
example: kubernetes
version:
description: The version of the provider
nullable: true
allOf:
- $ref: "#/components/schemas/VersionInfo"
PrometheusAlert:
type: object
description: Prometheus alert produced by AlertManager. This is only a subset of the full alert payload.
required:
- status
- receiver
- alerts
properties:
status:
type: string
description: The status of the alert
example: resolved
receiver:
type: string
description: The name of the receiver
example: webhook
alerts:
type: array
description: The list of alerts
items:
$ref: "#/components/schemas/PrometheusInnerAlert"
example:
{
"receiver": "scale-up",
"status": "firing",
"alerts": [{
"status": "firing",
"labels": {
"alertname": "APIHighInvocationRate",
"code": "200",
"function_name": "func_nodeinfo",
"instance": "gateway:8080",
"job": "gateway",
"monitor": "faas-monitor",
"service": "gateway",
"severity": "major",
"value": "8.998200359928017"
},
"annotations": {
"description": "High invocation total on gateway:8080",
"summary": "High invocation total on gateway:8080"
},
"startsAt": "2017-03-15T15:52:57.805Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://4156cb797423:9090/graph?g0.expr=rate%28gateway_function_invocation_total%5B10s%5D%29+%3E+5\u0026g0.tab=0"
}],
"groupLabels": {
"alertname": "APIHighInvocationRate",
"service": "gateway"
},
"commonLabels": {
"alertname": "APIHighInvocationRate",
"code": "200",
"function_name": "func_nodeinfo",
"instance": "gateway:8080",
"job": "gateway",
"monitor": "faas-monitor",
"service": "gateway",
"severity": "major",
"value": "8.998200359928017"
},
"commonAnnotations": {
"description": "High invocation total on gateway:8080",
"summary": "High invocation total on gateway:8080"
},
"externalURL": "http://f054879d97db:9093",
"version": "3",
"groupKey": 18195285354214864953
}
PrometheusInnerAlert:
type: object
description: A single alert produced by Prometheus
required:
- status
- labels
properties:
status:
type: string
description: The status of the alert
example: resolved
labels:
$ref: "#/components/schemas/PrometheusInnerAlertLabel"
PrometheusInnerAlertLabel:
type: object
description: A single label of a Prometheus alert
required:
- alertname
- function_name
properties:
alertname:
type: string
description: The name of the alert
function_name:
type: string
description: The name of the function
example: nodeinfo
FunctionDeployment:
required:
- service
- image
type: object
properties:
service:
type: string
description: Name of deployed function
example: nodeinfo
image:
type: string
description: Docker image in accessible registry
example: functions/nodeinfo:latest
namespace:
type: string
description: Namespace to deploy function to. When omitted, the default namespace
is used, typically this is `openfaas-fn` but is configured by the provider.
example: openfaas-fn
envProcess:
type: string
description: |
Process for watchdog to fork, i.e. the command to start the function process.
This value configures the `fprocess` env variable.
example: node main.js
constraints:
type: array
items:
type: string
description: Constraints are specific to OpenFaaS Provider
example: node.platform.os == linux
envVars:
type: object
additionalProperties:
type: string
description: Overrides to environmental variables
secrets:
type: array
items:
type: string
description: An array of names of secrets that are required to be loaded
from the Docker Swarm.
example: secret-name-1
labels:
type: object
nullable: true
additionalProperties:
type: string
description: A map of labels for making scheduling or routing decisions
example:
foo: bar
annotations:
type: object
nullable: true
additionalProperties:
type: string
description: A map of annotations for management, orchestration, events
and build tasks
example:
topics: awesome-kafka-topic
foo: bar
limits:
nullable: true
allOf:
- $ref: "#/components/schemas/FunctionResources"
requests:
nullable: true
allOf:
- $ref: "#/components/schemas/FunctionResources"
readOnlyRootFilesystem:
type: boolean
description: Make the root filesystem of the function read-only
# DEPRECATED FIELDS, these fields are ignored in all current providers
registryAuth:
type: string
description: |
Deprecated: Private registry base64-encoded basic auth (as present in ~/.docker/config.json)
Use a Kubernetes Secret with registry-auth secret type to provide this value instead.
This value is completely ignored.
example: dXNlcjpwYXNzd29yZA==
deprecated: true
network:
type: string
description: |
Deprecated: Network, usually func_functions for Swarm.
This value is completely ignored.
deprecated: true
example: func_functions
FunctionStatus:
type: object
required:
- name
- image
properties:
name:
type: string
description: The name of the function
example: nodeinfo
image:
type: string
description: The fully qualified docker image name of the function
example: functions/nodeinfo:latest
namespace:
type: string
description: The namespace of the function
example: openfaas-fn
envProcess:
type: string
description: Process for watchdog to fork
example: node main.js
envVars:
type: object
additionalProperties:
type: string
description: environment variables for the function runtime
constraints:
type: array
items:
type: string
description: Constraints are specific to OpenFaaS Provider
example: node.platform.os == linux
secrets:
type: array
items:
type: string
description: An array of names of secrets that are made available to the function
labels:
type: object
nullable: true
additionalProperties:
type: string
description: A map of labels for making scheduling or routing decisions
example:
foo: bar
annotations:
type: object
nullable: true
additionalProperties:
type: string
description: A map of annotations for management, orchestration, events
and build tasks
example:
topics: awesome-kafka-topic
foo: bar
limits:
nullable: true
allOf:
- $ref: "#/components/schemas/FunctionResources"
requests:
nullable: true
allOf:
- $ref: "#/components/schemas/FunctionResources"
readOnlyRootFilesystem:
type: boolean
description: removes write-access from the root filesystem mount-point.
invocationCount:
type: number
description: The amount of invocations for the specified function
format: integer
example: 1337
replicas:
type: number
description: The current minimal ammount of replicas
format: integer
example: 2
availableReplicas:
type: number
description: The current available amount of replicas
format: integer
example: 2
createdAt:
type: string
description: |
is the time read back from the faas backend's
data store for when the function or its container was created.
format: date-time
usage:
nullable: true
allOf:
- $ref: "#/components/schemas/FunctionUsage"
FunctionResources:
type: object
properties:
memory:
type: string
description: The amount of memory that is allocated for the function
example: 128M
cpu:
type: string
description: The amount of cpu that is allocated for the function
example: '0.01'
FunctionUsage:
type: object
properties:
cpu:
type: number
description: |
is the increase in CPU usage since the last measurement
equivalent to Kubernetes' concept of millicores.
format: double
example: 0.01
totalMemoryBytes:
type: number
description: is the total memory usage in bytes.
format: double
example: 1337
DeleteFunctionRequest:
required:
- functionName
type: object
properties:
functionName:
type: string
description: Name of deployed function
example: nodeinfo
ScaleServiceRequest:
required:
- serviceName
- namespace
- replicas
type: object
properties:
serviceName:
type: string
description: Name of deployed function
example: nodeinfo
namespace:
type: string
description: Namespace the function is deployed to.
example: openfaas-fn
replicas:
type: integer
format: int64
minimum: 0
description: Number of replicas to scale to
example: 2
SecretDescription:
required:
- name
type: object
properties:
name:
type: string
description: Name of secret
example: aws-key
namespace:
type: string
description: Namespace of secret
example: openfaas-fn
SecretValues:
type: object
properties:
value:
type: string
description: Value of secret in plain-text
example: changeme
rawValue:
type: string
format: byte
description: |
Value of secret in base64.
This can be used to provide raw binary data when the `value` field is omitted.
example: Y2hhbmdlbWU=
Secret:
type: object
allOf:
- $ref: "#/components/schemas/SecretDescription"
- $ref: "#/components/schemas/SecretValues"
LogEntry:
type: object
required:
- name
- namespace
- instance
- timestamp
- text
properties:
name:
type: string
description: the function name
namespace:
type: string
description: the namespace of the function
instance:
type: string
description: the name/id of the specific function instance
timestamp:
type: string
description: the timestamp of when the log message was recorded
format: date-time
text:
type: string
description: raw log message content
ListNamespaceResponse:
type: array
items:
type: string
description: Namespace name
example: openfaas-fn

View File

@ -1,628 +0,0 @@
swagger: '2.0'
info:
description: OpenFaaS API documentation
version: 0.8.12
title: OpenFaaS API Gateway
license:
name: MIT
basePath: /
schemes:
- http
paths:
'/system/functions':
get:
summary: 'Get a list of deployed functions with: stats and image digest'
consumes:
- application/json
produces:
- application/json
responses:
'200':
description: List of deployed functions.
schema:
type: array
items:
$ref: '#/definitions/FunctionListEntry'
post:
summary: Deploy a new function.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Function to deploy
required: true
schema:
$ref: '#/definitions/FunctionDefinition'
responses:
'202':
description: Accepted
'400':
description: Bad Request
'500':
description: Internal Server Error
put:
summary: Update a function.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Function to update
required: true
schema:
$ref: '#/definitions/FunctionDefinition'
responses:
'200':
description: Accepted
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
delete:
summary: Remove a deployed function.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Function to delete
required: true
schema:
$ref: '#/definitions/DeleteFunctionRequest'
responses:
'200':
description: OK
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
'/system/alert':
post:
summary: 'Event-sink for AlertManager, for auto-scaling'
description: 'Internal use for AlertManager, requires valid AlertManager alert JSON'
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Incoming alert
schema:
type: object
example: |-
{"receiver": "scale-up",
"status": "firing",
"alerts": [{
"status": "firing",
"labels": {
"alertname": "APIHighInvocationRate",
"code": "200",
"function_name": "func_nodeinfo",
"instance": "gateway:8080",
"job": "gateway",
"monitor": "faas-monitor",
"service": "gateway",
"severity": "major",
"value": "8.998200359928017"
},
"annotations": {
"description": "High invocation total on gateway:8080",
"summary": "High invocation total on gateway:8080"
},
"startsAt": "2017-03-15T15:52:57.805Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://4156cb797423:9090/graph?g0.expr=rate%28gateway_function_invocation_total%5B10s%5D%29+%3E+5\u0026g0.tab=0"
}],
"groupLabels": {
"alertname": "APIHighInvocationRate",
"service": "gateway"
},
"commonLabels": {
"alertname": "APIHighInvocationRate",
"code": "200",
"function_name": "func_nodeinfo",
"instance": "gateway:8080",
"job": "gateway",
"monitor": "faas-monitor",
"service": "gateway",
"severity": "major",
"value": "8.998200359928017"
},
"commonAnnotations": {
"description": "High invocation total on gateway:8080",
"summary": "High invocation total on gateway:8080"
},
"externalURL": "http://f054879d97db:9093",
"version": "3",
"groupKey": 18195285354214864953
}
responses:
'200':
description: Alert handled successfully
'500':
description: Internal error with swarm or request JSON invalid
'/async-function/{functionName}':
post:
summary: 'Invoke a function asynchronously in OpenFaaS'
description: >-
See https://docs.openfaas.com/reference/async/.
parameters:
- in: path
name: functionName
description: Function name
type: string
required: true
- in: body
name: input
description: (Optional) data to pass to function
schema:
type: string
format: binary
example:
'{"hello": "world"}'
required: false
responses:
'202':
description: Request accepted and queued
'404':
description: Not Found
'500':
description: Internal Server Error
'/function/{functionName}':
post:
summary: Invoke a function defined in OpenFaaS
parameters:
- in: path
name: functionName
description: Function name
type: string
required: true
- in: body
name: input
description: (Optional) data to pass to function
schema:
type: string
format: binary
example:
'{"hello": "world"}'
required: false
responses:
'200':
description: Value returned from function
'404':
description: Not Found
'500':
description: Internal server error
'/system/scale-function/{functionName}':
post:
summary: Scale a function
parameters:
- in: path
name: functionName
description: Function name
type: string
required: true
- in: body
name: input
description: Function to scale plus replica count
schema:
type: string
format: binary
example:
'{"service": "hello-world", "replicas": 10}'
required: false
responses:
'200':
description: Scaling OK
'202':
description: Scaling OK
'404':
description: Function not found
'500':
description: Error scaling function
'/system/function/{functionName}':
get:
summary: Get a summary of an OpenFaaS function
parameters:
- in: path
name: functionName
description: Function name
type: string
required: true
responses:
'200':
description: Function Summary
schema:
$ref: '#/definitions/FunctionListEntry'
'404':
description: Not Found
'500':
description: Internal Server Error
'/system/secrets':
get:
summary: 'Get a list of secret names and metadata from the provider'
consumes:
- application/json
produces:
- application/json
responses:
'200':
description: List of submitted secrets.
schema:
$ref: '#/definitions/SecretName'
post:
summary: Create a new secret.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: A new secret to create
required: true
schema:
$ref: '#/definitions/Secret'
responses:
'201':
description: Created
'400':
description: Bad Request
'500':
description: Internal Server Error
put:
summary: Update a secret.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Secret to update
required: true
schema:
$ref: '#/definitions/Secret'
responses:
'200':
description: Ok
'400':
description: Bad Request
'404':
description: Not Found
'405':
description: Method Not Allowed. Secret update is not allowed in faas-swarm.
'500':
description: Internal Server Error
delete:
summary: Remove a secret.
description: ''
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: Secret to delete
required: true
schema:
$ref: '#/definitions/SecretName'
responses:
'204':
description: OK
'400':
description: Bad Request
'404':
description: Not Found
'500':
description: Internal Server Error
'/system/logs':
get:
summary: Get a stream of the logs for a specific function
produces:
- application/x-ndjson
parameters:
- in: query
name: name
description: Function name
type: string
required: true
- in: query
name: since
description: Only return logs after a specific date (RFC3339)
type: string
required: false
- in: query
name: tail
description: Sets the maximum number of log messages to return, <=0 means unlimited
type: integer
required: false
- in: query
name: follow
description: When true, the request will stream logs until the request timeout
type: boolean
required: false
responses:
'200':
description: Newline delimited stream of log messages
schema:
$ref: '#/definitions/LogEntry'
'404':
description: Not Found
'500':
description: Internal Server Error
'/system/info':
get:
summary: Get info such as provider version number and provider orchestrator
produces:
- application/json
responses:
'200':
description: Info result
schema:
$ref: '#/definitions/Info'
'404':
description: Provider does not support info endpoint
'500':
description: Internal Server Error
'/healthz':
get:
summary: Healthcheck
responses:
'200':
description: Healthy
'500':
description: Not healthy
securityDefinitions:
basicAuth:
type: basic
definitions:
Info:
type: object
properties:
provider:
type: object
description: The OpenFaaS Provider
properties:
provider:
type: string
example: faas-netes
orchestration:
type: string
example: kubernetes
version:
type: object
description: Version of the OpenFaaS Provider
properties:
commit_message:
type: string
example: Sample Message
sha:
type: string
example: 7108418d9dd6b329ddff40e7393b3166f8160a88
release:
type: string
format: semver
example: 0.2.6
version:
type: object
description: Version of the Gateway
properties:
commit_message:
type: string
example: Sample Message
sha:
type: string
example: 7108418d9dd6b329ddff40e7393b3166f8160a88
release:
type: string
format: semver
example: 0.8.9
arch:
type: string
description: "Platform architecture"
example: "x86_64"
required:
- provider
- version
DeleteFunctionRequest:
type: object
properties:
functionName:
type: string
description: Name of deployed function
example: nodeinfo
required:
- functionName
FunctionDefinition:
type: object
properties:
service:
type: string
description: Name of deployed function
example: nodeinfo
network:
type: string
description: Network, usually func_functions for Swarm (deprecated)
example: func_functions
image:
type: string
description: Docker image in accessible registry
example: functions/nodeinfo:latest
envProcess:
type: string
description: Process for watchdog to fork
example: node main.js
envVars:
type: object
additionalProperties:
type: string
description: Overrides to environmental variables
constraints:
type: array
items:
type: string
description: Constraints are specific to OpenFaaS Provider
example: "node.platform.os == linux"
labels:
description: A map of labels for making scheduling or routing decisions
type: object
additionalProperties:
type: string
example:
foo: bar
annotations:
description: A map of annotations for management, orchestration, events and build tasks
type: object
additionalProperties:
type: string
example:
topics: awesome-kafka-topic
foo: bar
secrets:
type: array
items:
type: string
description: An array of names of secrets that are required to be loaded from the Docker Swarm.
example: "secret-name-1"
registryAuth:
type: string
description: >-
Private registry base64-encoded basic auth (as present in
~/.docker/config.json)
example: dXNlcjpwYXNzd29yZA==
limits:
type: object
properties:
memory:
type: string
example: "128M"
cpu:
type: string
example: "0.01"
requests:
type: object
properties:
memory:
type: string
example: "128M"
cpu:
type: string
example: "0.01"
readOnlyRootFilesystem:
type: boolean
description: Make the root filesystem of the function read-only
required:
- service
- image
- envProcess
FunctionListEntry:
type: object
properties:
name:
description: The name of the function
type: string
example: nodeinfo
image:
description: The fully qualified docker image name of the function
type: string
example: functions/nodeinfo:latest
invocationCount:
description: The amount of invocations for the specified function
type: number
format: integer
example: 1337
replicas:
description: The current minimal ammount of replicas
type: number
format: integer
example: 2
availableReplicas:
description: The current available amount of replicas
type: number
format: integer
example: 2
envProcess:
description: Process for watchdog to fork
type: string
example: node main.js
labels:
description: A map of labels for making scheduling or routing decisions
type: object
additionalProperties:
type: string
example:
foo: bar
annotations:
description: A map of annotations for management, orchestration, events and build tasks
type: object
additionalProperties:
type: string
example:
topics: awesome-kafka-topic
foo: bar
required:
- name
- image
- invocationCount
- replicas
- availableReplicas
- envProcess
- labels
Secret:
type: object
properties:
name:
type: string
description: Name of secret
example: aws-key
value:
type: string
description: Value of secret in plain-text
example: changeme
required:
- name
LogEntry:
type: object
properties:
name:
type: string
description: the function name
instance:
type: string
description: the name/id of the specific function instance
timestamp:
type: string
format: date-time
description: the timestamp of when the log message was recorded
text:
type: string
description: raw log message content
SecretName:
type: object
properties:
name:
type: string
description: Name of secret
example: aws-key
externalDocs:
description: More documentation available on Github
url: 'https://github.com/openfaas/faas'

View File

@ -1,13 +0,0 @@
auth plugins
============
Auth plugins must implement request checking on a HTTP port and path such as `:8080/validate`.
* Valid requests: return 2xx
* Invalid requests: return non 2xx
It is up to the developer to pick whether a request body is required for validation. For strategies such as [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication), headers are sufficient.
Plugins available:
* [basic-auth](./basic-auth/)

View File

@ -1 +0,0 @@
basic-auth

View File

@ -1,45 +0,0 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} ghcr.io/openfaas/license-check:0.4.0 as license-check
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.17 as build
ENV GO111MODULE=off
ENV CGO_ENABLED=0
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETOS
ARG TARGETARCH
COPY --from=license-check /license-check /usr/bin/
WORKDIR /go/src/handler
COPY . .
# Run a gofmt and exclude all vendored code.
RUN license-check -path ./ --verbose=false "OpenFaaS Authors" "OpenFaaS Author(s)"
RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*"))"
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test -v ./...
RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
--ldflags "-s -w" -a -installsuffix cgo -o handler .
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.16.0 as ship
# Add non-root user
RUN addgroup -S app && adduser -S -g app app \
&& mkdir -p /home/app \
&& chown app /home/app
WORKDIR /home/app
COPY --from=build /go/src/handler/handler .
RUN chown -R app /home/app
USER app
WORKDIR /home/app
CMD ["./handler"]

View File

@ -1,13 +0,0 @@
basic-auth
============
This component implements [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) as an OpenFaaS authentication plug-in.
To run this plugin you will need to create and bind a secret named `basic-auth-user` and `basic-auth-password`
| Option | Usage |
|---------------------------------|--------------|
| `port` | Set the HTTP port |
| `secret_mount_path` | It is recommended that this is set to `/var/openfaas/secrets` |
| `user_filename` | File to read from disk for username, default empty |
| `pass_filename` | File to read from disk for username, default empty |

View File

@ -1,8 +0,0 @@
module github.com/openfaas/faas/auth/basic-auth
go 1.17
require (
github.com/openfaas/faas-provider v0.18.9
github.com/pkg/errors v0.8.1
)

View File

@ -1,27 +0,0 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/openfaas/faas-provider v0.18.9 h1:nHPlq9PYLGLuyhuXfASlBPOvXiZC/fJqHOr6m+0Fn1s=
github.com/openfaas/faas-provider v0.18.9/go.mod h1:S217qfIaMrv+XKJxgbhBzJzCfyFvoIF+BvYdDo6XIDQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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=
go.uber.org/goleak v1.1.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,81 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"strconv"
"time"
"github.com/openfaas/faas-provider/auth"
"github.com/pkg/errors"
)
func main() {
port := 8080
if val, ok := os.LookupEnv("port"); ok {
intOut, err := strconv.Atoi(val)
if err != nil {
panic(errors.Wrap(err, fmt.Sprintf("value of `port`: %s, not a valid port", val)))
}
port = intOut
}
s := &http.Server{
Addr: fmt.Sprintf(":%d", port),
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
MaxHeaderBytes: 1 << 20, // Max header of 1MB
}
credentialsReader := auth.ReadBasicAuthFromDisk{
SecretMountPath: os.Getenv("secret_mount_path"),
UserFilename: os.Getenv("user_filename"),
PasswordFilename: os.Getenv("pass_filename"),
}
credentials, err := credentialsReader.Read()
if err != nil {
panic(errors.Wrap(err, "unable to read basic auth credentials, check `secret_mount_path`"))
}
authHandler := auth.DecorateWithBasicAuth(func(w http.ResponseWriter, r *http.Request) {
}, credentials)
http.HandleFunc("/validate", makeLogger(authHandler))
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
log.Printf("Listening on: %d\n", port)
log.Fatal(s.ListenAndServe())
}
func makeLogger(next http.Handler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
rr := httptest.NewRecorder()
next.ServeHTTP(rr, r)
log.Printf("Validated request %d.\n", rr.Code)
resHeader := rr.Header()
copyHeaders(w.Header(), &resHeader)
w.WriteHeader(rr.Code)
if rr.Body != nil {
w.Write(rr.Body.Bytes())
}
}
}
func copyHeaders(destination http.Header, source *http.Header) {
for k, v := range *source {
vClone := make([]string, len(v))
copy(vClone, v)
(destination)[k] = vClone
}
}

View File

@ -1,29 +0,0 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func Test_makeLogger_CopiesResponseHeaders(t *testing.T) {
handler := http.HandlerFunc(makeLogger(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Unit-Test", "true")
})))
s := httptest.NewServer(handler)
defer s.Close()
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
got := rr.Header().Get("X-Unit-Test")
want := "true"
if want != got {
t.Errorf("Header X-Unit-Test, want: %s, got %s", want, got)
}
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Alex Ellis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,30 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package auth
import (
"crypto/subtle"
"net/http"
)
// DecorateWithBasicAuth enforces basic auth as a middleware with given credentials
func DecorateWithBasicAuth(next http.HandlerFunc, credentials *BasicAuthCredentials) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, password, ok := r.BasicAuth()
const noMatch = 0
if !ok ||
user != credentials.User ||
subtle.ConstantTimeCompare([]byte(credentials.Password), []byte(password)) == noMatch {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("invalid credentials"))
return
}
next.ServeHTTP(w, r)
}
}

View File

@ -1,66 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package auth
import (
"fmt"
"io/ioutil"
"path"
"strings"
)
// BasicAuthCredentials for credentials
type BasicAuthCredentials struct {
User string
Password string
}
type ReadBasicAuth interface {
Read() (*BasicAuthCredentials, error)
}
type ReadBasicAuthFromDisk struct {
SecretMountPath string
UserFilename string
PasswordFilename string
}
func (r *ReadBasicAuthFromDisk) Read() (*BasicAuthCredentials, error) {
var credentials *BasicAuthCredentials
if len(r.SecretMountPath) == 0 {
return nil, fmt.Errorf("invalid SecretMountPath specified for reading secrets")
}
userKey := "basic-auth-user"
if len(r.UserFilename) > 0 {
userKey = r.UserFilename
}
passwordKey := "basic-auth-password"
if len(r.PasswordFilename) > 0 {
passwordKey = r.PasswordFilename
}
userPath := path.Join(r.SecretMountPath, userKey)
user, userErr := ioutil.ReadFile(userPath)
if userErr != nil {
return nil, fmt.Errorf("unable to load %s", userPath)
}
userPassword := path.Join(r.SecretMountPath, passwordKey)
password, passErr := ioutil.ReadFile(userPassword)
if passErr != nil {
return nil, fmt.Errorf("Unable to load %s", userPassword)
}
credentials = &BasicAuthCredentials{
User: strings.TrimSpace(string(user)),
Password: strings.TrimSpace(string(password)),
}
return credentials, nil
}

View File

@ -1,24 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

View File

@ -1,15 +0,0 @@
language: go
go_import_path: github.com/pkg/errors
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- tip
script:
- go test -v ./...

View File

@ -1,23 +0,0 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,52 +0,0 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
if err != nil {
return err
}
```
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
## Adding context to an error
The errors.Wrap function returns a new error that adds context to the original error. For example
```go
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```
## Retrieving the cause of an error
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
```go
type causer interface {
Cause() error
}
```
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
```go
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
```
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
Before proposing a change, please discuss your change by raising an issue.
## License
BSD-2-Clause

View File

@ -1,32 +0,0 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\pkg\errors
shallow_clone: true # for startup speed
environment:
GOPATH: C:\gopath
platform:
- x64
# http://www.appveyor.com/docs/installed-software
install:
# some helpful output for debugging builds
- go version
- go env
# pre-installed MinGW at C:\MinGW is 32bit only
# but MSYS2 at C:\msys64 has mingw64
- set PATH=C:\msys64\mingw64\bin;%PATH%
- gcc --version
- g++ --version
build_script:
- go install -v ./...
test_script:
- set PATH=C:\gopath\bin;%PATH%
- go test -v ./...
#artifacts:
# - path: '%GOPATH%\bin\*.exe'
deploy: off

View File

@ -1,282 +0,0 @@
// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
// if err != nil {
// return err
// }
//
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
// type causer interface {
// Cause() error
// }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
// switch err := errors.Cause(err).(type) {
// case *MyError:
// // handle specifically
// default:
// // unknown error
// }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported:
//
// %s print the error. If the error has a Cause it will be
// printed recursively.
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface:
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// The returned errors.StackTrace type is defined as
//
// type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errors
import (
"fmt"
"io"
)
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type withStack struct {
error
*stack
}
func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
}
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

View File

@ -1,147 +0,0 @@
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strings"
)
// Frame represents a program counter inside a stack frame.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}

View File

@ -1,6 +0,0 @@
# github.com/openfaas/faas-provider v0.18.9
## explicit; go 1.16
github.com/openfaas/faas-provider/auth
# github.com/pkg/errors v0.8.1
## explicit
github.com/pkg/errors

View File

@ -52,7 +52,6 @@ It would be great to hear from you especially if you have any of the above and w
| Title | Show | Date |
|---------------------------------------------------------------------|--------------|----------|
| [The Cube Interview - Monetising Open Source at KubeCon Valencia](https://www.youtube.com/watch?v=iyMIF4olLWI) | the Cube | 19-May-2022 |
| ["Balancing OSS Sacrifice with Success"](https://github.com/readme/alex-ellis) | GitHub ReadME program | 04-Sep-2020 |
| [Episode #116 Independent Open Source, with Alex Ellis](https://kubernetespodcast.com/episode/116-independent-open-source/) | Kubernetes Podcast by Google | 12-Aug-2020 |
| [The explosive growth of CNCF, what is Kubernetes and independent open source](https://blog.newrelic.com/engineering/observy-mcobservface-episode-no-2-alex-ellis/) | Observy McObservface | 29-07-2020 |
@ -85,18 +84,55 @@ It would be great to hear from you especially if you have any of the above and w
| [Digital Transformation of Vision Banco Paraguay with Serverless Functions @ KubeCon](https://kccna18.sched.com/event/GraO/digital-transformation-of-vision-banco-paraguay-with-serverless-functions-alex-ellis-vmware-patricio-diaz-vision-banco-saeca) | Alex Ellis & Patricio Diaz | 13-Dec-2018 |
| [Introducing "faas" - Cool Hacks Keynote at Dockercon 2017](https://blog.docker.com/2017/04/dockercon-2017-mobys-cool-hack-sessions/) | Alex Ellis | 04-April-2017 |
### 2022
### 2023
#### Blog posts and write-ups 2022
#### Blog posts, write-ups and videos 2023
[Back to top](#openfaas-community)
| Blog/repo name and description | Author | Site | Date |
| Blog/video/repo name and description | Author | Site | Date |
|-------------------------------------------------------------------------|--------------|----------|-------------|
| [Building OpenFaaS Serverless function to detect weather using OpenWeatherMap and Python](https://www.faizanbashir.me/building-openfaas-serverless-function-to-detect-weather-using-openweathermap-and-python) | Faizan Bashir | faizanbashir.me | 02-Apr-2023 |
| [Fine-tuning the cold-start in OpenFaaS ](https://www.openfaas.com/blog/fine-tuning-the-cold-start/) | Alex Ellis | openfaas.com | 28-Mar-2023 |
| [How do changes to the Docker Hub affect OpenFaaS?](https://www.openfaas.com/blog/how-does-docker-hub-affect-openfaas/) | Alex Ellis | openfaas.com | 20-Mar-2023 |
| [Cluster auto-scaling with DigitalOcean Kubernetes and OpenFaaS](https://www.openfaas.com/blog/cluster-autoscaling-with-digitalocean/) | Alex Ellis | openfaas.com | 16-Mar-2023 |
| [Import leads from Google Forms into your CRM with functions](https://www.openfaas.com/blog/import-leads-from-google-forms-to-crm/) | Alex Ellis | openfaas.com | 02-Mar-2023 |
| [Using OpenFaaS on AKS](https://learn.microsoft.com/en-us/azure/aks/openfaas) | Various | learn.microsoft.com | 27-Feb-2023 |
| [How to integrate OpenFaaS functions with managed AWS services](https://www.openfaas.com/blog/integrate-openfaas-with-managed-aws-services/) | Han Verstraete | openfaas.com | 19-Jan-2023 |
### 2022
#### Blog posts, write-ups and videos 2022
[Back to top](#openfaas-community)
| Blog/video/repo name and description | Author | Site | Date |
|-------------------------------------------------------------------------|--------------|----------|-------------|
| [Trigger OpenFaaS functions from PostgreSQL with AWS Aurora](https://www.openfaas.com/blog/trigger-functions-from-postgres/) | Han Verstraete | openfaas.com | 16-Dec-2022 |
| [Introducing our new Python template for production](https://www.openfaas.com/blog/openfaas-pro-python-template/) | Han Verstraete | openfaas.com | 06-Dec-2022 |
| [Deploy Serverless Function on k3s/Kubernetes with OpenFaaS (x86/Arm, Linux VM, Go)](https://www.youtube.com/watch?v=-8MrDWg6K6s) | David Hwang | youtube.com | 09-Nov-2022 |
| [Rethinking Auto-scaling for OpenFaaS](https://www.openfaas.com/blog/autoscaling-functions/) | Han Verstraete | openfaas.com | 05-Nov-2022 |
| [Custom health and readiness checks for your OpenFaaS Functions](https://www.openfaas.com/blog/health-and-readiness-for-functions/) | Alex Ellis | openfaas.com | 26-Oct-2022 |
| [Generate PDFs at scale on Kubernetes using OpenFaaS and Puppeteer](https://www.openfaas.com/blog/pdf-generation-at-scale-on-kubernetes/) | Han Verstraete | openfaas.com | 06-Oct-2022 |
| [Eliminate vendor locking of Serverless workloads with OpenFaaS](https://awstip.com/eliminate-vendor-lock-in-of-serverless-workloads-with-openfaas-474807383ce1) | Meher Chaitanya | medium.com | 06-Oct-2022 |
| [Use the Serverless Function Method to Build a Machine Learning Microservice System](https://blog.infuseai.io/use-serverless-function-method-to-build-a-ml-microservice-system-a108f3f2c1c) | SimonLiu | blog.infuseai.io | 30-Aug-2022 |
| [Go Functions as a Service With Kubernetes and OpenFaaS](https://dominikbraun.io/blog/go-functions-as-a-service-with-kubernetes-and-openfaas/) | Dominik Braun | dominikbraun.io | 24-Aug-2022 |
| [Exploring the Fan out and Fan in pattern with OpenFaaS](https://www.openfaas.com/blog/fan-out-and-back-in-using-functions/) | Han Verstraete | openfaas.com | 22-Aug-2022 |
| [Finding Raspberry Pis with Raspberry Pis](https://www.openfaas.com/blog/searching-for-raspberrypi/) | Alex Ellis | openfaas.com | 08-Aug-2022 |
| [The Next Generation of Queuing: JetStream for OpenFaaS](https://www.openfaas.com/blog/jetstream-for-openfaas/) | Han Verstraete | openfaas.com | 21-Jul-2022 |
| [How to update your OpenFaaS functions automatically with the Argo CD Image Updater](https://www.openfaas.com/blog/argocd-image-updater-for-functions/) | Han Verstraete | openfaas.com | 04-Jul-2022 |
| [How to build functions from source code with the Function Builder API](https://www.openfaas.com/blog/how-to-build-via-api/) | Han Verstraete | openfaas.com | 23-Jun-2022 |
| [OpenFaaS First Function](https://rpi4cluster.com/k3s/k3s-openfaas-function/) | Vlado Portos | rpi4cluster.com | 22-Jun-2022 |
| [OpenFaaS](https://rpi4cluster.com/k3s/k3s-openfaas/) | Vlado Portos | rpi4cluster.com | 22-Jun-2022 |
| [How to package OpenFaaS functions with Helm](https://www.openfaas.com/blog/howto-package-functions-with-helm/) | Han Verstraete | openfaas.com | 09-Jun-2022 |
| [The Event-Driven Edge with OpenFaaS](https://www.openfaas.com/blog/eventdriven-edge/) | Han Verstraete | openfaas.com | 01-Jun-2022 |
| [Running faasd on Azure Arm-based Virtual Machines](https://blog.ediri.io/running-faasd-on-azure-arm-based-virtual-machines) | Engin Diri | blog.ediri.io | 27-May-2022 |
| [WebAssembly functions in OpenFaaS using Sat (Part1)](https://www.wasm.builders/suborbital/webassembly-functions-in-openfaas-using-sat-part-1-2omk) | Connor Hicks | wasm.builders | 04-May-2022 |
| [Building a RESTful API with functions](https://simonemms.com/blog/2022/04/24/building-a-restful-api-with-serverless-functions/) | Simon Emms | simonemms.com | 24-April-2022 |
| [Building a RESTful API with functions](https://simonemms.com/blog/2022/04/24/building-a-restful-api-with-serverless-functions/) | Simon Emms | simonemms.com | 24-Apr-2022 |
| [How to process your data the resilient way with back pressure](https://www.openfaas.com/blog/limits-and-backpressure/) | Alex Ellis | openfaas.com | 12-May-2022 |
| [A Deep Dive into Golang for OpenFaaS Functions](https://www.openfaas.com/blog/golang-deep-dive/) | Alex Ellis | openfaas.com | 13-April-2022 |
| [Open-Faas on Centos 7](https://medium.com/geekculture/open-faas-on-centos-7-c4dc629f28fe) | Heshani Samarasekara | medium.com | 07-May-2022 |
| [A Deep Dive into Golang for OpenFaaS Functions](https://www.openfaas.com/blog/golang-deep-dive/) | Alex Ellis | openfaas.com | 13-Apr-2022 |
| [Serverless Architecture with OpenFaaS and Java](https://www.xenonstack.com/blog/serverless-open-faas-java) | Navdeep Singh Gill | xenonstack.com | 13-Mar-2022 |
| [My Journey Contributing To OpenFaaS So Far](https://www.openfaas.com/blog/my-journey-contributing-to-openfaas/) | Nitishkumar Singh | openfaas.com | 02-Mar-2022 |
| [Your pocket-sized cloud with a Raspberry Pi](https://blog.alexellis.io/your-pocket-sized-cloud/) | Alex Ellis | openfaas.com | 23-Mar-2022 |
| [Hosting a React App with OpenFaaS](https://www.openfaas.com/blog/react-app/) | Alex Ellis | openfaas.com | 01-Mar-2022 |
@ -125,6 +161,7 @@ Mainly virtual due to pandemic.
|-------------------------------------------------------------------------|--------------|----------|-------------|
| [Configure your OpenFaaS functions for staging and production](https://www.openfaas.com/blog/custom-environments/) | Alex Ellis | openfaas.com | 09-Dec-2021 |
| [OpenFaaS - Run Containerized Functions On Your Own Terms](https://iximiuz.com/en/posts/openfaas-case-study/) | Ivan Velichko | iximiuz.com | 02-Dec-2021 |
| [Making a Docker Dev Environment for OpenFaaS](https://www.felipecruz.es/making-a-docker-dev-environment-for-openfaas/) | Felipe Cruz | felipecruz.es | 30-Nov-2021 |
| [Build at the Edge with OpenFaaS and GitHub Actions](https://www.openfaas.com/blog/edge-actions/) | Alex Ellis | openfaas.com | 29-Nov-2021 |
| [Improving long-running jobs for OpenFaaS users](https://www.openfaas.com/blog/long-running-jobs/) | Alex Ellis | openfaas.com | 05-Nov-2021 |
| [Derek says goodbye to Docker Swarm](https://www.openfaas.com/blog/migrating-derek-from-docker-swarm/) | Alex Ellis | openfaas.com | 05-Oct-2021 |
@ -146,6 +183,7 @@ Mainly virtual due to pandemic.
| [How to integrate with GitHub the right way with GitHub Apps](https://www.openfaas.com/blog/integrate-with-github-apps-and-faasd/) | Batuhan Apaydın | openfaas.com | 26-Jan-2021 |
| [Serverless with OpenFaaS and .NET](https://goncalo-a-oliveira.medium.com/serverless-with-openfaas-and-net-6a66b5c30a5f) | Batuhan Apaydın | medium.com | 20-Jan-2021 |
| [How I discovered faas and what it changed for me](https://releasecandidate.dev/posts/2021/discovery-faasd-and-openfaas/) | Peter Thaleikis | releasecandidate.dev | 05-Jan-2021 |
| [Installing OpenFaaS On k3s (Single Node)](https://midnightprogrammer.net/post/installing-openfaas-on-k3s-single-node/) | Pashant Khandelwal | midnightprogrammer.net | 04-Jan-2021 |
#### Events in 2021

View File

@ -1,10 +0,0 @@
## contrib
This folder contains miscellaneous scripts and packages for CI, development and custom Docker images.
* Hack on the UI Portal with [HACK.md](./HACK.md)
* Deploy a [Grafana dashboard](./grafana.json)
* Build and publish all project images for ARMHF with [publish-armhf.sh](./publish-armhf.sh)
* Run experimental end-to-end tests with Docker in Docker with [dind.sh](./dind.sh)
Custom Docker images for ARMHF/ARM64 are available for AlertManager and Prometheus in this folder.

View File

@ -1,84 +0,0 @@
#!/bin/bash
# "openfaas/nats-queue-worker"
# ^ Already multi-arch
declare -a repos=("openfaas/faas")
HERE=`pwd`
ARCH=$(uname -m)
#if [ ! -z "$CACHED" ]; then
rm -rf staging || :
mkdir -p staging/openfaas
mkdir -p staging/openfaas-incubator
#fi
get_image_names() {
if [ "openfaas-incubator/faas-idler" = $1 ]; then
images=("openfaas/faas-idler")
elif [ "openfaas/faas" = $1 ]; then
images=("openfaas/gateway" "openfaas/basic-auth-plugin")
elif [ "openfaas/nats-queue-worker" = $1 ]; then
images=("openfaas/queue-worker")
elif [ "openfaas-incubator/openfaas-operator" = $1 ]; then
images=("openfaas/openfaas-operator")
else
images=($1)
fi
}
if [ "$ARCH" = "armv7l" ] ; then
ARM_VERSION="armhf"
elif [ "$ARCH" = "aarch64" ] ; then
ARM_VERSION="arm64"
fi
echo "Target architecture: ${ARM_VERSION}"
for r in "${repos[@]}"
do
cd $HERE
echo -e "\nBuilding: $r\n"
git clone https://github.com/$r ./staging/$r
cd ./staging/$r
pwd
export TAG=$(git describe --abbrev=0 --tags)
echo "Latest release: $TAG"
get_image_names $r
for IMAGE in "${images[@]}"
do
TAG_PRESENT=$(curl -s "https://hub.docker.com/v2/repositories/${IMAGE}/tags/${TAG}-${ARM_VERSION}/" | grep -Po '"message": *"[^"]*"' | grep -io 'not found')
if [ "$TAG_PRESENT" = "not found" ]; then
break
fi
done
if [ "$TAG_PRESENT" = "not found" ]; then
make ci-${ARM_VERSION}-build ci-${ARM_VERSION}-push
else
for IMAGE in "${images[@]}"
do
echo "Image is already present: ${IMAGE}:${TAG}-${ARM_VERSION}"
done
fi
done
echo "Docker images"
for r in "${repos[@]}"
do
cd $HERE
cd ./staging/$r
export TAG=$(git describe --abbrev=0 --tags)
echo "$r"
get_image_names $r
for IMAGE in "${images[@]}"
do
echo " ${IMAGE}:${TAG}-${ARM_VERSION}"
done
done

View File

@ -1,6 +1,6 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} ghcr.io/openfaas/license-check:0.4.0 as license-check
FROM --platform=${BUILDPLATFORM:-linux/amd64} ghcr.io/openfaas/license-check:0.4.1 as license-check
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.17 as build
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.20 as build
ENV GO111MODULE=on
ENV CGO_ENABLED=0
@ -44,7 +44,7 @@ RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build --
-X github.com/openfaas/faas/gateway/types.Arch=${TARGETARCH}" \
-a -installsuffix cgo -o gateway .
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.16.0 as ship
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.18.3 as ship
LABEL org.label-schema.license="MIT" \
org.label-schema.vcs-url="https://github.com/openfaas/faas" \

28
gateway/Makefile Normal file
View File

@ -0,0 +1,28 @@
export DOCKER_CLI_EXPERIMENTAL=enabled
PLATFORM := "linux/amd64,linux/arm/v7,linux/arm64"
TAG?=dev
SERVER?=ttl.sh
OWNER?=openfaas
NAME=gateway
.PHONY: buildx-local
buildx-local:
@echo $(SERVER)/$(OWNER)/$(NAME):$(TAG) \
&& docker buildx create --use --name=multiarch --node multiarch \
&& docker buildx build \
--progress=plain \
--platform linux/amd64 \
--output "type=docker,push=false" \
--tag $(SERVER)/$(OWNER)/$(NAME):$(TAG) .
.PHONY: buildx-push
buildx-push:
@echo $(SERVER)/$(OWNER)/$(NAME):$(TAG) \
&& docker buildx create --use --name=multiarch --node multiarch \
&& docker buildx build \
--progress=plain \
--platform $(PLATFORM) \
--output "type=image,push=true" \
--tag $(SERVER)/$(OWNER)/$(NAME):$(TAG) .

View File

@ -1,14 +1,35 @@
module github.com/openfaas/faas/gateway
go 1.16
go 1.20
require (
github.com/docker/distribution v2.8.1+incompatible
github.com/docker/distribution v2.8.3+incompatible
github.com/gorilla/mux v1.8.0
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/openfaas/faas-provider v0.18.7
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0
go.uber.org/goleak v1.1.10
github.com/openfaas/faas-provider v0.24.4
github.com/openfaas/nats-queue-worker v0.0.0-20231023101743-fa54e89c9db2
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.5.0
go.uber.org/goleak v1.2.1
golang.org/x/sync v0.4.0
)
// replace github.com/openfaas/faas-provider => ../../faas-provider
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/nats-io/nats.go v1.31.0 // indirect
github.com/nats-io/nkeys v0.4.5 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nats-io/stan.go v0.10.4 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

View File

@ -1,265 +1,563 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o=
github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs=
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack/v2 v2.1.0 h1:J2g2hMyjSefUPTnkLRU2MnsLLsPRB1n4Z/wJRN07GuA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/raft v1.3.1 h1:zDT8ke8y2aP4wf9zPTB2uSIeavJ3Hx/ceY4jxI2JxuY=
github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM=
github.com/hashicorp/raft v1.5.0 h1:uNs9EfJ4FwiArZRxxfd/dQ5d33nV31/CdCHArH89hT8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.12 h1:famVnQVu7QwryBN4jNseQdUKES71ZAOnB6UQQJPZvqk=
github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0=
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
github.com/nats-io/jwt/v2 v2.0.2 h1:ejVCLO8gu6/4bOKIHQpmB5UhhUJfAQw55yvLWpfmKjI=
github.com/nats-io/jwt/v2 v2.0.2/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
github.com/nats-io/nats-server/v2 v2.2.6/go.mod h1:sEnFaxqe09cDmfMgACxZbziXnhQFhwk+aKkZjBBRYrI=
github.com/nats-io/nats-server/v2 v2.3.2 h1:SGJLWrjBHsl0DsdY8PeTR3YKEfiUEYVVq2STw9d8MSY=
github.com/nats-io/nats-server/v2 v2.3.2/go.mod h1:dUf7Cm5z5LbciFVwWx54owyCKm8x4/hL6p7rrljhLFY=
github.com/nats-io/nats-streaming-server v0.22.0 h1:2egnq86o9roTqUfELlqykf7ZZkNvRsXjVf4EbaLysHo=
github.com/nats-io/nats-streaming-server v0.22.0/go.mod h1:Jyu3eUQaUAjwd5TiBuLagKdQRofPrHoIXt1kL0U/e5o=
github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 h1:9GqilBhZaR3xYis0JgMlJjNw933WIobdjKhilXm+Vls=
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/jwt/v2 v2.5.2 h1:DhGH+nKt+wIkDxM6qnVSKjokq5t59AZV5HRcFW0zJwU=
github.com/nats-io/nats-server/v2 v2.10.3 h1:nk2QVLpJUh3/AhZCJlQdTfj2oeLDvWnn1Z6XzGlNFm0=
github.com/nats-io/nats-server/v2 v2.10.3/go.mod h1:lzrskZ/4gyMAh+/66cCd+q74c6v7muBypzfWhP/MAaM=
github.com/nats-io/nats-streaming-server v0.25.5 h1:DX6xaPhKvVLhdpNsuEmmD+O9LfWSnw8cvxQU/H9LRy8=
github.com/nats-io/nats-streaming-server v0.25.5/go.mod h1:dSBVdHGsT/tV91lT4MWFfE6+yjRCNhRIYJpBaTHFdAo=
github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA=
github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E=
github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nkeys v0.4.5 h1:Zdz2BUlFm4fJlierwvGK+yl20IAKUm7eV6AAZXEhkPk=
github.com/nats-io/nkeys v0.4.5/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nats-io/stan.go v0.9.0 h1:TB73Y31au++0sU0VmnBy2pYkSrwH0zUFNRB9YePHqC4=
github.com/nats-io/stan.go v0.9.0/go.mod h1:0jEuBXKauB1HHJswHM/lx05K48TJ1Yxj6VIfM4k+aB4=
github.com/openfaas/faas-provider v0.18.6/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98=
github.com/openfaas/faas-provider v0.18.7 h1:Oq3N7KrlAkAZ23N5gzZfdA9F62vpUFk3ZkQpQuHeRBU=
github.com/openfaas/faas-provider v0.18.7/go.mod h1:S217qfIaMrv+XKJxgbhBzJzCfyFvoIF+BvYdDo6XIDQ=
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9 h1:dpG1UcgTesGfLetgT3ns1cAhP8XMDZqxnxTW1MlnwSc=
github.com/openfaas/nats-queue-worker v0.0.0-20210726161954-ada9a31504c9/go.mod h1:ajlN2z+D8JPBq3kWNv4WLT6mtKPqlgeE3dYEx39d1tk=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/nats-io/stan.go v0.10.4 h1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=
github.com/nats-io/stan.go v0.10.4/go.mod h1:3XJXH8GagrGqajoO/9+HgPyKV5MWsv7S5ccdda+pc6k=
github.com/openfaas/faas-provider v0.19.1 h1:xH8lTWabfDZwzIvC0u1AO48ghD3BNw6Vo231DLqTeI0=
github.com/openfaas/faas-provider v0.19.1/go.mod h1:Farrp+9Med8LeK3aoYpqplMP8f5ebTILbCSLg2LPLZk=
github.com/openfaas/faas-provider v0.24.4 h1:Zzbkabgd0PoQmnRjy53NbMXjhLaIyoIiwP3qaLkm9rE=
github.com/openfaas/faas-provider v0.24.4/go.mod h1:NsETIfEndZn4mn/w/XnBTcDTwKqULCziphLp7KgeRcA=
github.com/openfaas/nats-queue-worker v0.0.0-20230303171817-9dfe6fa61387 h1:D4xbdy309Wdyhlm6PgJqUV/aR77VQQG8UTF+q0ay71c=
github.com/openfaas/nats-queue-worker v0.0.0-20230303171817-9dfe6fa61387/go.mod h1:s86POyW6C8S4CALFRhO8ax5sR2uaQUJQ0HaQGvbTpTc=
github.com/openfaas/nats-queue-worker v0.0.0-20231023101743-fa54e89c9db2 h1:I8U2kq2h7Wl6pkd4hjRK6P0/o3AcCNdfmNJS5gdgxKU=
github.com/openfaas/nats-queue-worker v0.0.0-20231023101743-fa54e89c9db2/go.mod h1:Ckz9JKcyzKtzLd9Obc5wVAXJhq05lKL3Ck+5r3MnKE0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
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.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
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.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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.uber.org/goleak v1.1.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.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/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -6,11 +6,12 @@ package handlers
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"math"
"net/http"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/requests"
"github.com/openfaas/faas/gateway/scaling"
)
@ -19,30 +20,31 @@ import (
func MakeAlertHandler(service scaling.ServiceQuery, defaultNamespace string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("Alert received.")
if r.Body == nil {
http.Error(w, "A body is required for this endpoint", http.StatusBadRequest)
return
}
body, readErr := ioutil.ReadAll(r.Body)
defer r.Body.Close()
log.Println(string(body))
if readErr != nil {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to read alert."))
log.Println(readErr)
log.Println(err)
return
}
var req requests.PrometheusAlert
err := json.Unmarshal(body, &req)
if err != nil {
if err := json.Unmarshal(body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to parse alert, bad format."))
log.Println(err)
return
}
errors := handleAlerts(&req, service, defaultNamespace)
errors := handleAlerts(req, service, defaultNamespace)
if len(errors) > 0 {
log.Println(errors)
var errorOutput string
@ -58,7 +60,7 @@ func MakeAlertHandler(service scaling.ServiceQuery, defaultNamespace string) htt
}
}
func handleAlerts(req *requests.PrometheusAlert, service scaling.ServiceQuery, defaultNamespace string) []error {
func handleAlerts(req requests.PrometheusAlert, service scaling.ServiceQuery, defaultNamespace string) []error {
var errors []error
for _, alert := range req.Alerts {
if err := scaleService(alert, service, defaultNamespace); err != nil {
@ -73,7 +75,7 @@ func handleAlerts(req *requests.PrometheusAlert, service scaling.ServiceQuery, d
func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQuery, defaultNamespace string) error {
var err error
serviceName, namespace := getNamespace(defaultNamespace, alert.Labels.FunctionName)
serviceName, namespace := middleware.GetNamespace(defaultNamespace, alert.Labels.FunctionName)
if len(serviceName) > 0 {
queryResponse, getErr := service.GetReplicas(serviceName, namespace)
@ -100,6 +102,7 @@ func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQu
func CalculateReplicas(status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) uint64 {
var newReplicas uint64
maxReplicas = uint64(math.Min(float64(maxReplicas), float64(scaling.DefaultMaxReplicas)))
step := uint64(math.Ceil(float64(maxReplicas) / 100 * float64(scalingFactor)))
if status == "firing" && step > 0 {

View File

@ -1,4 +1,4 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Copyright (c) Alex Ellis 1017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
@ -12,9 +12,9 @@ import (
func TestDisabledScale(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(0)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != minReplicas {
t.Logf("Expected not to scale, but replicas were: %d", newReplicas)
got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if got != minReplicas {
t.Logf("Expected not to scale, but replicas were: %d", got)
t.Fail()
}
}
@ -22,20 +22,23 @@ func TestDisabledScale(t *testing.T) {
func TestParameterEdge(t *testing.T) {
minReplicas := uint64(0)
scalingFactor := uint64(0)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 0 {
got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if got != 0 {
t.Log("Expected not to scale")
t.Fail()
}
}
func TestScalingWithSameUpperLowerLimit(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
// status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64)
newReplicas := CalculateReplicas("firing", minReplicas, minReplicas, minReplicas, scalingFactor)
if newReplicas != 1 {
t.Logf("Replicas - want: %d, got: %d", minReplicas, newReplicas)
func TestScaling_SameUpperLowerLimit(t *testing.T) {
minReplicas := uint64(5)
maxReplicas := uint64(5)
scalingFactor := uint64(10)
got := CalculateReplicas("firing", minReplicas, minReplicas, maxReplicas, scalingFactor)
want := minReplicas
if want != got {
t.Logf("Replicas - want: %d, got: %d", want, got)
t.Fail()
}
}
@ -43,58 +46,62 @@ func TestScalingWithSameUpperLowerLimit(t *testing.T) {
func TestMaxScale(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(100)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 20 {
t.Log("Expected ceiling of 20 replicas")
t.Fail()
got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas*2, minReplicas, scalingFactor)
if got != scaling.DefaultMaxReplicas {
t.Fatalf("want ceiling: %d, but got: %d", scaling.DefaultMaxReplicas, got)
}
}
func TestInitialScale(t *testing.T) {
func TestInitialScale_From1_Factor10(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 5 {
t.Log("Expected the increment to equal 5")
t.Fail()
scalingFactor := uint64(10)
got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
want := uint64(2)
if got != want {
t.Fatalf("want: %d, but got: %d", want, got)
}
}
func TestScale(t *testing.T) {
func TestScale_midrange_factor25(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
newReplicas := CalculateReplicas("firing", 4, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 8 {
t.Log("Expected newReplicas to equal 8")
t.Fail()
scalingFactor := uint64(25)
current := uint64(4)
maxReplicas := uint64(scaling.DefaultMaxReplicas)
got := CalculateReplicas("firing", current, maxReplicas, minReplicas, scalingFactor)
want := uint64(5)
if want != got {
t.Fatalf("want: %d, but got: %d", want, got)
}
}
func TestScaleCeiling(t *testing.T) {
func TestScale_Ceiling_IsDefaultMaxReplicas(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
newReplicas := CalculateReplicas("firing", 20, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 20 {
t.Log("Expected ceiling of 20 replicas")
t.Fail()
scalingFactor := uint64(10)
current := uint64(scaling.DefaultMaxReplicas)
got := CalculateReplicas("firing", current, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if got != scaling.DefaultMaxReplicas {
t.Fatalf("want: %d, but got: %d", scaling.DefaultMaxReplicas, got)
}
}
func TestScaleCeilingEdge(t *testing.T) {
func TestScaleCeilingReplicasOver(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
newReplicas := CalculateReplicas("firing", 19, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 20 {
t.Log("Expected ceiling of 20 replicas")
t.Fail()
scalingFactor := uint64(10)
got := CalculateReplicas("firing", 19, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if got != scaling.DefaultMaxReplicas {
t.Fatalf("want: %d, but got: %d", scaling.DefaultMaxReplicas, got)
}
}
func TestBackingOff(t *testing.T) {
minReplicas := uint64(1)
scalingFactor := uint64(20)
newReplicas := CalculateReplicas("resolved", 8, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 1 {
scalingFactor := uint64(10)
got := CalculateReplicas("resolved", 8, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if got != 1 {
t.Log("Expected backing off to 1 replica")
t.Fail()
}
@ -104,9 +111,9 @@ func TestScaledUpFrom1(t *testing.T) {
currentReplicas := uint64(1)
maxReplicas := uint64(5)
scalingFactor := uint64(30)
newReplicas := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if newReplicas <= currentReplicas {
t.Log("Expected newReplicas > currentReplica")
got := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if got <= currentReplicas {
t.Log("Expected got > currentReplica")
t.Fail()
}
}
@ -115,9 +122,9 @@ func TestScaledUpWithSmallParam(t *testing.T) {
currentReplicas := uint64(1)
maxReplicas := uint64(4)
scalingFactor := uint64(1)
newReplicas := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if newReplicas <= currentReplicas {
t.Log("Expected newReplicas > currentReplica")
got := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if got <= currentReplicas {
t.Log("Expected got > currentReplica")
t.Fail()
}
}

View File

@ -1,82 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
import (
"fmt"
"log"
"net/http"
"net/url"
"strings"
"testing"
)
func TestSingleHostBaseURLResolver(t *testing.T) {
urlVal, _ := url.Parse("http://upstream:8080/")
r := SingleHostBaseURLResolver{BaseURL: urlVal.String()}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := "http://upstream:8080"
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
const watchdogPort = 8080
func TestFunctionAsHostBaseURLResolver_WithNamespaceOverride(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
namespace := "openfaas-fn"
newNS := "production-fn"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello."+newNS, nil)
resolved := r.Resolve(req)
newSuffix := strings.Replace(suffix, namespace, newNS, -1)
want := fmt.Sprintf("http://hello.%s:%d", newSuffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithSuffix(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello.%s:%d", suffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithoutSuffix(t *testing.T) {
suffix := ""
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello%s:%d", suffix, watchdogPort)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}

View File

@ -9,10 +9,14 @@ import (
"time"
"github.com/docker/distribution/uuid"
"github.com/openfaas/faas/gateway/version"
)
// MakeCallIDMiddleware middleware tags a request with a uid
func MakeCallIDMiddleware(next http.HandlerFunc) http.HandlerFunc {
version := version.Version
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
if len(r.Header.Get("X-Call-Id")) == 0 {
@ -24,6 +28,8 @@ func MakeCallIDMiddleware(next http.HandlerFunc) http.HandlerFunc {
r.Header.Add("X-Start-Time", fmt.Sprintf("%d", start.UTC().UnixNano()))
w.Header().Add("X-Start-Time", fmt.Sprintf("%d", start.UTC().UnixNano()))
w.Header().Add("X-Served-By", fmt.Sprintf("openfaas-community/%s", version))
next(w, r)
}
}

View File

@ -1,47 +0,0 @@
package handlers
import (
"context"
"io"
"log"
"net/http"
"time"
)
// MakeExternalAuthHandler make an authentication proxy handler
func MakeExternalAuthHandler(next http.HandlerFunc, upstreamTimeout time.Duration, upstreamURL string, passBody bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
req, _ := http.NewRequest(http.MethodGet, upstreamURL, nil)
copyHeaders(req.Header, &r.Header)
deadlineContext, cancel := context.WithTimeout(
context.Background(),
upstreamTimeout)
defer cancel()
res, err := http.DefaultClient.Do(req.WithContext(deadlineContext))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("ExternalAuthHandler: %s", err.Error())
return
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode == http.StatusOK {
next.ServeHTTP(w, r)
return
}
copyHeaders(w.Header(), &res.Header)
w.WriteHeader(res.StatusCode)
if res.Body != nil {
io.Copy(w, res.Body)
}
}
}

View File

@ -1,239 +0,0 @@
package handlers
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func Test_External_Auth_Wrapper_FailsInvalidAuth(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
rr := httptest.NewRecorder()
handler(rr, req)
if rr.Code == http.StatusOK {
t.Errorf("Status incorrect, did not want: %d, but got %d", http.StatusOK, rr.Code)
}
}
func Test_External_Auth_Wrapper_FailsInvalidAuth_WritesBody(t *testing.T) {
wantBody := []byte(`invalid credentials`)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
w.Write(wantBody)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
rr := httptest.NewRecorder()
handler(rr, req)
if rr.Code == http.StatusOK {
t.Errorf("Status incorrect, did not want: %d, but got %d", http.StatusOK, rr.Code)
}
if bytes.Compare(rr.Body.Bytes(), wantBody) != 0 {
t.Errorf("Body incorrect, want: %s, but got %s", []byte(wantBody), rr.Body)
}
}
func Test_External_Auth_Wrapper_PassesValidAuth(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
rr := httptest.NewRecorder()
handler(rr, req)
want := http.StatusNotImplemented
if rr.Code != want {
t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
}
}
func Test_External_Auth_Wrapper_WithoutRequiredHeaderFailsAuth(t *testing.T) {
wantToken := "secret-key"
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Token") == wantToken {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusUnauthorized)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
// use an invalid token
req.Header.Set("X-Token", "invalid-key")
rr := httptest.NewRecorder()
handler(rr, req)
want := http.StatusUnauthorized
if rr.Code != want {
t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
}
}
func Test_External_Auth_Wrapper_WithoutRequiredHeaderFailsAuth_ProxiesServerHeaders(t *testing.T) {
wantToken := "secret-key"
wantRealm := `Basic realm="Restricted"`
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Token") == wantToken {
w.WriteHeader(http.StatusOK)
return
}
w.Header().Set("Www-Authenticate", wantRealm)
w.WriteHeader(http.StatusUnauthorized)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
// use an invalid token
req.Header.Set("X-Token", "invalid-key")
rr := httptest.NewRecorder()
handler(rr, req)
want := http.StatusUnauthorized
if rr.Code != want {
t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
}
got := rr.Header().Get("Www-Authenticate")
if got != wantRealm {
t.Errorf("Www-Authenticate header, want: %s, but got %s, %q", wantRealm, got, rr.Header())
}
}
func Test_External_Auth_Wrapper_WithRequiredHeaderPassesValidAuth(t *testing.T) {
wantToken := "secret-key"
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Token") == wantToken {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusUnauthorized)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
req.Header.Set("X-Token", wantToken)
rr := httptest.NewRecorder()
handler(rr, req)
want := http.StatusNotImplemented
if rr.Code != want {
t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
}
}
func Test_External_Auth_Wrapper_TimeoutGivesInternalServerError(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(50 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
defer s.Close()
next := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
passBody := false
handler := MakeExternalAuthHandler(next, time.Millisecond*10, s.URL, passBody)
req := httptest.NewRequest(http.MethodGet, s.URL, nil)
rr := httptest.NewRecorder()
handler(rr, req)
want := http.StatusInternalServerError
if rr.Code != want {
t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
}
wantSubstring := "context deadline exceeded\n"
if !strings.HasSuffix(string(rr.Body.Bytes()), wantSubstring) {
t.Errorf("Body incorrect, want to have suffix: %q, but got %q", []byte(wantSubstring), rr.Body)
}
}
// // Test_External_Auth_Wrapper_PassesValidAuthButOnly200IsValid this test exists
// // to document the TODO action to consider all "2xx" statuses as valid.
// func Test_External_Auth_Wrapper_PassesValidAuthButOnly200IsValid(t *testing.T) {
// s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusAccepted)
// }))
// defer s.Close()
// next := func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusNotImplemented)
// }
// passBody := false
// handler := MakeExternalAuthHandler(next, time.Second*5, s.URL, passBody)
// req := httptest.NewRequest(http.MethodGet, s.URL, nil)
// rr := httptest.NewRecorder()
// handler(rr, req)
// want := http.StatusUnauthorized
// if rr.Code != want {
// t.Errorf("Status incorrect, want: %d, but got %d", want, rr.Code)
// }
// }

View File

@ -10,40 +10,17 @@ import (
"log"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/types"
)
// functionMatcher parses out the service name (group 1) and rest of path (group 2).
var functionMatcher = regexp.MustCompile("^/?(?:async-)?function/([^/?]+)([^?]*)")
// Indices and meta-data for functionMatcher regex parts
const (
hasPathCount = 3
routeIndex = 0 // routeIndex corresponds to /function/ or /async-function/
nameIndex = 1 // nameIndex is the function name
pathIndex = 2 // pathIndex is the path i.e. /employee/:id/
)
// BaseURLResolver URL resolver for upstream requests
type BaseURLResolver interface {
Resolve(r *http.Request) string
}
// URLPathTransformer Transform the incoming URL path for upstream requests
type URLPathTransformer interface {
Transform(r *http.Request) string
}
// MakeForwardingProxyHandler create a handler which forwards HTTP requests
func MakeForwardingProxyHandler(proxy *types.HTTPClientReverseProxy,
notifiers []HTTPNotifier,
baseURLResolver BaseURLResolver,
urlPathTransformer URLPathTransformer,
baseURLResolver middleware.BaseURLResolver,
urlPathTransformer middleware.URLPathTransformer,
serviceAuthInjector middleware.AuthInjector) http.HandlerFunc {
writeRequestURI := false
@ -165,80 +142,6 @@ func deleteHeaders(target *http.Header, exclude *[]string) {
}
}
// SingleHostBaseURLResolver resolves URLs against a single BaseURL
type SingleHostBaseURLResolver struct {
BaseURL string
}
// Resolve the base URL for a request
func (s SingleHostBaseURLResolver) Resolve(r *http.Request) string {
baseURL := s.BaseURL
if strings.HasSuffix(baseURL, "/") {
baseURL = baseURL[0 : len(baseURL)-1]
}
return baseURL
}
// FunctionAsHostBaseURLResolver resolves URLs using a function from the URL as a host
type FunctionAsHostBaseURLResolver struct {
FunctionSuffix string
FunctionNamespace string
}
// Resolve the base URL for a request
func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string {
svcName := getServiceName(r.URL.Path)
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
if index := strings.LastIndex(svcName, "."); index > -1 && len(svcName) > index+1 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, "", -1)
} else {
suffix = "." + f.FunctionSuffix
}
}
return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort)
}
// TransparentURLPathTransformer passes the requested URL path through untouched.
type TransparentURLPathTransformer struct {
}
// Transform returns the URL path unchanged.
func (f TransparentURLPathTransformer) Transform(r *http.Request) string {
return r.URL.Path
}
// FunctionPrefixTrimmingURLPathTransformer removes the "/function/servicename/" prefix from the URL path.
type FunctionPrefixTrimmingURLPathTransformer struct {
}
// Transform removes the "/function/servicename/" prefix from the URL path.
func (f FunctionPrefixTrimmingURLPathTransformer) Transform(r *http.Request) string {
ret := r.URL.Path
if ret != "" {
// When forwarding to a function, since the `/function/xyz` portion
// of a path like `/function/xyz/rest/of/path` is only used or needed
// by the Gateway, we want to trim it down to `/rest/of/path` for the
// upstream request. In the following regex, in the case of a match
// the r.URL.Path will be at `0`, the function name at `1` and the
// rest of the path (the part we are interested in) at `2`.
matcher := functionMatcher.Copy()
parts := matcher.FindStringSubmatch(ret)
if len(parts) == hasPathCount {
ret = parts[pathIndex]
}
}
return ret
}
// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the

View File

@ -6,10 +6,12 @@ package handlers
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"testing"
"github.com/openfaas/faas/gateway/pkg/middleware"
)
func Test_buildUpstreamRequest_Body_Method_Query(t *testing.T) {
@ -31,7 +33,7 @@ func Test_buildUpstreamRequest_Body_Method_Query(t *testing.T) {
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
upstreamBytes, _ := io.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
@ -170,7 +172,7 @@ func Test_getServiceName(t *testing.T) {
t.Fatal(err)
}
service := getServiceName(u.Path)
service := middleware.GetServiceName(u.Path)
if service != s.serviceName {
t.Fatalf("Incorrect service name - want: %s, got: %s", s.serviceName, service)
}
@ -195,7 +197,7 @@ func Test_buildUpstreamRequest_WithPathNoQuery(t *testing.T) {
t.Fail()
}
transformer := FunctionPrefixTrimmingURLPathTransformer{}
transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request)
wantTransformedPath := functionPath
@ -210,7 +212,7 @@ func Test_buildUpstreamRequest_WithPathNoQuery(t *testing.T) {
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
upstreamBytes, _ := io.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
@ -251,7 +253,7 @@ func Test_buildUpstreamRequest_WithNoPathNoQuery(t *testing.T) {
t.Fail()
}
transformer := FunctionPrefixTrimmingURLPathTransformer{}
transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request)
wantTransformedPath := "/"
@ -266,7 +268,7 @@ func Test_buildUpstreamRequest_WithNoPathNoQuery(t *testing.T) {
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
upstreamBytes, _ := io.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
@ -305,7 +307,7 @@ func Test_buildUpstreamRequest_WithPathAndQuery(t *testing.T) {
t.Fail()
}
transformer := FunctionPrefixTrimmingURLPathTransformer{}
transformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
transformedPath := transformer.Transform(request)
wantTransformedPath := functionPath
@ -320,7 +322,7 @@ func Test_buildUpstreamRequest_WithPathAndQuery(t *testing.T) {
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
upstreamBytes, _ := io.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))

View File

@ -5,7 +5,7 @@ package handlers
import "net/http"
//HealthzHandler healthz hanlder for mertics server
// HealthzHandler healthz hanlder for mertics server
func HealthzHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {

View File

@ -5,10 +5,9 @@ package handlers
import (
"encoding/json"
"io"
"log"
"net/http"
"io/ioutil"
"net/http/httptest"
providerTypes "github.com/openfaas/faas-provider/types"
@ -27,7 +26,7 @@ func MakeInfoHandler(h http.Handler) http.HandlerFunc {
var provider *providerTypes.ProviderInfo
upstreamBody, _ := ioutil.ReadAll(upstreamCall.Body)
upstreamBody, _ := io.ReadAll(upstreamCall.Body)
err := json.Unmarshal(upstreamBody, &provider)
if err != nil {
log.Printf("Error unmarshalling provider json from body %s. Error %s\n", upstreamBody, err.Error())

View File

@ -3,7 +3,7 @@ package handlers
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"net/url"
@ -52,7 +52,7 @@ func Test_logsProxyDoesNotLeakGoroutinesWhenProviderClosesConnection(t *testing.
t.Fatalf("unexpected error sending log request: %s", err)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error reading the response body: %s", err)
}
@ -126,7 +126,7 @@ func Test_logsProxyDoesNotLeakGoroutinesWhenClientClosesConnection(t *testing.T)
go func() {
defer resp.Body.Close()
defer close(errCh)
_, err := ioutil.ReadAll(resp.Body)
_, err := io.ReadAll(resp.Body)
errCh <- err
}()
cancel()

View File

@ -3,10 +3,13 @@
package handlers
import "testing"
import (
"github.com/openfaas/faas/gateway/pkg/middleware"
"testing"
)
func Test_getNamespace_Default(t *testing.T) {
root, ns := getNamespace("openfaas-fn", "figlet.openfaas-fn")
root, ns := middleware.GetNamespace("openfaas-fn", "figlet.openfaas-fn")
wantRoot := "figlet"
wantNs := "openfaas-fn"
@ -19,7 +22,7 @@ func Test_getNamespace_Default(t *testing.T) {
}
func Test_getNamespace_Override(t *testing.T) {
root, ns := getNamespace("fn", "figlet.fn")
root, ns := middleware.GetNamespace("fn", "figlet.fn")
wantRoot := "figlet"
wantNs := "fn"
@ -32,7 +35,7 @@ func Test_getNamespace_Override(t *testing.T) {
}
func Test_getNamespace_Empty(t *testing.T) {
root, ns := getNamespace("", "figlet")
root, ns := middleware.GetNamespace("", "figlet")
wantRoot := "figlet"
wantNs := ""

View File

@ -6,50 +6,21 @@ package handlers
import (
"net/http"
"time"
"github.com/openfaas/faas-provider/httputil"
)
// MakeNotifierWrapper wraps a http.HandlerFunc in an interceptor to pass to HTTPNotifier
func MakeNotifierWrapper(next http.HandlerFunc, notifiers []HTTPNotifier) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
then := time.Now()
writer := newWriteInterceptor(w)
next(&writer, r)
url := r.URL.String()
writer := httputil.NewHttpWriteInterceptor(w)
next(writer, r)
for _, notifier := range notifiers {
notifier.Notify(r.Method, url, url, writer.Status(), "completed", time.Since(then))
}
}
}
func newWriteInterceptor(w http.ResponseWriter) writeInterceptor {
return writeInterceptor{
w: w,
}
}
type writeInterceptor struct {
CapturedStatusCode int
w http.ResponseWriter
}
func (c *writeInterceptor) Status() int {
if c.CapturedStatusCode == 0 {
return http.StatusOK
}
return c.CapturedStatusCode
}
func (c *writeInterceptor) Header() http.Header {
return c.w.Header()
}
func (c *writeInterceptor) Write(data []byte) (int, error) {
return c.w.Write(data)
}
func (c *writeInterceptor) WriteHeader(code int) {
c.CapturedStatusCode = code
c.w.WriteHeader(code)
}

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/prometheus/client_golang/prometheus"
)
@ -16,20 +17,6 @@ type HTTPNotifier interface {
Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration)
}
// PrometheusServiceNotifier notifier for core service endpoints
type PrometheusServiceNotifier struct {
ServiceMetrics *metrics.ServiceMetricOptions
}
// Notify about service metrics
func (psn PrometheusServiceNotifier) Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration) {
code := fmt.Sprintf("%d", statusCode)
path := urlToLabel(URL)
psn.ServiceMetrics.Counter.WithLabelValues(method, path, code).Inc()
psn.ServiceMetrics.Histogram.WithLabelValues(method, path, code).Observe(duration.Seconds())
}
func urlToLabel(path string) string {
if len(path) > 0 {
path = strings.TrimRight(path, "/")
@ -49,7 +36,7 @@ type PrometheusFunctionNotifier struct {
// Notify records metrics in Prometheus
func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration) {
serviceName := getServiceName(originalURL)
serviceName := middleware.GetServiceName(originalURL)
if len(p.FunctionNamespace) > 0 {
if !strings.Contains(serviceName, ".") {
serviceName = fmt.Sprintf("%s.%s", serviceName, p.FunctionNamespace)
@ -74,24 +61,6 @@ func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalUR
}
func getServiceName(urlValue string) string {
var serviceName string
forward := "/function/"
if strings.HasPrefix(urlValue, forward) {
// With a path like `/function/xyz/rest/of/path?q=a`, the service
// name we wish to locate is just the `xyz` portion. With a positive
// match on the regex below, it will return a three-element slice.
// The item at index `0` is the same as `urlValue`, at `1`
// will be the service name we need, and at `2` the rest of the path.
matcher := functionMatcher.Copy()
matches := matcher.FindStringSubmatch(urlValue)
if len(matches) == hasPathCount {
serviceName = matches[nameIndex]
}
}
return strings.Trim(serviceName, "/")
}
// LoggingNotifier notifies a log about a request
type LoggingNotifier struct {
}
@ -99,6 +68,6 @@ type LoggingNotifier struct {
// Notify the LoggingNotifier about a request
func (LoggingNotifier) Notify(method string, URL string, originalURL string, statusCode int, event string, duration time.Duration) {
if event == "completed" {
log.Printf("Forwarded [%s] to %s - [%d] - %fs seconds", method, originalURL, statusCode, duration.Seconds())
log.Printf("Forwarded [%s] to %s - [%d] - %.4fs", method, originalURL, statusCode, duration.Seconds())
}
}

View File

@ -5,7 +5,7 @@ package handlers
import (
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/url"
@ -14,24 +14,24 @@ import (
"github.com/gorilla/mux"
ftypes "github.com/openfaas/faas-provider/types"
"github.com/openfaas/faas/gateway/metrics"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling"
)
const queueAnnotation = "com.openfaas.queue"
// MakeQueuedProxy accepts work onto a queue
func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer, pathTransformer URLPathTransformer, defaultNS string, functionQuery scaling.FunctionQuery) http.HandlerFunc {
func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer, pathTransformer middleware.URLPathTransformer, defaultNS string, functionQuery scaling.FunctionQuery) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
defer r.Body.Close()
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
var err error
body, err = io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
callbackURL, err := getCallbackURLHeader(r.Header)
@ -43,12 +43,6 @@ func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer,
vars := mux.Vars(r)
name := vars["name"]
queueName, err := getQueueName(name, functionQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req := &ftypes.QueueRequest{
Function: name,
Body: body,
@ -58,16 +52,12 @@ func MakeQueuedProxy(metrics metrics.MetricOptions, queuer ftypes.RequestQueuer,
Header: r.Header,
Host: r.Host,
CallbackURL: callbackURL,
QueueName: queueName,
}
if len(queueName) > 0 {
log.Printf("Queueing %s to: %s\n", name, queueName)
}
if err = queuer.Queue(req); err != nil {
fmt.Printf("Queue error: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("Error queuing request: %v", err)
http.Error(w, fmt.Sprintf("Error queuing request: %s", err.Error()),
http.StatusInternalServerError)
return
}
@ -91,21 +81,6 @@ func getCallbackURLHeader(header http.Header) (*url.URL, error) {
return callbackURL, nil
}
func getQueueName(name string, fnQuery scaling.FunctionQuery) (queueName string, err error) {
fn, ns := getNameParts(name)
annotations, err := fnQuery.GetAnnotations(fn, ns)
if err != nil {
return "", err
}
queueName = ""
if v := annotations[queueAnnotation]; len(v) > 0 {
queueName = v
}
return queueName, err
}
func getNameParts(name string) (fn, ns string) {
fn = name
ns = ""

View File

@ -7,18 +7,11 @@ import (
"fmt"
"log"
"net/http"
"strings"
"github.com/openfaas/faas/gateway/pkg/middleware"
"github.com/openfaas/faas/gateway/scaling"
)
func getNamespace(defaultNamespace, fullName string) (string, string) {
if index := strings.LastIndex(fullName, "."); index > -1 {
return fullName[:index], fullName[index+1:]
}
return fullName, defaultNamespace
}
// MakeScalingHandler creates handler which can scale a function from
// zero to N replica(s). After scaling the next http.HandlerFunc will
// be called. If the function is not ready after the configured
@ -28,7 +21,7 @@ func MakeScalingHandler(next http.HandlerFunc, scaler scaling.FunctionScaler, co
return func(w http.ResponseWriter, r *http.Request) {
functionName, namespace := getNamespace(defaultNamespace, getServiceName(r.URL.String()))
functionName, namespace := middleware.GetNamespace(defaultNamespace, middleware.GetServiceName(r.URL.String()))
res := scaler.Scale(functionName, namespace)
@ -55,6 +48,7 @@ func MakeScalingHandler(next http.HandlerFunc, scaler scaling.FunctionScaler, co
return
}
log.Printf("[Scale] function=%s.%s 0=>N timed-out after %fs\n", functionName, namespace, res.Duration.Seconds())
log.Printf("[Scale] function=%s.%s 0=>N timed-out after %.4fs\n",
functionName, namespace, res.Duration.Seconds())
}
}

View File

@ -26,10 +26,6 @@ const NameExpression = "-a-zA-Z_0-9."
func main() {
if len(version.GitCommitMessage) == 0 {
version.GitCommitMessage = "See GitHub for latest changes"
}
osEnv := types.OsEnv{}
readConfig := types.ReadConfig{}
config, configErr := readConfig.Read(osEnv)
@ -37,15 +33,18 @@ func main() {
if configErr != nil {
log.Fatalln(configErr)
}
log.Printf("HTTP Read Timeout: %s", config.ReadTimeout)
log.Printf("HTTP Write Timeout: %s", config.WriteTimeout)
if !config.UseExternalProvider() {
log.Fatalln("You must provide an external provider via 'functions_provider_url' env-var.")
}
log.Printf("Binding to external function provider: %s", config.FunctionsProviderURL)
fmt.Printf("OpenFaaS Gateway - Community Edition (CE)\n"+
"\nVersion: %s Commit: %s\nTimeouts: read=%s\twrite=%s\tupstream=%s\nFunction provider: %s\n\n",
version.BuildVersion(),
version.GitCommitSHA,
config.ReadTimeout,
config.WriteTimeout,
config.UpstreamTimeout,
config.FunctionsProviderURL)
// credentials is used for service-to-service auth
var credentials *auth.BasicAuthCredentials
@ -66,9 +65,6 @@ func main() {
servicePollInterval := time.Second * 5
metadataQuery := metrics.NewMetadataQuery(credentials)
fmt.Println(metadataQuery)
metricsOptions := metrics.BuildMetricsOptions()
exporter := metrics.NewExporter(metricsOptions, credentials, config.Namespace)
exporter.StartServiceWatcher(*config.FunctionsProviderURL, metricsOptions, "func", servicePollInterval)
@ -86,29 +82,18 @@ func main() {
FunctionNamespace: config.Namespace,
}
prometheusServiceNotifier := handlers.PrometheusServiceNotifier{
ServiceMetrics: metricsOptions.ServiceMetrics,
}
functionNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusNotifier}
forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier, prometheusServiceNotifier}
forwardingNotifiers := []handlers.HTTPNotifier{loggingNotifier}
quietNotifier := []handlers.HTTPNotifier{}
urlResolver := handlers.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
var functionURLResolver handlers.BaseURLResolver
var functionURLTransformer handlers.URLPathTransformer
nilURLTransformer := handlers.TransparentURLPathTransformer{}
trimURLTransformer := handlers.FunctionPrefixTrimmingURLPathTransformer{}
urlResolver := middleware.SingleHostBaseURLResolver{BaseURL: config.FunctionsProviderURL.String()}
var functionURLResolver middleware.BaseURLResolver
var functionURLTransformer middleware.URLPathTransformer
nilURLTransformer := middleware.TransparentURLPathTransformer{}
trimURLTransformer := middleware.FunctionPrefixTrimmingURLPathTransformer{}
if config.DirectFunctions {
functionURLResolver = handlers.FunctionAsHostBaseURLResolver{
FunctionSuffix: config.DirectFunctionsSuffix,
FunctionNamespace: config.Namespace,
}
functionURLTransformer = trimURLTransformer
} else {
functionURLResolver = urlResolver
functionURLTransformer = nilURLTransformer
}
functionURLResolver = urlResolver
functionURLTransformer = nilURLTransformer
var serviceAuthInjector middleware.AuthInjector
@ -116,7 +101,20 @@ func main() {
serviceAuthInjector = &middleware.BasicAuthInjector{Credentials: credentials}
}
decorateExternalAuth := handlers.MakeExternalAuthHandler
// externalServiceQuery is used to query metadata from the provider about a function
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
scalingConfig := scaling.ScalingConfig{
MaxPollCount: uint(1000),
SetScaleRetries: uint(20),
FunctionPollInterval: time.Millisecond * 100,
CacheExpiry: time.Millisecond * 250, // freshness of replica values before going stale
ServiceQuery: externalServiceQuery,
}
// This cache can be used to query a function's annotations.
functionAnnotationCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
cachedFunctionQuery := scaling.NewCachedFunctionQuery(functionAnnotationCache, externalServiceQuery)
faasHandlers.Proxy = handlers.MakeCallIDMiddleware(
handlers.MakeForwardingProxyHandler(reverseProxy, functionNotifiers, functionURLResolver, functionURLTransformer, nil),
@ -132,32 +130,27 @@ func main() {
faasHandlers.SecretHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.NamespaceListerHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.NamespaceMutatorHandler = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
externalServiceQuery := plugin.NewExternalServiceQuery(*config.FunctionsProviderURL, serviceAuthInjector)
faasHandlers.Alert = handlers.MakeNotifierWrapper(
handlers.MakeAlertHandler(externalServiceQuery, config.Namespace),
forwardingNotifiers,
quietNotifier,
)
faasHandlers.LogProxyHandler = handlers.NewLogHandlerFunc(*config.LogsProviderURL, config.WriteTimeout)
scalingConfig := scaling.ScalingConfig{
MaxPollCount: uint(1000),
SetScaleRetries: uint(20),
FunctionPollInterval: time.Millisecond * 100,
CacheExpiry: time.Millisecond * 250, // freshness of replica values before going stale
ServiceQuery: externalServiceQuery,
}
functionProxy := faasHandlers.Proxy
if config.ScaleFromZero {
scalingFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
scaler := scaling.NewFunctionScaler(scalingConfig, scalingFunctionCache)
functionProxy = handlers.MakeScalingHandler(faasHandlers.Proxy, scaler, scalingConfig, config.Namespace)
functionProxy = handlers.MakeScalingHandler(functionProxy, scaler, scalingConfig, config.Namespace)
}
if config.UseNATS() {
log.Println("Async enabled: Using NATS Streaming.")
log.Println("Async enabled: Using NATS Streaming")
log.Println("Deprecation Notice: NATS Streaming is no longer maintained and won't receive updates from June 2023")
maxReconnect := 60
interval := time.Second * 2
@ -168,44 +161,41 @@ func main() {
log.Fatalln(queueErr)
}
queueFunctionCache := scaling.NewFunctionCache(scalingConfig.CacheExpiry)
functionQuery := scaling.NewCachedFunctionQuery(queueFunctionCache, externalServiceQuery)
faasHandlers.QueuedProxy = handlers.MakeNotifierWrapper(
handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, natsQueue, trimURLTransformer, config.Namespace, functionQuery)),
handlers.MakeCallIDMiddleware(handlers.MakeQueuedProxy(metricsOptions, natsQueue, trimURLTransformer, config.Namespace, cachedFunctionQuery)),
forwardingNotifiers,
)
}
prometheusQuery := metrics.NewPrometheusQuery(config.PrometheusHost, config.PrometheusPort, &http.Client{})
faasHandlers.ListFunctions = metrics.AddMetricsHandler(faasHandlers.ListFunctions, prometheusQuery)
faasHandlers.ScaleFunction = handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector)
faasHandlers.ScaleFunction = scaling.MakeHorizontalScalingHandler(handlers.MakeForwardingProxyHandler(reverseProxy, forwardingNotifiers, urlResolver, nilURLTransformer, serviceAuthInjector))
if credentials != nil {
faasHandlers.Alert =
decorateExternalAuth(faasHandlers.Alert, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.Alert, credentials)
faasHandlers.UpdateFunction =
decorateExternalAuth(faasHandlers.UpdateFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.UpdateFunction, credentials)
faasHandlers.DeleteFunction =
decorateExternalAuth(faasHandlers.DeleteFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.DeleteFunction, credentials)
faasHandlers.DeployFunction =
decorateExternalAuth(faasHandlers.DeployFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.DeployFunction, credentials)
faasHandlers.ListFunctions =
decorateExternalAuth(faasHandlers.ListFunctions, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.ListFunctions, credentials)
faasHandlers.ScaleFunction =
decorateExternalAuth(faasHandlers.ScaleFunction, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.ScaleFunction, credentials)
faasHandlers.FunctionStatus =
decorateExternalAuth(faasHandlers.FunctionStatus, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.FunctionStatus, credentials)
faasHandlers.InfoHandler =
decorateExternalAuth(faasHandlers.InfoHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
faasHandlers.AsyncReport =
decorateExternalAuth(faasHandlers.AsyncReport, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.InfoHandler, credentials)
faasHandlers.SecretHandler =
decorateExternalAuth(faasHandlers.SecretHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.SecretHandler, credentials)
faasHandlers.LogProxyHandler =
decorateExternalAuth(faasHandlers.LogProxyHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.LogProxyHandler, credentials)
faasHandlers.NamespaceListerHandler =
decorateExternalAuth(faasHandlers.NamespaceListerHandler, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)
auth.DecorateWithBasicAuth(faasHandlers.NamespaceListerHandler, credentials)
faasHandlers.NamespaceMutatorHandler =
auth.DecorateWithBasicAuth(faasHandlers.NamespaceMutatorHandler, credentials)
}
r := mux.NewRouter()
@ -229,13 +219,13 @@ func main() {
r.HandleFunc("/system/logs", faasHandlers.LogProxyHandler).Methods(http.MethodGet)
r.HandleFunc("/system/namespaces", faasHandlers.NamespaceListerHandler).Methods(http.MethodGet)
r.HandleFunc("/system/namespace/{namespace:["+NameExpression+"]*}", faasHandlers.NamespaceMutatorHandler).
Methods(http.MethodPost, http.MethodDelete, http.MethodPut, http.MethodGet)
if faasHandlers.QueuedProxy != nil {
r.HandleFunc("/async-function/{name:["+NameExpression+"]+}/", faasHandlers.QueuedProxy).Methods(http.MethodPost)
r.HandleFunc("/async-function/{name:["+NameExpression+"]+}", faasHandlers.QueuedProxy).Methods(http.MethodPost)
r.HandleFunc("/async-function/{name:["+NameExpression+"]+}/{params:.*}", faasHandlers.QueuedProxy).Methods(http.MethodPost)
r.HandleFunc("/system/async-report", handlers.MakeNotifierWrapper(faasHandlers.AsyncReport, forwardingNotifiers))
}
fs := http.FileServer(http.Dir("./assets/"))
@ -247,9 +237,11 @@ func main() {
uiHandler := http.StripPrefix("/ui", fsCORS)
if credentials != nil {
r.PathPrefix("/ui/").Handler(
decorateExternalAuth(uiHandler.ServeHTTP, config.UpstreamTimeout, config.AuthProxyURL, config.AuthProxyPassBody)).Methods(http.MethodGet)
auth.DecorateWithBasicAuth(uiHandler.ServeHTTP, credentials)).
Methods(http.MethodGet)
} else {
r.PathPrefix("/ui/").Handler(uiHandler).Methods(http.MethodGet)
r.PathPrefix("/ui/").Handler(uiHandler).
Methods(http.MethodGet)
}
//Start metrics server in a goroutine
@ -273,7 +265,7 @@ func main() {
log.Fatal(s.ListenAndServe())
}
//runMetricsServer Listen on a separate HTTP port for Prometheus metrics to keep this accessible from
// runMetricsServer Listen on a separate HTTP port for Prometheus metrics to keep this accessible from
// the internal network only.
func runMetricsServer() {
metricsHandler := metrics.PrometheusHandler()

View File

@ -3,7 +3,7 @@ package metrics
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/http/httptest"
@ -28,25 +28,23 @@ func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQuery
}
defer upstreamCall.Body.Close()
upstreamBody, _ := ioutil.ReadAll(upstreamCall.Body)
upstreamBody, _ := io.ReadAll(upstreamCall.Body)
if recorder.Code != http.StatusOK {
log.Printf("List functions responded with code %d, body: %s",
recorder.Code,
string(upstreamBody))
http.Error(w, "Metrics hander: unexpected status code retrieving functions from backend", http.StatusInternalServerError)
http.Error(w, string(upstreamBody), recorder.Code)
return
}
var functions []types.FunctionStatus
err := json.Unmarshal(upstreamBody, &functions)
if err != nil {
log.Printf("Metrics upstream error: %s", err)
log.Printf("Metrics upstream error: %s, value: %s", err, string(upstreamBody))
http.Error(w, "Error parsing metrics from upstream provider/backend", http.StatusInternalServerError)
http.Error(w, "Unable to parse list of functions from provider", http.StatusInternalServerError)
return
}
@ -63,8 +61,8 @@ func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQuery
results, err := prometheusQuery.Fetch(url.QueryEscape(q))
if err != nil {
// log the error but continue, the mixIn will correctly handle the empty results.
log.Printf("Error querying Prometheus: %s\n", err.Error())
return
}
mixIn(&functions, results)
}
@ -72,7 +70,7 @@ func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQuery
bytesOut, err := json.Marshal(functions)
if err != nil {
log.Printf("Error serializing functions: %s", err)
http.Error(w, "error writing response after adding metrics", http.StatusInternalServerError)
http.Error(w, "Error writing response after adding metrics", http.StatusInternalServerError)
return
}

View File

@ -5,6 +5,7 @@ import (
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
types "github.com/openfaas/faas-provider/types"
@ -55,6 +56,34 @@ func Test_PrometheusMetrics_MixedInto_Services(t *testing.T) {
}
}
func Test_MetricHandler_ForwardsErrors(t *testing.T) {
functionsHandler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusConflict)
w.Write([]byte("test error case"))
}
// explicitly set the query fetcher to nil because it should
// not be called when a non-200 response is returned from the
// functions handler, if it is called then the test will panic
handler := AddMetricsHandler(functionsHandler, nil)
rr := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodGet, "/system/functions", nil)
handler.ServeHTTP(rr, request)
if status := rr.Code; status != http.StatusConflict {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusConflict)
}
if rr.Header().Get("Content-Type") != "text/plain; charset=utf-8" {
t.Errorf("Want 'text/plain; charset=utf-8' content-type, got: %s", rr.Header().Get("Content-Type"))
}
body := strings.TrimSpace(rr.Body.String())
if body != "test error case" {
t.Errorf("Want 'test error case', got: %q", body)
}
}
func Test_FunctionsHandler_ReturnsJSONAndOneFunction(t *testing.T) {
functionsHandler := makeFunctionsHandler()

View File

@ -7,19 +7,17 @@ package metrics
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"net/url"
"path"
"strconv"
"time"
"log"
"github.com/openfaas/faas-provider/auth"
types "github.com/openfaas/faas-provider/types"
"github.com/openfaas/faas/gateway/scaling"
"github.com/prometheus/client_golang/prometheus"
)
@ -48,10 +46,6 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
e.metricOptions.GatewayFunctionsHistogram.Describe(ch)
e.metricOptions.ServiceReplicasGauge.Describe(ch)
e.metricOptions.GatewayFunctionInvocationStarted.Describe(ch)
e.metricOptions.ServiceTargetLoadGauge.Describe(ch)
e.metricOptions.ServiceMetrics.Counter.Describe(ch)
e.metricOptions.ServiceMetrics.Histogram.Describe(ch)
}
// Collect collects data to be consumed by prometheus
@ -62,7 +56,6 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.metricOptions.GatewayFunctionInvocationStarted.Collect(ch)
e.metricOptions.ServiceReplicasGauge.Reset()
e.metricOptions.ServiceTargetLoadGauge.Reset()
for _, service := range e.services {
var serviceName string
@ -76,52 +69,9 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.metricOptions.ServiceReplicasGauge.
WithLabelValues(serviceName).
Set(float64(service.Replicas))
// Set minimum replicas
minReplicas := scaling.DefaultMinReplicas
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.MinScaleLabel]; ok && len(v) > 0 {
val, _ := strconv.Atoi(v)
minReplicas = val
}
}
e.metricOptions.ServiceMinReplicasGauge.
WithLabelValues(serviceName).
Set(float64(minReplicas))
// Set scale type
scaleType := scaling.DefaultTypeScale
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.ScaleTypeLabel]; ok && len(v) > 0 {
scaleType = v
}
}
// Set target load
targetScale := scaling.DefaultTargetLoad
if service.Labels != nil {
a := *service.Labels
if v, ok := a[scaling.TargetLoadLabel]; ok && len(v) > 0 {
val, _ := strconv.Atoi(v)
targetScale = val
}
}
e.metricOptions.ServiceTargetLoadGauge.
WithLabelValues(serviceName, scaleType).
Set(float64(targetScale))
}
e.metricOptions.ServiceReplicasGauge.Collect(ch)
e.metricOptions.ServiceMinReplicasGauge.Collect(ch)
e.metricOptions.ServiceTargetLoadGauge.Collect(ch)
e.metricOptions.ServiceMetrics.Counter.Collect(ch)
e.metricOptions.ServiceMetrics.Histogram.Collect(ch)
}
// StartServiceWatcher starts a ticker and collects service replica counts to expose to prometheus
@ -188,7 +138,7 @@ func (e *Exporter) getHTTPClient(timeout time.Duration) http.Client {
}
func (e *Exporter) getFunctions(endpointURL url.URL, namespace string) ([]types.FunctionStatus, error) {
timeout := 3 * time.Second
timeout := 5 * time.Second
proxyClient := e.getHTTPClient(timeout)
endpointURL.Path = path.Join(endpointURL.Path, "/system/functions")
@ -209,15 +159,16 @@ func (e *Exporter) getFunctions(endpointURL url.URL, namespace string) ([]types.
return services, err
}
bytesOut, readErr := ioutil.ReadAll(res.Body)
bytesOut, readErr := io.ReadAll(res.Body)
if readErr != nil {
return services, readErr
}
unmarshalErr := json.Unmarshal(bytesOut, &services)
if unmarshalErr != nil {
return services, unmarshalErr
if err := json.Unmarshal(bytesOut, &services); err != nil {
return services, fmt.Errorf("error unmarshalling response: %s, error: %s",
string(bytesOut), err)
}
return services, nil
}
@ -230,7 +181,7 @@ func (e *Exporter) getNamespaces(endpointURL url.URL) ([]string, error) {
get.SetBasicAuth(e.credentials.User, e.credentials.Password)
}
timeout := 3 * time.Second
timeout := 5 * time.Second
proxyClient := e.getHTTPClient(timeout)
res, err := proxyClient.Do(get)
@ -242,14 +193,13 @@ func (e *Exporter) getNamespaces(endpointURL url.URL) ([]string, error) {
return namespaces, nil
}
bytesOut, readErr := ioutil.ReadAll(res.Body)
bytesOut, readErr := io.ReadAll(res.Body)
if readErr != nil {
return namespaces, readErr
}
unmarshalErr := json.Unmarshal(bytesOut, &namespaces)
if unmarshalErr != nil {
return namespaces, unmarshalErr
if err := json.Unmarshal(bytesOut, &namespaces); err != nil {
return namespaces, fmt.Errorf("error unmarshalling response: %s, error: %s", string(bytesOut), err)
}
return namespaces, nil
}

View File

@ -41,21 +41,21 @@ func Test_Describe_DescribesThePrometheusMetrics(t *testing.T) {
go exporter.Describe(ch)
d := <-ch
expectedGatewayFunctionInvocationDesc := `Desc{fqName: "gateway_function_invocation_total", help: "Function metrics", constLabels: {}, variableLabels: [function_name code]}`
expectedGatewayFunctionInvocationDesc := `Desc{fqName: "gateway_function_invocation_total", help: "Function metrics", constLabels: {}, variableLabels: {function_name,code}}`
actualGatewayFunctionInvocationDesc := d.String()
if expectedGatewayFunctionInvocationDesc != actualGatewayFunctionInvocationDesc {
t.Errorf("Want\n%s\ngot\n%s", expectedGatewayFunctionInvocationDesc, actualGatewayFunctionInvocationDesc)
}
d = <-ch
expectedGatewayFunctionsHistogramDesc := `Desc{fqName: "gateway_functions_seconds", help: "Function time taken", constLabels: {}, variableLabels: [function_name code]}`
expectedGatewayFunctionsHistogramDesc := `Desc{fqName: "gateway_functions_seconds", help: "Function time taken", constLabels: {}, variableLabels: {function_name,code}}`
actualGatewayFunctionsHistogramDesc := d.String()
if expectedGatewayFunctionsHistogramDesc != actualGatewayFunctionsHistogramDesc {
t.Errorf("Want\n%s\ngot\n%s", expectedGatewayFunctionsHistogramDesc, actualGatewayFunctionsHistogramDesc)
}
d = <-ch
expectedServiceReplicasGaugeDesc := `Desc{fqName: "gateway_service_count", help: "Current count of replicas for function", constLabels: {}, variableLabels: [function_name]}`
expectedServiceReplicasGaugeDesc := `Desc{fqName: "gateway_service_count", help: "Current count of replicas for function", constLabels: {}, variableLabels: {function_name}}`
actualServiceReplicasGaugeDesc := d.String()
if expectedServiceReplicasGaugeDesc != actualServiceReplicasGaugeDesc {
t.Errorf("Want\n%s\ngot\n%s", expectedServiceReplicasGaugeDesc, actualServiceReplicasGaugeDesc)

View File

@ -17,11 +17,7 @@ type MetricOptions struct {
GatewayFunctionsHistogram *prometheus.HistogramVec
GatewayFunctionInvocationStarted *prometheus.CounterVec
ServiceReplicasGauge *prometheus.GaugeVec
ServiceMinReplicasGauge *prometheus.GaugeVec
ServiceTargetLoadGauge *prometheus.GaugeVec
ServiceMetrics *ServiceMetricOptions
ServiceReplicasGauge *prometheus.GaugeVec
}
// ServiceMetricOptions provides RED metrics
@ -71,42 +67,6 @@ func BuildMetricsOptions() MetricOptions {
[]string{"function_name"},
)
serviceMinReplicas := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "gateway",
Name: "service_min",
Help: "Minium replicas for function",
},
[]string{"function_name"},
)
serviceTargetLoad := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "gateway",
Name: "service_target_load",
Help: "Target load for function",
},
[]string{"function_name", "scaling_type"},
)
// For automatic monitoring and alerting (RED method)
histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: "http",
Name: "request_duration_seconds",
Help: "Seconds spent serving HTTP requests.",
Buckets: prometheus.DefBuckets,
}, []string{"method", "path", "status"})
// Can be used Kubernetes HPA v2
counter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Subsystem: "http",
Name: "requests_total",
Help: "The total number of HTTP requests.",
},
[]string{"method", "path", "status"},
)
gatewayFunctionInvocationStarted := prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "gateway",
@ -117,18 +77,10 @@ func BuildMetricsOptions() MetricOptions {
[]string{"function_name"},
)
serviceMetricOptions := &ServiceMetricOptions{
Counter: counter,
Histogram: histogram,
}
metricsOptions := MetricOptions{
GatewayFunctionsHistogram: gatewayFunctionsHistogram,
GatewayFunctionInvocation: gatewayFunctionInvocation,
ServiceReplicasGauge: serviceReplicas,
ServiceMinReplicasGauge: serviceMinReplicas,
ServiceTargetLoadGauge: serviceTargetLoad,
ServiceMetrics: serviceMetricOptions,
GatewayFunctionInvocationStarted: gatewayFunctionInvocationStarted,
}

View File

@ -3,7 +3,7 @@ package metrics
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
)
@ -44,7 +44,7 @@ func (q PrometheusQuery) Fetch(query string) (*VectorQueryResponse, error) {
defer res.Body.Close()
}
bytesOut, readErr := ioutil.ReadAll(res.Body)
bytesOut, readErr := io.ReadAll(res.Body)
if readErr != nil {
return nil, readErr
}

View File

@ -0,0 +1,155 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package middleware
import (
"fmt"
"log"
"net/http"
"net/url"
"strings"
"testing"
)
func Test_SingleHostBaseURLResolver_BuildURL(t *testing.T) {
newNamespace := "production-fn"
function := "figlet"
r := SingleHostBaseURLResolver{BaseURL: "http://faas-netes.openfaas:8080"}
want := "http://faas-netes.openfaas:8080/function/figlet.production-fn/healthz"
got := r.BuildURL(function, newNamespace, "/healthz", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func Test_SingleHostBaseURLResolver_BuildURL_DefaultNamespace(t *testing.T) {
newNamespace := "openfaas-fn"
function := "figlet"
r := SingleHostBaseURLResolver{BaseURL: "http://faas-netes.openfaas:8080"}
want := "http://faas-netes.openfaas:8080/function/figlet.openfaas-fn/_/health"
got := r.BuildURL(function, newNamespace, "/_/health", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestSingleHostBaseURLResolver(t *testing.T) {
urlVal, _ := url.Parse("http://upstream:8080/")
r := SingleHostBaseURLResolver{BaseURL: urlVal.String()}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := "http://upstream:8080"
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
const watchdogPort = 8080
func TestURL_NonDefaultNamespaceWithPath(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080/healthz"
got := r.BuildURL(function, newNamespace, "/healthz", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestURL_NonDefaultNamespaceWithout(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080"
got := r.BuildURL(function, newNamespace, "", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestURL_DefaultNamespaceWithPath(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc"
namespace := "openfaas-fn"
newNamespace := "production-fn"
function := "figlet"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
want := "http://figlet.production-fn.local.cluster.svc:8080/_/health"
got := r.BuildURL(function, newNamespace, "/_/health", true)
if got != want {
t.Fatalf("r.URL failed, want: %s got: %s", want, got)
}
}
func TestFunctionAsHostBaseURLResolver_WithNamespaceOverride(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
namespace := "openfaas-fn"
newNS := "production-fn"
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix, FunctionNamespace: namespace}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello."+newNS, nil)
resolved := r.Resolve(req)
newSuffix := strings.Replace(suffix, namespace, newNS, -1)
want := fmt.Sprintf("http://hello.%s:%d", newSuffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithSuffix(t *testing.T) {
suffix := "openfaas-fn.local.cluster.svc."
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello.%s:%d", suffix, watchdogPort)
log.Println(want)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}
func TestFunctionAsHostBaseURLResolver_WithoutSuffix(t *testing.T) {
suffix := ""
r := FunctionAsHostBaseURLResolver{FunctionSuffix: suffix}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/function/hello", nil)
resolved := r.Resolve(req)
want := fmt.Sprintf("http://hello%s:%d", suffix, watchdogPort)
if resolved != want {
t.Logf("r.Resolve failed, want: %s got: %s", want, resolved)
t.Fail()
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
package middleware
import (
"net/http"

View File

@ -0,0 +1,156 @@
package middleware
import (
"fmt"
"net/http"
"net/url"
"path"
"regexp"
"strings"
)
// functionMatcher parses out the service name (group 1) and rest of path (group 2).
var functionMatcher = regexp.MustCompile("^/?(?:async-)?function/([^/?]+)([^?]*)")
// Indices and meta-data for functionMatcher regex parts
const (
hasPathCount = 3
routeIndex = 0 // routeIndex corresponds to /function/ or /async-function/
nameIndex = 1 // nameIndex is the function name
pathIndex = 2 // pathIndex is the path i.e. /employee/:id/
)
// BaseURLResolver URL resolver for upstream requests
type BaseURLResolver interface {
Resolve(r *http.Request) string
BuildURL(function, namespace, healthPath string, directFunctions bool) string
}
// URLPathTransformer Transform the incoming URL path for upstream requests
type URLPathTransformer interface {
Transform(r *http.Request) string
}
// SingleHostBaseURLResolver resolves URLs against a single BaseURL
type SingleHostBaseURLResolver struct {
BaseURL string
}
func (s SingleHostBaseURLResolver) BuildURL(function, namespace, healthPath string, directFunctions bool) string {
u, _ := url.Parse(s.BaseURL)
base := fmt.Sprintf("/function/%s.%s/", function, namespace)
if len(healthPath) > 0 {
u.Path = path.Join(base, healthPath)
} else {
u.Path = base
}
return u.String()
}
// Resolve the base URL for a request
func (s SingleHostBaseURLResolver) Resolve(r *http.Request) string {
baseURL := s.BaseURL
if strings.HasSuffix(baseURL, "/") {
baseURL = baseURL[0 : len(baseURL)-1]
}
return baseURL
}
// FunctionAsHostBaseURLResolver resolves URLs using a function from the URL as a host
type FunctionAsHostBaseURLResolver struct {
FunctionSuffix string
FunctionNamespace string
}
// Resolve the base URL for a request
func (f FunctionAsHostBaseURLResolver) Resolve(r *http.Request) string {
svcName := GetServiceName(r.URL.Path)
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
if index := strings.LastIndex(svcName, "."); index > -1 && len(svcName) > index+1 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, "", -1)
} else {
suffix = "." + f.FunctionSuffix
}
}
return fmt.Sprintf("http://%s%s:%d", svcName, suffix, watchdogPort)
}
func (f FunctionAsHostBaseURLResolver) BuildURL(function, namespace, healthPath string, directFunctions bool) string {
svcName := function
const watchdogPort = 8080
var suffix string
if len(f.FunctionSuffix) > 0 {
suffix = strings.Replace(f.FunctionSuffix, f.FunctionNamespace, namespace, 1)
}
u, _ := url.Parse(fmt.Sprintf("http://%s.%s:%d", svcName, suffix, watchdogPort))
if len(healthPath) > 0 {
u.Path = healthPath
}
return u.String()
}
// TransparentURLPathTransformer passes the requested URL path through untouched.
type TransparentURLPathTransformer struct {
}
// Transform returns the URL path unchanged.
func (f TransparentURLPathTransformer) Transform(r *http.Request) string {
return r.URL.Path
}
// FunctionPrefixTrimmingURLPathTransformer removes the "/function/servicename/" prefix from the URL path.
type FunctionPrefixTrimmingURLPathTransformer struct {
}
// Transform removes the "/function/servicename/" prefix from the URL path.
func (f FunctionPrefixTrimmingURLPathTransformer) Transform(r *http.Request) string {
ret := r.URL.Path
if ret != "" {
// When forwarding to a function, since the `/function/xyz` portion
// of a path like `/function/xyz/rest/of/path` is only used or needed
// by the Gateway, we want to trim it down to `/rest/of/path` for the
// upstream request. In the following regex, in the case of a match
// the r.URL.Path will be at `0`, the function name at `1` and the
// rest of the path (the part we are interested in) at `2`.
matcher := functionMatcher.Copy()
parts := matcher.FindStringSubmatch(ret)
if len(parts) == hasPathCount {
ret = parts[pathIndex]
}
}
return ret
}
func GetServiceName(urlValue string) string {
var serviceName string
forward := "/function/"
if strings.HasPrefix(urlValue, forward) {
// With a path like `/function/xyz/rest/of/path?q=a`, the service
// name we wish to locate is just the `xyz` portion. With a positive
// match on the regex below, it will return a three-element slice.
// The item at index `0` is the same as `urlValue`, at `1`
// will be the service name we need, and at `2` the rest of the path.
matcher := functionMatcher.Copy()
matches := matcher.FindStringSubmatch(urlValue)
if len(matches) == hasPathCount {
serviceName = matches[nameIndex]
}
}
return strings.Trim(serviceName, "/")
}

View File

@ -0,0 +1,10 @@
package middleware
import "strings"
func GetNamespace(defaultNamespace, fullName string) (string, string) {
if index := strings.LastIndex(fullName, "."); index > -1 {
return fullName[:index], fullName[index+1:]
}
return fullName, defaultNamespace
}

View File

@ -2,6 +2,8 @@ package middleware
import "net/http"
// AuthInjector is an interface for injecting authentication information into a request
// which will be proxied or made to a remote/upstream service.
type AuthInjector interface {
Inject(r *http.Request)
}

View File

@ -1,7 +1,7 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers
package middleware
import (
"net/http"

View File

@ -7,7 +7,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@ -32,7 +32,7 @@ type ExternalServiceQuery struct {
// NewExternalServiceQuery proxies service queries to external plugin via HTTP
func NewExternalServiceQuery(externalURL url.URL, authInjector middleware.AuthInjector) scaling.ServiceQuery {
timeout := 5 * time.Second
timeout := 3 * time.Second
proxyClient := http.Client{
Transport: &http.Transport{
@ -82,35 +82,28 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
res, err := s.ProxyClient.Do(req)
if err != nil {
log.Printf("Unable to connect to %s, error: %s", urlPath, err)
log.Println(urlPath, err)
return emptyServiceQueryResponse, err
}
var bytesOut []byte
if res.Body != nil {
bytesOut, _ = io.ReadAll(res.Body)
defer res.Body.Close()
}
if res.StatusCode == http.StatusOK {
if err := json.Unmarshal(bytesOut, &function); err != nil {
log.Printf("Unable to unmarshal: %q, %s", string(bytesOut), err)
return emptyServiceQueryResponse, err
}
// log.Printf("GetReplicas [%s.%s] took: %fs", serviceName, serviceNamespace, time.Since(start).Seconds())
} else {
var body []byte
if res.Body != nil {
defer res.Body.Close()
body, _ = ioutil.ReadAll(res.Body)
}
if res.StatusCode == http.StatusOK {
err = json.Unmarshal(body, &function)
if err != nil {
log.Printf("Unable to unmarshal %s, error: %s", string(body), err)
}
log.Printf("GetReplicas [%s.%s] took: %fs",
serviceName,
serviceNamespace,
time.Since(start).Seconds())
} else {
log.Printf("GetReplicas [%s.%s] took: %fs, code: %d",
serviceName,
serviceNamespace,
time.Since(start).Seconds(),
res.StatusCode)
return emptyServiceQueryResponse, fmt.Errorf("server returned non-200 status code (%d) for function, %s", res.StatusCode, serviceName)
}
log.Printf("GetReplicas [%s.%s] took: %.4fs, code: %d\n", serviceName, serviceNamespace, time.Since(start).Seconds(), res.StatusCode)
return emptyServiceQueryResponse, fmt.Errorf("server returned non-200 status code (%d) for function, %s, body: %s", res.StatusCode, serviceName, string(bytesOut))
}
minReplicas := uint64(scaling.DefaultMinReplicas)
@ -118,20 +111,17 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
scalingFactor := uint64(scaling.DefaultScalingFactor)
availableReplicas := function.AvailableReplicas
targetLoad := uint64(scaling.DefaultTargetLoad)
if function.Labels != nil {
labels := *function.Labels
minReplicas = extractLabelValue(labels[scaling.MinScaleLabel], minReplicas)
maxReplicas = extractLabelValue(labels[scaling.MaxScaleLabel], maxReplicas)
extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor)
targetLoad = extractLabelValue(labels[scaling.TargetLoadLabel], targetLoad)
if extractedScalingFactor > 0 && extractedScalingFactor <= 100 {
scalingFactor = extractedScalingFactor
} else {
log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor)
return scaling.ServiceQueryResponse{}, fmt.Errorf("bad scaling factor: %d, is not in range of [0 - 100]", extractedScalingFactor)
}
}
@ -142,7 +132,6 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
ScalingFactor: scalingFactor,
AvailableReplicas: availableReplicas,
Annotations: function.Annotations,
TargetLoad: targetLoad,
}, err
}
@ -183,7 +172,8 @@ func (s ExternalServiceQuery) SetReplicas(serviceName, serviceNamespace string,
err = fmt.Errorf("error scaling HTTP code %d, %s", res.StatusCode, urlPath)
}
log.Printf("SetReplicas [%s.%s] took: %fs", serviceName, serviceNamespace, time.Since(start).Seconds())
log.Printf("SetReplicas [%s.%s] took: %.4fs",
serviceName, serviceNamespace, time.Since(start).Seconds())
return err
}

View File

@ -75,7 +75,6 @@ func TestGetReplicasExistentFn(t *testing.T) {
MinReplicas: uint64(scaling.DefaultMinReplicas),
ScalingFactor: uint64(scaling.DefaultScalingFactor),
AvailableReplicas: 0,
TargetLoad: 10,
}
var injector middleware.AuthInjector

View File

@ -1,18 +0,0 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Package requests package provides a client SDK or library for
// the OpenFaaS gateway REST API
package requests
// AsyncReport is the report from a function executed on a queue worker.
type AsyncReport struct {
FunctionName string `json:"name"`
StatusCode int `json:"statusCode"`
TimeTaken float64 `json:"timeTaken"`
}
// DeleteFunctionRequest delete a deployed function
type DeleteFunctionRequest struct {
FunctionName string `json:"functionName"`
}

View File

@ -3,12 +3,18 @@
package scaling
import "fmt"
import (
"fmt"
"log"
"golang.org/x/sync/singleflight"
)
type CachedFunctionQuery struct {
cache FunctionCacher
serviceQuery ServiceQuery
emptyAnnotations map[string]string
singleFlight *singleflight.Group
}
func NewCachedFunctionQuery(cache FunctionCacher, serviceQuery ServiceQuery) FunctionQuery {
@ -16,6 +22,7 @@ func NewCachedFunctionQuery(cache FunctionCacher, serviceQuery ServiceQuery) Fun
cache: cache,
serviceQuery: serviceQuery,
emptyAnnotations: map[string]string{},
singleFlight: &singleflight.Group{},
}
}
@ -35,13 +42,21 @@ func (c *CachedFunctionQuery) Get(fn string, ns string) (ServiceQueryResponse, e
query, hit := c.cache.Get(fn, ns)
if !hit {
key := fmt.Sprintf("GetReplicas-%s.%s", fn, ns)
queryResponse, err, _ := c.singleFlight.Do(key, func() (interface{}, error) {
log.Printf("Cache miss - run GetReplicas")
// If there is a cache miss, then fetch the value from the provider API
return c.serviceQuery.GetReplicas(fn, ns)
})
// If there is a cache miss, then fetch the value from the provider API
queryResponse, err := c.serviceQuery.GetReplicas(fn, ns)
if err != nil {
return ServiceQueryResponse{}, err
}
c.cache.Set(fn, ns, queryResponse)
if queryResponse != nil {
c.cache.Set(fn, ns, queryResponse.(ServiceQueryResponse))
}
} else {
return query, nil
}

View File

@ -4,21 +4,26 @@ import (
"fmt"
"log"
"time"
"github.com/openfaas/faas/gateway/types"
"golang.org/x/sync/singleflight"
)
// NewFunctionScaler create a new scaler with the specified
// ScalingConfig
func NewFunctionScaler(config ScalingConfig, functionCacher FunctionCacher) FunctionScaler {
return FunctionScaler{
Cache: functionCacher,
Config: config,
Cache: functionCacher,
Config: config,
SingleFlight: &singleflight.Group{},
}
}
// FunctionScaler scales from zero
type FunctionScaler struct {
Cache FunctionCacher
Config ScalingConfig
Cache FunctionCacher
Config ScalingConfig
SingleFlight *singleflight.Group
}
// FunctionScaleResult holds the result of scaling from zero
@ -34,6 +39,8 @@ type FunctionScaleResult struct {
func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResult {
start := time.Now()
// First check the cache, if there are available replicas, then the
// request can be served.
if cachedResponse, hit := f.Cache.Get(functionName, namespace); hit &&
cachedResponse.AvailableReplicas > 0 {
return FunctionScaleResult{
@ -44,7 +51,12 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
}
}
queryResponse, err := f.Config.ServiceQuery.GetReplicas(functionName, namespace)
// The wasn't a hit, or there were no available replicas found
// so query the live endpoint
getKey := fmt.Sprintf("GetReplicas-%s.%s", functionName, namespace)
res, err, _ := f.SingleFlight.Do(getKey, func() (interface{}, error) {
return f.Config.ServiceQuery.GetReplicas(functionName, namespace)
})
if err != nil {
return FunctionScaleResult{
@ -54,36 +66,78 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
Duration: time.Since(start),
}
}
if res == nil {
return FunctionScaleResult{
Error: fmt.Errorf("empty response from server"),
Available: false,
Found: false,
Duration: time.Since(start),
}
}
// Check if there are available replicas in the live data
if res.(ServiceQueryResponse).AvailableReplicas > 0 {
return FunctionScaleResult{
Error: nil,
Available: true,
Found: true,
Duration: time.Since(start),
}
}
// Store the result of GetReplicas in the cache
queryResponse := res.(ServiceQueryResponse)
f.Cache.Set(functionName, namespace, queryResponse)
if queryResponse.AvailableReplicas == 0 {
// If the desired replica count is 0, then a scale up event
// is required.
if queryResponse.Replicas == 0 {
minReplicas := uint64(1)
if queryResponse.MinReplicas > 0 {
minReplicas = queryResponse.MinReplicas
}
scaleResult := backoff(func(attempt int) error {
queryResponse, err := f.Config.ServiceQuery.GetReplicas(functionName, namespace)
// In a retry-loop, first query desired replicas, then
// set them if the value is still at 0.
scaleResult := types.Retry(func(attempt int) error {
res, err, _ := f.SingleFlight.Do(getKey, func() (interface{}, error) {
return f.Config.ServiceQuery.GetReplicas(functionName, namespace)
})
if err != nil {
return err
}
// Cache the response
queryResponse = res.(ServiceQueryResponse)
f.Cache.Set(functionName, namespace, queryResponse)
// The scale up is complete because the desired replica count
// has been set to 1 or more.
if queryResponse.Replicas > 0 {
return nil
}
log.Printf("[Scale %d] function=%s 0 => %d requested", attempt, functionName, minReplicas)
setScaleErr := f.Config.ServiceQuery.SetReplicas(functionName, namespace, minReplicas)
if setScaleErr != nil {
return fmt.Errorf("unable to scale function [%s], err: %s", functionName, setScaleErr)
// Request a scale up to the minimum amount of replicas
setKey := fmt.Sprintf("SetReplicas-%s.%s", functionName, namespace)
if _, err, _ := f.SingleFlight.Do(setKey, func() (interface{}, error) {
log.Printf("[Scale %d/%d] function=%s 0 => %d requested",
attempt, int(f.Config.SetScaleRetries), functionName, minReplicas)
if err := f.Config.ServiceQuery.SetReplicas(functionName, namespace, minReplicas); err != nil {
return nil, fmt.Errorf("unable to scale function [%s], err: %s", functionName, err)
}
return nil, nil
}); err != nil {
return err
}
return nil
}, int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval)
}, "Scale", int(f.Config.SetScaleRetries), f.Config.FunctionPollInterval)
if scaleResult != nil {
return FunctionScaleResult{
@ -94,37 +148,44 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
}
}
for i := 0; i < int(f.Config.MaxPollCount); i++ {
queryResponse, err := f.Config.ServiceQuery.GetReplicas(functionName, namespace)
if err == nil {
f.Cache.Set(functionName, namespace, queryResponse)
}
totalTime := time.Since(start)
}
if err != nil {
return FunctionScaleResult{
Error: err,
Available: false,
Found: true,
Duration: totalTime,
}
}
// Holding pattern for at least one function replica to be available
for i := 0; i < int(f.Config.MaxPollCount); i++ {
if queryResponse.AvailableReplicas > 0 {
res, err, _ := f.SingleFlight.Do(getKey, func() (interface{}, error) {
return f.Config.ServiceQuery.GetReplicas(functionName, namespace)
})
queryResponse := res.(ServiceQueryResponse)
log.Printf("[Scale] function=%s 0 => %d successful - %fs",
functionName, queryResponse.AvailableReplicas, totalTime.Seconds())
return FunctionScaleResult{
Error: nil,
Available: true,
Found: true,
Duration: totalTime,
}
}
time.Sleep(f.Config.FunctionPollInterval)
if err == nil {
f.Cache.Set(functionName, namespace, queryResponse)
}
totalTime := time.Since(start)
if err != nil {
return FunctionScaleResult{
Error: err,
Available: false,
Found: true,
Duration: totalTime,
}
}
if queryResponse.AvailableReplicas > 0 {
log.Printf("[Ready] function=%s waited for - %.4fs", functionName, totalTime.Seconds())
return FunctionScaleResult{
Error: nil,
Available: true,
Found: true,
Duration: totalTime,
}
}
time.Sleep(f.Config.FunctionPollInterval)
}
return FunctionScaleResult{
@ -134,23 +195,3 @@ func (f *FunctionScaler) Scale(functionName, namespace string) FunctionScaleResu
Duration: time.Since(start),
}
}
type routine func(attempt int) error
func backoff(r routine, attempts int, interval time.Duration) error {
var err error
for i := 0; i < attempts; i++ {
res := r(i)
if res != nil {
err = res
log.Printf("Attempt: %d, had error: %s\n", i, res)
} else {
err = nil
break
}
time.Sleep(interval)
}
return err
}

View File

@ -1,17 +1,23 @@
package scaling
import (
"bytes"
"encoding/json"
"io"
"net/http"
"github.com/openfaas/faas-provider/types"
)
const (
// DefaultMinReplicas is the minimal amount of replicas for a service.
DefaultMinReplicas = 1
// DefaultMaxReplicas is the amount of replicas a service will auto-scale up to.
DefaultMaxReplicas = 20
DefaultMaxReplicas = 5
// DefaultScalingFactor is the defining proportion for the scaling increments.
DefaultScalingFactor = 20
// DefaultTargetLoad
DefaultTargetLoad = 10
DefaultScalingFactor = 10
DefaultTypeScale = "rps"
@ -23,10 +29,44 @@ const (
// ScalingFactorLabel label indicates the scaling factor for a function
ScalingFactorLabel = "com.openfaas.scale.factor"
// TargetLoadLabel see also DefaultTargetScale
TargetLoadLabel = "com.openfaas.scale.target"
// ScaleTypeLabel see also DefaultScaleType
ScaleTypeLabel = "com.openfaas.scale.type"
)
func MakeHorizontalScalingHandler(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
return
}
if r.Body == nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
scaleRequest := types.ScaleServiceRequest{}
if err := json.Unmarshal(body, &scaleRequest); err != nil {
http.Error(w, "Error unmarshalling request body", http.StatusBadRequest)
return
}
if scaleRequest.Replicas < 1 {
scaleRequest.Replicas = 1
}
if scaleRequest.Replicas > DefaultMaxReplicas {
scaleRequest.Replicas = DefaultMaxReplicas
}
upstreamReq, _ := json.Marshal(scaleRequest)
// Restore the io.ReadCloser to its original state
r.Body = io.NopCloser(bytes.NewBuffer(upstreamReq))
next.ServeHTTP(w, r)
}
}

View File

@ -17,5 +17,4 @@ type ServiceQueryResponse struct {
ScalingFactor uint64
AvailableReplicas uint64
Annotations *map[string]string
TargetLoad uint64
}

73
gateway/scaling/single.go Normal file
View File

@ -0,0 +1,73 @@
package scaling
import (
"log"
"sync"
)
type Call struct {
wg *sync.WaitGroup
res *SingleFlightResult
}
type SingleFlight struct {
lock *sync.RWMutex
calls map[string]*Call
}
type SingleFlightResult struct {
Result interface{}
Error error
}
func NewSingleFlight() *SingleFlight {
return &SingleFlight{
lock: &sync.RWMutex{},
calls: map[string]*Call{},
}
}
func (s *SingleFlight) Do(key string, f func() (interface{}, error)) (interface{}, error) {
s.lock.Lock()
if call, ok := s.calls[key]; ok {
s.lock.Unlock()
call.wg.Wait()
return call.res.Result, call.res.Error
}
var call *Call
if s.calls[key] == nil {
call = &Call{
wg: &sync.WaitGroup{},
}
s.calls[key] = call
}
call.wg.Add(1)
s.lock.Unlock()
go func() {
log.Printf("Miss, so running: %s", key)
res, err := f()
s.lock.Lock()
call.res = &SingleFlightResult{
Result: res,
Error: err,
}
call.wg.Done()
delete(s.calls, key)
s.lock.Unlock()
}()
call.wg.Wait()
return call.res.Result, call.res.Error
}

View File

@ -28,9 +28,6 @@ type HandlerSet struct {
// QueuedProxy queue work and return synchronous response
QueuedProxy http.HandlerFunc
// AsyncReport report a deferred execution result
AsyncReport http.HandlerFunc
// ScaleFunction enables a function to be scaled
ScaleFunction http.HandlerFunc
@ -45,4 +42,6 @@ type HandlerSet struct {
// NamespaceListerHandler lists namespaces
NamespaceListerHandler http.HandlerFunc
NamespaceMutatorHandler http.HandlerFunc
}

View File

@ -8,7 +8,6 @@ import (
"net/url"
"os"
"strconv"
"strings"
"time"
)
@ -59,7 +58,7 @@ func (ReadConfig) Read(hasEnv HasEnv) (*GatewayConfig, error) {
PrometheusPort: 9090,
}
defaultDuration := time.Second * 8
defaultDuration := time.Second * 60
cfg.ReadTimeout = parseIntOrDurationValue(hasEnv.Getenv("read_timeout"), defaultDuration)
cfg.WriteTimeout = parseIntOrDurationValue(hasEnv.Getenv("write_timeout"), defaultDuration)
@ -129,9 +128,6 @@ func (ReadConfig) Read(hasEnv HasEnv) (*GatewayConfig, error) {
cfg.PrometheusHost = prometheusHost
}
cfg.DirectFunctions = parseBoolValue(hasEnv.Getenv("direct_functions"))
cfg.DirectFunctionsSuffix = hasEnv.Getenv("direct_functions_suffix")
cfg.UseBasicAuth = parseBoolValue(hasEnv.Getenv("basic_auth"))
secretPath := hasEnv.Getenv("secret_mount_path")
@ -169,12 +165,6 @@ func (ReadConfig) Read(hasEnv HasEnv) (*GatewayConfig, error) {
cfg.Namespace = hasEnv.Getenv("function_namespace")
if len(cfg.DirectFunctionsSuffix) > 0 && len(cfg.Namespace) > 0 {
if strings.HasPrefix(cfg.DirectFunctionsSuffix, cfg.Namespace) == false {
return nil, fmt.Errorf("function_namespace must be a sub-string of direct_functions_suffix")
}
}
return &cfg, nil
}
@ -214,12 +204,6 @@ type GatewayConfig struct {
// Port to connect to Prometheus.
PrometheusPort int
// If set to true we will access upstream functions directly rather than through the upstream provider
DirectFunctions bool
// If set this will be used to resolve functions directly
DirectFunctionsSuffix string
// If set, reads secrets from file-system for enabling basic auth.
UseBasicAuth bool

View File

@ -38,16 +38,6 @@ func TestRead_UseExternalProvider_Defaults(t *testing.T) {
t.Fail()
}
if config.DirectFunctions != false {
t.Log("Default for DirectFunctions should be false")
t.Fail()
}
if len(config.DirectFunctionsSuffix) > 0 {
t.Log("Default for DirectFunctionsSuffix should be empty")
t.Fail()
}
if len(config.Namespace) > 0 {
t.Log("Default for Namespace should be empty")
t.Fail()
@ -89,54 +79,6 @@ func TestRead_NamespaceOverrideAgressWithFunctionSuffix_Valid(t *testing.T) {
}
}
func TestRead_NamespaceOverrideAgressWithFunctionSuffix_Invalid(t *testing.T) {
defaults := NewEnvBucket()
readConfig := ReadConfig{}
defaults.Setenv("direct_functions", "true")
wantSuffix := "openfaas-fn.cluster.local.svc."
defaults.Setenv("direct_functions_suffix", wantSuffix)
defaults.Setenv("function_namespace", "fn")
_, err := readConfig.Read(defaults)
if err == nil {
t.Logf("Expected an error because function_namespace should be a sub-string of direct_functions_suffix")
t.Fail()
return
}
want := "function_namespace must be a sub-string of direct_functions_suffix"
if want != err.Error() {
t.Logf("Error want: %s, got: %s", want, err.Error())
t.Fail()
}
}
func TestRead_DirectFunctionsOverride(t *testing.T) {
defaults := NewEnvBucket()
readConfig := ReadConfig{}
defaults.Setenv("direct_functions", "true")
wantSuffix := "openfaas-fn.cluster.local.svc."
defaults.Setenv("direct_functions_suffix", wantSuffix)
config, _ := readConfig.Read(defaults)
if config.DirectFunctions != true {
t.Logf("DirectFunctions should be true, got: %v", config.DirectFunctions)
t.Fail()
}
if config.DirectFunctionsSuffix != wantSuffix {
t.Logf("DirectFunctionsSuffix want: %s, got: %s", wantSuffix, config.DirectFunctionsSuffix)
t.Fail()
}
}
func TestRead_ScaleZeroDefaultAndOverride(t *testing.T) {
defaults := NewEnvBucket()
readConfig := ReadConfig{}
@ -166,13 +108,16 @@ func TestRead_EmptyTimeoutConfig(t *testing.T) {
config, _ := readConfig.Read(defaults)
if (config.ReadTimeout) != time.Duration(8)*time.Second {
t.Log("ReadTimeout incorrect")
t.Fail()
want := time.Second * 60
got := config.ReadTimeout
if got != want {
t.Fatalf("config.ReadTimeout want: %s, but got: %s", want, got)
}
if (config.WriteTimeout) != time.Duration(8)*time.Second {
t.Log("WriteTimeout incorrect")
t.Fail()
want = time.Second * 60
got = config.WriteTimeout
if got != want {
t.Fatalf("config.WriteTimeout want: %s, but got: %s", want, got)
}
}

25
gateway/types/retry.go Normal file
View File

@ -0,0 +1,25 @@
package types
import (
"log"
"time"
)
type routine func(attempt int) error
func Retry(r routine, label string, attempts int, interval time.Duration) error {
var err error
for i := 0; i < attempts; i++ {
res := r(i)
if res != nil {
err = res
log.Printf("[%s]: %d/%d, error: %s\n", label, i, attempts, res)
} else {
err = nil
break
}
time.Sleep(interval)
}
return err
}

View File

@ -0,0 +1,43 @@
package types
import (
"fmt"
"testing"
"time"
)
func Test_retry_early_success(t *testing.T) {
called := 0
maxRetries := 10
routine := func(i int) error {
called++
if called == 5 {
return nil
}
return fmt.Errorf("not called 5 times yet")
}
Retry(routine, "test", maxRetries, time.Millisecond*5)
want := 5
if called != want {
t.Errorf("want: %d, got: %d", want, called)
}
}
func Test_retry_until_max_attempts(t *testing.T) {
called := 0
maxRetries := 10
routine := func(i int) error {
called++
return fmt.Errorf("unable to pass condition for routine")
}
Retry(routine, "test", maxRetries, time.Millisecond*5)
want := maxRetries
if called != want {
t.Errorf("want: %d, got: %d", want, called)
}
}

View File

@ -1,8 +0,0 @@
language: go
go:
- "1.x"
- master
env:
- TAGS=""
- TAGS="-tags purego"
script: go test $TAGS -v ./...

View File

@ -1,10 +1,9 @@
# xxhash
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash)
[![Build Status](https://travis-ci.org/cespare/xxhash.svg?branch=master)](https://travis-ci.org/cespare/xxhash)
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a
high-quality hashing algorithm that is much faster than anything in the Go
standard library.
@ -25,8 +24,11 @@ func (*Digest) WriteString(string) (int, error)
func (*Digest) Sum64() uint64
```
This implementation provides a fast pure-Go implementation and an even faster
assembly implementation for amd64.
The package is written with optimized pure Go and also contains even faster
assembly implementations for amd64 and arm64. If desired, the `purego` build tag
opts into using the Go code even on those architectures.
[xxHash]: http://cyan4973.github.io/xxHash/
## Compatibility
@ -45,23 +47,26 @@ I recommend using the latest release of Go.
Here are some quick benchmarks comparing the pure-Go and assembly
implementations of Sum64.
| input size | purego | asm |
| --- | --- | --- |
| 5 B | 979.66 MB/s | 1291.17 MB/s |
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
| input size | purego | asm |
| ---------- | --------- | --------- |
| 4 B | 1.3 GB/s | 1.2 GB/s |
| 16 B | 2.9 GB/s | 3.5 GB/s |
| 100 B | 6.9 GB/s | 8.1 GB/s |
| 4 KB | 11.7 GB/s | 16.7 GB/s |
| 10 MB | 12.0 GB/s | 17.3 GB/s |
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
the following commands under Go 1.11.2:
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C
CPU using the following commands under Go 1.19.2:
```
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$')
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
```
## Projects using this package
- [InfluxDB](https://github.com/influxdata/influxdb)
- [Prometheus](https://github.com/prometheus/prometheus)
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)

View File

@ -1,3 +0,0 @@
module github.com/cespare/xxhash/v2
go 1.11

View File

10
gateway/vendor/github.com/cespare/xxhash/v2/testall.sh generated vendored Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
set -eu -o pipefail
# Small convenience script for running the tests with various combinations of
# arch/tags. This assumes we're running on amd64 and have qemu available.
go test ./...
go test -tags purego ./...
GOARCH=arm64 go test
GOARCH=arm64 go test -tags purego

View File

@ -16,19 +16,11 @@ const (
prime5 uint64 = 2870177450012600261
)
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
// possible in the Go code is worth a small (but measurable) performance boost
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
// convenience in the Go code in a few places where we need to intentionally
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
// result overflows a uint64).
var (
prime1v = prime1
prime2v = prime2
prime3v = prime3
prime4v = prime4
prime5v = prime5
)
// Store the primes in an array as well.
//
// The consts are used when possible in Go code to avoid MOVs but we need a
// contiguous array of the assembly code.
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
// Digest implements hash.Hash64.
type Digest struct {
@ -50,10 +42,10 @@ func New() *Digest {
// Reset clears the Digest's state so that it can be reused.
func (d *Digest) Reset() {
d.v1 = prime1v + prime2
d.v1 = primes[0] + prime2
d.v2 = prime2
d.v3 = 0
d.v4 = -prime1v
d.v4 = -primes[0]
d.total = 0
d.n = 0
}
@ -69,21 +61,23 @@ func (d *Digest) Write(b []byte) (n int, err error) {
n = len(b)
d.total += uint64(n)
memleft := d.mem[d.n&(len(d.mem)-1):]
if d.n+n < 32 {
// This new data doesn't even fill the current block.
copy(d.mem[d.n:], b)
copy(memleft, b)
d.n += n
return
}
if d.n > 0 {
// Finish off the partial block.
copy(d.mem[d.n:], b)
c := copy(memleft, b)
d.v1 = round(d.v1, u64(d.mem[0:8]))
d.v2 = round(d.v2, u64(d.mem[8:16]))
d.v3 = round(d.v3, u64(d.mem[16:24]))
d.v4 = round(d.v4, u64(d.mem[24:32]))
b = b[32-d.n:]
b = b[c:]
d.n = 0
}
@ -133,21 +127,20 @@ func (d *Digest) Sum64() uint64 {
h += d.total
i, end := 0, d.n
for ; i+8 <= end; i += 8 {
k1 := round(0, u64(d.mem[i:i+8]))
b := d.mem[:d.n&(len(d.mem)-1)]
for ; len(b) >= 8; b = b[8:] {
k1 := round(0, u64(b[:8]))
h ^= k1
h = rol27(h)*prime1 + prime4
}
if i+4 <= end {
h ^= uint64(u32(d.mem[i:i+4])) * prime1
if len(b) >= 4 {
h ^= uint64(u32(b[:4])) * prime1
h = rol23(h)*prime2 + prime3
i += 4
b = b[4:]
}
for i < end {
h ^= uint64(d.mem[i]) * prime5
for ; len(b) > 0; b = b[1:] {
h ^= uint64(b[0]) * prime5
h = rol11(h) * prime1
i++
}
h ^= h >> 33
@ -193,7 +186,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error {
b, d.v4 = consumeUint64(b)
b, d.total = consumeUint64(b)
copy(d.mem[:], b)
b = b[len(d.mem):]
d.n = int(d.total % uint64(len(d.mem)))
return nil
}

View File

@ -1,215 +1,209 @@
//go:build !appengine && gc && !purego
// +build !appengine
// +build gc
// +build !purego
#include "textflag.h"
// Register allocation:
// AX h
// CX pointer to advance through b
// DX n
// BX loop end
// R8 v1, k1
// R9 v2
// R10 v3
// R11 v4
// R12 tmp
// R13 prime1v
// R14 prime2v
// R15 prime4v
// Registers:
#define h AX
#define d AX
#define p SI // pointer to advance through b
#define n DX
#define end BX // loop end
#define v1 R8
#define v2 R9
#define v3 R10
#define v4 R11
#define x R12
#define prime1 R13
#define prime2 R14
#define prime4 DI
// round reads from and advances the buffer pointer in CX.
// It assumes that R13 has prime1v and R14 has prime2v.
#define round(r) \
MOVQ (CX), R12 \
ADDQ $8, CX \
IMULQ R14, R12 \
ADDQ R12, r \
ROLQ $31, r \
IMULQ R13, r
#define round(acc, x) \
IMULQ prime2, x \
ADDQ x, acc \
ROLQ $31, acc \
IMULQ prime1, acc
// mergeRound applies a merge round on the two registers acc and val.
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
#define mergeRound(acc, val) \
IMULQ R14, val \
ROLQ $31, val \
IMULQ R13, val \
XORQ val, acc \
IMULQ R13, acc \
ADDQ R15, acc
// round0 performs the operation x = round(0, x).
#define round0(x) \
IMULQ prime2, x \
ROLQ $31, x \
IMULQ prime1, x
// mergeRound applies a merge round on the two registers acc and x.
// It assumes that prime1, prime2, and prime4 have been loaded.
#define mergeRound(acc, x) \
round0(x) \
XORQ x, acc \
IMULQ prime1, acc \
ADDQ prime4, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that there is at least one block
// to process.
#define blockLoop() \
loop: \
MOVQ +0(p), x \
round(v1, x) \
MOVQ +8(p), x \
round(v2, x) \
MOVQ +16(p), x \
round(v3, x) \
MOVQ +24(p), x \
round(v4, x) \
ADDQ $32, p \
CMPQ p, end \
JLE loop
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT, $0-32
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
// Load fixed primes.
MOVQ ·prime1v(SB), R13
MOVQ ·prime2v(SB), R14
MOVQ ·prime4v(SB), R15
MOVQ ·primes+0(SB), prime1
MOVQ ·primes+8(SB), prime2
MOVQ ·primes+24(SB), prime4
// Load slice.
MOVQ b_base+0(FP), CX
MOVQ b_len+8(FP), DX
LEAQ (CX)(DX*1), BX
MOVQ b_base+0(FP), p
MOVQ b_len+8(FP), n
LEAQ (p)(n*1), end
// The first loop limit will be len(b)-32.
SUBQ $32, BX
SUBQ $32, end
// Check whether we have at least one block.
CMPQ DX, $32
CMPQ n, $32
JLT noBlocks
// Set up initial state (v1, v2, v3, v4).
MOVQ R13, R8
ADDQ R14, R8
MOVQ R14, R9
XORQ R10, R10
XORQ R11, R11
SUBQ R13, R11
MOVQ prime1, v1
ADDQ prime2, v1
MOVQ prime2, v2
XORQ v3, v3
XORQ v4, v4
SUBQ prime1, v4
// Loop until CX > BX.
blockLoop:
round(R8)
round(R9)
round(R10)
round(R11)
blockLoop()
CMPQ CX, BX
JLE blockLoop
MOVQ v1, h
ROLQ $1, h
MOVQ v2, x
ROLQ $7, x
ADDQ x, h
MOVQ v3, x
ROLQ $12, x
ADDQ x, h
MOVQ v4, x
ROLQ $18, x
ADDQ x, h
MOVQ R8, AX
ROLQ $1, AX
MOVQ R9, R12
ROLQ $7, R12
ADDQ R12, AX
MOVQ R10, R12
ROLQ $12, R12
ADDQ R12, AX
MOVQ R11, R12
ROLQ $18, R12
ADDQ R12, AX
mergeRound(AX, R8)
mergeRound(AX, R9)
mergeRound(AX, R10)
mergeRound(AX, R11)
mergeRound(h, v1)
mergeRound(h, v2)
mergeRound(h, v3)
mergeRound(h, v4)
JMP afterBlocks
noBlocks:
MOVQ ·prime5v(SB), AX
MOVQ ·primes+32(SB), h
afterBlocks:
ADDQ DX, AX
ADDQ n, h
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
ADDQ $24, BX
ADDQ $24, end
CMPQ p, end
JG try4
CMPQ CX, BX
JG fourByte
loop8:
MOVQ (p), x
ADDQ $8, p
round0(x)
XORQ x, h
ROLQ $27, h
IMULQ prime1, h
ADDQ prime4, h
wordLoop:
// Calculate k1.
MOVQ (CX), R8
ADDQ $8, CX
IMULQ R14, R8
ROLQ $31, R8
IMULQ R13, R8
CMPQ p, end
JLE loop8
XORQ R8, AX
ROLQ $27, AX
IMULQ R13, AX
ADDQ R15, AX
try4:
ADDQ $4, end
CMPQ p, end
JG try1
CMPQ CX, BX
JLE wordLoop
MOVL (p), x
ADDQ $4, p
IMULQ prime1, x
XORQ x, h
fourByte:
ADDQ $4, BX
CMPQ CX, BX
JG singles
ROLQ $23, h
IMULQ prime2, h
ADDQ ·primes+16(SB), h
MOVL (CX), R8
ADDQ $4, CX
IMULQ R13, R8
XORQ R8, AX
ROLQ $23, AX
IMULQ R14, AX
ADDQ ·prime3v(SB), AX
singles:
ADDQ $4, BX
CMPQ CX, BX
try1:
ADDQ $4, end
CMPQ p, end
JGE finalize
singlesLoop:
MOVBQZX (CX), R12
ADDQ $1, CX
IMULQ ·prime5v(SB), R12
XORQ R12, AX
loop1:
MOVBQZX (p), x
ADDQ $1, p
IMULQ ·primes+32(SB), x
XORQ x, h
ROLQ $11, h
IMULQ prime1, h
ROLQ $11, AX
IMULQ R13, AX
CMPQ CX, BX
JL singlesLoop
CMPQ p, end
JL loop1
finalize:
MOVQ AX, R12
SHRQ $33, R12
XORQ R12, AX
IMULQ R14, AX
MOVQ AX, R12
SHRQ $29, R12
XORQ R12, AX
IMULQ ·prime3v(SB), AX
MOVQ AX, R12
SHRQ $32, R12
XORQ R12, AX
MOVQ h, x
SHRQ $33, x
XORQ x, h
IMULQ prime2, h
MOVQ h, x
SHRQ $29, x
XORQ x, h
IMULQ ·primes+16(SB), h
MOVQ h, x
SHRQ $32, x
XORQ x, h
MOVQ AX, ret+24(FP)
MOVQ h, ret+24(FP)
RET
// writeBlocks uses the same registers as above except that it uses AX to store
// the d pointer.
// func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
// Load fixed primes needed for round.
MOVQ ·prime1v(SB), R13
MOVQ ·prime2v(SB), R14
MOVQ ·primes+0(SB), prime1
MOVQ ·primes+8(SB), prime2
// Load slice.
MOVQ b_base+8(FP), CX
MOVQ b_len+16(FP), DX
LEAQ (CX)(DX*1), BX
SUBQ $32, BX
MOVQ b_base+8(FP), p
MOVQ b_len+16(FP), n
LEAQ (p)(n*1), end
SUBQ $32, end
// Load vN from d.
MOVQ d+0(FP), AX
MOVQ 0(AX), R8 // v1
MOVQ 8(AX), R9 // v2
MOVQ 16(AX), R10 // v3
MOVQ 24(AX), R11 // v4
MOVQ s+0(FP), d
MOVQ 0(d), v1
MOVQ 8(d), v2
MOVQ 16(d), v3
MOVQ 24(d), v4
// We don't need to check the loop condition here; this function is
// always called with at least one block of data to process.
blockLoop:
round(R8)
round(R9)
round(R10)
round(R11)
CMPQ CX, BX
JLE blockLoop
blockLoop()
// Copy vN back to d.
MOVQ R8, 0(AX)
MOVQ R9, 8(AX)
MOVQ R10, 16(AX)
MOVQ R11, 24(AX)
MOVQ v1, 0(d)
MOVQ v2, 8(d)
MOVQ v3, 16(d)
MOVQ v4, 24(d)
// The number of bytes written is CX minus the old base pointer.
SUBQ b_base+8(FP), CX
MOVQ CX, ret+32(FP)
// The number of bytes written is p minus the old base pointer.
SUBQ b_base+8(FP), p
MOVQ p, ret+32(FP)
RET

View File

@ -0,0 +1,183 @@
//go:build !appengine && gc && !purego
// +build !appengine
// +build gc
// +build !purego
#include "textflag.h"
// Registers:
#define digest R1
#define h R2 // return value
#define p R3 // input pointer
#define n R4 // input length
#define nblocks R5 // n / 32
#define prime1 R7
#define prime2 R8
#define prime3 R9
#define prime4 R10
#define prime5 R11
#define v1 R12
#define v2 R13
#define v3 R14
#define v4 R15
#define x1 R20
#define x2 R21
#define x3 R22
#define x4 R23
#define round(acc, x) \
MADD prime2, acc, x, acc \
ROR $64-31, acc \
MUL prime1, acc
// round0 performs the operation x = round(0, x).
#define round0(x) \
MUL prime2, x \
ROR $64-31, x \
MUL prime1, x
#define mergeRound(acc, x) \
round0(x) \
EOR x, acc \
MADD acc, prime4, prime1, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that n >= 32.
#define blockLoop() \
LSR $5, n, nblocks \
PCALIGN $16 \
loop: \
LDP.P 16(p), (x1, x2) \
LDP.P 16(p), (x3, x4) \
round(v1, x1) \
round(v2, x2) \
round(v3, x3) \
round(v4, x4) \
SUB $1, nblocks \
CBNZ nblocks, loop
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
LDP b_base+0(FP), (p, n)
LDP ·primes+0(SB), (prime1, prime2)
LDP ·primes+16(SB), (prime3, prime4)
MOVD ·primes+32(SB), prime5
CMP $32, n
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
BLT afterLoop
ADD prime1, prime2, v1
MOVD prime2, v2
MOVD $0, v3
NEG prime1, v4
blockLoop()
ROR $64-1, v1, x1
ROR $64-7, v2, x2
ADD x1, x2
ROR $64-12, v3, x3
ROR $64-18, v4, x4
ADD x3, x4
ADD x2, x4, h
mergeRound(h, v1)
mergeRound(h, v2)
mergeRound(h, v3)
mergeRound(h, v4)
afterLoop:
ADD n, h
TBZ $4, n, try8
LDP.P 16(p), (x1, x2)
round0(x1)
// NOTE: here and below, sequencing the EOR after the ROR (using a
// rotated register) is worth a small but measurable speedup for small
// inputs.
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
round0(x2)
ROR $64-27, h
EOR x2 @> 64-27, h, h
MADD h, prime4, prime1, h
try8:
TBZ $3, n, try4
MOVD.P 8(p), x1
round0(x1)
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
try4:
TBZ $2, n, try2
MOVWU.P 4(p), x2
MUL prime1, x2
ROR $64-23, h
EOR x2 @> 64-23, h, h
MADD h, prime3, prime2, h
try2:
TBZ $1, n, try1
MOVHU.P 2(p), x3
AND $255, x3, x1
LSR $8, x3, x2
MUL prime5, x1
ROR $64-11, h
EOR x1 @> 64-11, h, h
MUL prime1, h
MUL prime5, x2
ROR $64-11, h
EOR x2 @> 64-11, h, h
MUL prime1, h
try1:
TBZ $0, n, finalize
MOVBU (p), x4
MUL prime5, x4
ROR $64-11, h
EOR x4 @> 64-11, h, h
MUL prime1, h
finalize:
EOR h >> 33, h
MUL prime2, h
EOR h >> 29, h
MUL prime3, h
EOR h >> 32, h
MOVD h, ret+24(FP)
RET
// func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
LDP ·primes+0(SB), (prime1, prime2)
// Load state. Assume v[1-4] are stored contiguously.
MOVD d+0(FP), digest
LDP 0(digest), (v1, v2)
LDP 16(digest), (v3, v4)
LDP b_base+8(FP), (p, n)
blockLoop()
// Store updated state.
STP (v1, v2), 0(digest)
STP (v3, v4), 16(digest)
BIC $31, n
MOVD n, ret+32(FP)
RET

View File

@ -1,3 +1,5 @@
//go:build (amd64 || arm64) && !appengine && gc && !purego
// +build amd64 arm64
// +build !appengine
// +build gc
// +build !purego

View File

@ -1,4 +1,5 @@
// +build !amd64 appengine !gc purego
//go:build (!amd64 && !arm64) || appengine || !gc || purego
// +build !amd64,!arm64 appengine !gc purego
package xxhash
@ -14,10 +15,10 @@ func Sum64(b []byte) uint64 {
var h uint64
if n >= 32 {
v1 := prime1v + prime2
v1 := primes[0] + prime2
v2 := prime2
v3 := uint64(0)
v4 := -prime1v
v4 := -primes[0]
for len(b) >= 32 {
v1 = round(v1, u64(b[0:8:len(b)]))
v2 = round(v2, u64(b[8:16:len(b)]))
@ -36,19 +37,18 @@ func Sum64(b []byte) uint64 {
h += uint64(n)
i, end := 0, len(b)
for ; i+8 <= end; i += 8 {
k1 := round(0, u64(b[i:i+8:len(b)]))
for ; len(b) >= 8; b = b[8:] {
k1 := round(0, u64(b[:8]))
h ^= k1
h = rol27(h)*prime1 + prime4
}
if i+4 <= end {
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
if len(b) >= 4 {
h ^= uint64(u32(b[:4])) * prime1
h = rol23(h)*prime2 + prime3
i += 4
b = b[4:]
}
for ; i < end; i++ {
h ^= uint64(b[i]) * prime5
for ; len(b) > 0; b = b[1:] {
h ^= uint64(b[0]) * prime5
h = rol11(h) * prime1
}

View File

@ -1,3 +1,4 @@
//go:build appengine
// +build appengine
// This file contains the safe implementations of otherwise unsafe-using code.

View File

@ -1,3 +1,4 @@
//go:build !appengine
// +build !appengine
// This file encapsulates usage of unsafe.
@ -6,41 +7,52 @@
package xxhash
import (
"reflect"
"unsafe"
)
// Notes:
//
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
// for some discussion about these unsafe conversions.
//
// In the future it's possible that compiler optimizations will make these
// unsafe operations unnecessary: https://golang.org/issue/2205.
// XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205.
// If that happens, even if we keep these functions they can be replaced with
// the trivial safe code.
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
//
// Both of these wrapper functions still incur function call overhead since they
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
// for strings to squeeze out a bit more speed. Mid-stack inlining should
// eventually fix this.
// var b []byte
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
// bh.Len = len(s)
// bh.Cap = len(s)
//
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
// weight to this sequence of expressions that any function that uses it will
// not be inlined. Instead, the functions below use a different unsafe
// conversion designed to minimize the inliner weight and allow both to be
// inlined. There is also a test (TestInlining) which verifies that these are
// inlined.
//
// See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 {
var b []byte
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
return Sum64(b)
}
// WriteString adds more data to d. It always returns len(s), nil.
// It may be faster than Write([]byte(s)) by avoiding a copy.
func (d *Digest) WriteString(s string) (n int, err error) {
var b []byte
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
return d.Write(b)
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
// d.Write always returns len(s), nil.
// Ignoring the return output and returning these fixed values buys a
// savings of 6 in the inliner's cost model.
return len(s), nil
}
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
}

View File

@ -1,179 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"fmt"
"strings"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
anypb "github.com/golang/protobuf/ptypes/any"
)
const urlPrefix = "type.googleapis.com/"
// AnyMessageName returns the message name contained in an anypb.Any message.
// Most type assertions should use the Is function instead.
//
// Deprecated: Call the any.MessageName method instead.
func AnyMessageName(any *anypb.Any) (string, error) {
name, err := anyMessageName(any)
return string(name), err
}
func anyMessageName(any *anypb.Any) (protoreflect.FullName, error) {
if any == nil {
return "", fmt.Errorf("message is nil")
}
name := protoreflect.FullName(any.TypeUrl)
if i := strings.LastIndex(any.TypeUrl, "/"); i >= 0 {
name = name[i+len("/"):]
}
if !name.IsValid() {
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
}
return name, nil
}
// MarshalAny marshals the given message m into an anypb.Any message.
//
// Deprecated: Call the anypb.New function instead.
func MarshalAny(m proto.Message) (*anypb.Any, error) {
switch dm := m.(type) {
case DynamicAny:
m = dm.Message
case *DynamicAny:
if dm == nil {
return nil, proto.ErrNil
}
m = dm.Message
}
b, err := proto.Marshal(m)
if err != nil {
return nil, err
}
return &anypb.Any{TypeUrl: urlPrefix + proto.MessageName(m), Value: b}, nil
}
// Empty returns a new message of the type specified in an anypb.Any message.
// It returns protoregistry.NotFound if the corresponding message type could not
// be resolved in the global registry.
//
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead
// to resolve the message name and create a new instance of it.
func Empty(any *anypb.Any) (proto.Message, error) {
name, err := anyMessageName(any)
if err != nil {
return nil, err
}
mt, err := protoregistry.GlobalTypes.FindMessageByName(name)
if err != nil {
return nil, err
}
return proto.MessageV1(mt.New().Interface()), nil
}
// UnmarshalAny unmarshals the encoded value contained in the anypb.Any message
// into the provided message m. It returns an error if the target message
// does not match the type in the Any message or if an unmarshal error occurs.
//
// The target message m may be a *DynamicAny message. If the underlying message
// type could not be resolved, then this returns protoregistry.NotFound.
//
// Deprecated: Call the any.UnmarshalTo method instead.
func UnmarshalAny(any *anypb.Any, m proto.Message) error {
if dm, ok := m.(*DynamicAny); ok {
if dm.Message == nil {
var err error
dm.Message, err = Empty(any)
if err != nil {
return err
}
}
m = dm.Message
}
anyName, err := AnyMessageName(any)
if err != nil {
return err
}
msgName := proto.MessageName(m)
if anyName != msgName {
return fmt.Errorf("mismatched message type: got %q want %q", anyName, msgName)
}
return proto.Unmarshal(any.Value, m)
}
// Is reports whether the Any message contains a message of the specified type.
//
// Deprecated: Call the any.MessageIs method instead.
func Is(any *anypb.Any, m proto.Message) bool {
if any == nil || m == nil {
return false
}
name := proto.MessageName(m)
if !strings.HasSuffix(any.TypeUrl, name) {
return false
}
return len(any.TypeUrl) == len(name) || any.TypeUrl[len(any.TypeUrl)-len(name)-1] == '/'
}
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
// allocate a proto.Message for the type specified in an anypb.Any message.
// The allocated message is stored in the embedded proto.Message.
//
// Example:
// var x ptypes.DynamicAny
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
// fmt.Printf("unmarshaled message: %v", x.Message)
//
// Deprecated: Use the any.UnmarshalNew method instead to unmarshal
// the any message contents into a new instance of the underlying message.
type DynamicAny struct{ proto.Message }
func (m DynamicAny) String() string {
if m.Message == nil {
return "<nil>"
}
return m.Message.String()
}
func (m DynamicAny) Reset() {
if m.Message == nil {
return
}
m.Message.Reset()
}
func (m DynamicAny) ProtoMessage() {
return
}
func (m DynamicAny) ProtoReflect() protoreflect.Message {
if m.Message == nil {
return nil
}
return dynamicAny{proto.MessageReflect(m.Message)}
}
type dynamicAny struct{ protoreflect.Message }
func (m dynamicAny) Type() protoreflect.MessageType {
return dynamicAnyType{m.Message.Type()}
}
func (m dynamicAny) New() protoreflect.Message {
return dynamicAnyType{m.Message.Type()}.New()
}
func (m dynamicAny) Interface() protoreflect.ProtoMessage {
return DynamicAny{proto.MessageV1(m.Message.Interface())}
}
type dynamicAnyType struct{ protoreflect.MessageType }
func (t dynamicAnyType) New() protoreflect.Message {
return dynamicAny{t.MessageType.New()}
}
func (t dynamicAnyType) Zero() protoreflect.Message {
return dynamicAny{t.MessageType.Zero()}
}

View File

@ -1,62 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: github.com/golang/protobuf/ptypes/any/any.proto
package any
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
)
// Symbols defined in public import of google/protobuf/any.proto.
type Any = anypb.Any
var File_github_com_golang_protobuf_ptypes_any_any_proto protoreflect.FileDescriptor
var file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = []byte{
0x0a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x2b, 0x5a, 0x29,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e,
0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65,
0x73, 0x2f, 0x61, 0x6e, 0x79, 0x3b, 0x61, 0x6e, 0x79, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = []interface{}{}
var file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_github_com_golang_protobuf_ptypes_any_any_proto_init() }
func file_github_com_golang_protobuf_ptypes_any_any_proto_init() {
if File_github_com_golang_protobuf_ptypes_any_any_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes,
DependencyIndexes: file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs,
}.Build()
File_github_com_golang_protobuf_ptypes_any_any_proto = out.File
file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = nil
file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = nil
file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = nil
}

View File

@ -1,10 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ptypes provides functionality for interacting with well-known types.
//
// Deprecated: Well-known types have specialized functionality directly
// injected into the generated packages for each message type.
// See the deprecation notice for each function for the suggested alternative.
package ptypes

View File

@ -1,76 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
durationpb "github.com/golang/protobuf/ptypes/duration"
)
// Range of google.protobuf.Duration as specified in duration.proto.
// This is about 10,000 years in seconds.
const (
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
minSeconds = -maxSeconds
)
// Duration converts a durationpb.Duration to a time.Duration.
// Duration returns an error if dur is invalid or overflows a time.Duration.
//
// Deprecated: Call the dur.AsDuration and dur.CheckValid methods instead.
func Duration(dur *durationpb.Duration) (time.Duration, error) {
if err := validateDuration(dur); err != nil {
return 0, err
}
d := time.Duration(dur.Seconds) * time.Second
if int64(d/time.Second) != dur.Seconds {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
if dur.Nanos != 0 {
d += time.Duration(dur.Nanos) * time.Nanosecond
if (d < 0) != (dur.Nanos < 0) {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
}
return d, nil
}
// DurationProto converts a time.Duration to a durationpb.Duration.
//
// Deprecated: Call the durationpb.New function instead.
func DurationProto(d time.Duration) *durationpb.Duration {
nanos := d.Nanoseconds()
secs := nanos / 1e9
nanos -= secs * 1e9
return &durationpb.Duration{
Seconds: int64(secs),
Nanos: int32(nanos),
}
}
// validateDuration determines whether the durationpb.Duration is valid
// according to the definition in google/protobuf/duration.proto.
// A valid durpb.Duration may still be too large to fit into a time.Duration
// Note that the range of durationpb.Duration is about 10,000 years,
// while the range of time.Duration is about 290 years.
func validateDuration(dur *durationpb.Duration) error {
if dur == nil {
return errors.New("duration: nil Duration")
}
if dur.Seconds < minSeconds || dur.Seconds > maxSeconds {
return fmt.Errorf("duration: %v: seconds out of range", dur)
}
if dur.Nanos <= -1e9 || dur.Nanos >= 1e9 {
return fmt.Errorf("duration: %v: nanos out of range", dur)
}
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
if (dur.Seconds < 0 && dur.Nanos > 0) || (dur.Seconds > 0 && dur.Nanos < 0) {
return fmt.Errorf("duration: %v: seconds and nanos have different signs", dur)
}
return nil
}

View File

@ -1,63 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: github.com/golang/protobuf/ptypes/duration/duration.proto
package duration
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
reflect "reflect"
)
// Symbols defined in public import of google/protobuf/duration.proto.
type Duration = durationpb.Duration
var File_github_com_golang_protobuf_ptypes_duration_duration_proto protoreflect.FileDescriptor
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = []byte{
0x0a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x35, 0x5a, 0x33, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73,
0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = []interface{}{}
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() }
func file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() {
if File_github_com_golang_protobuf_ptypes_duration_duration_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes,
DependencyIndexes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs,
}.Build()
File_github_com_golang_protobuf_ptypes_duration_duration_proto = out.File
file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = nil
file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = nil
file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = nil
}

View File

@ -1,112 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
timestamppb "github.com/golang/protobuf/ptypes/timestamp"
)
// Range of google.protobuf.Duration as specified in timestamp.proto.
const (
// Seconds field of the earliest valid Timestamp.
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
minValidSeconds = -62135596800
// Seconds field just after the latest valid Timestamp.
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
maxValidSeconds = 253402300800
)
// Timestamp converts a timestamppb.Timestamp to a time.Time.
// It returns an error if the argument is invalid.
//
// Unlike most Go functions, if Timestamp returns an error, the first return
// value is not the zero time.Time. Instead, it is the value obtained from the
// time.Unix function when passed the contents of the Timestamp, in the UTC
// locale. This may or may not be a meaningful time; many invalid Timestamps
// do map to valid time.Times.
//
// A nil Timestamp returns an error. The first return value in that case is
// undefined.
//
// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead.
func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
// Don't return the zero value on error, because corresponds to a valid
// timestamp. Instead return whatever time.Unix gives us.
var t time.Time
if ts == nil {
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
} else {
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
}
return t, validateTimestamp(ts)
}
// TimestampNow returns a google.protobuf.Timestamp for the current time.
//
// Deprecated: Call the timestamppb.Now function instead.
func TimestampNow() *timestamppb.Timestamp {
ts, err := TimestampProto(time.Now())
if err != nil {
panic("ptypes: time.Now() out of Timestamp range")
}
return ts
}
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
// It returns an error if the resulting Timestamp is invalid.
//
// Deprecated: Call the timestamppb.New function instead.
func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
ts := &timestamppb.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
if err := validateTimestamp(ts); err != nil {
return nil, err
}
return ts, nil
}
// TimestampString returns the RFC 3339 string for valid Timestamps.
// For invalid Timestamps, it returns an error message in parentheses.
//
// Deprecated: Call the ts.AsTime method instead,
// followed by a call to the Format method on the time.Time value.
func TimestampString(ts *timestamppb.Timestamp) string {
t, err := Timestamp(ts)
if err != nil {
return fmt.Sprintf("(%v)", err)
}
return t.Format(time.RFC3339Nano)
}
// validateTimestamp determines whether a Timestamp is valid.
// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
// and has a Nanos field in the range [0, 1e9).
//
// If the Timestamp is valid, validateTimestamp returns nil.
// Otherwise, it returns an error that describes the problem.
//
// Every valid Timestamp can be represented by a time.Time,
// but the converse is not true.
func validateTimestamp(ts *timestamppb.Timestamp) error {
if ts == nil {
return errors.New("timestamp: nil Timestamp")
}
if ts.Seconds < minValidSeconds {
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
}
if ts.Seconds >= maxValidSeconds {
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
}
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More