mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-18 20:16:36 +00:00
Compare commits
350 Commits
0.4.1
...
quick_shut
Author | SHA1 | Date | |
---|---|---|---|
5b36a3a923 | |||
7d073bd64b | |||
ebec399817 | |||
553054ddf5 | |||
86d2797873 | |||
0d3dc50f61 | |||
b1ea842fb1 | |||
e1d90bba60 | |||
6c8214b27e | |||
990f1a4df6 | |||
c41c2cd9fc | |||
9efd019e86 | |||
d09ab85bda | |||
c5c0fa05a2 | |||
aaf1811052 | |||
4d6b6dfdc5 | |||
b844a72067 | |||
8227285faa | |||
87773fd167 | |||
ecf82ec37b | |||
9e8c680f3f | |||
85c1082fac | |||
282b05802c | |||
7c118225b2 | |||
95792f8d58 | |||
60b724f014 | |||
e0db59d8a1 | |||
13304fa0b2 | |||
a65b989b15 | |||
6b6ff71c29 | |||
bb5b212663 | |||
9564e64980 | |||
6dbc33d045 | |||
5cedf28929 | |||
b7be42e5ec | |||
2b0cbeb25d | |||
d29f94a8d4 | |||
c5b463bee9 | |||
c0c4f2d068 | |||
886f5ba295 | |||
309310140c | |||
a88997e42c | |||
02e9b9961b | |||
fee46de596 | |||
6d297a96d6 | |||
2178d90a10 | |||
37c63a84a5 | |||
b43d2562a9 | |||
e668beef13 | |||
a574a0c06f | |||
4061b52b2a | |||
bc88d6170c | |||
fe057fbcf8 | |||
b44f57ce4d | |||
4ecc215a70 | |||
a995413971 | |||
912ac265f4 | |||
449bcf2691 | |||
1822114705 | |||
52f64dfaa2 | |||
7bd84766f3 | |||
e09f37e5cb | |||
4b132315c7 | |||
ab4708246d | |||
b807ff0725 | |||
f74f5e6a4f | |||
95c41ea758 | |||
8ac45f5379 | |||
3579061423 | |||
761d1847bf | |||
8003748b73 | |||
a2ea804d2c | |||
551e6645b7 | |||
77867f17e3 | |||
5aed707354 | |||
8fbdd1a461 | |||
8dd48b8957 | |||
6763ed6d66 | |||
acb5d0bd1c | |||
2c9eb3904e | |||
b42066d1a1 | |||
17188b8de9 | |||
0c0088e8b0 | |||
c5f167df21 | |||
d5fcc7b2ab | |||
cbfefb6fa5 | |||
ea62c1b12d | |||
8f40618a5c | |||
3fe0d8d8d3 | |||
5aa4c69e03 | |||
12b5e8ca7f | |||
195e81f595 | |||
06fbca83bf | |||
e71d2c27c5 | |||
13f4a487ce | |||
13412841aa | |||
e76d0d34ba | |||
dec02f3240 | |||
73c7349e36 | |||
b8ada0d46b | |||
5ac51663da | |||
1e9d8fffa0 | |||
57322c4947 | |||
6b840f0226 | |||
12ada59bf1 | |||
2ae8b31ac0 | |||
4c9c66812a | |||
9da2d92613 | |||
5e29516f86 | |||
9f1b5e2f7b | |||
efcae9888c | |||
2885bb0c51 | |||
a4e092b217 | |||
dca036ee51 | |||
583f5ad1b0 | |||
659f98cc0d | |||
c7d9353991 | |||
29bb5ad9cc | |||
6262ff2f4a | |||
1d86c62792 | |||
0bf221b286 | |||
e8c2eeb052 | |||
6c0f91e810 | |||
27ba86fb52 | |||
e99c49d4e5 | |||
7f39890963 | |||
bc2fe46023 | |||
6a865769ec | |||
42b831cc57 | |||
13b71cd478 | |||
afaacd88a2 | |||
abb62aedc2 | |||
8444f8ac38 | |||
795ea368ff | |||
621fe6b01a | |||
507ee0a7f7 | |||
8f6d2fa6ec | |||
0e6983b351 | |||
31fc597205 | |||
d7fea9173e | |||
3d0adec851 | |||
b475aa8884 | |||
123ce3b849 | |||
17d09bb185 | |||
789e9a29fe | |||
b575c02338 | |||
cd4add32e1 | |||
e199827883 | |||
87f105d581 | |||
c6b2418461 | |||
237a026b79 | |||
4e8a1d810a | |||
d4454758d5 | |||
7afaa4a30b | |||
1aa7a2a320 | |||
a4a33b8596 | |||
954a61cee1 | |||
294ef0f17f | |||
32c00f0e9e | |||
2533c065bf | |||
9c04b8dfd7 | |||
c4936133f6 | |||
87f993847c | |||
9cdcac2c5c | |||
a8a3d73bc0 | |||
f33964310a | |||
03ad56e573 | |||
baea3006cb | |||
cb786d7c84 | |||
fc02b4c6fa | |||
ecee5d6eed | |||
8159fb88b7 | |||
a7f74f5163 | |||
baa9a1821c | |||
1a8e879f42 | |||
0d9c846117 | |||
8db2e2a54f | |||
0c790bbdae | |||
797ff0875c | |||
bc859e595f | |||
4e9b6070c1 | |||
1862c0e1f5 | |||
ae909c8df4 | |||
6f76a05bdf | |||
8f022cfb21 | |||
ff9225d45e | |||
1da2763a96 | |||
666d6c4871 | |||
2248a8a071 | |||
908bbfda9f | |||
b40a7cbe58 | |||
a66f65c2b9 | |||
ac1cc16f0c | |||
716ef6f51c | |||
92523c496b | |||
5561c5cc67 | |||
6c48911412 | |||
3ce724512b | |||
8d91895c79 | |||
7ca531a8b5 | |||
94210cc7f1 | |||
9e5eb84236 | |||
b20e5614c7 | |||
40829bbf88 | |||
87f49b0289 | |||
b817479828 | |||
faae82aa1c | |||
cddc10acbe | |||
1c8e8bb615 | |||
6e537d1fde | |||
c314af4f98 | |||
4189cfe52c | |||
9e2f571cf7 | |||
93825e8354 | |||
6752a61a95 | |||
16a8d2ac6c | |||
68ac4dfecb | |||
c2480ab30a | |||
d978c19e23 | |||
038b92c5b4 | |||
f1a1f374d9 | |||
24692466d8 | |||
bdfff4e8c5 | |||
e3589a4ed1 | |||
b865e55c85 | |||
89a728db16 | |||
2237dfd44d | |||
4423a5389a | |||
a6a4502c89 | |||
8b86e00128 | |||
3039773fbd | |||
5b92e7793d | |||
88f1aa0433 | |||
2b9efd29a0 | |||
db5312158c | |||
26debca616 | |||
50de0f34bb | |||
d64edeb648 | |||
42b9cc6b71 | |||
25c553a87c | |||
8bc39f752e | |||
cbff6fa8f6 | |||
3e29408518 | |||
04f1807d92 | |||
35e017b526 | |||
e54da61283 | |||
84353d0cae | |||
e33a60862d | |||
7b67ff22e6 | |||
19abc9f7b9 | |||
480f566819 | |||
cece6cf1ef | |||
22882e2643 | |||
667d74aaf7 | |||
9dcdbfb7e3 | |||
3a9b81200e | |||
734425de25 | |||
70e7e0d25a | |||
be8574ecd0 | |||
a0110b3019 | |||
87c71b090f | |||
dc8667d36a | |||
137d199cb5 | |||
560c295eb0 | |||
93325b713e | |||
2307fc71c5 | |||
853830c018 | |||
262770a0b7 | |||
0efb6d492f | |||
27cfe465ca | |||
d6c4ebaf96 | |||
e9d1423315 | |||
4bca5c36a5 | |||
10e7a2f07c | |||
4775a9a77c | |||
e07186ed5b | |||
2454c2a807 | |||
8bd2ba5334 | |||
c379b0ebcc | |||
226a20c362 | |||
02c9dcf74d | |||
0b88fc232d | |||
fcd1c9ab54 | |||
592f3d3cc0 | |||
b06364c3f4 | |||
75fd07797c | |||
65c2cb0732 | |||
44df1cef98 | |||
881f5171ee | |||
970015ac85 | |||
283e8ed2c1 | |||
d49011702b | |||
eb369fbb16 | |||
040b426a19 | |||
251cb2d08a | |||
5c48ac1a70 | |||
7c166979c9 | |||
36843ad1d4 | |||
3bc041ba04 | |||
dd3f9732b4 | |||
6c10d18f59 | |||
969fc566e1 | |||
a4710db664 | |||
df2de7ee5c | |||
2d8b2b1f73 | |||
6e5bc27d9a | |||
2eb1df9517 | |||
c133b9c4ab | |||
f09028e451 | |||
bacf8ebad5 | |||
d551721649 | |||
42e9c91ee9 | |||
cda1fe78b1 | |||
a3392634a7 | |||
95e278b29a | |||
d802ba70c1 | |||
cd76ff3ebc | |||
ed5110de30 | |||
2f3ba1335c | |||
24e065965f | |||
fd2ee55f9f | |||
d135999d3b | |||
3068d03279 | |||
fd4f53fe15 | |||
4b93ccba3f | |||
e6b814fd60 | |||
06890cddb9 | |||
40da0a35c3 | |||
0c0d05b2ea | |||
af46f34003 | |||
2f7269fc97 | |||
1f56c39675 | |||
7152b170bb | |||
47955954eb | |||
7f672f006a | |||
20ec76bf74 | |||
04d1688bfb | |||
a8f514f7d6 | |||
502d3fdecc | |||
5d098c9cb7 | |||
0935dc6867 | |||
e77d05ec94 | |||
7ab69b5317 | |||
098baba7cc | |||
9d688b9ea6 | |||
1458a2f2fb | |||
c18a038062 | |||
af0555a85b | |||
2ff8646669 | |||
d785bebf4c |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
vendor/** linguist-generated=true
|
||||
Gopkg.lock linguist-generated=true
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
@alexellis
|
60
.github/ISSUE_TEMPLATE.md
vendored
Normal file
60
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
## Due diligence
|
||||
|
||||
<!-- Due dilligence -->
|
||||
## My actions before raising this issue
|
||||
Before you ask for help or support, make sure that you've [consulted the manual for faasd](https://openfaas.gumroad.com/l/serverless-for-everyone-else). We can't answer questions that are already covered by the manual.
|
||||
|
||||
|
||||
<!-- How is this affecting you? What task are you trying to accomplish? -->
|
||||
## Why do you need this?
|
||||
|
||||
<!-- Attempts to mask or hide this may result in the issue being closed -->
|
||||
## 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
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
|
||||
## Current Behaviour
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
|
||||
## List All Possible Solutions and Workarounds
|
||||
<!--- Suggest a fix/reason for the bug, or ideas how to implement -->
|
||||
<!--- the addition or change -->
|
||||
<!--- Is there a workaround which could avoid making changes? -->
|
||||
|
||||
## Which Solution Do You Recommend?
|
||||
<!--- Pick your preferred solution, if you were to implement and maintain this change -->
|
||||
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Your Environment
|
||||
|
||||
* OS and architecture:
|
||||
|
||||
* Versions:
|
||||
|
||||
```sh
|
||||
go version
|
||||
|
||||
containerd -version
|
||||
|
||||
uname -a
|
||||
|
||||
cat /etc/os-release
|
||||
|
||||
faasd version
|
||||
```
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
- [ ] I have raised an issue to propose this change **this is required**
|
||||
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
|
||||
Commits:
|
||||
|
||||
- [ ] I've read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide
|
||||
- [ ] My commit message has a body and describe how this was tested and why it is required.
|
||||
- [ ] I have signed-off my commits with `git commit -s` for the Developer Certificate of Origin (DCO)
|
||||
|
||||
Code:
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
|
||||
Docs:
|
||||
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
32
.github/workflows/build.yaml
vendored
Normal file
32
.github/workflows/build.yaml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
GO111MODULE: off
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: test
|
||||
run: make test
|
||||
- name: dist
|
||||
run: make dist
|
||||
- name: prepare-test
|
||||
run: make prepare-test
|
||||
- name: test e2e
|
||||
run: make test-e2e
|
||||
|
||||
|
26
.github/workflows/publish.yaml
vendored
Normal file
26
.github/workflows/publish.yaml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Make publish
|
||||
run: make publish
|
||||
- name: Upload release binaries
|
||||
uses: alexellis/upload-assets@0.4.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
asset_paths: '["./bin/faasd*"]'
|
18
.github/workflows/verify-images.yaml
vendored
Normal file
18
.github/workflows/verify-images.yaml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: Verify Docker Compose Images
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.yaml'
|
||||
|
||||
jobs:
|
||||
verifyImages:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: alexellis/setup-arkade@v2
|
||||
- name: Verify chart images
|
||||
id: verify_images
|
||||
run: |
|
||||
VERBOSE=true make verify-compose
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ hosts
|
||||
|
||||
basic-auth-user
|
||||
basic-auth-password
|
||||
/bin
|
||||
/secrets
|
||||
.vscode
|
||||
|
20
.travis.yml
20
.travis.yml
@ -1,20 +0,0 @@
|
||||
sudo: required
|
||||
language: go
|
||||
go:
|
||||
- '1.12'
|
||||
script:
|
||||
- make dist
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: bccOSB+Mbk5ZJHyJfX82Xg/3/7mxiAYHx7P5m5KS1ncDuRpJBFjDV8Nx2PWYg341b5SMlCwsS3IJ9NkoGvRSKK+3YqeNfTeMabVNdKC2oL1i+4pdxGlbl57QXkzT4smqE8AykZEo4Ujk42rEr3e0gSHT2rXkV+Xt0xnoRVXn2tSRUDwsmwANnaBj6KpH2SjJ/lsfTifxrRB65uwcePaSjkqwR6htFraQtpONC9xYDdek6EoVQmoft/ONZJqi7HR+OcA1yhSt93XU6Vaf3678uLlPX9c/DxgIU9UnXRaOd0UUEiTHaMMWDe/bJSrKmgL7qY05WwbGMsXO/RdswwO1+zwrasrwf86SjdGX/P9AwobTW3eTEiBqw2J77UVbvLzDDoyJ5KrkbHRfPX8aIPO4OG9eHy/e7C3XVx4qv9bJBXQ3qD9YJtei9jmm8F/MCdPWuVYC0hEvHtuhP/xMm4esNUjFM5JUfDucvAuLL34NBYHBDP2XNuV4DkgQQPakfnlvYBd7OqyXCU6pzyWSasXpD1Rz8mD/x8aTUl2Ya4bnXQ8qAa5cnxfPqN2ADRlTw1qS7hl6LsXzNQ6r1mbuh/uFi67ybElIjBTfuMEeJOyYHkkLUHIBpooKrPyr0luAbf0By2D2N/eQQnM/RpixHNfZG/mvXx8ZCrs+wxgvG1Rm7rM=
|
||||
file:
|
||||
- ./bin/faasd
|
||||
- ./bin/faasd-armhf
|
||||
- ./bin/faasd-arm64
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
|
||||
env:
|
||||
- GO111MODULE=off
|
484
Gopkg.lock
generated
484
Gopkg.lock
generated
@ -1,484 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d5e752c67b445baa5b6cb6f8aa706775c2aa8e41aca95a0c651520ff2c80361a"
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = [
|
||||
".",
|
||||
"pkg/guid",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "6c72808b55902eae4c5943626030429ff20f3b63"
|
||||
version = "v0.4.14"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b28f788c0be42a6d26f07b282c5ff5f814ab7ad5833810ef0bc5f56fb9bedf11"
|
||||
name = "github.com/Microsoft/hcsshim"
|
||||
packages = [
|
||||
".",
|
||||
"internal/cow",
|
||||
"internal/hcs",
|
||||
"internal/hcserror",
|
||||
"internal/hns",
|
||||
"internal/interop",
|
||||
"internal/log",
|
||||
"internal/logfields",
|
||||
"internal/longpath",
|
||||
"internal/mergemaps",
|
||||
"internal/oc",
|
||||
"internal/safefile",
|
||||
"internal/schema1",
|
||||
"internal/schema2",
|
||||
"internal/timeout",
|
||||
"internal/vmcompute",
|
||||
"internal/wclayer",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "9e921883ac929bbe515b39793ece99ce3a9d7706"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74860eb071d52337d67e9ffd6893b29affebd026505aa917ec23131576a91a77"
|
||||
name = "github.com/alexellis/go-execute"
|
||||
packages = ["pkg/v1"]
|
||||
pruneopts = "UT"
|
||||
revision = "961405ea754427780f2151adff607fa740d377f7"
|
||||
version = "0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6076d857867a70e87dd1994407deb142f27436f1293b13e75cc053192d14eb0c"
|
||||
name = "github.com/alexellis/k3sup"
|
||||
packages = ["pkg/env"]
|
||||
pruneopts = "UT"
|
||||
revision = "f9a4adddc732742a9ee7962609408fb0999f2d7b"
|
||||
version = "0.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:386ca0ac781cc1b630b3ed21725759770174140164b3faf3810e6ed6366a970b"
|
||||
name = "github.com/containerd/containerd"
|
||||
packages = [
|
||||
".",
|
||||
"api/services/containers/v1",
|
||||
"api/services/content/v1",
|
||||
"api/services/diff/v1",
|
||||
"api/services/events/v1",
|
||||
"api/services/images/v1",
|
||||
"api/services/introspection/v1",
|
||||
"api/services/leases/v1",
|
||||
"api/services/namespaces/v1",
|
||||
"api/services/snapshots/v1",
|
||||
"api/services/tasks/v1",
|
||||
"api/services/version/v1",
|
||||
"api/types",
|
||||
"api/types/task",
|
||||
"archive",
|
||||
"archive/compression",
|
||||
"cio",
|
||||
"containers",
|
||||
"content",
|
||||
"content/proxy",
|
||||
"defaults",
|
||||
"diff",
|
||||
"errdefs",
|
||||
"events",
|
||||
"events/exchange",
|
||||
"filters",
|
||||
"identifiers",
|
||||
"images",
|
||||
"images/archive",
|
||||
"labels",
|
||||
"leases",
|
||||
"leases/proxy",
|
||||
"log",
|
||||
"mount",
|
||||
"namespaces",
|
||||
"oci",
|
||||
"pkg/dialer",
|
||||
"platforms",
|
||||
"plugin",
|
||||
"reference",
|
||||
"remotes",
|
||||
"remotes/docker",
|
||||
"remotes/docker/schema1",
|
||||
"rootfs",
|
||||
"runtime/linux/runctypes",
|
||||
"runtime/v2/runc/options",
|
||||
"snapshots",
|
||||
"snapshots/proxy",
|
||||
"sys",
|
||||
"version",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ff48f57fc83a8c44cf4ad5d672424a98ba37ded6"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7e9da25c7a952c63e31ed367a88eede43224b0663b58eb452870787d8ddb6c70"
|
||||
name = "github.com/containerd/continuity"
|
||||
packages = [
|
||||
"fs",
|
||||
"syscallx",
|
||||
"sysx",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f2a389ac0a02ce21c09edd7344677a601970f41c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1b9a7426259b5333d575785e21e1bd0decf18208f5bfb6424d24a50d5ddf83d0"
|
||||
name = "github.com/containerd/fifo"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6d66a41dbbc6819902f1589d0550bc01c18032c0a598a7cd656731e6df73861b"
|
||||
name = "github.com/containerd/ttrpc"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "92c8520ef9f86600c650dd540266a007bf03670f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b4683388adabc709dbb082c13ba35967f072379c85b4acde997c1ca75af5981"
|
||||
name = "github.com/containerd/typeurl"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a93fcdb778cd272c6e9b3028b2f42d813e785d40"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e495f9f1fb2bae55daeb76e099292054fe1f734947274b3cfc403ccda595d55a"
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [
|
||||
"digestset",
|
||||
"reference",
|
||||
"registry/api/errcode",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "0d3efadf0154c2b8a4e7b6621fff9809655cc580"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0938aba6e09d72d48db029d44dcfa304851f52e2d67cda920436794248e92793"
|
||||
name = "github.com/docker/go-events"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9461782956ad83b30282bf90e31fa6a70c255ba9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fa6faf4a2977dc7643de38ae599a95424d82f8ffc184045510737010a82c4ecd"
|
||||
name = "github.com/gogo/googleapis"
|
||||
packages = ["google/rpc"]
|
||||
pruneopts = "UT"
|
||||
revision = "d31c731455cb061f42baff3bda55bad0118b126b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4107f4e81e8fd2e80386b4ed56b05e3a1fe26ecc7275fe80bb9c3a80a7344ff4"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys",
|
||||
"types",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ba06b47c162d49f2af050fb4c75bcbc86a159d5c"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b7cb6054d3dff43b38ad2e92492f220f57ae6087ee797dca298139776749ace8"
|
||||
name = "github.com/golang/groupcache"
|
||||
packages = ["lru"]
|
||||
pruneopts = "UT"
|
||||
revision = "611e8accdfc92c4187d399e95ce826046d4c8d73"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f5ce1529abc1204444ec73779f44f94e2fa8fcdb7aca3c355b0c95947e4005c6"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
|
||||
name = "github.com/konsorten/go-windows-terminal-sequences"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:906eb1ca3c8455e447b99a45237b2b9615b665608fd07ad12cce847dd9a1ec43"
|
||||
name = "github.com/morikuni/aec"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "39771216ff4c63d11f5e604076f9c45e8be1067b"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bc62c2c038cc8ae51b68f6d52570501a763bb71e78736a9f65d60762429864a9"
|
||||
name = "github.com/opencontainers/go-digest"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c9281466c8b2f606084ac71339773efd177436e7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:70711188c19c53147099d106169d6a81941ed5c2658651432de564a7d60fd288"
|
||||
name = "github.com/opencontainers/image-spec"
|
||||
packages = [
|
||||
"identity",
|
||||
"specs-go",
|
||||
"specs-go/v1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:18d6ebfbabffccba7318a4e26028b0d41f23ff359df3dc07a53b37a9f3a4a994"
|
||||
name = "github.com/opencontainers/runc"
|
||||
packages = [
|
||||
"libcontainer/system",
|
||||
"libcontainer/user",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d736ef14f0288d6993a1845745d6756cfc9ddd5a"
|
||||
version = "v1.0.0-rc9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7a58202c5cdf3d2c1eb0621fe369315561cea7f036ad10f0f0479ac36bcc95eb"
|
||||
name = "github.com/opencontainers/runtime-spec"
|
||||
packages = ["specs-go"]
|
||||
pruneopts = "UT"
|
||||
revision = "29686dbc5559d93fb1ef402eeda3e35c38d75af4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:044c51736e2688a3e4f28f72537f8a7b3f9c188fab4477d5334d92dfe2c07ed5"
|
||||
name = "github.com/sethvargo/go-password"
|
||||
packages = ["password"]
|
||||
pruneopts = "UT"
|
||||
revision = "07c3d521e892540e71469bb0312866130714c038"
|
||||
version = "v0.1.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fd61cf4ae1953d55df708acb6b91492d538f49c305b364a014049914495db426"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e096613fb7cf34743d49af87d197663cfccd61876e2219853005a57baedfa562"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5"
|
||||
version = "v0.0.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:524b71991fc7d9246cc7dc2d9e0886ccb97648091c63e30eef619e6862c955dd"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e14e467ed00ab98665623c5060fa17e3d7079be560ffc33cabafd05d35894f05"
|
||||
name = "github.com/syndtr/gocapability"
|
||||
packages = ["capability"]
|
||||
pruneopts = "UT"
|
||||
revision = "d98352740cb2c55f81556b63d4a1ec64c5a319c2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2d9d06cb9d46dacfdbb45f8575b39fc0126d083841a29d4fbf8d97708f43107e"
|
||||
name = "github.com/vishvananda/netlink"
|
||||
packages = [
|
||||
".",
|
||||
"nl",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:975cb0c04431bf92e60b636a15897c4a3faba9f7dc04da505646630ac91d29d3"
|
||||
name = "github.com/vishvananda/netns"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0a2b9b5464df8343199164a0321edf3313202f7e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:aed53a5fa03c1270457e331cf8b7e210e3088a2278fec552c5c5d29c1664e161"
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"trace",
|
||||
"trace/internal",
|
||||
"trace/tracestate",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "aad2c527c5defcf89b5afab7f37274304195a6b2"
|
||||
version = "v0.22.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:676f320d34ccfa88bfa6d04bdf388ed7062af175355c805ef57ccda1a3f13432"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c0dbc17a35534bf2e581d7a942408dc936316da4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d6b0cfc5ae30841c4b116ac589629f56f8add0955a39f11d8c0d06ca67f5b3d5"
|
||||
name = "golang.org/x/sync"
|
||||
packages = [
|
||||
"errgroup",
|
||||
"semaphore",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "42b317875d0fa942474b76e1b46a6060d720ae6e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a76bac71eb452a046b47f82336ba792d8de988688a912f3fd0e8ec8e57fe1bb4"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "af0d71d358abe0ba3594483a5d519f429dbae3e9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/language",
|
||||
"internal/language/compact",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
|
||||
version = "v0.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:583a0c80f5e3a9343d33aea4aead1e1afcc0043db66fdf961ddd1fe8cd3a4faf"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "b31c10ee225f87dbb9f5f878ead9d64f34f5cbbb"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:48eafc052e46b4ebbc7882553873cf6198203e528627cefc94dcaf8553addd19"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"binarylog/grpc_binarylog_v1",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"credentials/internal",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclog",
|
||||
"health/grpc_health_v1",
|
||||
"internal",
|
||||
"internal/backoff",
|
||||
"internal/balancerload",
|
||||
"internal/binarylog",
|
||||
"internal/channelz",
|
||||
"internal/envconfig",
|
||||
"internal/grpcrand",
|
||||
"internal/grpcsync",
|
||||
"internal/syscall",
|
||||
"internal/transport",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"serviceconfig",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "6eaf6f47437a6b4e2153a190160ef39a92c7eceb"
|
||||
version = "v1.23.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/alexellis/go-execute/pkg/v1",
|
||||
"github.com/alexellis/k3sup/pkg/env",
|
||||
"github.com/containerd/containerd",
|
||||
"github.com/containerd/containerd/cio",
|
||||
"github.com/containerd/containerd/containers",
|
||||
"github.com/containerd/containerd/errdefs",
|
||||
"github.com/containerd/containerd/namespaces",
|
||||
"github.com/containerd/containerd/oci",
|
||||
"github.com/morikuni/aec",
|
||||
"github.com/opencontainers/runtime-spec/specs-go",
|
||||
"github.com/sethvargo/go-password/password",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/vishvananda/netlink",
|
||||
"github.com/vishvananda/netns",
|
||||
"golang.org/x/sys/unix",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
27
Gopkg.toml
27
Gopkg.toml
@ -1,27 +0,0 @@
|
||||
[[constraint]]
|
||||
name = "github.com/containerd/containerd"
|
||||
version = "1.3.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/morikuni/aec"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/alexellis/k3sup"
|
||||
version = "0.7.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/alexellis/go-execute"
|
||||
version = "0.3.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sethvargo/go-password"
|
||||
version = "0.1.3"
|
4
LICENSE
4
LICENSE
@ -1,6 +1,8 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Alex Ellis
|
||||
Copyright (c) 2020 Alex Ellis
|
||||
Copyright (c) 2020 OpenFaaS Ltd
|
||||
Copyright (c) 2020 OpenFaas Author(s)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
67
Makefile
67
Makefile
@ -1,15 +1,72 @@
|
||||
Version := $(shell git describe --tags --dirty)
|
||||
GitCommit := $(shell git rev-parse HEAD)
|
||||
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
||||
CONTAINERD_VER := 1.7.0
|
||||
CNI_VERSION := v0.9.1
|
||||
ARCH := amd64
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
.PHONY: all
|
||||
all: local
|
||||
all: test dist hashgen
|
||||
|
||||
.PHONY: publish
|
||||
publish: dist hashgen
|
||||
|
||||
local:
|
||||
CGO_ENABLED=0 GOOS=linux go build -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux go build -mod=vendor -o bin/faasd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
CGO_ENABLED=0 GOOS=linux go test -mod=vendor -ldflags $(LDFLAGS) ./...
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-armhf
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-arm64
|
||||
CGO_ENABLED=0 GOOS=linux go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-armhf
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-arm64
|
||||
|
||||
.PHONY: hashgen
|
||||
hashgen:
|
||||
for f in bin/faasd*; do shasum -a 256 $$f > $$f.sha256; done
|
||||
|
||||
.PHONY: prepare-test
|
||||
prepare-test:
|
||||
curl -sLSf https://github.com/containerd/containerd/releases/download/v$(CONTAINERD_VER)/containerd-$(CONTAINERD_VER)-linux-amd64.tar.gz > /tmp/containerd.tar.gz && sudo tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
|
||||
curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.7.0/containerd.service | sudo tee /etc/systemd/system/containerd.service
|
||||
sudo systemctl daemon-reload && sudo systemctl start containerd
|
||||
sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||
sudo mkdir -p /opt/cni/bin
|
||||
curl -sSL https://github.com/containernetworking/plugins/releases/download/$(CNI_VERSION)/cni-plugins-linux-$(ARCH)-$(CNI_VERSION).tgz | sudo tar -xz -C /opt/cni/bin
|
||||
sudo cp bin/faasd /usr/local/bin/
|
||||
sudo /usr/local/bin/faasd install
|
||||
sudo systemctl status -l containerd --no-pager
|
||||
sudo journalctl -u faasd-provider --no-pager
|
||||
sudo systemctl status -l faasd-provider --no-pager
|
||||
sudo systemctl status -l faasd --no-pager
|
||||
curl -sSLf https://cli.openfaas.com | sudo sh
|
||||
echo "Sleeping for 2m" && sleep 120 && sudo journalctl -u faasd --no-pager
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e:
|
||||
sudo cat /var/lib/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin
|
||||
/usr/local/bin/faas-cli store deploy figlet --env write_timeout=1s --env read_timeout=1s --label testing=true
|
||||
sleep 5
|
||||
/usr/local/bin/faas-cli list -v
|
||||
/usr/local/bin/faas-cli describe figlet | grep testing
|
||||
uname | /usr/local/bin/faas-cli invoke figlet
|
||||
uname | /usr/local/bin/faas-cli invoke figlet --async
|
||||
sleep 10
|
||||
/usr/local/bin/faas-cli list -v
|
||||
/usr/local/bin/faas-cli remove figlet
|
||||
sleep 3
|
||||
/usr/local/bin/faas-cli list
|
||||
sleep 3
|
||||
journalctl -t openfaas-fn:figlet --no-pager
|
||||
|
||||
# Removed due to timing issue in CI on GitHub Actions
|
||||
# /usr/local/bin/faas-cli logs figlet --since 15m --follow=false | grep Forking
|
||||
|
||||
verify-compose:
|
||||
@echo Verifying docker-compose.yaml images in remote registries && \
|
||||
arkade chart verify --verbose=$(VERBOSE) -f ./docker-compose.yaml
|
249
README.md
249
README.md
@ -1,158 +1,171 @@
|
||||
# faasd - serverless with containerd
|
||||
# faasd - a lightweight & portable faas engine
|
||||
|
||||
[](https://travis-ci.com/alexellis/faasd)
|
||||
[](https://github.com/sponsors/openfaas)
|
||||
[](https://github.com/openfaas/faasd/actions)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||

|
||||
|
||||
faasd is a Golang supervisor that bundles OpenFaaS for use with containerd instead of a container orchestrator like Kubernetes or Docker Swarm.
|
||||
faasd is [OpenFaaS](https://github.com/openfaas/) reimagined, but without the cost and complexity of Kubernetes. It runs on a single host with very modest requirements, making it fast and easy to manage. Under the hood it uses [containerd](https://containerd.io/) and [Container Networking Interface (CNI)](https://github.com/containernetworking/cni) along with the same core OpenFaaS components from the main project.
|
||||
|
||||
## About faasd:
|
||||

|
||||
|
||||
* faasd is a single Golang binary
|
||||
* faasd is multi-arch, so works on `x86_64`, armhf and arm64
|
||||
* faasd downloads, starts and supervises the core components to run OpenFaaS
|
||||
## Use-cases and tutorials
|
||||
|
||||
## What does faasd deploy?
|
||||
faasd is just another way to run OpenFaaS, so many things you read in the docs or in blog posts will work the same way.
|
||||
|
||||
* [faas-containerd](https://github.com/alexellis/faas-containerd/)
|
||||
* [Prometheus](https://github.com/prometheus/prometheus)
|
||||
* [the OpenFaaS gateway](https://github.com/openfaas/faas/tree/master/gateway)
|
||||
Videos and overviews:
|
||||
|
||||
You can use the standard [faas-cli](https://github.com/openfaas/faas-cli) with faasd along with pre-packaged functions in the Function Store, or build your own with the template store.
|
||||
* [Exploring of serverless use-cases from commercial and personal users (YouTube)](https://www.youtube.com/watch?v=mzuXVuccaqI)
|
||||
* [Meet faasd. Look Ma’ No Kubernetes! (YouTube)](https://www.youtube.com/watch?v=ZnZJXI377ak)
|
||||
|
||||
### faas-containerd supports:
|
||||
Use-cases and tutorials:
|
||||
|
||||
* `faas list`
|
||||
* `faas describe`
|
||||
* `faas deploy --update=true --replace=false`
|
||||
* `faas invoke`
|
||||
* `faas invoke --async`
|
||||
* [Serverless Node.js that you can run anywhere](https://www.openfaas.com/blog/serverless-nodejs/)
|
||||
* [Simple Serverless with Golang Functions and Microservices](https://www.openfaas.com/blog/golang-serverless/)
|
||||
* [Build a Flask microservice with OpenFaaS](https://www.openfaas.com/blog/openfaas-flask/)
|
||||
* [Get started with Java 11 and Vert.x on Kubernetes with OpenFaaS](https://www.openfaas.com/blog/get-started-with-java-openjdk11/)
|
||||
* [Deploy to faasd via GitHub Actions](https://www.openfaas.com/blog/openfaas-functions-with-github-actions/)
|
||||
* [Scrape and automate websites with Puppeteer](https://www.openfaas.com/blog/puppeteer-scraping/)
|
||||
|
||||
Other operations are pending development in the provider.
|
||||
Additional resources:
|
||||
|
||||
### Pre-reqs
|
||||
* The official handbook - [Serverless For Everyone Else](https://gumroad.com/l/serverless-for-everyone-else)
|
||||
* For reference: [OpenFaaS docs](https://docs.openfaas.com)
|
||||
* For use-cases and tutorials: [OpenFaaS blog](https://openfaas.com/blog/)
|
||||
* For self-paced learning: [OpenFaaS workshop](https://github.com/openfaas/workshop/)
|
||||
|
||||
* Linux - ideally Ubuntu, which is used for testing
|
||||
* Installation steps as per [faas-containerd](https://github.com/alexellis/faas-containerd) for building and for development
|
||||
* [netns](https://github.com/genuinetools/netns/releases) binary in `$PATH`
|
||||
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
||||
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
||||
### About faasd
|
||||
|
||||
## Backlog
|
||||
* faasd is a static Golang binary
|
||||
* uses the same core components and ecosystem of OpenFaaS
|
||||
* uses containerd for its runtime and CNI for networking
|
||||
* is multi-arch, so works on Intel `x86_64` and ARM out the box
|
||||
* can run almost any other stateful container through its `docker-compose.yaml` file
|
||||
|
||||
Pending:
|
||||
Most importantly, it's easy to manage so you can set it up and leave it alone to run your functions.
|
||||
|
||||
* [ ] Use CNI to create network namespaces and adapters
|
||||
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
||||
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
|
||||
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||
[](https://www.youtube.com/watch?v=WX1tZoSXy8E)
|
||||
|
||||
Done:
|
||||
> Demo of faasd running asynchronous functions
|
||||
|
||||
* [x] Inject / manage IPs between core components for service to service communication - i.e. so Prometheus can scrape the OpenFaaS gateway - done via `/etc/hosts` mount
|
||||
* [x] Add queue-worker and NATS
|
||||
* [x] Create faasd.service and faas-containerd.service
|
||||
* [x] Self-install / create systemd service via `faasd install`
|
||||
* [x] Restart containers upon restart of faasd
|
||||
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
||||
* [x] Determine armhf/arm64 containers to run for gateway
|
||||
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
||||
Watch the video: [faasd walk-through with cloud-init and Multipass](https://www.youtube.com/watch?v=WX1tZoSXy8E)
|
||||
|
||||
## Hacking (build from source)
|
||||
### What does faasd deploy?
|
||||
|
||||
First run faas-containerd
|
||||
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider) for containerd - CRUD for functions and services, implements the OpenFaaS REST API
|
||||
* [Prometheus](https://github.com/prometheus/prometheus) - for monitoring of services, metrics, scaling and dashboards
|
||||
* [OpenFaaS Gateway](https://github.com/openfaas/faas/tree/master/gateway) - the UI portal, CLI, and other OpenFaaS tooling can talk to this.
|
||||
* [OpenFaaS queue-worker for NATS](https://github.com/openfaas/nats-queue-worker) - run your invocations in the background without adding any code. See also: [asynchronous invocations](https://docs.openfaas.com/reference/triggers/#async-nats-streaming)
|
||||
* [NATS](https://nats.io) for asynchronous processing and queues
|
||||
|
||||
```sh
|
||||
cd $GOPATH/src/github.com/alexellis/faas-containerd
|
||||
faasd relies on industry-standard tools for running containers:
|
||||
|
||||
# You'll need to install containerd and its pre-reqs first
|
||||
# https://github.com/alexellis/faas-containerd/
|
||||
* [CNI](https://github.com/containernetworking/plugins)
|
||||
* [containerd](https://github.com/containerd/containerd)
|
||||
* [runc](https://github.com/opencontainers/runc)
|
||||
|
||||
sudo ./faas-containerd
|
||||
You can use the standard [faas-cli](https://github.com/openfaas/faas-cli) along with pre-packaged functions from *the Function Store*, or build your own using any OpenFaaS template.
|
||||
|
||||
### When should you use faasd over OpenFaaS on Kubernetes?
|
||||
|
||||
* To deploy microservices and functions that you can update and monitor remotely
|
||||
* When you don't have the bandwidth to learn or manage Kubernetes
|
||||
* To deploy embedded apps in IoT and edge use-cases
|
||||
* To distribute applications to a customer or client
|
||||
* You have a cost sensitive project - run faasd on a 1GB VM for 5-10 USD / mo or on your Raspberry Pi
|
||||
* When you just need a few functions or microservices, without the cost of a cluster
|
||||
|
||||
faasd does not create the same maintenance burden you'll find with maintaining, upgrading, and securing a Kubernetes cluster. You can deploy it and walk away, in the worst case, just deploy a new VM and deploy your functions again.
|
||||
|
||||
You can learn more about supported OpenFaaS features in the [ROADMAP.md](/docs/ROADMAP.md)
|
||||
|
||||
## Learning faasd
|
||||
|
||||
The faasd project is MIT licensed and open source, and you will find some documentation, blog posts and videos for free.
|
||||
|
||||
However, "Serverless For Everyone Else" is the official handbook and was written to contribute funds towards the upkeep and maintenance of the project.
|
||||
|
||||
### The official handbook and docs for faasd
|
||||
|
||||
<a href="https://gumroad.com/l/serverless-for-everyone-else">
|
||||
<img src="https://www.alexellis.io/serverless.png" width="40%"></a>
|
||||
|
||||
You'll learn how to deploy code in any language, lift and shift Dockerfiles, run requests in queues, write background jobs and to integrate with databases. faasd packages the same code as OpenFaaS, so you get built-in metrics for your HTTP endpoints, a user-friendly CLI, pre-packaged functions and templates from the store and a UI.
|
||||
|
||||
Topics include:
|
||||
|
||||
* Should you deploy to a VPS or Raspberry Pi?
|
||||
* Deploying your server with bash, cloud-init or terraform
|
||||
* Using a private container registry
|
||||
* Finding functions in the store
|
||||
* Building your first function with Node.js
|
||||
* Using environment variables for configuration
|
||||
* Using secrets from functions, and enabling authentication tokens
|
||||
* Customising templates
|
||||
* Monitoring your functions with Grafana and Prometheus
|
||||
* Scheduling invocations and background jobs
|
||||
* Tuning timeouts, parallelism, running tasks in the background
|
||||
* Adding TLS to faasd and custom domains for functions
|
||||
* Self-hosting on your Raspberry Pi
|
||||
* Adding a database for storage with InfluxDB and Postgresql
|
||||
* Troubleshooting and logs
|
||||
* CI/CD with GitHub Actions and multi-arch
|
||||
* Taking things further, community and case-studies
|
||||
|
||||
View sample pages, reviews and testimonials on Gumroad:
|
||||
|
||||
["Serverless For Everyone Else"](https://gumroad.com/l/serverless-for-everyone-else)
|
||||
|
||||
### Deploy faasd
|
||||
|
||||
The easiest way to deploy faasd is with cloud-init, we give several examples below, and post IaaS platforms will accept "user-data" pasted into their UI, or via their API.
|
||||
|
||||
For trying it out on MacOS or Windows, we recommend using [multipass](https://multipass.run) to run faasd in a VM.
|
||||
|
||||
If you don't use cloud-init, or have already created your Linux server you can use the installation script as per below:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openfaas/faasd --depth=1
|
||||
cd faasd
|
||||
|
||||
./hack/install.sh
|
||||
```
|
||||
|
||||
Then run faasd, which brings up the gateway and Prometheus as containers
|
||||
> This approach also works for Raspberry Pi
|
||||
|
||||
```sh
|
||||
cd $GOPATH/src/github.com/alexellis/faasd
|
||||
go build
|
||||
It's recommended that you do not install Docker on the same host as faasd, since 1) they may both use different versions of containerd and 2) docker's networking rules can disrupt faasd's networking. When using faasd - make your faasd server a faasd server, and build container image on your laptop or in a CI pipeline.
|
||||
|
||||
# Install with systemd
|
||||
# sudo ./faasd install
|
||||
#### Deployment tutorials
|
||||
|
||||
# Or run interactively
|
||||
# sudo ./faasd up
|
||||
```
|
||||
* [Use multipass on Windows, MacOS or Linux](/docs/MULTIPASS.md)
|
||||
* [Deploy to DigitalOcean with Terraform and TLS](https://www.openfaas.com/blog/faasd-tls-terraform/)
|
||||
* [Deploy to any IaaS with cloud-init](https://blog.alexellis.io/deploy-serverless-faasd-with-cloud-init/)
|
||||
* [Deploy faasd to your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
|
||||
|
||||
### Build and run (binaries)
|
||||
Terraform scripts:
|
||||
|
||||
```sh
|
||||
# For x86_64
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
* [Provision faasd on DigitalOcean with Terraform](docs/bootstrap/README.md)
|
||||
* [Provision faasd with TLS on DigitalOcean with Terraform](docs/bootstrap/digitalocean-terraform/README.md)
|
||||
|
||||
# armhf
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-armhf" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
### Function and template store
|
||||
|
||||
# arm64
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-arm64" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
```
|
||||
For community functions see `faas-cli store --help`
|
||||
|
||||
### At run-time
|
||||
For templates built by the community see: `faas-cli template store list`, you can also use the `dockerfile` template if you just want to migrate an existing service without the benefits of using a template.
|
||||
|
||||
Look in `hosts` in the current working folder to get the IP for the gateway or Prometheus
|
||||
### Community support
|
||||
|
||||
```sh
|
||||
127.0.0.1 localhost
|
||||
172.19.0.1 faas-containerd
|
||||
172.19.0.2 prometheus
|
||||
Commercial users and solo business owners should become OpenFaaS GitHub Sponsors to receive regular email updates on changes, tutorials and new features.
|
||||
|
||||
172.19.0.3 gateway
|
||||
172.19.0.4 nats
|
||||
172.19.0.5 queue-worker
|
||||
```
|
||||
If you are learning faasd, or want to share your use-case, you can join the OpenFaaS Slack community.
|
||||
|
||||
Since faas-containerd uses containerd heavily it is not running as a container, but as a stand-alone process. Its port is available via the bridge interface, i.e. netns0.
|
||||
* [Become an OpenFaaS GitHub Sponsor](https://github.com/sponsors/openfaas/)
|
||||
* [Join the weekly Office Hours call](https://docs.openfaas.com/community/)
|
||||
|
||||
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://172.19.0.2:9090/targets
|
||||
### Backlog, features, design limitations and any known issues
|
||||
|
||||
* faas-containerd runs on 172.19.0.1:8081
|
||||
For open backlog items, shipped features, design limitations and any known issues, see [ROADMAP.md](docs/ROADMAP.md)
|
||||
|
||||
* Now go to the gateway's IP address as shown above on port 8080, i.e. http://172.19.0.3:8080 - you can also use this address to deploy OpenFaaS Functions via the `faas-cli`.
|
||||
|
||||
* basic-auth
|
||||
|
||||
You will then need to get the basic-auth password, it is written to `$GOPATH/src/github.com/alexellis/faasd/basic-auth-password` if you followed the above instructions.
|
||||
The default Basic Auth username is `admin`, which is written to `$GOPATH/src/github.com/alexellis/faasd/basic-auth-user`, if you wish to use a non-standard user then create this file and add your username (no newlines or other characters)
|
||||
|
||||
#### Installation with systemd
|
||||
|
||||
* `faasd install` - install faasd and containerd with systemd, run in `$GOPATH/src/github.com/alexellis/faasd`
|
||||
* `journalctl -u faasd` - faasd systemd logs
|
||||
* `journalctl -u faas-containerd` - faas-containerd systemd logs
|
||||
|
||||
### Appendix
|
||||
|
||||
Removing containers:
|
||||
|
||||
```sh
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr task rm -f
|
||||
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr container rm
|
||||
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr snapshot rm
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
https://github.com/renatofq/ctrofb/blob/31968e4b4893f3603e9998f21933c4131523bb5d/cmd/network.go
|
||||
|
||||
https://github.com/renatofq/catraia/blob/c4f62c86bddbfadbead38cd2bfe6d920fba26dce/catraia-net/network.go
|
||||
|
||||
https://github.com/containernetworking/plugins
|
||||
|
||||
https://github.com/containerd/go-cni
|
||||
Want to build a patch without setting up a complete development environment? See [docs/PATCHES.md](docs/PATCHES.md)
|
||||
|
||||
Are you looking to hack on faasd? Follow the [developer instructions](docs/DEV.md) for a manual installation, or use the `hack/install.sh` script and pick up from there.
|
||||
|
20
cloud-config.txt
Normal file
20
cloud-config.txt
Normal file
@ -0,0 +1,20 @@
|
||||
#cloud-config
|
||||
ssh_authorized_keys:
|
||||
## Note: Replace with your own public key
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
|
||||
|
||||
package_update: true
|
||||
|
||||
packages:
|
||||
- runc
|
||||
- git
|
||||
|
||||
runcmd:
|
||||
- curl -sfL https://raw.githubusercontent.com/openfaas/faasd/master/hack/install.sh | bash -s -
|
||||
- systemctl status -l containerd --no-pager
|
||||
- journalctl -u faasd-provider --no-pager
|
||||
- systemctl status -l faasd-provider --no-pager
|
||||
- systemctl status -l faasd --no-pager
|
||||
- curl -sSLf https://cli.openfaas.com | sh
|
||||
- sleep 60 && journalctl -u faasd --no-pager
|
||||
- cat /var/lib/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin
|
60
cmd/collect.go
Normal file
60
cmd/collect.go
Normal file
@ -0,0 +1,60 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/runtime/v2/logging"
|
||||
"github.com/coreos/go-systemd/journal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CollectCommand() *cobra.Command {
|
||||
return collectCmd
|
||||
}
|
||||
|
||||
var collectCmd = &cobra.Command{
|
||||
Use: "collect",
|
||||
Short: "Collect logs to the journal",
|
||||
RunE: runCollect,
|
||||
}
|
||||
|
||||
func runCollect(_ *cobra.Command, _ []string) error {
|
||||
logging.Run(logStdio)
|
||||
return nil
|
||||
}
|
||||
|
||||
// logStdio copied from
|
||||
// https://github.com/containerd/containerd/pull/3085
|
||||
// https://github.com/stellarproject/orbit
|
||||
func logStdio(ctx context.Context, config *logging.Config, ready func() error) error {
|
||||
// construct any log metadata for the container
|
||||
vars := map[string]string{
|
||||
"SYSLOG_IDENTIFIER": fmt.Sprintf("%s:%s", config.Namespace, config.ID),
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
// forward both stdout and stderr to the journal
|
||||
go copy(&wg, config.Stdout, journal.PriInfo, vars)
|
||||
go copy(&wg, config.Stderr, journal.PriErr, vars)
|
||||
// signal that we are ready and setup for the container to be started
|
||||
if err := ready(); err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func copy(wg *sync.WaitGroup, r io.Reader, pri journal.Priority, vars map[string]string) {
|
||||
defer wg.Done()
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
if s.Err() != nil {
|
||||
return
|
||||
}
|
||||
journal.Send(s.Text(), pri, vars)
|
||||
}
|
||||
}
|
102
cmd/install.go
102
cmd/install.go
@ -2,10 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
systemd "github.com/alexellis/faasd/pkg/systemd"
|
||||
systemd "github.com/openfaas/faasd/pkg/systemd"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -16,29 +18,52 @@ var installCmd = &cobra.Command{
|
||||
RunE: runInstall,
|
||||
}
|
||||
|
||||
const workingDirectoryPermission = 0644
|
||||
|
||||
const faasdwd = "/var/lib/faasd"
|
||||
|
||||
const faasdProviderWd = "/var/lib/faasd-provider"
|
||||
|
||||
func runInstall(_ *cobra.Command, _ []string) error {
|
||||
|
||||
err := binExists("/usr/local/bin/", "faas-containerd")
|
||||
if err := ensureWorkingDir(path.Join(faasdwd, "secrets")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureWorkingDir(faasdProviderWd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if basicAuthErr := makeBasicAuthFiles(path.Join(faasdwd, "secrets")); basicAuthErr != nil {
|
||||
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||
}
|
||||
|
||||
if err := cp("docker-compose.yaml", faasdwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cp("prometheus.yml", faasdwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cp("resolv.conf", faasdwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := binExists("/usr/local/bin/", "faasd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binExists("/usr/local/bin/", "faasd")
|
||||
err = systemd.InstallUnit("faasd-provider", map[string]string{
|
||||
"Cwd": faasdProviderWd,
|
||||
"SecretMountPath": path.Join(faasdwd, "secrets")})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binExists("/usr/local/bin/", "netns")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.InstallUnit("faas-containerd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.InstallUnit("faasd")
|
||||
err = systemd.InstallUnit("faasd", map[string]string{"Cwd": faasdwd})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,7 +73,7 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Enable("faas-containerd")
|
||||
err = systemd.Enable("faasd-provider")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -58,7 +83,7 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Start("faas-containerd")
|
||||
err = systemd.Start("faasd-provider")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -68,6 +93,12 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(`Check status with:
|
||||
sudo journalctl -u faasd --lines 100 -f
|
||||
|
||||
Login with:
|
||||
sudo cat /var/lib/faasd/secrets/basic-auth-password | faas-cli login -s`)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -78,3 +109,42 @@ func binExists(folder, name string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func ensureSecretsDir(folder string) error {
|
||||
if _, err := os.Stat(folder); err != nil {
|
||||
err = os.MkdirAll(folder, secretDirPermission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func ensureWorkingDir(folder string) error {
|
||||
if _, err := os.Stat(folder); err != nil {
|
||||
err = os.MkdirAll(folder, workingDirectoryPermission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cp(source, destFolder string) error {
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
out, err := os.Create(path.Join(destFolder, source))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, file)
|
||||
|
||||
return err
|
||||
}
|
||||
|
176
cmd/provider.go
Normal file
176
cmd/provider.go
Normal file
@ -0,0 +1,176 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
bootstrap "github.com/openfaas/faas-provider"
|
||||
"github.com/openfaas/faas-provider/logs"
|
||||
"github.com/openfaas/faas-provider/proxy"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||
faasdlogs "github.com/openfaas/faasd/pkg/logs"
|
||||
"github.com/openfaas/faasd/pkg/provider/config"
|
||||
"github.com/openfaas/faasd/pkg/provider/handlers"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const secretDirPermission = 0755
|
||||
|
||||
func makeProviderCmd() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "provider",
|
||||
Short: "Run the faasd-provider",
|
||||
}
|
||||
|
||||
command.Flags().String("pull-policy", "Always", `Set to "Always" to force a pull of images upon deployment, or "IfNotPresent" to try to use a cached image.`)
|
||||
|
||||
command.RunE = func(_ *cobra.Command, _ []string) error {
|
||||
|
||||
pullPolicy, flagErr := command.Flags().GetString("pull-policy")
|
||||
if flagErr != nil {
|
||||
return flagErr
|
||||
}
|
||||
|
||||
alwaysPull := false
|
||||
if pullPolicy == "Always" {
|
||||
alwaysPull = true
|
||||
}
|
||||
|
||||
config, providerConfig, err := config.ReadFromEnv(types.OsEnv{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("faasd-provider starting..\tService Timeout: %s\n", config.WriteTimeout.String())
|
||||
printVersion()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
||||
[]byte(`127.0.0.1 localhost`), workingDirectoryPermission)
|
||||
|
||||
if writeHostsErr != nil {
|
||||
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
||||
}
|
||||
|
||||
writeResolvErr := ioutil.WriteFile(path.Join(wd, "resolv.conf"),
|
||||
[]byte(`nameserver 8.8.8.8`), workingDirectoryPermission)
|
||||
|
||||
if writeResolvErr != nil {
|
||||
return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr)
|
||||
}
|
||||
|
||||
cni, err := cninetwork.InitNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := containerd.New(providerConfig.Sock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
invokeResolver := handlers.NewInvokeResolver(client)
|
||||
|
||||
baseUserSecretsPath := path.Join(wd, "secrets")
|
||||
if err := moveSecretsToDefaultNamespaceSecrets(
|
||||
baseUserSecretsPath,
|
||||
faasd.DefaultFunctionNamespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapHandlers := types.FaaSHandlers{
|
||||
FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver),
|
||||
DeleteFunction: handlers.MakeDeleteHandler(client, cni),
|
||||
DeployFunction: handlers.MakeDeployHandler(client, cni, baseUserSecretsPath, alwaysPull),
|
||||
FunctionLister: handlers.MakeReadHandler(client),
|
||||
FunctionStatus: handlers.MakeReplicaReaderHandler(client),
|
||||
ScaleFunction: handlers.MakeReplicaUpdateHandler(client, cni),
|
||||
UpdateFunction: handlers.MakeUpdateHandler(client, cni, baseUserSecretsPath, alwaysPull),
|
||||
Health: func(w http.ResponseWriter, r *http.Request) {},
|
||||
Info: handlers.MakeInfoHandler(Version, GitCommit),
|
||||
ListNamespaces: handlers.MakeNamespacesLister(client),
|
||||
Secrets: handlers.MakeSecretHandler(client.NamespaceService(), baseUserSecretsPath),
|
||||
Logs: logs.NewLogHandlerFunc(faasdlogs.New(), config.ReadTimeout),
|
||||
}
|
||||
|
||||
log.Printf("Listening on: 0.0.0.0:%d\n", *config.TCPPort)
|
||||
bootstrap.Serve(&bootstrapHandlers, config)
|
||||
return nil
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
/*
|
||||
* Mutiple namespace support was added after release 0.13.0
|
||||
* Function will help users to migrate on multiple namespace support of faasd
|
||||
*/
|
||||
func moveSecretsToDefaultNamespaceSecrets(baseSecretPath string, defaultNamespace string) error {
|
||||
newSecretPath := path.Join(baseSecretPath, defaultNamespace)
|
||||
|
||||
err := ensureSecretsDir(newSecretPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(baseSecretPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
|
||||
newPath := path.Join(newSecretPath, f.Name())
|
||||
|
||||
// A non-nil error means the file wasn't found in the
|
||||
// destination path
|
||||
if _, err := os.Stat(newPath); err != nil {
|
||||
oldPath := path.Join(baseSecretPath, f.Name())
|
||||
|
||||
if err := copyFile(oldPath, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[Migration] Copied %s to %s", oldPath, newPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
inputFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s failed %w", src, err)
|
||||
}
|
||||
defer inputFile.Close()
|
||||
|
||||
outputFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_APPEND, secretDirPermission)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s failed %w", dst, err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
// Changed from os.Rename due to issue in #201
|
||||
if _, err := io.Copy(outputFile, inputFile); err != nil {
|
||||
return fmt.Errorf("writing into %s failed %w", outputFile.Name(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
23
cmd/root.go
23
cmd/root.go
@ -14,6 +14,12 @@ func init() {
|
||||
rootCommand.AddCommand(versionCmd)
|
||||
rootCommand.AddCommand(upCmd)
|
||||
rootCommand.AddCommand(installCmd)
|
||||
rootCommand.AddCommand(makeProviderCmd())
|
||||
rootCommand.AddCommand(collectCmd)
|
||||
}
|
||||
|
||||
func RootCommand() *cobra.Command {
|
||||
return rootCommand
|
||||
}
|
||||
|
||||
var (
|
||||
@ -40,7 +46,12 @@ var rootCommand = &cobra.Command{
|
||||
Use: "faasd",
|
||||
Short: "Start faasd",
|
||||
Long: `
|
||||
faasd - serverless without Kubernetes
|
||||
faasd - Serverless For Everyone Else
|
||||
|
||||
Learn how to build, secure, and monitor functions with faasd with
|
||||
the eBook:
|
||||
|
||||
https://gumroad.com/l/serverless-for-everyone-else
|
||||
`,
|
||||
RunE: runRootCommand,
|
||||
SilenceUsage: true,
|
||||
@ -63,11 +74,11 @@ var versionCmd = &cobra.Command{
|
||||
func parseBaseCommand(_ *cobra.Command, _ []string) {
|
||||
printLogo()
|
||||
|
||||
fmt.Printf(
|
||||
`faasd
|
||||
Commit: %s
|
||||
Version: %s
|
||||
`, GitCommit, GetVersion())
|
||||
printVersion()
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
fmt.Printf("faasd version: %s\tcommit: %s\n", GetVersion(), GitCommit)
|
||||
}
|
||||
|
||||
func printLogo() {
|
||||
|
248
cmd/up.go
248
cmd/up.go
@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@ -12,45 +11,57 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alexellis/faasd/pkg"
|
||||
"github.com/alexellis/k3sup/pkg/env"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sethvargo/go-password/password"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/openfaas/faasd/pkg"
|
||||
)
|
||||
|
||||
// upConfig are the CLI flags used by the `faasd up` command to deploy the faasd service
|
||||
type upConfig struct {
|
||||
// composeFilePath is the path to the compose file specifying the faasd service configuration
|
||||
// See https://compose-spec.io/ for more information about the spec,
|
||||
//
|
||||
// currently, this must be the name of a file in workingDir, which is set to the value of
|
||||
// `faasdwd = /var/lib/faasd`
|
||||
composeFilePath string
|
||||
|
||||
// working directory to assume the compose file is in, should be faasdwd.
|
||||
// this is not configurable but may be in the future.
|
||||
workingDir string
|
||||
}
|
||||
|
||||
func init() {
|
||||
configureUpFlags(upCmd.Flags())
|
||||
}
|
||||
|
||||
var upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "Start faasd",
|
||||
RunE: runUp,
|
||||
}
|
||||
|
||||
func runUp(_ *cobra.Command, _ []string) error {
|
||||
func runUp(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
clientArch, clientOS := env.GetClientArch()
|
||||
printVersion()
|
||||
|
||||
if clientOS != "Linux" {
|
||||
return fmt.Errorf("You can only use faasd on Linux")
|
||||
}
|
||||
clientSuffix := ""
|
||||
switch clientArch {
|
||||
case "x86_64":
|
||||
clientSuffix = ""
|
||||
break
|
||||
case "armhf":
|
||||
case "armv7l":
|
||||
clientSuffix = "-armhf"
|
||||
break
|
||||
case "arm64":
|
||||
case "aarch64":
|
||||
clientSuffix = "-arm64"
|
||||
cfg, err := parseUpFlags(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authFileErr := errors.Wrap(makeBasicAuthFiles(), "Could not create gateway auth files")
|
||||
if authFileErr != nil {
|
||||
return authFileErr
|
||||
services, err := loadServiceDefinition(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
services := makeServiceDefinitions(clientSuffix)
|
||||
basicAuthErr := makeBasicAuthFiles(path.Join(cfg.workingDir, "secrets"))
|
||||
if basicAuthErr != nil {
|
||||
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
supervisor, err := pkg.NewSupervisor("/run/containerd/containerd.sock")
|
||||
@ -58,19 +69,15 @@ func runUp(_ *cobra.Command, _ []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Supervisor created in: %s\n", time.Since(start).String())
|
||||
log.Printf("Supervisor created in: %s\n", units.HumanDuration(time.Since(start)))
|
||||
|
||||
start = time.Now()
|
||||
|
||||
err = supervisor.Start(services)
|
||||
|
||||
if err != nil {
|
||||
if err := supervisor.Start(services); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer supervisor.Close()
|
||||
|
||||
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
|
||||
log.Printf("Supervisor init done in: %s\n", units.HumanDuration(time.Since(start)))
|
||||
|
||||
shutdownTimeout := time.Second * 1
|
||||
timeout := time.Second * 60
|
||||
@ -85,28 +92,52 @@ func runUp(_ *cobra.Command, _ []string) error {
|
||||
<-sig
|
||||
|
||||
log.Printf("Signal received.. shutting down server in %s\n", shutdownTimeout.String())
|
||||
|
||||
err := supervisor.Remove(services)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// TODO: close proxies
|
||||
time.AfterFunc(shutdownTimeout, func() {
|
||||
wg.Done()
|
||||
})
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wd, _ := os.Getwd()
|
||||
proxy := pkg.NewProxy(path.Join(wd, "hosts"), timeout)
|
||||
proxy.Start()
|
||||
}()
|
||||
localResolver := pkg.NewLocalResolver(path.Join(cfg.workingDir, "hosts"))
|
||||
go localResolver.Start()
|
||||
|
||||
proxies := map[uint32]*pkg.Proxy{}
|
||||
for _, svc := range services {
|
||||
for _, port := range svc.Ports {
|
||||
|
||||
listenPort := port.Port
|
||||
if _, ok := proxies[listenPort]; ok {
|
||||
return fmt.Errorf("port %d already allocated", listenPort)
|
||||
}
|
||||
|
||||
hostIP := "0.0.0.0"
|
||||
if len(port.HostIP) > 0 {
|
||||
hostIP = port.HostIP
|
||||
}
|
||||
|
||||
upstream := fmt.Sprintf("%s:%d", svc.Name, port.TargetPort)
|
||||
proxies[listenPort] = pkg.NewProxy(upstream, listenPort, hostIP, timeout, localResolver)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: track proxies for later cancellation when receiving sigint/term
|
||||
for _, v := range proxies {
|
||||
go v.Start()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeBasicAuthFiles() error {
|
||||
wd, _ := os.Getwd()
|
||||
pwdFile := wd + "/basic-auth-password"
|
||||
func makeBasicAuthFiles(wd string) error {
|
||||
|
||||
pwdFile := path.Join(wd, "basic-auth-password")
|
||||
authPassword, err := password.Generate(63, 10, 0, false, true)
|
||||
|
||||
if err != nil {
|
||||
@ -118,7 +149,7 @@ func makeBasicAuthFiles() error {
|
||||
return err
|
||||
}
|
||||
|
||||
userFile := wd + "/basic-auth-user"
|
||||
userFile := path.Join(wd, "basic-auth-user")
|
||||
err = makeFile(userFile, "admin")
|
||||
if err != nil {
|
||||
return err
|
||||
@ -127,119 +158,50 @@ func makeBasicAuthFiles() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeFile will create a file with the specified content if it does not exist yet.
|
||||
// if the file already exists, the method is a noop.
|
||||
func makeFile(filePath, fileContents string) error {
|
||||
_, err := os.Stat(filePath)
|
||||
if err == nil {
|
||||
log.Printf("File exists: %q, Using data from this file",filePath)
|
||||
log.Printf("File exists: %q\n", filePath)
|
||||
return nil
|
||||
} else if os.IsNotExist(err) {
|
||||
log.Printf("Writing file: %q", filePath)
|
||||
return ioutil.WriteFile(filePath, []byte(fileContents), 0644)
|
||||
log.Printf("Writing to: %q\n", filePath)
|
||||
return ioutil.WriteFile(filePath, []byte(fileContents), workingDirectoryPermission)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
||||
wd, _ := os.Getwd()
|
||||
// load the docker compose file and then parse it as supervisor Services
|
||||
// the logic for loading the compose file comes from the compose reference implementation
|
||||
// https://github.com/compose-spec/compose-ref/blob/master/compose-ref.go#L353
|
||||
func loadServiceDefinition(cfg upConfig) ([]pkg.Service, error) {
|
||||
|
||||
secretMountDir := "/run/secrets/"
|
||||
|
||||
return []pkg.Service{
|
||||
pkg.Service{
|
||||
Name: "basic-auth-plugin",
|
||||
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
|
||||
Env: []string{
|
||||
"port=8080",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
"user_filename=basic-auth-user",
|
||||
"pass_filename=basic-auth-password",
|
||||
},
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src:path.Join(wd, "/basic-auth-password"),
|
||||
Dest:secretMountDir + "basic-auth-password",
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "/basic-auth-user"),
|
||||
Dest: secretMountDir + "basic-auth-user",
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
Args: nil,
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "nats",
|
||||
Env: []string{""},
|
||||
Image: "docker.io/library/nats-streaming:0.11.2",
|
||||
Caps: []string{},
|
||||
Args: []string{"/nats-streaming-server", "-m", "8222", "--store=memory", "--cluster_id=faas-cluster"},
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "prometheus",
|
||||
Env: []string{},
|
||||
Image: "docker.io/prom/prometheus:v2.14.0",
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "prometheus.yml"),
|
||||
Dest: "/etc/prometheus/prometheus.yml",
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "gateway",
|
||||
Env: []string{
|
||||
"basic_auth=true",
|
||||
"functions_provider_url=http://faas-containerd:8081/",
|
||||
"direct_functions=false",
|
||||
"read_timeout=60s",
|
||||
"write_timeout=60s",
|
||||
"upstream_timeout=65s",
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
|
||||
"auth_proxy_pass_body=false",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
},
|
||||
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src:path.Join(wd, "/basic-auth-password"),
|
||||
Dest:secretMountDir + "basic-auth-password",
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "/basic-auth-user"),
|
||||
Dest: secretMountDir + "basic-auth-user",
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "queue-worker",
|
||||
Env: []string{
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"gateway_invoke=true",
|
||||
"faas_gateway_address=gateway",
|
||||
"ack_wait=5m5s",
|
||||
"max_inflight=1",
|
||||
"write_debug=false",
|
||||
"basic_auth=true",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
},
|
||||
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src:path.Join(wd, "/basic-auth-password"),
|
||||
Dest:secretMountDir + "basic-auth-password",
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "/basic-auth-user"),
|
||||
Dest: secretMountDir + "basic-auth-user",
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
serviceConfig, err := pkg.LoadComposeFile(cfg.workingDir, cfg.composeFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pkg.ParseCompose(serviceConfig)
|
||||
}
|
||||
|
||||
// ConfigureUpFlags will define the flags for the `faasd up` command. The flag struct, configure, and
|
||||
// parse are split like this to simplify testability.
|
||||
func configureUpFlags(flags *flag.FlagSet) {
|
||||
flags.StringP("file", "f", "docker-compose.yaml", "compose file specifying the faasd service configuration")
|
||||
}
|
||||
|
||||
// ParseUpFlags will load the flag values into an upFlags object. Errors will be underlying
|
||||
// Get errors from the pflag library.
|
||||
func parseUpFlags(cmd *cobra.Command) (upConfig, error) {
|
||||
parsed := upConfig{}
|
||||
path, err := cmd.Flags().GetString("file")
|
||||
if err != nil {
|
||||
return parsed, errors.Wrap(err, "can not parse compose file path flag")
|
||||
}
|
||||
|
||||
parsed.composeFilePath = path
|
||||
parsed.workingDir = faasdwd
|
||||
return parsed, err
|
||||
}
|
||||
|
94
docker-compose.yaml
Normal file
94
docker-compose.yaml
Normal file
@ -0,0 +1,94 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
|
||||
nats:
|
||||
image: docker.io/library/nats-streaming:0.25.3
|
||||
# nobody
|
||||
user: "65534"
|
||||
command:
|
||||
- "/nats-streaming-server"
|
||||
- "-m"
|
||||
- "8222"
|
||||
- "--store=file"
|
||||
- "--dir=/nats"
|
||||
- "--cluster_id=faas-cluster"
|
||||
volumes:
|
||||
# Data directory
|
||||
- type: bind
|
||||
source: ./nats
|
||||
target: /nats
|
||||
# ports:
|
||||
# - "127.0.0.1:8222:8222"
|
||||
|
||||
prometheus:
|
||||
image: docker.io/prom/prometheus:v2.42.0
|
||||
# nobody
|
||||
user: "65534"
|
||||
volumes:
|
||||
# Config directory
|
||||
- type: bind
|
||||
source: ./prometheus.yml
|
||||
target: /etc/prometheus/prometheus.yml
|
||||
# Data directory
|
||||
- type: bind
|
||||
source: ./prometheus
|
||||
target: /prometheus
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
ports:
|
||||
- "127.0.0.1:9090:9090"
|
||||
|
||||
gateway:
|
||||
image: ghcr.io/openfaas/gateway:0.26.3
|
||||
environment:
|
||||
- basic_auth=true
|
||||
- functions_provider_url=http://faasd-provider:8081/
|
||||
- direct_functions=false
|
||||
- read_timeout=60s
|
||||
- write_timeout=60s
|
||||
- upstream_timeout=65s
|
||||
- faas_nats_address=nats
|
||||
- faas_nats_port=4222
|
||||
- secret_mount_path=/run/secrets
|
||||
- scale_from_zero=true
|
||||
- function_namespace=openfaas-fn
|
||||
volumes:
|
||||
# we assume cwd == /var/lib/faasd
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-password
|
||||
target: /run/secrets/basic-auth-password
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-user
|
||||
target: /run/secrets/basic-auth-user
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
depends_on:
|
||||
- nats
|
||||
- prometheus
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
queue-worker:
|
||||
image: ghcr.io/openfaas/queue-worker:0.13.3
|
||||
environment:
|
||||
- faas_nats_address=nats
|
||||
- faas_nats_port=4222
|
||||
- gateway_invoke=true
|
||||
- faas_gateway_address=gateway
|
||||
- ack_wait=5m5s
|
||||
- max_inflight=1
|
||||
- write_debug=false
|
||||
- basic_auth=true
|
||||
- secret_mount_path=/run/secrets
|
||||
volumes:
|
||||
# we assume cwd == /var/lib/faasd
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-password
|
||||
target: /run/secrets/basic-auth-password
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-user
|
||||
target: /run/secrets/basic-auth-user
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
depends_on:
|
||||
- nats
|
395
docs/DEV.md
Normal file
395
docs/DEV.md
Normal file
@ -0,0 +1,395 @@
|
||||
## Instructions for building and testing faasd locally
|
||||
|
||||
> Note: if you're just wanting to try out faasd, then it's likely that you're on the wrong page. This is a detailed set of instructions for those wanting to contribute or customise faasd. Feel free to go back to the homepage and pick a tutorial instead.
|
||||
|
||||
Do you want to help the community test a pull request?
|
||||
|
||||
See these instructions instead: [Testing patches](/docs/PATCHES.md)
|
||||
|
||||
### Pre-reqs
|
||||
|
||||
> It's recommended that you do not install Docker on the same host as faasd, since 1) they may both use different versions of containerd and 2) docker's networking rules can disrupt faasd's networking. When using faasd - make your faasd server a faasd server, and build container image on your laptop or in a CI pipeline.
|
||||
|
||||
* Linux
|
||||
|
||||
PC / Cloud - any Linux that containerd works on should be fair game, but faasd is tested with Ubuntu 18.04
|
||||
|
||||
For Raspberry Pi Raspbian Stretch or newer also works fine
|
||||
|
||||
For MacOS users try [multipass.run](https://multipass.run) or [Vagrant](https://www.vagrantup.com/)
|
||||
|
||||
For Windows users, install [Git Bash](https://git-scm.com/downloads) along with multipass or vagrant. You can also use WSL1 or WSL2 which provides a Linux environment.
|
||||
|
||||
You will also need [containerd](https://github.com/containerd/containerd) and the [CNI plugins](https://github.com/containernetworking/plugins)
|
||||
|
||||
[faas-cli](https://github.com/openfaas/faas-cli) is optional, but recommended.
|
||||
|
||||
If you're using multipass, then allocate sufficient resources:
|
||||
|
||||
```bash
|
||||
multipass launch \
|
||||
--memory 4G \
|
||||
-c 2 \
|
||||
-n faasd
|
||||
|
||||
# Then access its shell
|
||||
multipass shell faasd
|
||||
```
|
||||
|
||||
### Get runc
|
||||
|
||||
```bash
|
||||
sudo apt update \
|
||||
&& sudo apt install -qy \
|
||||
runc \
|
||||
bridge-utils \
|
||||
make
|
||||
```
|
||||
|
||||
### Get faas-cli (optional)
|
||||
|
||||
Having `faas-cli` on your dev machine is useful for testing and debug.
|
||||
|
||||
```bash
|
||||
curl -sLS https://cli.openfaas.com | sudo sh
|
||||
```
|
||||
|
||||
#### Install the CNI plugins:
|
||||
|
||||
* For PC run `export ARCH=amd64`
|
||||
* For RPi/armhf run `export ARCH=arm`
|
||||
* For arm64 run `export ARCH=arm64`
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
export ARCH=amd64
|
||||
export CNI_VERSION=v0.9.1
|
||||
|
||||
sudo mkdir -p /opt/cni/bin
|
||||
curl -sSL https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-${ARCH}-${CNI_VERSION}.tgz | sudo tar -xz -C /opt/cni/bin
|
||||
|
||||
# Make a config folder for CNI definitions
|
||||
sudo mkdir -p /etc/cni/net.d
|
||||
|
||||
# Make an initial loopback configuration
|
||||
sudo sh -c 'cat >/etc/cni/net.d/99-loopback.conf <<-EOF
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "loopback"
|
||||
}
|
||||
EOF'
|
||||
```
|
||||
|
||||
### Get containerd
|
||||
|
||||
You have three options - binaries for PC, binaries for armhf, or build from source.
|
||||
|
||||
* Install containerd `x86_64` only
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
export VER=1.7.0
|
||||
curl -sSL https://github.com/containerd/containerd/releases/download/v$VER/containerd-$VER-linux-amd64.tar.gz -o /tmp/containerd.tar.gz \
|
||||
&& sudo tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
|
||||
|
||||
containerd -version
|
||||
```
|
||||
|
||||
* Or get my containerd binaries for Raspberry Pi (armhf)
|
||||
|
||||
Building `containerd` on armhf is extremely slow, so I've provided binaries for you.
|
||||
|
||||
```bash
|
||||
export VER=1.7.0
|
||||
curl -sSL https://github.com/alexellis/containerd-armhf/releases/download/v$VER/containerd-$VER-linux-armhf.tar.gz
|
||||
| sudo tar -xvz --strip-components=2 -C /usr/local/bin/
|
||||
```
|
||||
|
||||
* Or clone / build / install [containerd](https://github.com/containerd/containerd) from source:
|
||||
|
||||
```bash
|
||||
export GOPATH=$HOME/go/
|
||||
mkdir -p $GOPATH/src/github.com/containerd
|
||||
cd $GOPATH/src/github.com/containerd
|
||||
git clone https://github.com/containerd/containerd
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.7.0
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
||||
containerd --version
|
||||
```
|
||||
|
||||
#### Ensure containerd is running
|
||||
|
||||
```bash
|
||||
curl -sLS https://raw.githubusercontent.com/containerd/containerd/v1.7.0/containerd.service > /tmp/containerd.service
|
||||
|
||||
# Extend the timeouts for low-performance VMs
|
||||
echo "[Manager]" | tee -a /tmp/containerd.service
|
||||
echo "DefaultTimeoutStartSec=3m" | tee -a /tmp/containerd.service
|
||||
|
||||
sudo cp /tmp/containerd.service /lib/systemd/system/
|
||||
sudo systemctl enable containerd
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart containerd
|
||||
```
|
||||
|
||||
Or run ad-hoc. This step can be useful for exploring why containerd might fail to start.
|
||||
|
||||
```bash
|
||||
sudo containerd &
|
||||
```
|
||||
|
||||
#### Enable forwarding
|
||||
|
||||
> This is required to allow containers in containerd to access the Internet via your computer's primary network interface.
|
||||
|
||||
```bash
|
||||
sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||
```
|
||||
|
||||
Make the setting permanent:
|
||||
|
||||
```bash
|
||||
echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
|
||||
```
|
||||
|
||||
### Hacking (build from source)
|
||||
|
||||
#### Get build packages
|
||||
|
||||
```bash
|
||||
sudo apt update \
|
||||
&& sudo apt install -qy \
|
||||
runc \
|
||||
bridge-utils \
|
||||
make
|
||||
```
|
||||
|
||||
You may find alternative package names for CentOS and other Linux distributions.
|
||||
|
||||
#### Install Go 1.13 (x86_64)
|
||||
|
||||
```bash
|
||||
curl -SLf https://golang.org/dl/go1.16.linux-amd64.tar.gz > /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
|
||||
go version
|
||||
```
|
||||
|
||||
You should also add the following to `~/.bash_profile`:
|
||||
|
||||
```bash
|
||||
echo "export GOPATH=\$HOME/go/" | tee -a $HOME/.bash_profile
|
||||
echo "export PATH=\$PATH:/usr/local/go/bin/" | tee -a $HOME/.bash_profile
|
||||
```
|
||||
|
||||
#### Or on Raspberry Pi (armhf)
|
||||
|
||||
```bash
|
||||
curl -SLsf https://golang.org/dl/go1.16.linux-armv6l.tar.gz > go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf go.tgz -C /usr/local/go/ --strip-components=1
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
|
||||
go version
|
||||
```
|
||||
|
||||
#### Clone faasd and its systemd unit files
|
||||
|
||||
```bash
|
||||
mkdir -p $GOPATH/src/github.com/openfaas/
|
||||
cd $GOPATH/src/github.com/openfaas/
|
||||
git clone https://github.com/openfaas/faasd
|
||||
```
|
||||
|
||||
#### Build `faasd` from source (optional)
|
||||
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/openfaas/faasd
|
||||
cd faasd
|
||||
make local
|
||||
|
||||
# Install the binary
|
||||
sudo cp bin/faasd /usr/local/bin
|
||||
```
|
||||
|
||||
#### Or, download and run `faasd` (binaries)
|
||||
|
||||
```bash
|
||||
# For x86_64
|
||||
export SUFFIX=""
|
||||
|
||||
# armhf
|
||||
export SUFFIX="-armhf"
|
||||
|
||||
# arm64
|
||||
export SUFFIX="-arm64"
|
||||
|
||||
# Then download
|
||||
curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.16.2/faasd$SUFFIX" \
|
||||
-o "/tmp/faasd" \
|
||||
&& chmod +x "/tmp/faasd"
|
||||
sudo mv /tmp/faasd /usr/local/bin/
|
||||
```
|
||||
|
||||
#### Install `faasd`
|
||||
|
||||
This step installs faasd as a systemd unit file, creates files in `/var/lib/faasd`, and writes out networking configuration for the CNI bridge networking plugin.
|
||||
|
||||
```bash
|
||||
sudo faasd install
|
||||
|
||||
2020/02/17 17:38:06 Writing to: "/var/lib/faasd/secrets/basic-auth-password"
|
||||
2020/02/17 17:38:06 Writing to: "/var/lib/faasd/secrets/basic-auth-user"
|
||||
Login with:
|
||||
sudo cat /var/lib/faasd/secrets/basic-auth-password | faas-cli login -s
|
||||
```
|
||||
|
||||
You can now log in either from this machine or a remote machine using the OpenFaaS UI, or CLI.
|
||||
|
||||
Check that faasd is ready:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u faasd
|
||||
```
|
||||
|
||||
You should see output like:
|
||||
|
||||
```bash
|
||||
Feb 17 17:46:35 gold-survive faasd[4140]: 2020/02/17 17:46:35 Starting faasd proxy on 8080
|
||||
Feb 17 17:46:35 gold-survive faasd[4140]: Gateway: 10.62.0.5:8080
|
||||
Feb 17 17:46:35 gold-survive faasd[4140]: 2020/02/17 17:46:35 [proxy] Wait for done
|
||||
Feb 17 17:46:35 gold-survive faasd[4140]: 2020/02/17 17:46:35 [proxy] Begin listen on 8080
|
||||
```
|
||||
|
||||
To get the CLI for the command above run:
|
||||
|
||||
```bash
|
||||
curl -sSLf https://cli.openfaas.com | sudo sh
|
||||
```
|
||||
|
||||
#### Make a change to `faasd`
|
||||
|
||||
There are two components you can hack on:
|
||||
|
||||
For function CRUD you will work on `faasd provider` which is started from `cmd/provider.go`
|
||||
|
||||
For faasd itself, you will work on the code from `faasd up`, which is started from `cmd/up.go`
|
||||
|
||||
Before working on either, stop the systemd services:
|
||||
|
||||
```
|
||||
sudo systemctl stop faasd & # up command
|
||||
sudo systemctl stop faasd-provider # provider command
|
||||
```
|
||||
|
||||
Here is a workflow you can use for each code change:
|
||||
|
||||
Enter the directory of the source code, and build a new binary:
|
||||
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/openfaas/faasd
|
||||
go build
|
||||
```
|
||||
|
||||
Copy that binary to `/usr/local/bin/`
|
||||
|
||||
```bash
|
||||
cp faasd /usr/local/bin/
|
||||
```
|
||||
|
||||
To run `faasd up`, run it from its working directory as root
|
||||
|
||||
```bash
|
||||
sudo -i
|
||||
cd /var/lib/faasd
|
||||
|
||||
faasd up
|
||||
```
|
||||
|
||||
Now to run `faasd provider`, run it from its working directory:
|
||||
|
||||
```bash
|
||||
sudo -i
|
||||
cd /var/lib/faasd-provider
|
||||
|
||||
faasd provider
|
||||
```
|
||||
|
||||
#### At run-time
|
||||
|
||||
Look in `hosts` in the current working folder or in `/var/lib/faasd/` to get the IP for the gateway or Prometheus
|
||||
|
||||
```bash
|
||||
127.0.0.1 localhost
|
||||
10.62.0.1 faasd-provider
|
||||
|
||||
10.62.0.2 prometheus
|
||||
10.62.0.3 gateway
|
||||
10.62.0.4 nats
|
||||
10.62.0.5 queue-worker
|
||||
```
|
||||
|
||||
The IP addresses are dynamic and may change on every launch.
|
||||
|
||||
Since faasd-provider uses containerd heavily it is not running as a container, but as a stand-alone process. Its port is available via the bridge interface, i.e. `openfaas0`
|
||||
|
||||
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://[prometheus_ip]:9090/targets
|
||||
|
||||
* faasd-provider runs on 10.62.0.1:8081, i.e. directly on the host, and accessible via the bridge interface from CNI.
|
||||
|
||||
* Now go to the gateway's IP address as shown above on port 8080, i.e. http://[gateway_ip]:8080 - you can also use this address to deploy OpenFaaS Functions via the `faas-cli`.
|
||||
|
||||
* basic-auth
|
||||
|
||||
You will then need to get the basic-auth password, it is written to `/var/lib/faasd/secrets/basic-auth-password` if you followed the above instructions.
|
||||
The default Basic Auth username is `admin`, which is written to `/var/lib/faasd/secrets/basic-auth-user`, if you wish to use a non-standard user then create this file and add your username (no newlines or other characters)
|
||||
|
||||
#### Installation with systemd
|
||||
|
||||
* `faasd install` - install faasd and containerd with systemd, this must be run from `$GOPATH/src/github.com/openfaas/faasd`
|
||||
* `journalctl -u faasd -f` - faasd service logs
|
||||
* `journalctl -u faasd-provider -f` - faasd-provider service logs
|
||||
|
||||
#### Uninstall
|
||||
|
||||
* Stop faasd and faasd-provider
|
||||
```
|
||||
sudo systemctl stop faasd
|
||||
sudo systemctl stop faasd-provider
|
||||
sudo systemctl stop containerd
|
||||
```
|
||||
|
||||
* Remove faasd from machine
|
||||
```
|
||||
sudo systemctl disable faasd
|
||||
sudo systemctl disable faasd-provider
|
||||
sudo systemctl disable containerd
|
||||
sudo rm -rf /usr/local/bin/faasd
|
||||
sudo rm -rf /var/lib/faasd
|
||||
sudo rm -rf /usr/lib/systemd/system/faasd-provider.service
|
||||
sudo rm -rf /usr/lib/systemd/system/faasd.service
|
||||
sudo rm -rf /usr/lib/systemd/system/containerd
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
* Remove additional dependencies. Be cautious as other software will be dependent on these.
|
||||
```
|
||||
sudo apt-get remove runc bridge-utils
|
||||
sudo rm -rf /opt/cni/bin
|
||||
```
|
142
docs/MULTIPASS.md
Normal file
142
docs/MULTIPASS.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Tutorial - faasd with multipass
|
||||
|
||||
## Get up and running with your own faasd installation on your Mac
|
||||
|
||||
[multipass from Canonical](https://multipass.run) is like Docker Desktop, but for getting Ubuntu instead of a Docker daemon. It works on MacOS, Linux, and Windows with the same consistent UX. It's not fully open-source, and uses some proprietary add-ons / binaries, but is free to use.
|
||||
|
||||
For Linux using Ubuntu, you can install the packages directly, or use `sudo snap install multipass --classic` and follow this tutorial. For Raspberry Pi, [see my tutorial here](https://blog.alexellis.io/faasd-for-lightweight-serverless/).
|
||||
|
||||
John McCabe has also tested faasd on Windows with multipass, [see his tweet](https://twitter.com/mccabejohn/status/1221899154672308224).
|
||||
|
||||
## Use-case:
|
||||
|
||||
Try out [faasd](https://github.com/openfaas/faasd) in a single command using a cloud-config file to get a VM which has:
|
||||
|
||||
* port 22 for administration and
|
||||
* port 8080 for the OpenFaaS REST API.
|
||||
|
||||

|
||||
|
||||
The above screenshot is [from my tweet](https://twitter.com/alexellisuk/status/1221408788395298819/), feel free to comment there.
|
||||
|
||||
It took me about 2-3 minutes to run through everything after installing multipass.
|
||||
|
||||
## Let's start the tutorial
|
||||
|
||||
* Get [multipass.run](https://multipass.run)
|
||||
|
||||
* Get my cloud-config.txt file
|
||||
|
||||
```sh
|
||||
curl -sSLO https://raw.githubusercontent.com/openfaas/faasd/master/cloud-config.txt
|
||||
```
|
||||
|
||||
* Boot the VM
|
||||
|
||||
The `cloud-config.txt` contains an ssh key to allow your local machine to access the VM. However, this must be updated with your local ssh key.
|
||||
This command will update the key with your local public key value and start the VM.
|
||||
|
||||
```sh
|
||||
sed "s/ssh-rsa.*/$(cat $HOME/.ssh/id_*.pub)/" cloud-config.txt | multipass launch --name faasd --cloud-init -
|
||||
```
|
||||
|
||||
This can also be done manually, just replace the 2nd line of the `cloud-config.txt` with the contents of your public ssh key, usually either `~/.ssh/id_rsa.pub` or `~/.ssh/id_ed25519.pub`
|
||||
|
||||
```
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
|
||||
```
|
||||
|
||||
* Get the VM's IP and connect with `ssh`
|
||||
|
||||
```sh
|
||||
multipass info faasd
|
||||
Name: faasd
|
||||
State: Running
|
||||
IPv4: 192.168.64.14
|
||||
Release: Ubuntu 18.04.3 LTS
|
||||
Image hash: a720c34066dc (Ubuntu 18.04 LTS)
|
||||
Load: 0.79 0.19 0.06
|
||||
Disk usage: 1.1G out of 4.7G
|
||||
Memory usage: 145.6M out of 985.7M
|
||||
```
|
||||
|
||||
Set the variable `IP`:
|
||||
|
||||
```
|
||||
export IP="192.168.64.14"
|
||||
```
|
||||
|
||||
You can also try to use `jq` to get the IP into a variable:
|
||||
|
||||
```sh
|
||||
export IP=$(multipass info faasd --format json| jq -r '.info.faasd.ipv4[0]')
|
||||
```
|
||||
|
||||
Connect to the IP listed:
|
||||
|
||||
```sh
|
||||
ssh ubuntu@$IP
|
||||
```
|
||||
|
||||
Log out once you know it works.
|
||||
|
||||
* Let's capture the authentication password into a file for use with `faas-cli`
|
||||
|
||||
```
|
||||
ssh ubuntu@$IP "sudo cat /var/lib/faasd/secrets/basic-auth-password" > basic-auth-password
|
||||
```
|
||||
|
||||
## Try faasd (OpenFaaS)
|
||||
|
||||
* Login from your laptop (the host)
|
||||
|
||||
```
|
||||
export OPENFAAS_URL=http://$IP:8080
|
||||
cat basic-auth-password | faas-cli login -s
|
||||
```
|
||||
|
||||
* Deploy a function and invoke it
|
||||
|
||||
```
|
||||
faas-cli store deploy figlet --env write_timeout=1s
|
||||
echo "faasd" | faas-cli invoke figlet
|
||||
|
||||
faas-cli describe figlet
|
||||
|
||||
# Run async
|
||||
curl -i -d "faasd-async" $OPENFAAS_URL/async-function/figlet
|
||||
|
||||
# Run async with a callback
|
||||
|
||||
curl -i -d "faasd-async" -H "X-Callback-Url: http://some-request-bin.com/path" $OPENFAAS_URL/async-function/figlet
|
||||
```
|
||||
|
||||
You can also checkout the other store functions: `faas-cli store list`
|
||||
|
||||
* Try the UI
|
||||
|
||||
Head over to the UI from your laptop and remember that your password is in the `basic-auth-password` file. The username is `admin`:
|
||||
|
||||
```
|
||||
echo http://$IP:8080
|
||||
```
|
||||
|
||||
* Stop/start the instance
|
||||
|
||||
```sh
|
||||
multipass stop faasd
|
||||
```
|
||||
|
||||
* Delete, if you want to:
|
||||
|
||||
```
|
||||
multipass delete --purge faasd
|
||||
```
|
||||
|
||||
You now have a faasd appliance on your Mac. You can also use this cloud-init file with public cloud like AWS or DigitalOcean.
|
||||
|
||||
* If you want a public IP for your faasd VM, then just head over to [inlets.dev](https://inlets.dev/)
|
||||
* Try my more complete walk-through / tutorial with Raspberry Pi, or run the same steps on your multipass VM, including how to develop your own functions and services - https://blog.alexellis.io/faasd-for-lightweight-serverless/
|
||||
* You might also like [Building containers without Docker](https://blog.alexellis.io/building-containers-without-docker/)
|
||||
* Star/fork [faasd](https://github.com/openfaas/faasd) on GitHub
|
88
docs/PATCHES.md
Normal file
88
docs/PATCHES.md
Normal file
@ -0,0 +1,88 @@
|
||||
## Instructions for testing a patch for faasd
|
||||
|
||||
### Launch a virtual machine
|
||||
|
||||
You can use any kind of Linux virtual machine, Ubuntu 20.04 is recommended.
|
||||
|
||||
Launch a cloud VM or use [Multipass](https://multipass.run), which is free to use an can be run locally. A Raspberry Pi 3 or 4 could also be used, but will need you to run `make dist` to cross compile a valid binary.
|
||||
|
||||
### Copy over your SSH key
|
||||
|
||||
Your SSH key will be used, so that you can copy a new faasd binary over to the host.
|
||||
|
||||
```bash
|
||||
multipass launch \
|
||||
--memory 4G \
|
||||
-c 2 \
|
||||
-n faasd
|
||||
|
||||
# Then access its shell
|
||||
multipass shell faasd
|
||||
|
||||
# Edit .ssh/authorized_keys
|
||||
|
||||
# Add .ssh/id_rsa.pub from your host and save the file
|
||||
```
|
||||
|
||||
### Install faasd on the VM
|
||||
|
||||
You start off with the upstream version of faasd on the host, then add the new version over the top later on.
|
||||
|
||||
```bash
|
||||
cd /tmp/
|
||||
git clone https://github.com/openfaas/faasd --depth=1
|
||||
cd faasd/hack
|
||||
./install.sh
|
||||
|
||||
# Run the login command given to you at the end of the script
|
||||
```
|
||||
|
||||
Get the multipass IP address:
|
||||
|
||||
```bash
|
||||
export IP=$(multipass info faasd --format json| jq -r '.info.faasd.ipv4[0]')
|
||||
```
|
||||
|
||||
### Build a new faasd binary with the patch
|
||||
|
||||
Check out faasd on your local computer
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openfaas/faasd
|
||||
cd faasd
|
||||
|
||||
gh pr checkout #PR_NUMBER_HERE
|
||||
|
||||
GOOS=linux go build
|
||||
|
||||
# You can also run "make dist" which is slower, but includes
|
||||
# a version and binaries for other platforms such as the Raspberry Pi
|
||||
```
|
||||
|
||||
### Copy it over to the VM
|
||||
|
||||
Now build a new faasd binary and copy it to the VM:
|
||||
|
||||
```bash
|
||||
scp faasd ubuntu@$IP:~/
|
||||
```
|
||||
|
||||
Now deploy the new version on the VM:
|
||||
|
||||
```bash
|
||||
killall -9 faasd-linux; killall -9 faasd-linux ; mv ./faasd-linux /usr/local/bin/faasd
|
||||
```
|
||||
|
||||
### Check it worked and test that patch
|
||||
|
||||
Now run a command with `faas-cli` such as:
|
||||
|
||||
* `faas-cli list`
|
||||
* `faas-cli version`
|
||||
|
||||
See the testing instructions on the PR and run through those steps.
|
||||
|
||||
Post your results on GitHub to assist the creator of the pull request.
|
||||
|
||||
You can see how to get the logs for various components using the [eBook Serverless For Everyone Else](https://gumroad.com/l/serverless-for-everyone-else), or by consulting the [DEV.md](/docs/DEV.md) guide.
|
||||
|
7
docs/README.md
Normal file
7
docs/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Documentation
|
||||
|
||||
- [Develop faasd](./DEV.md) - Instructions for building and testing faasd locally
|
||||
- [Testing faasd](./PATCHES.md) - Instructions for testing a patch for faasd
|
||||
- [Roadmap](./ROADMAP.md) - Overview of features, backlog and known issues
|
||||
- [Run faasd with multipass](./MULTIPASS.md) - Tutorial on how to run faasd on a local multipass VM
|
||||
- [Terraform modules](./TERRAFORM.md) - A collection of official and community provided terraform modules for faasd
|
151
docs/ROADMAP.md
Normal file
151
docs/ROADMAP.md
Normal file
@ -0,0 +1,151 @@
|
||||
# faasd backlog and features
|
||||
|
||||
It's important to understand the vision for faasd vs OpenFaaS CE/Pro.
|
||||
|
||||
faasd is a single-node implementation of OpenFaaS.
|
||||
|
||||
It is supposed to be a lightweight, low-overhead, way to deploy OpenFaaS functions for functions which do not need planet-scale.
|
||||
|
||||
It is not supposed to have multiple replicas, clustering, HA, or auto-scaling.
|
||||
|
||||
[Learn when to use faasd](https://docs.openfaas.com/deployment/)
|
||||
|
||||
## Supported operations
|
||||
|
||||
* `faas-cli login`
|
||||
* `faas-cli up`
|
||||
* `faas-cli list`
|
||||
* `faas-cli describe`
|
||||
* `faas-cli deploy --update=true --replace=false`
|
||||
* `faas-cli invoke --async`
|
||||
* `faas-cli invoke`
|
||||
* `faas-cli rm`
|
||||
* `faas-cli store list/deploy/inspect`
|
||||
* `faas-cli version`
|
||||
* `faas-cli namespace`
|
||||
* `faas-cli secret`
|
||||
* `faas-cli logs`
|
||||
* `faas-cli auth` - supported for Basic Authentication and OpenFaaS Pro with OIDC and Single-sign On.
|
||||
|
||||
The OpenFaaS REST API is supported by faasd, learn more in the [manual](https://store.openfaas.com/l/serverless-for-everyone-else) under "Can I get an API with that?"
|
||||
|
||||
## Constraints vs OpenFaaS on Kubernetes
|
||||
|
||||
faasd suits certain use-cases as mentioned in the [README.md](/README.md) file, for those who want a solution which can scale out horizontally with minimum effort, Kubernetes or K3s is a valid option.
|
||||
|
||||
Which is right for you? [Read a comparison in the OpenFaaS docs](https://docs.openfaas.com/deployment/)
|
||||
|
||||
### One replica per function
|
||||
|
||||
Functions only support one replica for each function, so that means horizontal scaling is not available.
|
||||
|
||||
It can scale vertically, and this may be a suitable alternative for many use-cases. See the [YAML reference for how to configure limits](https://docs.openfaas.com/reference/yaml/).
|
||||
|
||||
Workaround: deploy multiple, dynamically named functions `scraper-1`, `scraper-2`, `scraper-3` and set up a reverse proxy rule to load balance i.e. `scraper.example.com => [/function/scraper-1, /function/scraper-2, /function/scraper-3]`.
|
||||
|
||||
### Scale from zero may give a non-200
|
||||
|
||||
faasd itself does not implement a health check to determine if a function is ready for traffic. Since faasd doesn't support auto-scaling, this is unlikely to affect you.
|
||||
|
||||
Workaround: Have your client retry HTTP calls, or don't scale to zero.
|
||||
|
||||
### Leaf-node only - no clustering
|
||||
|
||||
faasd is operates on a leaf-node/single-node model. If this is an issue for you, but you have resource constraints, you will need to use [OpenFaaS CE or Pro on Kubernetes](https://docs.openfaas.com/deployment/).
|
||||
|
||||
There are no plans to add any form of clustering or multi-node support to faasd.
|
||||
|
||||
See past discussion at: [HA / resilience in faasd #225](https://github.com/openfaas/faasd/issues/225)
|
||||
|
||||
What about HA and fault tolerance?
|
||||
|
||||
To achieve fault tolerance, you could put two faasd instances behind a load balancer or proxy, but you will need to deploy the same set of functions to each.
|
||||
|
||||
An alternative would be to take regular VM backups or snapshots.
|
||||
|
||||
### No rolling updates are available today
|
||||
|
||||
When running `faas-cli deploy`, your old function is removed before the new one is started. This may cause a period of downtime, depending on the timeouts and grace periods you set.
|
||||
|
||||
Workaround: deploy uniquely named functions i.e. `scraper-1` and `scraper-2` with a reverse proxy rule that maps `/function/scraper` to the active version.
|
||||
|
||||
## Known issues
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There is a very detailed chapter on troubleshooting in the eBook [Serverless For Everyone Else](https://store.openfaas.com/l/serverless-for-everyone-else)
|
||||
|
||||
### Your function timed-out at 60 seconds
|
||||
|
||||
This is no longer an issue, see the manual for how to configure a longer timeout, updated 3rd October 2022.
|
||||
|
||||
### Non 200 HTTP status from the gateway upon reboot
|
||||
|
||||
This issue appears to happen sporadically and only for some users.
|
||||
|
||||
If you get a non 200 HTTP code from the gateway, or caddy after installing faasd, check the logs of faasd:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u faasd
|
||||
```
|
||||
|
||||
If you see the following error:
|
||||
|
||||
```
|
||||
unable to dial to 10.62.0.5:8080, error: dial tcp 10.62.0.5:8080: connect: no route to host
|
||||
```
|
||||
|
||||
Restart the faasd service with:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart faasd
|
||||
```
|
||||
|
||||
## Backlog
|
||||
|
||||
Should have:
|
||||
|
||||
* [ ] Restart any of the containers in docker-compose.yaml if they crash.
|
||||
* [ ] Asynchronous function deployment and deletion (currently synchronous/blocking)
|
||||
|
||||
Nice to Have:
|
||||
|
||||
* [ ] Live rolling-updates, with zero downtime (may require using IDs instead of names for function containers)
|
||||
* [ ] Apply a total memory limit for the host (if a node has 1GB of RAM, don't allow more than 1GB of RAM to be specified in the limits field)
|
||||
* [ ] Terraform for AWS EC2
|
||||
|
||||
Won't have:
|
||||
|
||||
* [ ] Clustering
|
||||
* [ ] Multiple replicas per function
|
||||
|
||||
### Completed
|
||||
|
||||
* [x] Docs or examples on how to use the various event connectors (Yes in the eBook)
|
||||
* [x] Resolve core services from functions by populating/sharing `/etc/hosts` between `faasd` and `faasd-provider`
|
||||
* [x] Provide a cloud-init configuration for faasd bootstrap
|
||||
* [x] Configure core services from a docker-compose.yaml file
|
||||
* [x] Store and fetch logs from the journal
|
||||
* [x] Add support for using container images in third-party public registries
|
||||
* [x] Add support for using container images in private third-party registries
|
||||
* [x] Provide a cloud-config.txt file for automated deployments of `faasd`
|
||||
* [x] Inject / manage IPs between core components for service to service communication - i.e. so Prometheus can scrape the OpenFaaS gateway - done via `/etc/hosts` mount
|
||||
* [x] Add queue-worker and NATS
|
||||
* [x] Create faasd.service and faasd-provider.service
|
||||
* [x] Self-install / create systemd service via `faasd install`
|
||||
* [x] Restart containers upon restart of faasd
|
||||
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
||||
* [x] Determine armhf/arm64 containers to run for gateway
|
||||
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faasd-provider HTTP API
|
||||
* [x] Setup custom working directory for faasd `/var/lib/faasd/`
|
||||
* [x] Use CNI to create network namespaces and adapters
|
||||
* [x] Optionally expose core services from the docker-compose.yaml file, locally or to all adapters.
|
||||
* [x] ~~[containerd can't pull image from Github Docker Package Registry](https://github.com/containerd/containerd/issues/3291)~~ ghcr.io support
|
||||
* [x] Provide [simple Caddyfile example](https://blog.alexellis.io/https-inlets-local-endpoints/) in the README showing how to expose the faasd proxy on port 80/443 with TLS
|
||||
* [x] Annotation support
|
||||
* [x] Hard memory limits for functions
|
||||
* [x] Terraform for DigitalOcean
|
||||
* [x] [Store and retrieve annotations in function spec](https://github.com/openfaas/faasd/pull/86) - in progress
|
||||
* [x] An installer for faasd and dependencies - runc, containerd
|
||||
* [x] Offer a recommendation or implement a strategy for faasd replication/HA
|
||||
|
14
docs/TERRAFORM.md
Normal file
14
docs/TERRAFORM.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Terraform Modules for faasd
|
||||
|
||||
| Terraform Module | Author | Origin | Status |
|
||||
|-----------|--------|--------|--------|
|
||||
| [faasd on DigitalOcean](https://github.com/openfaas/faasd/tree/master/docs/bootstrap/digitalocean-terraform) | OpenFaaS | Official | Active |
|
||||
| [faasd on DigitalOcean](https://github.com/jsiebens/terraform-digitalocean-faasd) | Johan Siebens | Community | Active |
|
||||
| [faasd on Google Cloud Platform](https://github.com/jsiebens/terraform-google-faasd) | Johan Siebens | Community | Active |
|
||||
| [faasd on Microsoft Azure ](https://github.com/jsiebens/terraform-azurerm-faasd) | Johan Siebens | Community | Active |
|
||||
| [faasd on Equinix Metal](https://github.com/jsiebens/terraform-equinix-faasd) | Johan Siebens | Community | Active |
|
||||
| [faasd on Scaleway](https://github.com/jsiebens/terraform-scaleway-faasd) | Johan Siebens | Community | Active |
|
||||
| [faasd on Amazon Web Services](https://github.com/jsiebens/terraform-aws-faasd) |Johan Siebens | Community | Active |
|
||||
| [faasd on Linode](https://github.com/itTrident/terraform-linode-faasd) | itTrident | Community | Active |
|
||||
| [faasd on Vultr](https://github.com/itTrident/terraform-vultr-faasd) | itTrident | Community | Active |
|
||||
| [faasd on Exoscale](https://github.com/itTrident/terraform-exoscale-faasd) | itTrident | Community | Active |
|
27
docs/bootstrap/cloud-config.tpl
Normal file
27
docs/bootstrap/cloud-config.tpl
Normal file
@ -0,0 +1,27 @@
|
||||
#cloud-config
|
||||
package_update: true
|
||||
|
||||
packages:
|
||||
- runc
|
||||
- git
|
||||
|
||||
runcmd:
|
||||
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz > /tmp/containerd.tar.gz && tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
|
||||
- curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.7.0/containerd.service | tee /etc/systemd/system/containerd.service
|
||||
- systemctl daemon-reload && systemctl start containerd
|
||||
- /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||
- mkdir -p /opt/cni/bin
|
||||
- curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz | tar -xz -C /opt/cni/bin
|
||||
- mkdir -p /go/src/github.com/openfaas/
|
||||
- mkdir -p /var/lib/faasd/secrets/
|
||||
- echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
|
||||
- echo admin > /var/lib/faasd/secrets/basic-auth-user
|
||||
- cd /go/src/github.com/openfaas/ && git clone --depth 1 --branch 0.16.2 https://github.com/openfaas/faasd
|
||||
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.16.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
||||
- systemctl status -l containerd --no-pager
|
||||
- journalctl -u faasd-provider --no-pager
|
||||
- systemctl status -l faasd-provider --no-pager
|
||||
- systemctl status -l faasd --no-pager
|
||||
- curl -sSLf https://cli.openfaas.com | sh
|
||||
- sleep 5 && journalctl -u faasd --no-pager
|
3
docs/bootstrap/digitalocean-terraform/.gitignore
vendored
Normal file
3
docs/bootstrap/digitalocean-terraform/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/.terraform/
|
||||
/terraform.tfstate
|
||||
/terraform.tfstate.backup
|
66
docs/bootstrap/digitalocean-terraform/.terraform.lock.hcl
generated
Normal file
66
docs/bootstrap/digitalocean-terraform/.terraform.lock.hcl
generated
Normal file
@ -0,0 +1,66 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/digitalocean/digitalocean" {
|
||||
version = "2.11.0"
|
||||
constraints = "2.11.0"
|
||||
hashes = [
|
||||
"h1:/qAnTOSP5KeZkF7wqLai34SKAs7aefulcUA3I8R7rRg=",
|
||||
"h1:PbXtjUfvxwmkycJ0Y9Dyn66Arrpk5L8/P381SXMx2O0=",
|
||||
"h1:lXLX9tmuxV7azTHd0xB0FAVrxyfBtotIz5LEJp8YUk0=",
|
||||
"zh:2191adc79bdfdb3b733e0619e4f391ae91c1631c5dafda42dab561d943651fa4",
|
||||
"zh:21a4f67e42dcdc10fbd7f8579247594844d09a469a3a54862d565913e4d6121d",
|
||||
"zh:557d98325fafcf2db91ea6d92f65373a48c4e995a1a7aeb57009661fee675250",
|
||||
"zh:68c0238cafc37433627e288fcd2c7e14f4f0afdd50b4f265d8d1f1addab6f19f",
|
||||
"zh:7e6d69720734455eb1c69880f049650276089b7fa09085e130d224abaeec887a",
|
||||
"zh:95bd93a696ec050c1cb5e724498fd12b1d69760d01e97c869be3252025691434",
|
||||
"zh:b1b075049e33aa08c032f41a497351c9894f16287a4449032d8b805bc6dcb596",
|
||||
"zh:ba91aa853372c828f808c09dbab2a5bc9493a7cf93210d1487f9637b2cac8ca4",
|
||||
"zh:bc43d27dfe014266697c2ac259f4311300391aa6aa7c5d23e382fe296df938d5",
|
||||
"zh:d3a04d2c76bfc1f46a117b1af7870a97353319ee8f924a37fe77861519f59525",
|
||||
"zh:d3da997c05a653df6cabb912c6c05ceb6bf77219b699f04daf44fd795c81c6ed",
|
||||
"zh:edd0659021b6634acf0f581d1be1985a81fcd1182e3ccb43de6eac6c43be9ab4",
|
||||
"zh:f588ace57b6c35d509ecaa7136e6a8049d227b0674104a1f958359b84862d8e3",
|
||||
"zh:f894ed195a3b9ebbfa1ba7c5d71be06df3a96d783ff064d22dd693ace34d638e",
|
||||
"zh:fb6b0d4b111fafdcb3bb9a7dbab88e2110a6ce6324de64ecf62933ee8b651ccf",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.1.0"
|
||||
hashes = [
|
||||
"h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=",
|
||||
"h1:EPIax4Ftp2SNdB9pUfoSjxoueDoLc/Ck3EUoeX0Dvsg=",
|
||||
"h1:rKYu5ZUbXwrLG1w81k7H3nce/Ys6yAxXhWcbtk36HjY=",
|
||||
"zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
|
||||
"zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
|
||||
"zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
|
||||
"zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
|
||||
"zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
|
||||
"zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
|
||||
"zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
|
||||
"zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
|
||||
"zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
|
||||
"zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
|
||||
"zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/template" {
|
||||
version = "2.2.0"
|
||||
hashes = [
|
||||
"h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=",
|
||||
"h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=",
|
||||
"h1:LN84cu+BZpVRvYlCzrbPfCRDaIelSyEx/W9Iwwgbnn4=",
|
||||
"zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386",
|
||||
"zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53",
|
||||
"zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603",
|
||||
"zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16",
|
||||
"zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776",
|
||||
"zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451",
|
||||
"zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae",
|
||||
"zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde",
|
||||
"zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d",
|
||||
"zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2",
|
||||
]
|
||||
}
|
48
docs/bootstrap/digitalocean-terraform/README.md
Normal file
48
docs/bootstrap/digitalocean-terraform/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Bootstrap faasd with TLS support on Digitalocean
|
||||
|
||||
1) [Sign up to DigitalOcean](https://www.digitalocean.com/?refcode=2962aa9e56a1&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=CopyPaste)
|
||||
2) [Download Terraform](https://www.terraform.io)
|
||||
3) Clone this gist using the URL from the address bar
|
||||
4) Run `terraform init`
|
||||
5) Configure terraform variables as needed by updating the `main.tfvars` file:
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------ | ------------------- | --------------- |
|
||||
| `do_token` | Digitalocean API token | None |
|
||||
| `do_domain` | Public domain used for the faasd gateway | None |
|
||||
| `do_subdomain` | Public subdomain used for the faasd gateway | `faasd` |
|
||||
| `letsencrypt_email` | Email used by when ordering TLS certificate from Letsencrypt | `""` |
|
||||
| `do_create_record` | When set to `true`, a new DNS record will be created. This works only if your domain (`do_domain`) is managed by Digitalocean | `false` |
|
||||
| `do_region` | Digitalocean region for creating the droplet | `fra1` |
|
||||
| `ssh_key_file` | Path to public SSH key file |`~/.ssh/id_rsa.pub` |
|
||||
|
||||
> Environment variables can also be used to set terraform variables when running the `terraform apply` command using the format `TF_VAR_name`.
|
||||
|
||||
6) Run `terraform apply`
|
||||
1) Add `-var-file=main.tfvars` if you have set the variables in `main.tfvars`.
|
||||
2) OR [use environment variables](https://www.terraform.io/docs/commands/environment-variables.html#tf_var_name) for setting the terraform variables when running the `apply` command
|
||||
|
||||
7) View the output for the login command and gateway URL i.e.
|
||||
|
||||
```
|
||||
droplet_ip = 178.128.39.201
|
||||
gateway_url = https://faasd.example.com/
|
||||
```
|
||||
|
||||
8) View the output for sensitive data via `terraform output` command
|
||||
|
||||
```bash
|
||||
terraform output login_cmd
|
||||
login_cmd = faas-cli login -g http://178.128.39.201:8080/ -p rvIU49CEcFcHmqxj
|
||||
|
||||
terraform output password
|
||||
password = rvIU49CEcFcHmqxj
|
||||
```
|
||||
|
||||
|
||||
9) Use your browser to access the OpenFaaS interface
|
||||
|
||||
Note that the user-data may take a couple of minutes to come up since it will be pulling in various components and preparing the machine.
|
||||
Also take into consideration the DNS propagation time for the new DNS record.
|
||||
|
||||
A single host with 1GB of RAM will be deployed for you, to remove at a later date simply use `terraform destroy`.
|
9
docs/bootstrap/digitalocean-terraform/cloud-config.tpl
Normal file
9
docs/bootstrap/digitalocean-terraform/cloud-config.tpl
Normal file
@ -0,0 +1,9 @@
|
||||
#! /bin/bash
|
||||
|
||||
mkdir -p /var/lib/faasd/secrets/
|
||||
echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
|
||||
|
||||
export FAASD_DOMAIN=${faasd_domain_name}
|
||||
export LETSENCRYPT_EMAIL=${letsencrypt_email}
|
||||
|
||||
curl -sfL https://raw.githubusercontent.com/openfaas/faasd/master/hack/install.sh | bash -s -
|
97
docs/bootstrap/digitalocean-terraform/main.tf
Normal file
97
docs/bootstrap/digitalocean-terraform/main.tf
Normal file
@ -0,0 +1,97 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0.4"
|
||||
required_providers {
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
version = "2.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "do_token" {
|
||||
description = "Digitalocean API token"
|
||||
}
|
||||
variable "do_domain" {
|
||||
description = "Your public domain"
|
||||
}
|
||||
variable "do_subdomain" {
|
||||
description = "Your public subdomain"
|
||||
default = "faasd"
|
||||
}
|
||||
variable "letsencrypt_email" {
|
||||
description = "Email used to order a certificate from Letsencrypt"
|
||||
}
|
||||
variable "do_create_record" {
|
||||
default = false
|
||||
description = "Whether to create a DNS record on Digitalocean"
|
||||
}
|
||||
variable "do_region" {
|
||||
default = "fra1"
|
||||
description = "The Digitalocean region where the faasd droplet will be created."
|
||||
}
|
||||
variable "ssh_key_file" {
|
||||
default = "~/.ssh/id_rsa.pub"
|
||||
description = "Path to the SSH public key file"
|
||||
}
|
||||
|
||||
provider "digitalocean" {
|
||||
token = var.do_token
|
||||
}
|
||||
|
||||
resource "random_password" "password" {
|
||||
length = 16
|
||||
special = true
|
||||
override_special = "_-#"
|
||||
}
|
||||
|
||||
data "template_file" "cloud_init" {
|
||||
template = file("cloud-config.tpl")
|
||||
vars = {
|
||||
gw_password = random_password.password.result,
|
||||
faasd_domain_name = "${var.do_subdomain}.${var.do_domain}"
|
||||
letsencrypt_email = var.letsencrypt_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "digitalocean_ssh_key" "faasd_ssh_key" {
|
||||
name = "ssh-key"
|
||||
public_key = file(var.ssh_key_file)
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "faasd" {
|
||||
region = var.do_region
|
||||
image = "ubuntu-18-04-x64"
|
||||
name = "faasd"
|
||||
size = "s-1vcpu-1gb"
|
||||
user_data = data.template_file.cloud_init.rendered
|
||||
ssh_keys = [
|
||||
digitalocean_ssh_key.faasd_ssh_key.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "digitalocean_record" "faasd" {
|
||||
domain = var.do_domain
|
||||
type = "A"
|
||||
name = var.do_subdomain
|
||||
value = digitalocean_droplet.faasd.ipv4_address
|
||||
# Only creates record if do_create_record is true
|
||||
count = var.do_create_record == true ? 1 : 0
|
||||
}
|
||||
|
||||
output "droplet_ip" {
|
||||
value = digitalocean_droplet.faasd.ipv4_address
|
||||
}
|
||||
|
||||
output "gateway_url" {
|
||||
value = "https://${var.do_subdomain}.${var.do_domain}/"
|
||||
}
|
||||
|
||||
output "password" {
|
||||
value = random_password.password.result
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "login_cmd" {
|
||||
value = "faas-cli login -g https://${var.do_subdomain}.${var.do_domain}/ -p ${random_password.password.result}"
|
||||
sensitive = true
|
||||
}
|
4
docs/bootstrap/digitalocean-terraform/main.tfvars
Normal file
4
docs/bootstrap/digitalocean-terraform/main.tfvars
Normal file
@ -0,0 +1,4 @@
|
||||
do_token = ""
|
||||
do_domain = ""
|
||||
do_subdomain = ""
|
||||
letsencrypt_email = ""
|
382
docs/media/logo.pdf
Normal file
382
docs/media/logo.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/media/social.png
Normal file
BIN
docs/media/social.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
92
go.mod
Normal file
92
go.mod
Normal file
@ -0,0 +1,92 @@
|
||||
module github.com/openfaas/faasd
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/alexellis/arkade v0.0.0-20230317160202-4d8f80c5b033
|
||||
github.com/alexellis/go-execute v0.5.0
|
||||
github.com/compose-spec/compose-go v0.0.0-20200528042322-36d8ce368e05
|
||||
github.com/containerd/containerd v1.7.0
|
||||
github.com/containerd/go-cni v1.1.9
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/docker/cli v23.0.1+incompatible
|
||||
github.com/docker/distribution v2.8.2+incompatible
|
||||
github.com/docker/docker v23.0.3+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1
|
||||
github.com/openfaas/faas-provider v0.21.0
|
||||
github.com/openfaas/faas/gateway v0.0.0-20230317100158-e44448c5dca2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vishvananda/netns v0.0.4
|
||||
golang.org/x/sys v0.6.0
|
||||
k8s.io/apimachinery v0.26.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.1.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.1 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.1.0 // indirect
|
||||
github.com/containernetworking/cni v1.1.2 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.14 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.16.3 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
)
|
401
go.sum
Normal file
401
go.sum
Normal file
@ -0,0 +1,401 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8=
|
||||
github.com/Microsoft/hcsshim v0.10.0-rc.7/go.mod h1:ILuwjA+kNW+MrN/w5un7n3mTqkwsFu4Bp05/okFUZlE=
|
||||
github.com/alexellis/arkade v0.0.0-20230317160202-4d8f80c5b033 h1:nq1a5V5MOoiLIKLOpB6HGeoRjdzKFoFQ6S1jMwIPdDY=
|
||||
github.com/alexellis/arkade v0.0.0-20230317160202-4d8f80c5b033/go.mod h1:T8i2qJQ5D13uTn+IgGCpC+ylJ3fb+bcnfrLppWcCuSo=
|
||||
github.com/alexellis/go-execute v0.5.0 h1:L8kgNlFzNbJov7jrInlaig7i6ZUSz/tYYmqvb8dyD0s=
|
||||
github.com/alexellis/go-execute v0.5.0/go.mod h1:AgHTcsCF9wrP0mMVTO8N+lFw1Biy71NybBOk8M+qgy8=
|
||||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
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/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
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/compose-spec/compose-go v0.0.0-20200528042322-36d8ce368e05 h1:3CoRXflT4IAdIpsFTqZBlLuHOkHYhBvW0iijkQKm4QY=
|
||||
github.com/compose-spec/compose-go v0.0.0-20200528042322-36d8ce368e05/go.mod h1:y75QUr1jcR5aFNf3Tj3dhwnujABGz6UaRrZ5qZwF1cc=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg=
|
||||
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc=
|
||||
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
||||
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
||||
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
|
||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||
github.com/containerd/go-cni v1.1.9 h1:ORi7P1dYzCwVM6XPN4n3CbkuOx/NZ2DOqy+SHRdo9rU=
|
||||
github.com/containerd/go-cni v1.1.9/go.mod h1:XYrZJ1d5W6E2VOvjffL3IZq0Dz6bsVlERHbekNK90PM=
|
||||
github.com/containerd/ttrpc v1.2.1 h1:VWv/Rzx023TBLv4WQ+9WPXlBG/s3rsRjY3i9AJ2BJdE=
|
||||
github.com/containerd/ttrpc v1.2.1/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak=
|
||||
github.com/containerd/typeurl/v2 v2.1.0 h1:yNAhJvbNEANt7ck48IlEGOxP7YAp6LLpGn5jZACDNIE=
|
||||
github.com/containerd/typeurl/v2 v2.1.0/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
|
||||
github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
|
||||
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
|
||||
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho=
|
||||
github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/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.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/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/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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo=
|
||||
github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
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/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
|
||||
github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/openfaas/faas-provider v0.21.0 h1:rnTy1Gpx+0YvqriQD8miQ2DfpOJXYZbV3VMqe8ri5lc=
|
||||
github.com/openfaas/faas-provider v0.21.0/go.mod h1:Farrp+9Med8LeK3aoYpqplMP8f5ebTILbCSLg2LPLZk=
|
||||
github.com/openfaas/faas/gateway v0.0.0-20230317100158-e44448c5dca2 h1:mSQlNX+etC2pd+yxZrkOj91vO0Vma75XHjI8+mKdS+A=
|
||||
github.com/openfaas/faas/gateway v0.0.0-20230317100158-e44448c5dca2/go.mod h1:iQNG+Up27CXDLHgIr9mcifTzaPD2mYOFTZW8MHxib7M=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
|
||||
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
|
||||
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
|
||||
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
|
||||
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
37
hack/build-containerd-arm64.sh
Normal file
37
hack/build-containerd-arm64.sh
Normal file
@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# See pre-reqs:
|
||||
# https://github.com/alexellis/containerd-arm
|
||||
|
||||
export ARCH="arm64"
|
||||
|
||||
if [ ! -d "/usr/local/go/bin" ]; then
|
||||
echo "Downloading Go.."
|
||||
|
||||
curl -SLsf https://golang.org/dl/go1.16.6.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
else
|
||||
echo "Go already present, skipping."
|
||||
fi
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
|
||||
go version
|
||||
|
||||
echo "Building containerd"
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/containerd
|
||||
cd $GOPATH/src/github.com/containerd
|
||||
git clone https://github.com/containerd/containerd
|
||||
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.7.0
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
||||
sudo containerd --version
|
@ -1,12 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
export ARCH="armv6l"
|
||||
echo "Downloading Go"
|
||||
# See pre-reqs:
|
||||
# https://github.com/alexellis/containerd-arm
|
||||
|
||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
export ARCH="arm64"
|
||||
|
||||
if [ ! -d "/usr/local/go/bin" ]; then
|
||||
echo "Downloading Go.."
|
||||
|
||||
curl -SLsf https://golang.org/dl/go1.16.6.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
else
|
||||
echo "Go already present, skipping."
|
||||
fi
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
@ -21,7 +29,7 @@ git clone https://github.com/containerd/containerd
|
||||
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.3.2
|
||||
git checkout v1.7.0
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
@ -1,12 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
export ARCH="amd64"
|
||||
echo "Downloading Go"
|
||||
|
||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
# See pre-reqs:
|
||||
# https://github.com/alexellis/containerd-arm
|
||||
|
||||
export ARCH="arm64"
|
||||
|
||||
if [ ! -d "/usr/local/go/bin" ]; then
|
||||
echo "Downloading Go.."
|
||||
|
||||
curl -SLsf https://golang.org/dl/go1.16.6.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
else
|
||||
echo "Go already present, skipping."
|
||||
fi
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
@ -21,7 +30,7 @@ git clone https://github.com/containerd/containerd
|
||||
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.3.2
|
||||
git checkout v1.7.0
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=faasd-containerd
|
||||
|
||||
[Service]
|
||||
MemoryLimit=500M
|
||||
ExecStart=/usr/local/bin/faas-containerd
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
WorkingDirectory=/usr/local/bin/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
15
hack/faasd-provider.service
Normal file
15
hack/faasd-provider.service
Normal file
@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=faasd-provider
|
||||
|
||||
[Service]
|
||||
MemoryLimit=500M
|
||||
Environment="secret_mount_path={{.SecretMountPath}}"
|
||||
Environment="basic_auth=true"
|
||||
Environment="hosts_dir=/var/lib/faasd"
|
||||
ExecStart=/usr/local/bin/faasd provider
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
WorkingDirectory={{.Cwd}}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=faasd
|
||||
After=faas-containerd.service
|
||||
After=faasd-provider.service
|
||||
|
||||
[Service]
|
||||
MemoryLimit=500M
|
||||
|
190
hack/install.sh
Executable file
190
hack/install.sh
Executable file
@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright OpenFaaS Author(s) 2022
|
||||
|
||||
set -e -x -o pipefail
|
||||
|
||||
export OWNER="openfaas"
|
||||
export REPO="faasd"
|
||||
|
||||
# On CentOS /usr/local/bin is not included in the PATH when using sudo.
|
||||
# Running arkade with sudo on CentOS requires the full path
|
||||
# to the arkade binary.
|
||||
export ARKADE=/usr/local/bin/arkade
|
||||
|
||||
# When running as a startup script (cloud-init), the HOME variable is not always set.
|
||||
# As it is required for arkade to properly download tools,
|
||||
# set the variable to /usr/local so arkade will download binaries to /usr/local/.arkade
|
||||
if [ -z "${HOME}" ]; then
|
||||
export HOME=/usr/local
|
||||
fi
|
||||
|
||||
version=""
|
||||
|
||||
echo "Finding latest version from GitHub"
|
||||
version=$(curl -sI https://github.com/$OWNER/$REPO/releases/latest | grep -i "location:" | awk -F"/" '{ printf "%s", $NF }' | tr -d '\r')
|
||||
echo "$version"
|
||||
|
||||
if [ ! $version ]; then
|
||||
echo "Failed while attempting to get latest version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=sudo
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
verify_system() {
|
||||
if ! [ -d /run/systemd ]; then
|
||||
fatal 'Can not find systemd to use as a process supervisor for faasd'
|
||||
fi
|
||||
}
|
||||
|
||||
has_yum() {
|
||||
[ -n "$(command -v yum)" ]
|
||||
}
|
||||
|
||||
has_apt_get() {
|
||||
[ -n "$(command -v apt-get)" ]
|
||||
}
|
||||
|
||||
has_pacman() {
|
||||
[ -n "$(command -v pacman)" ]
|
||||
}
|
||||
|
||||
install_required_packages() {
|
||||
if $(has_apt_get); then
|
||||
# Debian bullseye is missing iptables. Added to required packages
|
||||
# to get it working in raspberry pi. No such known issues in
|
||||
# other distros. Hence, adding only to this block.
|
||||
# reference: https://github.com/openfaas/faasd/pull/237
|
||||
$SUDO apt-get update -y
|
||||
$SUDO apt-get install -y curl runc bridge-utils iptables
|
||||
elif $(has_yum); then
|
||||
$SUDO yum check-update -y
|
||||
$SUDO yum install -y curl runc iptables-services
|
||||
elif $(has_pacman); then
|
||||
$SUDO pacman -Syy
|
||||
$SUDO pacman -Sy curl runc bridge-utils
|
||||
else
|
||||
fatal "Could not find apt-get, yum, or pacman. Cannot install dependencies on this OS."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_arkade(){
|
||||
curl -sLS https://get.arkade.dev | $SUDO sh
|
||||
arkade --help
|
||||
}
|
||||
|
||||
install_cni_plugins() {
|
||||
cni_version=v0.9.1
|
||||
$SUDO $ARKADE system install cni --version ${cni_version} --path /opt/cni/bin --progress=false
|
||||
}
|
||||
|
||||
install_containerd() {
|
||||
CONTAINERD_VER=1.7.0
|
||||
$SUDO systemctl unmask containerd || :
|
||||
|
||||
arch=$(uname -m)
|
||||
if [ $arch == "armv7l" ]; then
|
||||
$SUDO curl -fSLs "https://github.com/alexellis/containerd-arm/releases/download/v${CONTAINERD_VER}/containerd-${CONTAINERD_VER}-linux-armhf.tar.gz" --output "/tmp/containerd.tar.gz"
|
||||
$SUDO tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/
|
||||
$SUDO curl -fSLs https://raw.githubusercontent.com/containerd/containerd/v${CONTAINERD_VER}/containerd.service --output "/etc/systemd/system/containerd.service"
|
||||
$SUDO systemctl enable containerd
|
||||
$SUDO systemctl start containerd
|
||||
else
|
||||
$SUDO $ARKADE system install containerd --systemd --version v${CONTAINERD_VER} --progress=false
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
}
|
||||
|
||||
install_faasd() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64 | amd64)
|
||||
suffix=""
|
||||
;;
|
||||
aarch64)
|
||||
suffix=-arm64
|
||||
;;
|
||||
armv7l)
|
||||
suffix=-armhf
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture $arch"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
$SUDO curl -fSLs "https://github.com/openfaas/faasd/releases/download/${version}/faasd${suffix}" --output "/usr/local/bin/faasd"
|
||||
$SUDO chmod a+x "/usr/local/bin/faasd"
|
||||
|
||||
mkdir -p /tmp/faasd-${version}-installation/hack
|
||||
cd /tmp/faasd-${version}-installation
|
||||
$SUDO curl -fSLs "https://raw.githubusercontent.com/openfaas/faasd/${version}/docker-compose.yaml" --output "docker-compose.yaml"
|
||||
$SUDO curl -fSLs "https://raw.githubusercontent.com/openfaas/faasd/${version}/prometheus.yml" --output "prometheus.yml"
|
||||
$SUDO curl -fSLs "https://raw.githubusercontent.com/openfaas/faasd/${version}/resolv.conf" --output "resolv.conf"
|
||||
$SUDO curl -fSLs "https://raw.githubusercontent.com/openfaas/faasd/${version}/hack/faasd-provider.service" --output "hack/faasd-provider.service"
|
||||
$SUDO curl -fSLs "https://raw.githubusercontent.com/openfaas/faasd/${version}/hack/faasd.service" --output "hack/faasd.service"
|
||||
$SUDO /usr/local/bin/faasd install
|
||||
}
|
||||
|
||||
install_caddy() {
|
||||
if [ ! -z "${FAASD_DOMAIN}" ]; then
|
||||
CADDY_VER=v2.4.3
|
||||
arkade get --progress=false caddy -v ${CADDY_VER}
|
||||
|
||||
# /usr/bin/caddy is specified in the upstream service file.
|
||||
$SUDO install -m 755 $HOME/.arkade/bin/caddy /usr/bin/caddy
|
||||
|
||||
$SUDO curl -fSLs https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service --output /etc/systemd/system/caddy.service
|
||||
|
||||
$SUDO mkdir -p /etc/caddy
|
||||
$SUDO mkdir -p /var/lib/caddy
|
||||
|
||||
if $(id caddy >/dev/null 2>&1); then
|
||||
echo "User caddy already exists."
|
||||
else
|
||||
$SUDO useradd --system --home /var/lib/caddy --shell /bin/false caddy
|
||||
fi
|
||||
|
||||
$SUDO tee /etc/caddy/Caddyfile >/dev/null <<EOF
|
||||
{
|
||||
email "${LETSENCRYPT_EMAIL}"
|
||||
}
|
||||
|
||||
${FAASD_DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
}
|
||||
EOF
|
||||
|
||||
$SUDO chown --recursive caddy:caddy /var/lib/caddy
|
||||
$SUDO chown --recursive caddy:caddy /etc/caddy
|
||||
|
||||
$SUDO systemctl enable caddy
|
||||
$SUDO systemctl start caddy
|
||||
else
|
||||
echo "Skipping caddy installation as FAASD_DOMAIN."
|
||||
fi
|
||||
}
|
||||
|
||||
install_faas_cli() {
|
||||
arkade get --progress=false faas-cli
|
||||
$SUDO install -m 755 $HOME/.arkade/bin/faas-cli /usr/local/bin/
|
||||
}
|
||||
|
||||
verify_system
|
||||
install_required_packages
|
||||
|
||||
$SUDO /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||
echo "net.ipv4.conf.all.forwarding=1" | $SUDO tee -a /etc/sysctl.conf
|
||||
|
||||
install_arkade
|
||||
install_cni_plugins
|
||||
install_containerd
|
||||
install_faas_cli
|
||||
install_faasd
|
||||
install_caddy
|
18
main.go
18
main.go
@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/alexellis/faasd/cmd"
|
||||
"github.com/openfaas/faasd/cmd"
|
||||
)
|
||||
|
||||
// These values will be injected into these variables at the build time.
|
||||
@ -15,6 +16,21 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
if _, ok := os.LookupEnv("CONTAINER_ID"); ok {
|
||||
collect := cmd.RootCommand()
|
||||
collect.SetArgs([]string{"collect"})
|
||||
collect.SilenceUsage = true
|
||||
collect.SilenceErrors = true
|
||||
|
||||
err := collect.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err := cmd.Execute(Version, GitCommit); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
262
pkg/cninetwork/cni_network.go
Normal file
262
pkg/cninetwork/cni_network.go
Normal file
@ -0,0 +1,262 @@
|
||||
package cninetwork
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// CNIBinDir describes the directory where the CNI binaries are stored
|
||||
CNIBinDir = "/opt/cni/bin"
|
||||
|
||||
// CNIConfDir describes the directory where the CNI plugin's configuration is stored
|
||||
CNIConfDir = "/etc/cni/net.d"
|
||||
|
||||
// NetNSPathFmt gives the path to the a process network namespace, given the pid
|
||||
NetNSPathFmt = "/proc/%d/ns/net"
|
||||
|
||||
// CNIDataDir is the directory CNI stores allocated IP for containers
|
||||
CNIDataDir = "/var/run/cni"
|
||||
|
||||
// defaultCNIConfFilename is the vanity filename of default CNI configuration file
|
||||
defaultCNIConfFilename = "10-openfaas.conflist"
|
||||
|
||||
// defaultNetworkName names the "docker-bridge"-like CNI plugin-chain installed when no other CNI configuration is present.
|
||||
// This value appears in iptables comments created by CNI.
|
||||
defaultNetworkName = "openfaas-cni-bridge"
|
||||
|
||||
// defaultBridgeName is the default bridge device name used in the defaultCNIConf
|
||||
defaultBridgeName = "openfaas0"
|
||||
|
||||
// defaultSubnet is the default subnet used in the defaultCNIConf -- this value is set to not collide with common container networking subnets:
|
||||
defaultSubnet = "10.62.0.0/16"
|
||||
|
||||
// defaultIfPrefix is the interface name to be created in the container
|
||||
defaultIfPrefix = "eth"
|
||||
)
|
||||
|
||||
// defaultCNIConf is a CNI configuration that enables network access to containers (docker-bridge style)
|
||||
var defaultCNIConf = fmt.Sprintf(`
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "%s",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "%s",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "%s",
|
||||
"dataDir": "%s",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall"
|
||||
}
|
||||
]
|
||||
}
|
||||
`, defaultNetworkName, defaultBridgeName, defaultSubnet, CNIDataDir)
|
||||
|
||||
// InitNetwork writes configlist file and initializes CNI network
|
||||
func InitNetwork() (gocni.CNI, error) {
|
||||
|
||||
log.Printf("Writing network config...\n")
|
||||
if !dirExists(CNIConfDir) {
|
||||
if err := os.MkdirAll(CNIConfDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("cannot create directory: %s", CNIConfDir)
|
||||
}
|
||||
}
|
||||
|
||||
netConfig := path.Join(CNIConfDir, defaultCNIConfFilename)
|
||||
if err := ioutil.WriteFile(netConfig, []byte(defaultCNIConf), 644); err != nil {
|
||||
return nil, fmt.Errorf("cannot write network config: %s", defaultCNIConfFilename)
|
||||
}
|
||||
|
||||
// Initialize CNI library
|
||||
cni, err := gocni.New(
|
||||
gocni.WithPluginConfDir(CNIConfDir),
|
||||
gocni.WithPluginDir([]string{CNIBinDir}),
|
||||
gocni.WithInterfacePrefix(defaultIfPrefix),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing cni: %s", err)
|
||||
}
|
||||
|
||||
// Load the cni configuration
|
||||
if err := cni.Load(gocni.WithLoNetwork, gocni.WithConfListFile(filepath.Join(CNIConfDir, defaultCNIConfFilename))); err != nil {
|
||||
return nil, fmt.Errorf("failed to load cni configuration: %v", err)
|
||||
}
|
||||
|
||||
return cni, nil
|
||||
}
|
||||
|
||||
// CreateCNINetwork creates a CNI network interface and attaches it to the context
|
||||
func CreateCNINetwork(ctx context.Context, cni gocni.CNI, task containerd.Task, labels map[string]string) (*gocni.CNIResult, error) {
|
||||
id := netID(task)
|
||||
netns := netNamespace(task)
|
||||
result, err := cni.Setup(ctx, id, netns, gocni.WithLabels(labels))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to setup network for task %q: %v", id, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeleteCNINetwork deletes a CNI network based on task ID and Pid
|
||||
func DeleteCNINetwork(ctx context.Context, cni gocni.CNI, client *containerd.Client, name string) error {
|
||||
container, containerErr := client.LoadContainer(ctx, name)
|
||||
if containerErr == nil {
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
log.Printf("[Delete] unable to find task for container: %s\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("[Delete] removing CNI network for: %s\n", task.ID())
|
||||
|
||||
id := netID(task)
|
||||
netns := netNamespace(task)
|
||||
|
||||
if err := cni.Remove(ctx, id, netns); err != nil {
|
||||
return errors.Wrapf(err, "Failed to remove network for task: %q, %v", id, err)
|
||||
}
|
||||
log.Printf("[Delete] removed: %s from namespace: %s, ID: %s\n", name, netns, id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrapf(containerErr, "Unable to find container: %s, error: %s", name, containerErr)
|
||||
}
|
||||
|
||||
// GetIPAddress returns the IP address from container based on container name and PID
|
||||
func GetIPAddress(container string, PID uint32) (string, error) {
|
||||
CNIDir := path.Join(CNIDataDir, defaultNetworkName)
|
||||
|
||||
files, err := ioutil.ReadDir(CNIDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read CNI dir for container %s: %v", container, err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
// each fileName is an IP address
|
||||
fileName := file.Name()
|
||||
|
||||
resultsFile := filepath.Join(CNIDir, fileName)
|
||||
found, err := isCNIResultForPID(resultsFile, container, PID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if found {
|
||||
return fileName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to get IP address for container: %s", container)
|
||||
}
|
||||
|
||||
// isCNIResultForPID confirms if the CNI result file contains the
|
||||
// process name, PID and interface name
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// /var/run/cni/openfaas-cni-bridge/10.62.0.2
|
||||
//
|
||||
// nats-621
|
||||
// eth1
|
||||
func isCNIResultForPID(fileName, container string, PID uint32) (bool, error) {
|
||||
found := false
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to open CNI IP file for %s: %v", fileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
reader := bufio.NewReader(f)
|
||||
processLine, _ := reader.ReadString('\n')
|
||||
if strings.Contains(processLine, fmt.Sprintf("%s-%d", container, PID)) {
|
||||
ethNameLine, _ := reader.ReadString('\n')
|
||||
if strings.Contains(ethNameLine, defaultIfPrefix) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// CNIGateway returns the gateway for default subnet
|
||||
func CNIGateway() (string, error) {
|
||||
ip, _, err := net.ParseCIDR(defaultSubnet)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error formatting gateway for network %s", defaultSubnet)
|
||||
}
|
||||
ip = ip.To4()
|
||||
ip[3] = 1
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
// netID generates the network IF based on task name and task PID
|
||||
func netID(task containerd.Task) string {
|
||||
return fmt.Sprintf("%s-%d", task.ID(), task.Pid())
|
||||
}
|
||||
|
||||
// netNamespace generates the namespace path based on task PID.
|
||||
func netNamespace(task containerd.Task) string {
|
||||
return fmt.Sprintf(NetNSPathFmt, task.Pid())
|
||||
}
|
||||
|
||||
func dirEmpty(dirname string) (isEmpty bool) {
|
||||
if !dirExists(dirname) {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
// If the first file is EOF, the directory is empty
|
||||
if _, err = f.Readdir(1); err == io.EOF {
|
||||
isEmpty = true
|
||||
}
|
||||
return isEmpty
|
||||
}
|
||||
|
||||
func dirExists(dirname string) bool {
|
||||
exists, info := pathExists(dirname)
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func pathExists(path string) (bool, os.FileInfo) {
|
||||
info, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, info
|
||||
}
|
63
pkg/cninetwork/cni_network_test.go
Normal file
63
pkg/cninetwork/cni_network_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package cninetwork
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_isCNIResultForPID_Found(t *testing.T) {
|
||||
body := `nats-621
|
||||
eth1`
|
||||
fileName := `10.62.0.2`
|
||||
container := "nats"
|
||||
PID := uint32(621)
|
||||
fullPath := filepath.Join(os.TempDir(), fileName)
|
||||
|
||||
err := ioutil.WriteFile(fullPath, []byte(body), 0700)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer func() {
|
||||
os.Remove(fullPath)
|
||||
}()
|
||||
|
||||
got, err := isCNIResultForPID(fullPath, container, PID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
want := true
|
||||
if got != want {
|
||||
t.Fatalf("want %v, but got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isCNIResultForPID_NoMatch(t *testing.T) {
|
||||
body := `nats-621
|
||||
eth1`
|
||||
fileName := `10.62.0.3`
|
||||
container := "gateway"
|
||||
PID := uint32(621)
|
||||
fullPath := filepath.Join(os.TempDir(), fileName)
|
||||
|
||||
err := ioutil.WriteFile(fullPath, []byte(body), 0700)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer func() {
|
||||
os.Remove(fullPath)
|
||||
}()
|
||||
|
||||
got, err := isCNIResultForPID(fullPath, container, PID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
want := false
|
||||
if got != want {
|
||||
t.Fatalf("want %v, but got %v", want, got)
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
// Copyright Weaveworks
|
||||
// github.com/weaveworks/weave/net
|
||||
|
||||
package weave
|
||||
package cninetwork
|
||||
|
||||
import (
|
||||
"errors"
|
16
pkg/constants.go
Normal file
16
pkg/constants.go
Normal file
@ -0,0 +1,16 @@
|
||||
package pkg
|
||||
|
||||
const (
|
||||
// DefaultFunctionNamespace is the default containerd namespace functions are created
|
||||
DefaultFunctionNamespace = "openfaas-fn"
|
||||
|
||||
// NamespaceLabel indicates that a namespace is managed by faasd
|
||||
NamespaceLabel = "openfaas"
|
||||
|
||||
// FaasdNamespace is the containerd namespace services are created
|
||||
FaasdNamespace = "openfaas"
|
||||
|
||||
faasServicesPullAlways = false
|
||||
|
||||
defaultSnapshotter = "overlayfs"
|
||||
)
|
106
pkg/depgraph/depgraph.go
Normal file
106
pkg/depgraph/depgraph.go
Normal file
@ -0,0 +1,106 @@
|
||||
package depgraph
|
||||
|
||||
import "log"
|
||||
|
||||
// Node represents a node in a Graph with
|
||||
// 0 to many edges
|
||||
type Node struct {
|
||||
Name string
|
||||
Edges []*Node
|
||||
}
|
||||
|
||||
// Graph is a collection of nodes
|
||||
type Graph struct {
|
||||
nodes []*Node
|
||||
}
|
||||
|
||||
func NewDepgraph() *Graph {
|
||||
return &Graph{
|
||||
nodes: []*Node{},
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes returns the nodes within the graph
|
||||
func (g *Graph) Nodes() []*Node {
|
||||
return g.nodes
|
||||
}
|
||||
|
||||
// Contains returns true if the target Node is found
|
||||
// in its list
|
||||
func (g *Graph) Contains(target *Node) bool {
|
||||
for _, g := range g.nodes {
|
||||
if g.Name == target.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Add places a Node into the current Graph
|
||||
func (g *Graph) Add(target *Node) {
|
||||
g.nodes = append(g.nodes, target)
|
||||
}
|
||||
|
||||
// Remove deletes a target Node reference from the
|
||||
// list of nodes in the graph
|
||||
func (g *Graph) Remove(target *Node) {
|
||||
var found *int
|
||||
for i, n := range g.nodes {
|
||||
if n == target {
|
||||
found = &i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found != nil {
|
||||
g.nodes = append(g.nodes[:*found], g.nodes[*found+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve retruns a list of node names in order of their dependencies.
|
||||
// A use case may be for determining the correct order to install
|
||||
// software packages, or to start services.
|
||||
// Based upon the algorithm described by Ferry Boender in the following article
|
||||
// https://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/
|
||||
func (g *Graph) Resolve() []string {
|
||||
resolved := &Graph{}
|
||||
unresolved := &Graph{}
|
||||
for _, node := range g.nodes {
|
||||
resolve(node, resolved, unresolved)
|
||||
}
|
||||
|
||||
order := []string{}
|
||||
|
||||
for _, node := range resolved.Nodes() {
|
||||
order = append(order, node.Name)
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
// resolve mutates the resolved graph for a given starting
|
||||
// node. The unresolved graph is used to detect a circular graph
|
||||
// error and will throw a panic. This can be caught with a resolve
|
||||
// in a go routine.
|
||||
func resolve(node *Node, resolved, unresolved *Graph) {
|
||||
unresolved.Add(node)
|
||||
|
||||
for _, edge := range node.Edges {
|
||||
|
||||
if !resolved.Contains(edge) && unresolved.Contains(edge) {
|
||||
log.Panicf("edge: %s may be a circular dependency", edge.Name)
|
||||
}
|
||||
|
||||
resolve(edge, resolved, unresolved)
|
||||
}
|
||||
|
||||
for _, r := range resolved.nodes {
|
||||
if r.Name == node.Name {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resolved.Add(node)
|
||||
unresolved.Remove(node)
|
||||
}
|
41
pkg/depgraph/depgraph_test.go
Normal file
41
pkg/depgraph/depgraph_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package depgraph
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_RemoveMedial(t *testing.T) {
|
||||
g := Graph{nodes: []*Node{}}
|
||||
a := &Node{Name: "A"}
|
||||
b := &Node{Name: "B"}
|
||||
c := &Node{Name: "C"}
|
||||
|
||||
g.nodes = append(g.nodes, a)
|
||||
g.nodes = append(g.nodes, b)
|
||||
g.nodes = append(g.nodes, c)
|
||||
|
||||
g.Remove(b)
|
||||
|
||||
for _, n := range g.nodes {
|
||||
if n.Name == b.Name {
|
||||
t.Fatalf("Found deleted node: %s", n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemoveFinal(t *testing.T) {
|
||||
g := Graph{nodes: []*Node{}}
|
||||
a := &Node{Name: "A"}
|
||||
b := &Node{Name: "B"}
|
||||
c := &Node{Name: "C"}
|
||||
|
||||
g.nodes = append(g.nodes, a)
|
||||
g.nodes = append(g.nodes, b)
|
||||
g.nodes = append(g.nodes, c)
|
||||
|
||||
g.Remove(c)
|
||||
|
||||
for _, n := range g.nodes {
|
||||
if n.Name == c.Name {
|
||||
t.Fatalf("Found deleted node: %s", c.Name)
|
||||
}
|
||||
}
|
||||
}
|
41
pkg/deployment_order.go
Normal file
41
pkg/deployment_order.go
Normal file
@ -0,0 +1,41 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/openfaas/faasd/pkg/depgraph"
|
||||
)
|
||||
|
||||
func buildDeploymentOrder(svcs []Service) []string {
|
||||
|
||||
graph := buildServiceGraph(svcs)
|
||||
|
||||
order := graph.Resolve()
|
||||
|
||||
log.Printf("Start-up order:\n")
|
||||
for _, node := range order {
|
||||
log.Printf("- %s\n", node)
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
func buildServiceGraph(svcs []Service) *depgraph.Graph {
|
||||
graph := depgraph.NewDepgraph()
|
||||
|
||||
nodeMap := map[string]*depgraph.Node{}
|
||||
for _, s := range svcs {
|
||||
n := &depgraph.Node{Name: s.Name}
|
||||
nodeMap[s.Name] = n
|
||||
graph.Add(n)
|
||||
|
||||
}
|
||||
|
||||
for _, s := range svcs {
|
||||
for _, d := range s.DependsOn {
|
||||
nodeMap[s.Name].Edges = append(nodeMap[s.Name].Edges, nodeMap[d])
|
||||
}
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
224
pkg/deployment_order_test.go
Normal file
224
pkg/deployment_order_test.go
Normal file
@ -0,0 +1,224 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_buildDeploymentOrder_ARequiresB(t *testing.T) {
|
||||
svcs := []Service{
|
||||
{
|
||||
Name: "A",
|
||||
DependsOn: []string{"B"},
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
if len(order) < len(svcs) {
|
||||
t.Fatalf("length of order too short: %d", len(order))
|
||||
}
|
||||
|
||||
got := order[0]
|
||||
want := "B"
|
||||
if got != want {
|
||||
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildDeploymentOrder_ARequiresBAndC(t *testing.T) {
|
||||
svcs := []Service{
|
||||
{
|
||||
Name: "A",
|
||||
DependsOn: []string{"B", "C"},
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
if len(order) < len(svcs) {
|
||||
t.Fatalf("length of order too short: %d", len(order))
|
||||
}
|
||||
|
||||
a := indexStr(order, "a")
|
||||
b := indexStr(order, "b")
|
||||
c := indexStr(order, "c")
|
||||
|
||||
if a > b {
|
||||
t.Fatalf("a should be after dependencies")
|
||||
}
|
||||
if a > c {
|
||||
t.Fatalf("a should be after dependencies")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_buildDeploymentOrder_ARequiresBRequiresC(t *testing.T) {
|
||||
svcs := []Service{
|
||||
{
|
||||
Name: "A",
|
||||
DependsOn: []string{"B"},
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
DependsOn: []string{"C"},
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
if len(order) < len(svcs) {
|
||||
t.Fatalf("length of order too short: %d", len(order))
|
||||
}
|
||||
|
||||
got := order[0]
|
||||
want := "C"
|
||||
if got != want {
|
||||
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
|
||||
}
|
||||
got = order[1]
|
||||
want = "B"
|
||||
if got != want {
|
||||
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
|
||||
}
|
||||
got = order[2]
|
||||
want = "A"
|
||||
if got != want {
|
||||
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildDeploymentOrderCircularARequiresBRequiresA(t *testing.T) {
|
||||
svcs := []Service{
|
||||
{
|
||||
Name: "A",
|
||||
DependsOn: []string{"B"},
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
DependsOn: []string{"A"},
|
||||
},
|
||||
}
|
||||
|
||||
defer func() { recover() }()
|
||||
|
||||
buildDeploymentOrder(svcs)
|
||||
|
||||
t.Fatalf("did not panic as expected")
|
||||
}
|
||||
|
||||
func Test_buildDeploymentOrderComposeFile(t *testing.T) {
|
||||
// svcs := []Service{}
|
||||
file, err := LoadComposeFileWithArch("../", "docker-compose.yaml", func() (string, string) {
|
||||
return "x86_64", "Linux"
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unable to load compose file: %s", err)
|
||||
}
|
||||
|
||||
svcs, err := ParseCompose(file)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse compose file: %s", err)
|
||||
}
|
||||
|
||||
for _, s := range svcs {
|
||||
log.Printf("Service: %s\n", s.Name)
|
||||
for _, d := range s.DependsOn {
|
||||
log.Printf("Link: %s => %s\n", s.Name, d)
|
||||
}
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
if len(order) < len(svcs) {
|
||||
t.Fatalf("length of order too short: %d", len(order))
|
||||
}
|
||||
|
||||
queueWorker := indexStr(order, "queue-worker")
|
||||
nats := indexStr(order, "nats")
|
||||
gateway := indexStr(order, "gateway")
|
||||
prometheus := indexStr(order, "prometheus")
|
||||
|
||||
if prometheus > gateway {
|
||||
t.Fatalf("Prometheus order was after gateway, and should be before")
|
||||
}
|
||||
if nats > gateway {
|
||||
t.Fatalf("NATS order was after gateway, and should be before")
|
||||
}
|
||||
if nats > queueWorker {
|
||||
t.Fatalf("NATS order was after queue-worker, and should be before")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildDeploymentOrderOpenFaaS(t *testing.T) {
|
||||
svcs := []Service{
|
||||
{
|
||||
Name: "queue-worker",
|
||||
DependsOn: []string{"nats"},
|
||||
},
|
||||
{
|
||||
Name: "prometheus",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
{
|
||||
Name: "gateway",
|
||||
DependsOn: []string{"prometheus", "nats", "basic-auth-plugin"},
|
||||
},
|
||||
{
|
||||
Name: "basic-auth-plugin",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
{
|
||||
Name: "nats",
|
||||
DependsOn: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
if len(order) < len(svcs) {
|
||||
t.Fatalf("length of order too short: %d", len(order))
|
||||
}
|
||||
|
||||
queueWorker := indexStr(order, "queue-worker")
|
||||
nats := indexStr(order, "nats")
|
||||
gateway := indexStr(order, "gateway")
|
||||
prometheus := indexStr(order, "prometheus")
|
||||
|
||||
if prometheus > gateway {
|
||||
t.Fatalf("Prometheus order was after gateway, and should be before")
|
||||
}
|
||||
if nats > gateway {
|
||||
t.Fatalf("NATS order was after gateway, and should be before")
|
||||
}
|
||||
if nats > queueWorker {
|
||||
t.Fatalf("NATS order was after queue-worker, and should be before")
|
||||
}
|
||||
}
|
||||
|
||||
func indexStr(st []string, t string) int {
|
||||
for n, s := range st {
|
||||
if s == t {
|
||||
return n
|
||||
}
|
||||
|
||||
}
|
||||
return -1
|
||||
}
|
104
pkg/local_resolver.go
Normal file
104
pkg/local_resolver.go
Normal file
@ -0,0 +1,104 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LocalResolver provides hostname to IP look-up for faasd core services
|
||||
type LocalResolver struct {
|
||||
Path string
|
||||
Map map[string]string
|
||||
Mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewLocalResolver creates a new resolver for reading from a hosts file
|
||||
func NewLocalResolver(path string) Resolver {
|
||||
return &LocalResolver{
|
||||
Path: path,
|
||||
Mutex: &sync.RWMutex{},
|
||||
Map: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Start polling the disk for the hosts file in Path
|
||||
func (l *LocalResolver) Start() {
|
||||
var lastStat os.FileInfo
|
||||
|
||||
for {
|
||||
rebuild := false
|
||||
if info, err := os.Stat(l.Path); err == nil {
|
||||
if lastStat == nil {
|
||||
rebuild = true
|
||||
} else {
|
||||
if !lastStat.ModTime().Equal(info.ModTime()) {
|
||||
rebuild = true
|
||||
}
|
||||
}
|
||||
lastStat = info
|
||||
}
|
||||
|
||||
if rebuild {
|
||||
log.Printf("Resolver rebuilding map")
|
||||
l.rebuild()
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LocalResolver) rebuild() {
|
||||
l.Mutex.Lock()
|
||||
defer l.Mutex.Unlock()
|
||||
|
||||
fileData, fileErr := ioutil.ReadFile(l.Path)
|
||||
if fileErr != nil {
|
||||
log.Printf("resolver rebuild error: %s", fileErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(string(fileData), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
index := strings.Index(line, "\t")
|
||||
|
||||
if len(line) > 0 && index > -1 {
|
||||
ip := line[:index]
|
||||
host := line[index+1:]
|
||||
log.Printf("Resolver: %q=%q", host, ip)
|
||||
l.Map[host] = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get resolves a hostname to an IP, or timesout after the duration has passed
|
||||
func (l *LocalResolver) Get(upstream string, got chan<- string, timeout time.Duration) {
|
||||
start := time.Now()
|
||||
for {
|
||||
if val := l.get(upstream); len(val) > 0 {
|
||||
got <- val
|
||||
break
|
||||
}
|
||||
|
||||
if time.Now().After(start.Add(timeout)) {
|
||||
log.Printf("Timed out after %s getting host %q", timeout.String(), upstream)
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LocalResolver) get(upstream string) string {
|
||||
l.Mutex.RLock()
|
||||
defer l.Mutex.RUnlock()
|
||||
|
||||
if val, ok := l.Map[upstream]; ok {
|
||||
return val
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
183
pkg/logs/requestor.go
Normal file
183
pkg/logs/requestor.go
Normal file
@ -0,0 +1,183 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/openfaas/faas-provider/logs"
|
||||
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
)
|
||||
|
||||
type requester struct{}
|
||||
|
||||
// New returns a new journalctl log Requester
|
||||
func New() logs.Requester {
|
||||
return &requester{}
|
||||
}
|
||||
|
||||
// Query submits a log request to the actual logging system.
|
||||
func (r *requester) Query(ctx context.Context, req logs.Request) (<-chan logs.Message, error) {
|
||||
_, err := exec.LookPath("journalctl")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not find journalctl: %w", err)
|
||||
}
|
||||
|
||||
cmd := buildCmd(ctx, req)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create journalctl pipe: %w", err)
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create journalctl err pipe: %w", err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create journalctl: %w", err)
|
||||
}
|
||||
|
||||
// call start and get the stdout prior to streaming so that we can return a meaningful
|
||||
// error for as long as possible. If the cmd starts correctly, we are highly likely to
|
||||
// succeed anyway
|
||||
msgs := make(chan logs.Message)
|
||||
go streamLogs(ctx, cmd, stdout, msgs)
|
||||
go logErrOut(stderr)
|
||||
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
// buildCmd reeturns the equivalent of
|
||||
//
|
||||
// journalctl -t <namespace>:<name> \
|
||||
// --output=json \
|
||||
// --since=<timestamp> \
|
||||
// <--follow> \
|
||||
func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
||||
// // set the cursor position based on req, default to 5m
|
||||
since := time.Now().Add(-5 * time.Minute)
|
||||
if req.Since != nil && req.Since.Before(time.Now()) {
|
||||
since = *req.Since
|
||||
}
|
||||
|
||||
namespace := req.Namespace
|
||||
if namespace == "" {
|
||||
namespace = faasd.DefaultFunctionNamespace
|
||||
}
|
||||
|
||||
// find the description of the fields here
|
||||
// https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
|
||||
// the available fields can vary greatly, the selected fields were detemined by
|
||||
// trial and error with journalctl in an ubuntu VM (via multipass)
|
||||
args := []string{
|
||||
"--utc",
|
||||
"--no-pager",
|
||||
"--output=json",
|
||||
"--identifier=" + namespace + ":" + req.Name,
|
||||
fmt.Sprintf("--since=%s", since.UTC().Format("2006-01-02 15:04:05")),
|
||||
}
|
||||
|
||||
if req.Follow {
|
||||
args = append(args, "--follow")
|
||||
}
|
||||
|
||||
if req.Tail > 0 {
|
||||
args = append(args, fmt.Sprintf("--lines=%d", req.Tail))
|
||||
}
|
||||
|
||||
return exec.CommandContext(ctx, "journalctl", args...)
|
||||
}
|
||||
|
||||
// streamLogs copies log entries from the journalctl `cmd`/`out` to `msgs`
|
||||
// the loop is based on the Decoder example in the docs
|
||||
// https://golang.org/pkg/encoding/json/#Decoder.Decode
|
||||
func streamLogs(ctx context.Context, cmd *exec.Cmd, out io.ReadCloser, msgs chan logs.Message) {
|
||||
log.Println("starting journal stream using ", cmd.String())
|
||||
|
||||
// will ensure `out` is closed and all related resources cleaned up
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
log.Println("wait result", err)
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
log.Println("closing journal stream")
|
||||
close(msgs)
|
||||
}()
|
||||
|
||||
dec := json.NewDecoder(out)
|
||||
for dec.More() {
|
||||
if ctx.Err() != nil {
|
||||
log.Println("log stream context cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
// the journalctl outputs all the values as a string, so a struct with json
|
||||
// tags wont help much
|
||||
entry := map[string]string{}
|
||||
err := dec.Decode(&entry)
|
||||
if err != nil {
|
||||
log.Printf("error decoding journalctl output: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := parseEntry(entry)
|
||||
if err != nil {
|
||||
log.Printf("error parsing journalctl output: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
msgs <- msg
|
||||
}
|
||||
}
|
||||
|
||||
// parseEntry reads the deserialized json from journalctl into a log.Message
|
||||
//
|
||||
// The following fields are parsed from the journal
|
||||
// - MESSAGE
|
||||
// - _PID
|
||||
// - SYSLOG_IDENTIFIER
|
||||
// - __REALTIME_TIMESTAMP
|
||||
func parseEntry(entry map[string]string) (logs.Message, error) {
|
||||
logMsg := logs.Message{
|
||||
Text: entry["MESSAGE"],
|
||||
Instance: entry["_PID"],
|
||||
}
|
||||
|
||||
identifier := entry["SYSLOG_IDENTIFIER"]
|
||||
parts := strings.Split(identifier, ":")
|
||||
if len(parts) != 2 {
|
||||
return logMsg, fmt.Errorf("invalid SYSLOG_IDENTIFIER")
|
||||
}
|
||||
logMsg.Namespace = parts[0]
|
||||
logMsg.Name = parts[1]
|
||||
|
||||
ts, ok := entry["__REALTIME_TIMESTAMP"]
|
||||
if !ok {
|
||||
return logMsg, fmt.Errorf("missing required field __REALTIME_TIMESTAMP")
|
||||
}
|
||||
|
||||
ms, err := strconv.ParseInt(ts, 10, 64)
|
||||
if err != nil {
|
||||
return logMsg, fmt.Errorf("invalid timestamp: %w", err)
|
||||
}
|
||||
logMsg.Timestamp = time.Unix(0, ms*1000).UTC()
|
||||
|
||||
return logMsg, nil
|
||||
}
|
||||
|
||||
func logErrOut(out io.ReadCloser) {
|
||||
defer log.Println("stderr closed")
|
||||
defer out.Close()
|
||||
|
||||
io.Copy(log.Writer(), out)
|
||||
}
|
73
pkg/logs/requestor_test.go
Normal file
73
pkg/logs/requestor_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/openfaas/faas-provider/logs"
|
||||
)
|
||||
|
||||
func Test_parseEntry(t *testing.T) {
|
||||
rawEntry := `{ "__CURSOR" : "s=71c4550142d14ace8e2959e3540cc15c;i=133c;b=44864010f0d94baba7b6bf8019f82a56;m=2945cd3;t=5a00d4eb59180;x=8ed47f7f9b3d798", "__REALTIME_TIMESTAMP" : "1583353899094400", "__MONOTONIC_TIMESTAMP" : "43277523", "_BOOT_ID" : "44864010f0d94baba7b6bf8019f82a56", "SYSLOG_IDENTIFIER" : "openfaas-fn:nodeinfo", "_PID" : "2254", "MESSAGE" : "2020/03/04 20:31:39 POST / - 200 OK - ContentLength: 83", "_SOURCE_REALTIME_TIMESTAMP" : "1583353899094372" }`
|
||||
expectedEntry := logs.Message{
|
||||
Name: "nodeinfo",
|
||||
Namespace: "openfaas-fn",
|
||||
Text: "2020/03/04 20:31:39 POST / - 200 OK - ContentLength: 83",
|
||||
Timestamp: time.Unix(0, 1583353899094400*1000).UTC(),
|
||||
}
|
||||
|
||||
value := map[string]string{}
|
||||
json.Unmarshal([]byte(rawEntry), &value)
|
||||
|
||||
entry, err := parseEntry(value)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
|
||||
if entry.Name != expectedEntry.Name {
|
||||
t.Fatalf("want Name: %q, got %q", expectedEntry.Name, entry.Name)
|
||||
}
|
||||
|
||||
if entry.Namespace != expectedEntry.Namespace {
|
||||
t.Fatalf("want Namespace: %q, got %q", expectedEntry.Namespace, entry.Namespace)
|
||||
}
|
||||
|
||||
if entry.Timestamp != expectedEntry.Timestamp {
|
||||
t.Fatalf("want Timestamp: %q, got %q", expectedEntry.Timestamp, entry.Timestamp)
|
||||
}
|
||||
|
||||
if entry.Text != expectedEntry.Text {
|
||||
t.Fatalf("want Text: %q, got %q", expectedEntry.Text, entry.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildCmd(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
now := time.Now()
|
||||
req := logs.Request{
|
||||
Name: "loggyfunc",
|
||||
Namespace: "spacetwo",
|
||||
Follow: true,
|
||||
Since: &now,
|
||||
Tail: 5,
|
||||
}
|
||||
|
||||
expectedArgs := fmt.Sprintf(
|
||||
"--utc --no-pager --output=json --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
||||
now.UTC().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
|
||||
cmd := buildCmd(ctx, req).String()
|
||||
wantCmd := "journalctl"
|
||||
if !strings.Contains(cmd, wantCmd) {
|
||||
t.Fatalf("cmd want: %q, got: %q", wantCmd, cmd)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(cmd, expectedArgs) {
|
||||
t.Fatalf("arg want: %q\ngot: %q", expectedArgs, cmd)
|
||||
}
|
||||
}
|
37
pkg/provider/config/read.go
Normal file
37
pkg/provider/config/read.go
Normal file
@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
types "github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
type ProviderConfig struct {
|
||||
// Sock is the address of the containerd socket
|
||||
Sock string
|
||||
}
|
||||
|
||||
// ReadFromEnv loads the FaaSConfig and the Containerd specific config form the env variables
|
||||
func ReadFromEnv(hasEnv types.HasEnv) (*types.FaaSConfig, *ProviderConfig, error) {
|
||||
config, err := types.ReadConfig{}.Read(hasEnv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
serviceTimeout := types.ParseIntOrDurationValue(hasEnv.Getenv("service_timeout"), time.Second*60)
|
||||
|
||||
config.ReadTimeout = serviceTimeout
|
||||
config.WriteTimeout = serviceTimeout
|
||||
config.EnableBasicAuth = true
|
||||
config.MaxIdleConns = types.ParseIntValue(hasEnv.Getenv("max_idle_conns"), 1024)
|
||||
config.MaxIdleConnsPerHost = types.ParseIntValue(hasEnv.Getenv("max_idle_conns_per_host"), 1024)
|
||||
|
||||
port := types.ParseIntValue(hasEnv.Getenv("port"), 8081)
|
||||
config.TCPPort = &port
|
||||
|
||||
providerConfig := &ProviderConfig{
|
||||
Sock: types.ParseString(hasEnv.Getenv("sock"), "/run/containerd/containerd.sock"),
|
||||
}
|
||||
|
||||
return config, providerConfig, nil
|
||||
}
|
107
pkg/provider/config/read_test.go
Normal file
107
pkg/provider/config/read_test.go
Normal file
@ -0,0 +1,107 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type EnvBucket struct {
|
||||
Items map[string]string
|
||||
}
|
||||
|
||||
func NewEnvBucket() EnvBucket {
|
||||
return EnvBucket{
|
||||
Items: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (e EnvBucket) Getenv(key string) string {
|
||||
return e.Items[key]
|
||||
}
|
||||
|
||||
func (e EnvBucket) Setenv(key string, value string) {
|
||||
e.Items[key] = value
|
||||
}
|
||||
|
||||
func Test_SetSockByEnv(t *testing.T) {
|
||||
defaultSock := "/run/containerd/containerd.sock"
|
||||
expectedSock := "/non/default/value.sock"
|
||||
env := NewEnvBucket()
|
||||
_, config, err := ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.Sock != defaultSock {
|
||||
t.Fatalf("expected %q, got %q", defaultSock, config.Sock)
|
||||
}
|
||||
|
||||
env.Setenv("sock", expectedSock)
|
||||
_, config, err = ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.Sock != expectedSock {
|
||||
t.Fatalf("expected %q, got %q", expectedSock, config.Sock)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetServiceTimeout(t *testing.T) {
|
||||
defaultTimeout := "1m0s"
|
||||
|
||||
env := NewEnvBucket()
|
||||
config, _, err := ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.ReadTimeout.String() != defaultTimeout {
|
||||
t.Fatalf("expected %q, got %q", defaultTimeout, config.ReadTimeout)
|
||||
}
|
||||
|
||||
if config.WriteTimeout.String() != defaultTimeout {
|
||||
t.Fatalf("expected %q, got %q", defaultTimeout, config.WriteTimeout)
|
||||
}
|
||||
|
||||
newTimeout := "30s"
|
||||
env.Setenv("service_timeout", newTimeout)
|
||||
config, _, err = ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.ReadTimeout.String() != newTimeout {
|
||||
t.Fatalf("expected %q, got %q", newTimeout, config.ReadTimeout)
|
||||
}
|
||||
|
||||
if config.WriteTimeout.String() != newTimeout {
|
||||
t.Fatalf("expected %q, got %q", newTimeout, config.WriteTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetPort(t *testing.T) {
|
||||
defaultPort := 8081
|
||||
|
||||
env := NewEnvBucket()
|
||||
config, _, err := ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.TCPPort == nil {
|
||||
t.Fatal("expected non-nil TCPPort")
|
||||
}
|
||||
if *config.TCPPort != defaultPort {
|
||||
t.Fatalf("expected %d, got %d", defaultPort, config.TCPPort)
|
||||
}
|
||||
|
||||
newPort := 9091
|
||||
newPortStr := strconv.Itoa(newPort)
|
||||
env.Setenv("port", newPortStr)
|
||||
config, _, err = ReadFromEnv(env)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if config.TCPPort == nil {
|
||||
t.Fatal("expected non-nil TCPPort")
|
||||
}
|
||||
if *config.TCPPort != newPort {
|
||||
t.Fatalf("expected %d, got %d", newPort, config.TCPPort)
|
||||
}
|
||||
}
|
98
pkg/provider/handlers/delete.go
Normal file
98
pkg/provider/handlers/delete.go
Normal file
@ -0,0 +1,98 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
"github.com/openfaas/faas/gateway/requests"
|
||||
|
||||
cninetwork "github.com/openfaas/faasd/pkg/cninetwork"
|
||||
"github.com/openfaas/faasd/pkg/service"
|
||||
)
|
||||
|
||||
func MakeDeleteHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Body == nil {
|
||||
http.Error(w, "expected a body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
log.Printf("[Delete] request: %s\n", string(body))
|
||||
|
||||
req := requests.DeleteFunctionRequest{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
log.Printf("[Delete] error parsing input: %s\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
lookupNamespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), lookupNamespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := req.FunctionName
|
||||
|
||||
function, err := GetFunction(client, name, lookupNamespace)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("function: %s not found", name)
|
||||
log.Printf("[Delete] %s\n", msg)
|
||||
http.Error(w, msg, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := namespaces.WithNamespace(context.Background(), lookupNamespace)
|
||||
|
||||
// TODO: this needs to still happen if the task is paused
|
||||
if function.replicas != 0 {
|
||||
err = cninetwork.DeleteCNINetwork(ctx, cni, client, name)
|
||||
if err != nil {
|
||||
log.Printf("[Delete] error removing CNI network for: %s, %s\n", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
killTimeout := getKillTimeout(function.envVars)
|
||||
|
||||
if err := service.Remove(ctx, client, name, killTimeout); err != nil {
|
||||
log.Printf("[Delete] error removing %s, %s\n", name, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[Delete] deleted: %s\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
func getKillTimeout(envs map[string]string) time.Duration {
|
||||
killTimeout := time.Second * 5
|
||||
if v, ok := envs["healthcheck_interval"]; ok {
|
||||
dur, err := time.ParseDuration(v)
|
||||
if err == nil {
|
||||
killTimeout = dur
|
||||
}
|
||||
}
|
||||
return killTimeout
|
||||
}
|
317
pkg/provider/handlers/deploy.go
Normal file
317
pkg/provider/handlers/deploy.go
Normal file
@ -0,0 +1,317 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/oci"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
cninetwork "github.com/openfaas/faasd/pkg/cninetwork"
|
||||
"github.com/openfaas/faasd/pkg/service"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
const annotationLabelPrefix = "com.openfaas.annotations."
|
||||
|
||||
// MakeDeployHandler returns a handler to deploy a function
|
||||
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Body == nil {
|
||||
http.Error(w, "expected a body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
log.Printf("[Deploy] request: %s\n", string(body))
|
||||
|
||||
req := types.FunctionDeployment{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
log.Printf("[Deploy] - error parsing input: %s\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
namespace := getRequestNamespace(req.Namespace)
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), namespace)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
namespaceSecretMountPath := getNamespaceSecretMountPath(secretMountPath, namespace)
|
||||
err = validateSecrets(namespaceSecretMountPath, req.Secrets)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := req.Service
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
|
||||
deployErr := deploy(ctx, req, client, cni, namespaceSecretMountPath, alwaysPull)
|
||||
if deployErr != nil {
|
||||
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
|
||||
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prepull is an optimization which means an image can be pulled before a deployment
|
||||
// request, since a deployment request first deletes the active function before
|
||||
// trying to deploy a new one.
|
||||
func prepull(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, alwaysPull bool) (containerd.Image, error) {
|
||||
start := time.Now()
|
||||
r, err := reference.ParseNormalizedNamed(req.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imgRef := reference.TagNameOnly(r).String()
|
||||
|
||||
snapshotter := ""
|
||||
if val, ok := os.LookupEnv("snapshotter"); ok {
|
||||
snapshotter = val
|
||||
}
|
||||
|
||||
image, err := service.PrepareImage(ctx, client, imgRef, snapshotter, alwaysPull)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to pull image %s", imgRef)
|
||||
}
|
||||
|
||||
size, _ := image.Size(ctx)
|
||||
log.Printf("Image for: %s size: %d, took: %fs\n", image.Name(), size, time.Since(start).Seconds())
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) error {
|
||||
|
||||
snapshotter := ""
|
||||
if val, ok := os.LookupEnv("snapshotter"); ok {
|
||||
snapshotter = val
|
||||
}
|
||||
|
||||
image, err := prepull(ctx, req, client, alwaysPull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envs := prepareEnv(req.EnvProcess, req.EnvVars)
|
||||
mounts := getOSMounts()
|
||||
|
||||
for _, secret := range req.Secrets {
|
||||
mounts = append(mounts, specs.Mount{
|
||||
Destination: path.Join("/var/openfaas/secrets", secret),
|
||||
Type: "bind",
|
||||
Source: path.Join(secretMountPath, secret),
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
}
|
||||
|
||||
name := req.Service
|
||||
|
||||
labels, err := buildLabels(&req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to apply labels to container: %s, error: %w", name, err)
|
||||
}
|
||||
|
||||
var memory *specs.LinuxMemory
|
||||
if req.Limits != nil && len(req.Limits.Memory) > 0 {
|
||||
memory = &specs.LinuxMemory{}
|
||||
|
||||
qty, err := resource.ParseQuantity(req.Limits.Memory)
|
||||
if err != nil {
|
||||
log.Printf("error parsing (%q) as quantity: %s", req.Limits.Memory, err.Error())
|
||||
}
|
||||
v := qty.Value()
|
||||
memory.Limit = &v
|
||||
}
|
||||
|
||||
container, err := client.NewContainer(
|
||||
ctx,
|
||||
name,
|
||||
containerd.WithImage(image),
|
||||
containerd.WithSnapshotter(snapshotter),
|
||||
containerd.WithNewSnapshot(name+"-snapshot", image),
|
||||
containerd.WithNewSpec(oci.WithImageConfig(image),
|
||||
oci.WithHostname(name),
|
||||
oci.WithCapabilities([]string{"CAP_NET_RAW"}),
|
||||
oci.WithMounts(mounts),
|
||||
oci.WithEnv(envs),
|
||||
withMemory(memory)),
|
||||
containerd.WithContainerLabels(labels),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create container: %s, error: %w", name, err)
|
||||
}
|
||||
|
||||
return createTask(ctx, container, cni)
|
||||
|
||||
}
|
||||
|
||||
func buildLabels(request *types.FunctionDeployment) (map[string]string, error) {
|
||||
// Adapted from faas-swarm/handlers/deploy.go:buildLabels
|
||||
labels := map[string]string{}
|
||||
|
||||
if request.Labels != nil {
|
||||
for k, v := range *request.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if request.Annotations != nil {
|
||||
for k, v := range *request.Annotations {
|
||||
key := fmt.Sprintf("%s%s", annotationLabelPrefix, k)
|
||||
if _, ok := labels[key]; !ok {
|
||||
labels[key] = v
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("Key %s cannot be used as a label due to a conflict with annotation prefix %s", k, annotationLabelPrefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func createTask(ctx context.Context, container containerd.Container, cni gocni.CNI) error {
|
||||
|
||||
name := container.ID()
|
||||
|
||||
task, taskErr := container.NewTask(ctx, cio.BinaryIO("/usr/local/bin/faasd", nil))
|
||||
|
||||
if taskErr != nil {
|
||||
return fmt.Errorf("unable to start task: %s, error: %w", name, taskErr)
|
||||
}
|
||||
|
||||
log.Printf("Container ID: %s\tTask ID %s:\tTask PID: %d\t\n", name, task.ID(), task.Pid())
|
||||
|
||||
labels := map[string]string{}
|
||||
_, err := cninetwork.CreateCNINetwork(ctx, cni, task, labels)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ip, err := cninetwork.GetIPAddress(name, task.Pid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%s has IP: %s.\n", name, ip)
|
||||
|
||||
_, waitErr := task.Wait(ctx)
|
||||
if waitErr != nil {
|
||||
return errors.Wrapf(waitErr, "Unable to wait for task to start: %s", name)
|
||||
}
|
||||
|
||||
if startErr := task.Start(ctx); startErr != nil {
|
||||
return errors.Wrapf(startErr, "Unable to start task: %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareEnv(envProcess string, reqEnvVars map[string]string) []string {
|
||||
envs := []string{}
|
||||
fprocessFound := false
|
||||
fprocess := "fprocess=" + envProcess
|
||||
if len(envProcess) > 0 {
|
||||
fprocessFound = true
|
||||
}
|
||||
|
||||
for k, v := range reqEnvVars {
|
||||
if k == "fprocess" {
|
||||
fprocessFound = true
|
||||
fprocess = v
|
||||
} else {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
}
|
||||
if fprocessFound {
|
||||
envs = append(envs, fprocess)
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// getOSMounts provides a mount for os-specific files such
|
||||
// as the hosts file and resolv.conf
|
||||
func getOSMounts() []specs.Mount {
|
||||
// Prior to hosts_dir env-var, this value was set to
|
||||
// os.Getwd()
|
||||
hostsDir := "/var/lib/faasd"
|
||||
if v, ok := os.LookupEnv("hosts_dir"); ok && len(v) > 0 {
|
||||
hostsDir = v
|
||||
}
|
||||
|
||||
mounts := []specs.Mount{}
|
||||
mounts = append(mounts, specs.Mount{
|
||||
Destination: "/etc/resolv.conf",
|
||||
Type: "bind",
|
||||
Source: path.Join(hostsDir, "resolv.conf"),
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
|
||||
mounts = append(mounts, specs.Mount{
|
||||
Destination: "/etc/hosts",
|
||||
Type: "bind",
|
||||
Source: path.Join(hostsDir, "hosts"),
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return mounts
|
||||
}
|
||||
|
||||
func validateSecrets(secretMountPath string, secrets []string) error {
|
||||
for _, secret := range secrets {
|
||||
if _, err := os.Stat(path.Join(secretMountPath, secret)); err != nil {
|
||||
return fmt.Errorf("unable to find secret: %s", secret)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func withMemory(mem *specs.LinuxMemory) oci.SpecOpts {
|
||||
return func(ctx context.Context, _ oci.Client, c *containers.Container, s *oci.Spec) error {
|
||||
if mem != nil {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
if s.Linux.Resources.Memory == nil {
|
||||
s.Linux.Resources.Memory = &specs.LinuxMemory{}
|
||||
}
|
||||
s.Linux.Resources.Memory.Limit = mem.Limit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
77
pkg/provider/handlers/deploy_test.go
Normal file
77
pkg/provider/handlers/deploy_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func Test_BuildLabels_WithAnnotations(t *testing.T) {
|
||||
// Test each combination of nil/non-nil annotation + label
|
||||
tables := []struct {
|
||||
name string
|
||||
label map[string]string
|
||||
annotation map[string]string
|
||||
result map[string]string
|
||||
}{
|
||||
{"Empty label and annotations returns empty table map", nil, nil, map[string]string{}},
|
||||
{
|
||||
"Label with empty annotation returns valid map",
|
||||
map[string]string{"L1": "V1"},
|
||||
nil,
|
||||
map[string]string{"L1": "V1"}},
|
||||
{
|
||||
"Annotation with empty label returns valid map",
|
||||
nil,
|
||||
map[string]string{"A1": "V2"},
|
||||
map[string]string{fmt.Sprintf("%sA1", annotationLabelPrefix): "V2"}},
|
||||
{
|
||||
"Label and annotation provided returns valid combined map",
|
||||
map[string]string{"L1": "V1"},
|
||||
map[string]string{"A1": "V2"},
|
||||
map[string]string{
|
||||
"L1": "V1",
|
||||
fmt.Sprintf("%sA1", annotationLabelPrefix): "V2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tables {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := &types.FunctionDeployment{
|
||||
Labels: &tc.label,
|
||||
Annotations: &tc.annotation,
|
||||
}
|
||||
|
||||
val, err := buildLabels(request)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("want: no error got: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(val, tc.result) {
|
||||
t.Errorf("Want: %s, got: %s", val, tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BuildLabels_WithAnnotationCollision(t *testing.T) {
|
||||
request := &types.FunctionDeployment{
|
||||
Labels: &map[string]string{
|
||||
"function_name": "echo",
|
||||
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "Wed 25 Jul 06:41:43 BST 2018",
|
||||
},
|
||||
Annotations: &map[string]string{"current-time": "Wed 25 Jul 06:41:43 BST 2018"},
|
||||
}
|
||||
|
||||
val, err := buildLabels(request)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error, got %d values", len(val))
|
||||
}
|
||||
|
||||
}
|
242
pkg/provider/handlers/functions.go
Normal file
242
pkg/provider/handlers/functions.go
Normal file
@ -0,0 +1,242 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/openfaas/faasd/pkg"
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||
)
|
||||
|
||||
type Function struct {
|
||||
name string
|
||||
namespace string
|
||||
image string
|
||||
pid uint32
|
||||
replicas int
|
||||
IP string
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
secrets []string
|
||||
envVars map[string]string
|
||||
envProcess string
|
||||
memoryLimit int64
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
// ListFunctions returns a map of all functions with running tasks on namespace
|
||||
func ListFunctions(client *containerd.Client, namespace string) (map[string]*Function, error) {
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return nil, errors.New("namespace not valid")
|
||||
}
|
||||
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
functions := make(map[string]*Function)
|
||||
|
||||
containers, err := client.Containers(ctx)
|
||||
if err != nil {
|
||||
return functions, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
name := c.ID()
|
||||
f, err := GetFunction(client, name, namespace)
|
||||
if err != nil {
|
||||
log.Printf("skipping %s, error: %s", name, err)
|
||||
} else {
|
||||
functions[name] = &f
|
||||
}
|
||||
}
|
||||
|
||||
return functions, nil
|
||||
}
|
||||
|
||||
// GetFunction returns a function that matches name
|
||||
func GetFunction(client *containerd.Client, name string, namespace string) (Function, error) {
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
fn := Function{}
|
||||
|
||||
c, err := client.LoadContainer(ctx, name)
|
||||
if err != nil {
|
||||
return Function{}, fmt.Errorf("unable to find function: %s, error %w", name, err)
|
||||
}
|
||||
|
||||
image, err := c.Image(ctx)
|
||||
if err != nil {
|
||||
return fn, err
|
||||
}
|
||||
|
||||
containerName := c.ID()
|
||||
allLabels, labelErr := c.Labels(ctx)
|
||||
|
||||
if labelErr != nil {
|
||||
log.Printf("cannot list container %s labels: %s", containerName, labelErr)
|
||||
}
|
||||
|
||||
labels, annotations := buildLabelsAndAnnotations(allLabels)
|
||||
|
||||
spec, err := c.Spec(ctx)
|
||||
if err != nil {
|
||||
return Function{}, fmt.Errorf("unable to load function %s error: %w", name, err)
|
||||
}
|
||||
|
||||
info, err := c.Info(ctx)
|
||||
if err != nil {
|
||||
return Function{}, fmt.Errorf("can't load info for: %s, error %w", name, err)
|
||||
}
|
||||
|
||||
envVars, envProcess := readEnvFromProcessEnv(spec.Process.Env)
|
||||
secrets := readSecretsFromMounts(spec.Mounts)
|
||||
|
||||
fn.name = containerName
|
||||
fn.namespace = namespace
|
||||
fn.image = image.Name()
|
||||
fn.labels = labels
|
||||
fn.annotations = annotations
|
||||
fn.secrets = secrets
|
||||
fn.envVars = envVars
|
||||
fn.envProcess = envProcess
|
||||
fn.createdAt = info.CreatedAt
|
||||
fn.memoryLimit = readMemoryLimitFromSpec(spec)
|
||||
|
||||
replicas := 0
|
||||
task, err := c.Task(ctx, nil)
|
||||
if err == nil {
|
||||
// Task for container exists
|
||||
svc, err := task.Status(ctx)
|
||||
if err != nil {
|
||||
return Function{}, fmt.Errorf("unable to get task status for container: %s %w", name, err)
|
||||
}
|
||||
|
||||
if svc.Status == "running" {
|
||||
replicas = 1
|
||||
fn.pid = task.Pid()
|
||||
|
||||
// Get container IP address
|
||||
ip, err := cninetwork.GetIPAddress(name, task.Pid())
|
||||
if err != nil {
|
||||
return Function{}, err
|
||||
}
|
||||
fn.IP = ip
|
||||
}
|
||||
} else {
|
||||
replicas = 0
|
||||
}
|
||||
|
||||
fn.replicas = replicas
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func readEnvFromProcessEnv(env []string) (map[string]string, string) {
|
||||
foundEnv := make(map[string]string)
|
||||
fprocess := ""
|
||||
for _, e := range env {
|
||||
kv := strings.Split(e, "=")
|
||||
if len(kv) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if kv[0] == "PATH" {
|
||||
continue
|
||||
}
|
||||
|
||||
if kv[0] == "fprocess" {
|
||||
fprocess = kv[1]
|
||||
continue
|
||||
}
|
||||
|
||||
foundEnv[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
return foundEnv, fprocess
|
||||
}
|
||||
|
||||
func readSecretsFromMounts(mounts []specs.Mount) []string {
|
||||
secrets := []string{}
|
||||
for _, mnt := range mounts {
|
||||
x := strings.Split(mnt.Destination, "/var/openfaas/secrets/")
|
||||
if len(x) > 1 {
|
||||
secrets = append(secrets, x[1])
|
||||
}
|
||||
|
||||
}
|
||||
return secrets
|
||||
}
|
||||
|
||||
// buildLabelsAndAnnotations returns a separated list with labels first,
|
||||
// followed by annotations by checking each key of ctrLabels for a prefix.
|
||||
func buildLabelsAndAnnotations(ctrLabels map[string]string) (map[string]string, map[string]string) {
|
||||
labels := make(map[string]string)
|
||||
annotations := make(map[string]string)
|
||||
|
||||
for k, v := range ctrLabels {
|
||||
if strings.HasPrefix(k, annotationLabelPrefix) {
|
||||
annotations[strings.TrimPrefix(k, annotationLabelPrefix)] = v
|
||||
} else {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return labels, annotations
|
||||
}
|
||||
|
||||
func ListNamespaces(client *containerd.Client) []string {
|
||||
set := []string{}
|
||||
store := client.NamespaceService()
|
||||
namespaces, err := store.List(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("Error listing namespaces: %s", err.Error())
|
||||
set = append(set, faasd.DefaultFunctionNamespace)
|
||||
return set
|
||||
}
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
labels, err := store.Labels(context.Background(), namespace)
|
||||
if err != nil {
|
||||
log.Printf("Error listing label for namespace %s: %s", namespace, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if _, found := labels[pkg.NamespaceLabel]; found {
|
||||
set = append(set, namespace)
|
||||
}
|
||||
|
||||
if !findNamespace(faasd.DefaultFunctionNamespace, set) {
|
||||
set = append(set, faasd.DefaultFunctionNamespace)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func findNamespace(target string, items []string) bool {
|
||||
for _, n := range items {
|
||||
if n == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readMemoryLimitFromSpec(spec *specs.Spec) int64 {
|
||||
if spec.Linux == nil || spec.Linux.Resources == nil || spec.Linux.Resources.Memory == nil || spec.Linux.Resources.Memory.Limit == nil {
|
||||
return 0
|
||||
}
|
||||
return *spec.Linux.Resources.Memory.Limit
|
||||
}
|
133
pkg/provider/handlers/functions_test.go
Normal file
133
pkg/provider/handlers/functions_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
func Test_BuildLabelsAndAnnotationsFromServiceSpec_Annotations(t *testing.T) {
|
||||
container := map[string]string{
|
||||
"qwer": "ty",
|
||||
"dvor": "ak",
|
||||
fmt.Sprintf("%scurrent-time", annotationLabelPrefix): "5 Nov 20:10:20 PST 1955",
|
||||
fmt.Sprintf("%sfuture-time", annotationLabelPrefix): "21 Oct 20:10:20 PST 2015",
|
||||
}
|
||||
|
||||
labels, annotation := buildLabelsAndAnnotations(container)
|
||||
|
||||
if len(labels) != 2 {
|
||||
t.Errorf("want: %d labels got: %d", 2, len(labels))
|
||||
}
|
||||
|
||||
if len(annotation) != 2 {
|
||||
t.Errorf("want: %d annotation got: %d", 1, len(annotation))
|
||||
}
|
||||
|
||||
if _, ok := annotation["current-time"]; !ok {
|
||||
t.Errorf("want: '%s' entry in annotation map got: key not found", "current-time")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SplitMountToSecrets(t *testing.T) {
|
||||
type testCase struct {
|
||||
Name string
|
||||
Input []specs.Mount
|
||||
Want []string
|
||||
}
|
||||
tests := []testCase{
|
||||
{Name: "No matching openfaas secrets", Input: []specs.Mount{{Destination: "/foo/"}}, Want: []string{}},
|
||||
{Name: "Nil mounts", Input: nil, Want: []string{}},
|
||||
{Name: "No Mounts", Input: []specs.Mount{{Destination: "/foo/"}}, Want: []string{}},
|
||||
{Name: "One Mounts IS secret", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}}, Want: []string{"secret1"}},
|
||||
{Name: "Multiple Mounts 1 secret", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}, {Destination: "/some/other/path"}}, Want: []string{"secret1"}},
|
||||
{Name: "Multiple Mounts all secrets", Input: []specs.Mount{{Destination: "/var/openfaas/secrets/secret1"}, {Destination: "/var/openfaas/secrets/secret2"}}, Want: []string{"secret1", "secret2"}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
got := readSecretsFromMounts(tc.Input)
|
||||
if !reflect.DeepEqual(got, tc.Want) {
|
||||
t.Fatalf("Want %s, got %s", tc.Want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ProcessEnvToEnvVars(t *testing.T) {
|
||||
type testCase struct {
|
||||
Name string
|
||||
Input []string
|
||||
Want map[string]string
|
||||
fprocess string
|
||||
}
|
||||
tests := []testCase{
|
||||
{Name: "No matching EnvVars", Input: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "fprocess=python index.py"}, Want: make(map[string]string), fprocess: "python index.py"},
|
||||
{Name: "No EnvVars", Input: []string{}, Want: make(map[string]string), fprocess: ""},
|
||||
{Name: "One EnvVar", Input: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "fprocess=python index.py", "env=this"}, Want: map[string]string{"env": "this"}, fprocess: "python index.py"},
|
||||
{Name: "Multiple EnvVars", Input: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "this=that", "env=var", "fprocess=python index.py"}, Want: map[string]string{"this": "that", "env": "var"}, fprocess: "python index.py"},
|
||||
{Name: "Nil EnvVars", Input: nil, Want: make(map[string]string)},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
got, fprocess := readEnvFromProcessEnv(tc.Input)
|
||||
if !reflect.DeepEqual(got, tc.Want) {
|
||||
t.Fatalf("Want: %s, got: %s", tc.Want, got)
|
||||
}
|
||||
|
||||
if fprocess != tc.fprocess {
|
||||
t.Fatalf("Want fprocess: %s, got: %s", tc.fprocess, got)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_findNamespace(t *testing.T) {
|
||||
type testCase struct {
|
||||
Name string
|
||||
foundNamespaces []string
|
||||
namespace string
|
||||
Want bool
|
||||
}
|
||||
tests := []testCase{
|
||||
{Name: "Namespace Found", namespace: "fn", foundNamespaces: []string{"fn", "openfaas-fn"}, Want: true},
|
||||
{Name: "namespace Not Found", namespace: "fn", foundNamespaces: []string{"openfaas-fn"}, Want: false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
got := findNamespace(tc.namespace, tc.foundNamespaces)
|
||||
if got != tc.Want {
|
||||
t.Fatalf("Want %t, got %t", tc.Want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_readMemoryLimitFromSpec(t *testing.T) {
|
||||
type testCase struct {
|
||||
Name string
|
||||
Spec *specs.Spec
|
||||
Want int64
|
||||
}
|
||||
testLimit := int64(64)
|
||||
tests := []testCase{
|
||||
{Name: "specs.Linux not found", Spec: &specs.Spec{Linux: nil}, Want: int64(0)},
|
||||
{Name: "specs.LinuxResource not found", Spec: &specs.Spec{Linux: &specs.Linux{Resources: nil}}, Want: int64(0)},
|
||||
{Name: "specs.LinuxMemory not found", Spec: &specs.Spec{Linux: &specs.Linux{Resources: &specs.LinuxResources{Memory: nil}}}, Want: int64(0)},
|
||||
{Name: "specs.LinuxMemory.Limit not found", Spec: &specs.Spec{Linux: &specs.Linux{Resources: &specs.LinuxResources{Memory: &specs.LinuxMemory{Limit: nil}}}}, Want: int64(0)},
|
||||
{Name: "Memory limit set as Want", Spec: &specs.Spec{Linux: &specs.Linux{Resources: &specs.LinuxResources{Memory: &specs.LinuxMemory{Limit: &testLimit}}}}, Want: int64(64)},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
got := readMemoryLimitFromSpec(tc.Spec)
|
||||
if got != tc.Want {
|
||||
t.Fatalf("Want %d, got %d", tc.Want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
44
pkg/provider/handlers/info.go
Normal file
44
pkg/provider/handlers/info.go
Normal file
@ -0,0 +1,44 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// OrchestrationIdentifier identifier string for provider orchestration
|
||||
OrchestrationIdentifier = "containerd"
|
||||
|
||||
// ProviderName name of the provider
|
||||
ProviderName = "faasd"
|
||||
)
|
||||
|
||||
//MakeInfoHandler creates handler for /system/info endpoint
|
||||
func MakeInfoHandler(version, sha string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
infoResponse := types.ProviderInfo{
|
||||
Orchestration: OrchestrationIdentifier,
|
||||
Name: ProviderName,
|
||||
Version: &types.VersionInfo{
|
||||
Release: version,
|
||||
SHA: sha,
|
||||
},
|
||||
}
|
||||
|
||||
jsonOut, marshalErr := json.Marshal(infoResponse)
|
||||
if marshalErr != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonOut)
|
||||
}
|
||||
}
|
39
pkg/provider/handlers/info_test.go
Normal file
39
pkg/provider/handlers/info_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_InfoHandler(t *testing.T) {
|
||||
sha := "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||
version := "0.0.1"
|
||||
handler := MakeInfoHandler(version, sha)
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
handler(w, r)
|
||||
|
||||
resp := types.ProviderInfo{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error unmarshalling the response")
|
||||
}
|
||||
|
||||
if resp.Name != ProviderName {
|
||||
t.Fatalf("expected provider %q, got %q", ProviderName, resp.Name)
|
||||
}
|
||||
|
||||
if resp.Orchestration != OrchestrationIdentifier {
|
||||
t.Fatalf("expected orchestration %q, got %q", OrchestrationIdentifier, resp.Orchestration)
|
||||
}
|
||||
|
||||
if resp.Version.SHA != sha {
|
||||
t.Fatalf("expected orchestration %q, got %q", sha, resp.Version.SHA)
|
||||
}
|
||||
|
||||
if resp.Version.Release != version {
|
||||
t.Fatalf("expected release %q, got %q", version, resp.Version.Release)
|
||||
}
|
||||
}
|
56
pkg/provider/handlers/invoke_resolver.go
Normal file
56
pkg/provider/handlers/invoke_resolver.go
Normal file
@ -0,0 +1,56 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
)
|
||||
|
||||
const watchdogPort = 8080
|
||||
|
||||
type InvokeResolver struct {
|
||||
client *containerd.Client
|
||||
}
|
||||
|
||||
func NewInvokeResolver(client *containerd.Client) *InvokeResolver {
|
||||
return &InvokeResolver{client: client}
|
||||
}
|
||||
|
||||
func (i *InvokeResolver) Resolve(functionName string) (url.URL, error) {
|
||||
actualFunctionName := functionName
|
||||
log.Printf("Resolve: %q\n", actualFunctionName)
|
||||
|
||||
namespace := getNamespace(functionName, faasd.DefaultFunctionNamespace)
|
||||
|
||||
if strings.Contains(functionName, ".") {
|
||||
actualFunctionName = strings.TrimSuffix(functionName, "."+namespace)
|
||||
}
|
||||
|
||||
function, err := GetFunction(i.client, actualFunctionName, namespace)
|
||||
if err != nil {
|
||||
return url.URL{}, fmt.Errorf("%s not found", actualFunctionName)
|
||||
}
|
||||
|
||||
serviceIP := function.IP
|
||||
|
||||
urlStr := fmt.Sprintf("http://%s:%d", serviceIP, watchdogPort)
|
||||
|
||||
urlRes, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return url.URL{}, err
|
||||
}
|
||||
|
||||
return *urlRes, nil
|
||||
}
|
||||
|
||||
func getNamespace(name, defaultNamespace string) string {
|
||||
namespace := defaultNamespace
|
||||
if strings.Contains(name, ".") {
|
||||
namespace = name[strings.LastIndexAny(name, ".")+1:]
|
||||
}
|
||||
return namespace
|
||||
}
|
18
pkg/provider/handlers/namespaces.go
Normal file
18
pkg/provider/handlers/namespaces.go
Normal file
@ -0,0 +1,18 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
)
|
||||
|
||||
func MakeNamespacesLister(client *containerd.Client) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
list := ListNamespaces(client)
|
||||
body, _ := json.Marshal(list)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(body)
|
||||
}
|
||||
}
|
71
pkg/provider/handlers/read.go
Normal file
71
pkg/provider/handlers/read.go
Normal file
@ -0,0 +1,71 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func MakeReadHandler(client *containerd.Client) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
lookupNamespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), lookupNamespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
res := []types.FunctionStatus{}
|
||||
fns, err := ListFunctions(client, lookupNamespace)
|
||||
if err != nil {
|
||||
log.Printf("[Read] error listing functions. Error: %s\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fn := range fns {
|
||||
annotations := &fn.annotations
|
||||
labels := &fn.labels
|
||||
memory := resource.NewQuantity(fn.memoryLimit, resource.BinarySI)
|
||||
status := types.FunctionStatus{
|
||||
Name: fn.name,
|
||||
Image: fn.image,
|
||||
Replicas: uint64(fn.replicas),
|
||||
Namespace: fn.namespace,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
Secrets: fn.secrets,
|
||||
EnvVars: fn.envVars,
|
||||
EnvProcess: fn.envProcess,
|
||||
CreatedAt: fn.createdAt,
|
||||
}
|
||||
|
||||
// Do not remove below memory check for 0
|
||||
// Memory limit should not be included in status until set explicitly
|
||||
limit := &types.FunctionResources{Memory: memory.String()}
|
||||
if limit.Memory != "0" {
|
||||
status.Limits = limit
|
||||
}
|
||||
|
||||
res = append(res, status)
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(res)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(body)
|
||||
}
|
||||
}
|
54
pkg/provider/handlers/replicas.go
Normal file
54
pkg/provider/handlers/replicas.go
Normal file
@ -0,0 +1,54 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func MakeReplicaReaderHandler(client *containerd.Client) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
functionName := vars["name"]
|
||||
lookupNamespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), lookupNamespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if f, err := GetFunction(client, functionName, lookupNamespace); err == nil {
|
||||
found := types.FunctionStatus{
|
||||
Name: functionName,
|
||||
Image: f.image,
|
||||
AvailableReplicas: uint64(f.replicas),
|
||||
Replicas: uint64(f.replicas),
|
||||
Namespace: f.namespace,
|
||||
Labels: &f.labels,
|
||||
Annotations: &f.annotations,
|
||||
Secrets: f.secrets,
|
||||
EnvVars: f.envVars,
|
||||
EnvProcess: f.envProcess,
|
||||
CreatedAt: f.createdAt,
|
||||
}
|
||||
|
||||
functionBytes, _ := json.Marshal(found)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(functionBytes)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
142
pkg/provider/handlers/scale.go
Normal file
142
pkg/provider/handlers/scale.go
Normal file
@ -0,0 +1,142 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func MakeReplicaUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Body == nil {
|
||||
http.Error(w, "expected a body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
log.Printf("[Scale] request: %s\n", string(body))
|
||||
|
||||
req := types.ScaleServiceRequest{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[Scale] error parsing input: %s\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
namespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), namespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := req.ServiceName
|
||||
|
||||
if _, err := GetFunction(client, name, namespace); err != nil {
|
||||
msg := fmt.Sprintf("service %s not found", name)
|
||||
log.Printf("[Scale] %s\n", msg)
|
||||
http.Error(w, msg, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
|
||||
ctr, ctrErr := client.LoadContainer(ctx, name)
|
||||
if ctrErr != nil {
|
||||
msg := fmt.Sprintf("cannot load service %s, error: %s", name, ctrErr)
|
||||
log.Printf("[Scale] %s\n", msg)
|
||||
http.Error(w, msg, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var taskExists bool
|
||||
var taskStatus *containerd.Status
|
||||
|
||||
task, taskErr := ctr.Task(ctx, nil)
|
||||
if taskErr != nil {
|
||||
msg := fmt.Sprintf("cannot load task for service %s, error: %s", name, taskErr)
|
||||
log.Printf("[Scale] %s\n", msg)
|
||||
taskExists = false
|
||||
} else {
|
||||
taskExists = true
|
||||
status, statusErr := task.Status(ctx)
|
||||
if statusErr != nil {
|
||||
msg := fmt.Sprintf("cannot load task status for %s, error: %s", name, statusErr)
|
||||
log.Printf("[Scale] %s\n", msg)
|
||||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
taskStatus = &status
|
||||
}
|
||||
}
|
||||
|
||||
createNewTask := false
|
||||
|
||||
// Scale to zero
|
||||
if req.Replicas == 0 {
|
||||
// If a task is running, pause it
|
||||
if taskExists && taskStatus.Status == containerd.Running {
|
||||
if pauseErr := task.Pause(ctx); pauseErr != nil {
|
||||
wrappedPauseErr := fmt.Errorf("error pausing task %s, error: %s", name, pauseErr)
|
||||
log.Printf("[Scale] %s\n", wrappedPauseErr.Error())
|
||||
http.Error(w, wrappedPauseErr.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if taskExists {
|
||||
if taskStatus != nil {
|
||||
if taskStatus.Status == containerd.Paused {
|
||||
if resumeErr := task.Resume(ctx); resumeErr != nil {
|
||||
log.Printf("[Scale] error resuming task %s, error: %s\n", name, resumeErr)
|
||||
http.Error(w, resumeErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else if taskStatus.Status == containerd.Stopped {
|
||||
// Stopped tasks cannot be restarted, must be removed, and created again
|
||||
if _, delErr := task.Delete(ctx); delErr != nil {
|
||||
log.Printf("[Scale] error deleting stopped task %s, error: %s\n", name, delErr)
|
||||
http.Error(w, delErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
createNewTask = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createNewTask = true
|
||||
}
|
||||
|
||||
if createNewTask {
|
||||
deployErr := createTask(ctx, ctr, cni)
|
||||
if deployErr != nil {
|
||||
log.Printf("[Scale] error deploying %s, error: %s\n", name, deployErr)
|
||||
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
178
pkg/provider/handlers/secret.go
Normal file
178
pkg/provider/handlers/secret.go
Normal file
@ -0,0 +1,178 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
provider "github.com/openfaas/faasd/pkg/provider"
|
||||
)
|
||||
|
||||
const secretFilePermission = 0644
|
||||
const secretDirPermission = 0755
|
||||
|
||||
func MakeSecretHandler(store provider.Labeller, mountPath string) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err := os.MkdirAll(mountPath, secretFilePermission)
|
||||
if err != nil {
|
||||
log.Printf("Creating path: %s, error: %s\n", mountPath, err)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
listSecrets(store, w, r, mountPath)
|
||||
case http.MethodPost:
|
||||
createSecret(w, r, mountPath)
|
||||
case http.MethodPut:
|
||||
createSecret(w, r, mountPath)
|
||||
case http.MethodDelete:
|
||||
deleteSecret(w, r, mountPath)
|
||||
default:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func listSecrets(store provider.Labeller, w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||
|
||||
lookupNamespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(store, lookupNamespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mountPath = getNamespaceSecretMountPath(mountPath, lookupNamespace)
|
||||
|
||||
files, err := os.ReadDir(mountPath)
|
||||
if os.IsNotExist(err) {
|
||||
bytesOut, _ := json.Marshal([]types.Secret{})
|
||||
w.Write(bytesOut)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error Occured: %s \n", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
secrets := []types.Secret{}
|
||||
for _, f := range files {
|
||||
secrets = append(secrets, types.Secret{Name: f.Name(), Namespace: lookupNamespace})
|
||||
}
|
||||
|
||||
bytesOut, _ := json.Marshal(secrets)
|
||||
w.Write(bytesOut)
|
||||
}
|
||||
|
||||
func createSecret(w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||
secret, err := parseSecret(r)
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = validateSecret(secret)
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[secret] is valid: %q", secret.Name)
|
||||
namespace := getRequestNamespace(secret.Namespace)
|
||||
mountPath = getNamespaceSecretMountPath(mountPath, namespace)
|
||||
|
||||
err = os.MkdirAll(mountPath, secretDirPermission)
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := secret.RawValue
|
||||
if len(data) == 0 {
|
||||
data = []byte(secret.Value)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(path.Join(mountPath, secret.Name), data, secretFilePermission)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func deleteSecret(w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||
secret, err := parseSecret(r)
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
namespace := getRequestNamespace(readNamespaceFromQuery(r))
|
||||
mountPath = getNamespaceSecretMountPath(mountPath, namespace)
|
||||
|
||||
err = os.Remove(path.Join(mountPath, secret.Name))
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[secret] error %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func parseSecret(r *http.Request) (types.Secret, error) {
|
||||
secret := types.Secret{}
|
||||
bytesOut, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytesOut, &secret)
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
|
||||
return secret, err
|
||||
}
|
||||
|
||||
const traverseErrorSt = "directory traversal found in name"
|
||||
|
||||
func isTraversal(name string) bool {
|
||||
return strings.Contains(name, fmt.Sprintf("%s", string(os.PathSeparator))) ||
|
||||
strings.Contains(name, "..")
|
||||
}
|
||||
|
||||
func validateSecret(secret types.Secret) error {
|
||||
if strings.TrimSpace(secret.Name) == "" {
|
||||
return fmt.Errorf("non-empty name is required")
|
||||
}
|
||||
if isTraversal(secret.Name) {
|
||||
return fmt.Errorf(traverseErrorSt)
|
||||
}
|
||||
return nil
|
||||
}
|
252
pkg/provider/handlers/secret_test.go
Normal file
252
pkg/provider/handlers/secret_test.go
Normal file
@ -0,0 +1,252 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
"github.com/openfaas/faasd/pkg"
|
||||
provider "github.com/openfaas/faasd/pkg/provider"
|
||||
)
|
||||
|
||||
func Test_parseSecret(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
payload string
|
||||
expError string
|
||||
expSecret types.Secret
|
||||
}{
|
||||
{
|
||||
name: "no error when name is valid without extention and with no traversal",
|
||||
payload: `{"name": "authorized_keys", "value": "foo"}`,
|
||||
expSecret: types.Secret{Name: "authorized_keys", Value: "foo"},
|
||||
},
|
||||
{
|
||||
name: "no error when name is valid and parses RawValue correctly",
|
||||
payload: `{"name": "authorized_keys", "rawValue": "YmFy"}`,
|
||||
expSecret: types.Secret{Name: "authorized_keys", RawValue: []byte("bar")},
|
||||
},
|
||||
{
|
||||
name: "no error when name is valid with dot and with no traversal",
|
||||
payload: `{"name": "authorized.keys", "value": "foo"}`,
|
||||
expSecret: types.Secret{Name: "authorized.keys", Value: "foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
reader := strings.NewReader(tc.payload)
|
||||
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||
secret, err := parseSecret(r)
|
||||
if err != nil && tc.expError == "" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if tc.expError != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error: %s, got nil", tc.expError)
|
||||
}
|
||||
if err.Error() != tc.expError {
|
||||
t.Fatalf("expected error: %s, got: %s", tc.expError, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(secret, tc.expSecret) {
|
||||
t.Fatalf("expected secret: %+v, got: %+v", tc.expSecret, secret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretCreation(t *testing.T) {
|
||||
mountPath, err := os.MkdirTemp("", "test_secret_creation")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating temp directory: %s", err)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(mountPath)
|
||||
|
||||
handler := MakeSecretHandler(nil, mountPath)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
verb string
|
||||
payload string
|
||||
status int
|
||||
secretPath string
|
||||
secret string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "returns error when the name contains a traversal",
|
||||
verb: http.MethodPost,
|
||||
payload: `{"name": "/root/.ssh/authorized_keys", "value": "foo"}`,
|
||||
status: http.StatusBadRequest,
|
||||
err: "directory traversal found in name\n",
|
||||
},
|
||||
{
|
||||
name: "returns error when the name contains a traversal",
|
||||
verb: http.MethodPost,
|
||||
payload: `{"name": "..", "value": "foo"}`,
|
||||
status: http.StatusBadRequest,
|
||||
err: "directory traversal found in name\n",
|
||||
},
|
||||
{
|
||||
name: "empty request returns a validation error",
|
||||
verb: http.MethodPost,
|
||||
payload: `{}`,
|
||||
status: http.StatusBadRequest,
|
||||
err: "non-empty name is required\n",
|
||||
},
|
||||
{
|
||||
name: "can create secret from string",
|
||||
verb: http.MethodPost,
|
||||
payload: `{"name": "foo", "value": "bar"}`,
|
||||
status: http.StatusOK,
|
||||
secretPath: "/openfaas-fn/foo",
|
||||
secret: "bar",
|
||||
},
|
||||
{
|
||||
name: "can create secret from raw value",
|
||||
verb: http.MethodPost,
|
||||
payload: `{"name": "foo", "rawValue": "YmFy"}`,
|
||||
status: http.StatusOK,
|
||||
secretPath: "/openfaas-fn/foo",
|
||||
secret: "bar",
|
||||
},
|
||||
{
|
||||
name: "can create secret in non-default namespace from raw value",
|
||||
verb: http.MethodPost,
|
||||
payload: `{"name": "pity", "rawValue": "dGhlIGZvbw==", "namespace": "a-team"}`,
|
||||
status: http.StatusOK,
|
||||
secretPath: "/a-team/pity",
|
||||
secret: "the foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.verb, "http://example.com/foo", strings.NewReader(tc.payload))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != tc.status {
|
||||
t.Logf("response body: %s", w.Body.String())
|
||||
t.Fatalf("expected status: %d, got: %d", tc.status, resp.StatusCode)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && w.Body.String() != tc.err {
|
||||
t.Fatalf("expected error message: %q, got %q", tc.err, w.Body.String())
|
||||
|
||||
}
|
||||
|
||||
if tc.secretPath != "" {
|
||||
data, err := os.ReadFile(filepath.Join(mountPath, tc.secretPath))
|
||||
if err != nil {
|
||||
t.Fatalf("can not read the secret from disk: %s", err)
|
||||
}
|
||||
|
||||
if string(data) != tc.secret {
|
||||
t.Fatalf("expected secret value: %s, got %s", tc.secret, string(data))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSecrets(t *testing.T) {
|
||||
mountPath, err := os.MkdirTemp("", "test_secret_creation")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating temp directory: %s", err)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(mountPath)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
verb string
|
||||
namespace string
|
||||
labels map[string]string
|
||||
status int
|
||||
secretPath string
|
||||
secret string
|
||||
err string
|
||||
expected []types.Secret
|
||||
}{
|
||||
{
|
||||
name: "Get empty secret list for default namespace having no secret",
|
||||
verb: http.MethodGet,
|
||||
status: http.StatusOK,
|
||||
secretPath: "/test-fn/foo",
|
||||
secret: "bar",
|
||||
expected: make([]types.Secret, 0),
|
||||
},
|
||||
{
|
||||
name: "Get empty secret list for non-default namespace having no secret",
|
||||
verb: http.MethodGet,
|
||||
status: http.StatusOK,
|
||||
secretPath: "/test-fn/foo",
|
||||
secret: "bar",
|
||||
expected: make([]types.Secret, 0),
|
||||
namespace: "other-ns",
|
||||
labels: map[string]string{
|
||||
pkg.NamespaceLabel: "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
labelStore := provider.NewFakeLabeller(tc.labels)
|
||||
|
||||
handler := MakeSecretHandler(labelStore, mountPath)
|
||||
|
||||
path := "http://example.com/foo"
|
||||
if len(tc.namespace) > 0 {
|
||||
path = path + fmt.Sprintf("?namespace=%s", tc.namespace)
|
||||
}
|
||||
req := httptest.NewRequest(tc.verb, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != tc.status {
|
||||
t.Fatalf("want status: %d, but got: %d", tc.status, resp.StatusCode)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && w.Body.String() != tc.err {
|
||||
t.Fatalf("want error message: %q, but got %q", tc.err, w.Body.String())
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("can't read response of list %v", err)
|
||||
}
|
||||
|
||||
var res []types.Secret
|
||||
err = json.Unmarshal(body, &res)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal %q, error: %v", string(body), err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(res, tc.expected) {
|
||||
t.Fatalf("want response: %v, but got: %v", tc.expected, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
103
pkg/provider/handlers/update.go
Normal file
103
pkg/provider/handlers/update.go
Normal file
@ -0,0 +1,103 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
|
||||
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||
"github.com/openfaas/faasd/pkg/service"
|
||||
)
|
||||
|
||||
func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Body == nil {
|
||||
http.Error(w, "expected a body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
log.Printf("[Update] request: %s\n", string(body))
|
||||
|
||||
req := types.FunctionDeployment{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
log.Printf("[Update] error parsing input: %s\n", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
name := req.Service
|
||||
namespace := getRequestNamespace(req.Namespace)
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
valid, err := validNamespace(client.NamespaceService(), namespace)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !valid {
|
||||
http.Error(w, "namespace not valid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
namespaceSecretMountPath := getNamespaceSecretMountPath(secretMountPath, namespace)
|
||||
|
||||
function, err := GetFunction(client, name, namespace)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("service %s not found", name)
|
||||
log.Printf("[Update] %s\n", msg)
|
||||
http.Error(w, msg, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateSecrets(namespaceSecretMountPath, req.Secrets); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
ctx := namespaces.WithNamespace(context.Background(), namespace)
|
||||
|
||||
if _, err := prepull(ctx, req, client, alwaysPull); err != nil {
|
||||
log.Printf("[Update] error with pre-pull: %s, %s\n", name, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if function.replicas != 0 {
|
||||
err = cninetwork.DeleteCNINetwork(ctx, cni, client, name)
|
||||
if err != nil {
|
||||
log.Printf("[Update] error removing CNI network for %s, %s\n", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
killTimeout := getKillTimeout(function.annotations)
|
||||
|
||||
if err := service.Remove(ctx, client, name, killTimeout); err != nil {
|
||||
log.Printf("[Update] error removing %s, %s\n", name, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// The pull has already been done in pre-pull, so we can force this pull to "false"
|
||||
pull := false
|
||||
|
||||
if err := deploy(ctx, req, client, cni, namespaceSecretMountPath, pull); err != nil {
|
||||
log.Printf("[Update] error deploying %s, error: %s\n", name, err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
47
pkg/provider/handlers/utils.go
Normal file
47
pkg/provider/handlers/utils.go
Normal file
@ -0,0 +1,47 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/openfaas/faasd/pkg"
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
provider "github.com/openfaas/faasd/pkg/provider"
|
||||
)
|
||||
|
||||
func getRequestNamespace(namespace string) string {
|
||||
|
||||
if len(namespace) > 0 {
|
||||
return namespace
|
||||
}
|
||||
return faasd.DefaultFunctionNamespace
|
||||
}
|
||||
|
||||
func readNamespaceFromQuery(r *http.Request) string {
|
||||
q := r.URL.Query()
|
||||
return q.Get("namespace")
|
||||
}
|
||||
|
||||
func getNamespaceSecretMountPath(userSecretPath string, namespace string) string {
|
||||
return path.Join(userSecretPath, namespace)
|
||||
}
|
||||
|
||||
// validNamespace indicates whether the namespace is eligable to be
|
||||
// used for OpenFaaS functions.
|
||||
func validNamespace(store provider.Labeller, namespace string) (bool, error) {
|
||||
if namespace == faasd.DefaultFunctionNamespace {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
labels, err := store.Labels(context.Background(), namespace)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if value, found := labels[pkg.NamespaceLabel]; found && value == "true" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
75
pkg/provider/handlers/utils_test.go
Normal file
75
pkg/provider/handlers/utils_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
faasd "github.com/openfaas/faasd/pkg"
|
||||
)
|
||||
|
||||
func Test_getRequestNamespace(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
requestNamespace string
|
||||
expectedNamespace string
|
||||
}{
|
||||
{name: "RequestNamespace is not provided", requestNamespace: "", expectedNamespace: faasd.DefaultFunctionNamespace},
|
||||
{name: "RequestNamespace is provided", requestNamespace: "user-namespace", expectedNamespace: "user-namespace"},
|
||||
}
|
||||
|
||||
for _, tc := range tables {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actualNamespace := getRequestNamespace(tc.requestNamespace)
|
||||
if actualNamespace != tc.expectedNamespace {
|
||||
t.Errorf("Want: %s, got: %s", actualNamespace, tc.expectedNamespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getNamespaceSecretMountPath(t *testing.T) {
|
||||
userSecretPath := "/var/openfaas/secrets"
|
||||
tables := []struct {
|
||||
name string
|
||||
requestNamespace string
|
||||
expectedSecretPath string
|
||||
}{
|
||||
{name: "Default Namespace is provided", requestNamespace: faasd.DefaultFunctionNamespace, expectedSecretPath: "/var/openfaas/secrets/" + faasd.DefaultFunctionNamespace},
|
||||
{name: "User Namespace is provided", requestNamespace: "user-namespace", expectedSecretPath: "/var/openfaas/secrets/user-namespace"},
|
||||
}
|
||||
|
||||
for _, tc := range tables {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actualNamespace := getNamespaceSecretMountPath(userSecretPath, tc.requestNamespace)
|
||||
if actualNamespace != tc.expectedSecretPath {
|
||||
t.Errorf("Want: %s, got: %s", actualNamespace, tc.expectedSecretPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_readNamespaceFromQuery(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
queryNamespace string
|
||||
expectedNamespace string
|
||||
}{
|
||||
{name: "No Namespace is provided", queryNamespace: "", expectedNamespace: ""},
|
||||
{name: "User Namespace is provided", queryNamespace: "user-namespace", expectedNamespace: "user-namespace"},
|
||||
}
|
||||
|
||||
for _, tc := range tables {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
url := fmt.Sprintf("/test?namespace=%s", tc.queryNamespace)
|
||||
r := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
|
||||
actualNamespace := readNamespaceFromQuery(r)
|
||||
if actualNamespace != tc.expectedNamespace {
|
||||
t.Errorf("Want: %s, got: %s", actualNamespace, tc.expectedNamespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
25
pkg/provider/labeller.go
Normal file
25
pkg/provider/labeller.go
Normal file
@ -0,0 +1,25 @@
|
||||
package provider
|
||||
|
||||
import "context"
|
||||
|
||||
// Labeller can return labels for a namespace from containerd.
|
||||
type Labeller interface {
|
||||
Labels(ctx context.Context, namespace string) (map[string]string, error)
|
||||
}
|
||||
|
||||
//
|
||||
// FakeLabeller can be used to fake labels applied on namespace to mark
|
||||
// them valid/invalid for openfaas functions
|
||||
type FakeLabeller struct {
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
func NewFakeLabeller(labels map[string]string) Labeller {
|
||||
return &FakeLabeller{
|
||||
labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FakeLabeller) Labels(ctx context.Context, namespace string) (map[string]string, error) {
|
||||
return s.labels, nil
|
||||
}
|
152
pkg/proxy.go
152
pkg/proxy.go
@ -3,100 +3,114 @@ package pkg
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewProxy(hosts string, timeout time.Duration) *Proxy {
|
||||
// NewProxy creates a HTTP proxy to expose a host
|
||||
func NewProxy(upstream string, listenPort uint32, hostIP string, timeout time.Duration, resolver Resolver) *Proxy {
|
||||
|
||||
return &Proxy{
|
||||
Hosts: hosts,
|
||||
Timeout: timeout,
|
||||
Upstream: upstream,
|
||||
Port: listenPort,
|
||||
HostIP: hostIP,
|
||||
Timeout: timeout,
|
||||
Resolver: resolver,
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy for exposing a private container
|
||||
type Proxy struct {
|
||||
Hosts string
|
||||
Timeout time.Duration
|
||||
|
||||
// Port on which to listen to traffic
|
||||
Port uint32
|
||||
|
||||
// Upstream is where to send traffic when received
|
||||
Upstream string
|
||||
|
||||
// The IP to use to bind locally
|
||||
HostIP string
|
||||
|
||||
Resolver Resolver
|
||||
}
|
||||
|
||||
// Start listening and forwarding HTTP to the host
|
||||
func (p *Proxy) Start() error {
|
||||
tcp := 8080
|
||||
|
||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
log.Printf("Starting faasd proxy on %d\n", tcp)
|
||||
data := struct{ host string }{
|
||||
host: "",
|
||||
upstreamHost, upstreamPort, err := getUpstream(p.Upstream, p.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileData, fileErr := ioutil.ReadFile(p.Hosts)
|
||||
if fileErr != nil {
|
||||
return fileErr
|
||||
log.Printf("Looking up IP for: %q", upstreamHost)
|
||||
got := make(chan string, 1)
|
||||
|
||||
go p.Resolver.Get(upstreamHost, got, time.Second*5)
|
||||
|
||||
ipAddress := <-got
|
||||
close(got)
|
||||
|
||||
upstreamAddr := fmt.Sprintf("%s:%d", ipAddress, upstreamPort)
|
||||
|
||||
localBind := fmt.Sprintf("%s:%d", p.HostIP, p.Port)
|
||||
log.Printf("Proxy from: %s, to: %s (%s)\n", localBind, p.Upstream, ipAddress)
|
||||
|
||||
l, err := net.Listen("tcp", localBind)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(fileData), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Index(line, "gateway") > -1 {
|
||||
data.host = line[:strings.Index(line, "\t")]
|
||||
defer l.Close()
|
||||
for {
|
||||
// Wait for a connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
acceptErr := fmt.Errorf("Unable to accept on %d, error: %s",
|
||||
p.Port,
|
||||
err.Error())
|
||||
log.Printf("%s", acceptErr.Error())
|
||||
return acceptErr
|
||||
}
|
||||
|
||||
upstream, err := net.Dial("tcp", upstreamAddr)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("unable to dial to %s, error: %s", upstreamAddr, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
go pipe(conn, upstream)
|
||||
go pipe(upstream, conn)
|
||||
}
|
||||
fmt.Printf("Gateway: %s\n", data.host)
|
||||
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", tcp),
|
||||
ReadTimeout: p.Timeout,
|
||||
WriteTimeout: p.Timeout,
|
||||
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query := ""
|
||||
if len(r.URL.RawQuery) > 0 {
|
||||
query = "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
upstream := fmt.Sprintf("http://%s:8080%s%s", data.host, r.URL.Path, query)
|
||||
fmt.Printf("[faasd] proxy: %s\n", upstream)
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
wrapper := ioutil.NopCloser(r.Body)
|
||||
upReq, upErr := http.NewRequest(r.Method, upstream, wrapper)
|
||||
|
||||
if upErr != nil {
|
||||
log.Println(upErr)
|
||||
|
||||
http.Error(w, upErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
upRes, upResErr := http.DefaultClient.Do(upReq)
|
||||
|
||||
if upResErr != nil {
|
||||
log.Println(upResErr)
|
||||
|
||||
http.Error(w, upResErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range upRes.Header {
|
||||
w.Header().Set(k, v[0])
|
||||
}
|
||||
|
||||
w.WriteHeader(upRes.StatusCode)
|
||||
io.Copy(w, upRes.Body)
|
||||
|
||||
}),
|
||||
}
|
||||
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
|
||||
func pipe(from net.Conn, to net.Conn) {
|
||||
defer from.Close()
|
||||
io.Copy(from, to)
|
||||
}
|
||||
|
||||
func getUpstream(val string, defaultPort uint32) (string, uint32, error) {
|
||||
upstreamHostname := val
|
||||
upstreamPort := defaultPort
|
||||
|
||||
if in := strings.Index(val, ":"); in > -1 {
|
||||
upstreamHostname = val[:in]
|
||||
port, err := strconv.ParseInt(val[in+1:], 10, 32)
|
||||
if err != nil {
|
||||
return "", defaultPort, err
|
||||
}
|
||||
upstreamPort = uint32(port)
|
||||
}
|
||||
|
||||
return upstreamHostname, upstreamPort, nil
|
||||
}
|
||||
|
86
pkg/proxy_test.go
Normal file
86
pkg/proxy_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Proxy_ToPrivateServer(t *testing.T) {
|
||||
|
||||
wantBodyText := "OK"
|
||||
wantBody := []byte(wantBodyText)
|
||||
upstreamSvr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(wantBody)
|
||||
|
||||
}))
|
||||
|
||||
defer upstreamSvr.Close()
|
||||
port := 8080
|
||||
u, _ := url.Parse(upstreamSvr.URL)
|
||||
log.Println("Host", u.Host)
|
||||
|
||||
upstreamAddr := u.Host
|
||||
proxy := NewProxy(upstreamAddr, 8080, "127.0.0.1", time.Second*1, &mockResolver{})
|
||||
|
||||
gwChan := make(chan string, 1)
|
||||
doneCh := make(chan bool)
|
||||
|
||||
go proxy.Start()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
gwChan <- u.Host
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d", port), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 1; i < 11; i++ {
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Logf("Try %d, gave error: %s", i, err)
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
} else {
|
||||
|
||||
resBody, _ := ioutil.ReadAll(res.Body)
|
||||
if string(resBody) != string(wantBody) {
|
||||
t.Errorf("want %s, but got %s in body", string(wantBody), string(resBody))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
doneCh <- true
|
||||
}()
|
||||
}
|
||||
|
||||
type mockResolver struct {
|
||||
}
|
||||
|
||||
func (m *mockResolver) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (m *mockResolver) Get(upstream string, got chan<- string, timeout time.Duration) {
|
||||
got <- upstream
|
||||
}
|
12
pkg/resolver.go
Normal file
12
pkg/resolver.go
Normal file
@ -0,0 +1,12 @@
|
||||
package pkg
|
||||
|
||||
import "time"
|
||||
|
||||
// Resolver resolves an upstream IP address for a given upstream host
|
||||
type Resolver interface {
|
||||
// Start any polling or connections required to resolve
|
||||
Start()
|
||||
|
||||
// Get an IP address using an asynchronous operation
|
||||
Get(upstream string, got chan<- string, timeout time.Duration)
|
||||
}
|
@ -4,78 +4,102 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// dockerConfigDir contains "config.json"
|
||||
const dockerConfigDir = "/var/lib/faasd/.docker/"
|
||||
|
||||
// Remove removes a container
|
||||
func Remove(ctx context.Context, client *containerd.Client, name string) error {
|
||||
func Remove(ctx context.Context, client *containerd.Client, name string, killTimeout time.Duration) error {
|
||||
|
||||
container, containerErr := client.LoadContainer(ctx, name)
|
||||
|
||||
if containerErr == nil {
|
||||
found := true
|
||||
t, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
found = false
|
||||
} else {
|
||||
return fmt.Errorf("unable to get task %s: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
status, _ := t.Status(ctx)
|
||||
fmt.Printf("Status of %s is: %s\n", name, status.Status)
|
||||
|
||||
log.Printf("Need to kill %s\n", name)
|
||||
err := killTask(ctx, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), name, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), name, err)
|
||||
}
|
||||
} else {
|
||||
container, err := client.LoadContainer(ctx, name)
|
||||
if err != nil {
|
||||
// Perhaps the container was already removed, but the snapshot is still there
|
||||
service := client.SnapshotService("")
|
||||
key := name + "snapshot"
|
||||
|
||||
// Don't return an error if the snapshot doesn't exist
|
||||
if _, err := client.SnapshotService("").Stat(ctx, key); err == nil {
|
||||
service.Remove(ctx, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
taskFound := true
|
||||
t, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
taskFound = false
|
||||
} else {
|
||||
return fmt.Errorf("unable to get task %w: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if taskFound {
|
||||
status, err := t.Status(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Unable to get status for: %s, error: %s", name, err.Error())
|
||||
} else {
|
||||
log.Printf("Status of %s is: %s\n", name, status.Status)
|
||||
}
|
||||
|
||||
if err = killTask(ctx, t, killTimeout); err != nil {
|
||||
return fmt.Errorf("error killing task %s, %s, %w", container.ID(), name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := container.Delete(ctx, containerd.WithSnapshotCleanup); err != nil {
|
||||
return fmt.Errorf("error deleting container %s, %s, %w", container.ID(), name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// From Stellar
|
||||
func killTask(ctx context.Context, task containerd.Task) error {
|
||||
// Adapted from Stellar - https://github.com/stellarproject
|
||||
func killTask(ctx context.Context, task containerd.Task, killTimeout time.Duration) error {
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
id := task.ID()
|
||||
|
||||
defer wg.Done()
|
||||
if task != nil {
|
||||
wait, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error waiting on task: %s", err)
|
||||
log.Printf("error waiting on task: %s: %s", id, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
|
||||
log.Printf("error killing container task: %s", err)
|
||||
log.Printf("error killing task: %s with SIGTERM: %s", id, err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-wait:
|
||||
task.Delete(ctx)
|
||||
_, err := task.Delete(ctx)
|
||||
if err != nil {
|
||||
log.Printf("error deleting task: %s: %s", id, err)
|
||||
}
|
||||
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
case <-time.After(killTimeout):
|
||||
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
||||
log.Printf("error force killing container task: %s", err)
|
||||
log.Printf("error killing task: %s with SIGTERM: %s", id, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -86,20 +110,71 @@ func killTask(ctx context.Context, task containerd.Task) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string) (containerd.Image, error) {
|
||||
func getResolver(ctx context.Context, configFile *configfile.ConfigFile) (remotes.Resolver, error) {
|
||||
// credsFunc is based on https://github.com/moby/buildkit/blob/0b130cca040246d2ddf55117eeff34f546417e40/session/auth/authprovider/authprovider.go#L35
|
||||
credFunc := func(host string) (string, string, error) {
|
||||
if host == "registry-1.docker.io" {
|
||||
host = "https://index.docker.io/v1/"
|
||||
}
|
||||
ac, err := configFile.GetAuthConfig(host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if ac.IdentityToken != "" {
|
||||
return "", ac.IdentityToken, nil
|
||||
}
|
||||
return ac.Username, ac.Password, nil
|
||||
}
|
||||
|
||||
var empty containerd.Image
|
||||
image, err := client.GetImage(ctx, imageName)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
authOpts := []docker.AuthorizerOpt{docker.WithAuthCreds(credFunc)}
|
||||
authorizer := docker.NewDockerAuthorizer(authOpts...)
|
||||
opts := docker.ResolverOptions{
|
||||
Hosts: docker.ConfigureDefaultRegistries(docker.WithAuthorizer(authorizer)),
|
||||
}
|
||||
return docker.NewResolver(opts), nil
|
||||
}
|
||||
|
||||
func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string, pullAlways bool) (containerd.Image, error) {
|
||||
var (
|
||||
empty containerd.Image
|
||||
resolver remotes.Resolver
|
||||
)
|
||||
|
||||
if _, statErr := os.Stat(filepath.Join(dockerConfigDir, config.ConfigFileName)); statErr == nil {
|
||||
configFile, err := config.Load(dockerConfigDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolver, err = getResolver(ctx, configFile)
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
} else if !os.IsNotExist(statErr) {
|
||||
return empty, statErr
|
||||
}
|
||||
|
||||
var image containerd.Image
|
||||
if pullAlways {
|
||||
img, err := pullImage(ctx, client, resolver, imageName)
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
img, err := client.Pull(ctx, imageName, containerd.WithPullUnpack)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot pull: %s", err)
|
||||
}
|
||||
image = img
|
||||
} else {
|
||||
img, err := client.GetImage(ctx, imageName)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return empty, err
|
||||
}
|
||||
img, err := pullImage(ctx, client, resolver, imageName)
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
image = img
|
||||
} else {
|
||||
image = img
|
||||
}
|
||||
}
|
||||
|
||||
unpacked, err := image.IsUnpacked(ctx, snapshotter)
|
||||
@ -115,3 +190,23 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func pullImage(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, imageName string) (containerd.Image, error) {
|
||||
|
||||
var empty containerd.Image
|
||||
|
||||
rOpts := []containerd.RemoteOpt{
|
||||
containerd.WithPullUnpack,
|
||||
}
|
||||
|
||||
if resolver != nil {
|
||||
rOpts = append(rOpts, containerd.WithResolver(resolver))
|
||||
}
|
||||
|
||||
img, err := client.Pull(ctx, imageName, rOpts...)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot pull: %s", err)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
@ -6,88 +6,149 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alexellis/faasd/pkg/service"
|
||||
"github.com/alexellis/faasd/pkg/weave"
|
||||
"github.com/alexellis/arkade/pkg/env"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
gocni "github.com/containerd/go-cni"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||
"github.com/openfaas/faasd/pkg/service"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/oci"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
const defaultSnapshotter = "overlayfs"
|
||||
const (
|
||||
// workingDirectoryPermission user read/write/execute, group and others: read-only
|
||||
workingDirectoryPermission = 0744
|
||||
removalGracePeriod = time.Second * 5
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
// Image is the container image registry reference, in an OCI format.
|
||||
Image string
|
||||
Env []string
|
||||
Name string
|
||||
Mounts []Mount
|
||||
Caps []string
|
||||
Args []string
|
||||
DependsOn []string
|
||||
Ports []ServicePort
|
||||
|
||||
// User in the docker-compose.yaml spec can set as follows:
|
||||
// a user-id, username, userid:groupid or user:group
|
||||
User string
|
||||
}
|
||||
|
||||
type ServicePort struct {
|
||||
TargetPort uint32
|
||||
Port uint32
|
||||
HostIP string
|
||||
}
|
||||
|
||||
type Mount struct {
|
||||
Src string
|
||||
Dest string
|
||||
}
|
||||
|
||||
type Supervisor struct {
|
||||
client *containerd.Client
|
||||
cni gocni.CNI
|
||||
}
|
||||
|
||||
func NewSupervisor(sock string) (*Supervisor, error) {
|
||||
client, err := containerd.New(sock)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cni, err := cninetwork.InitNetwork()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Supervisor{
|
||||
client: client,
|
||||
cni: cni,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) Close() {
|
||||
defer s.client.Close()
|
||||
}
|
||||
|
||||
func (s *Supervisor) Remove(svcs []Service) error {
|
||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||
|
||||
for _, svc := range svcs {
|
||||
err := service.Remove(ctx, s.client, svc.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) Start(svcs []Service) error {
|
||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||
ctx := namespaces.WithNamespace(context.Background(), FaasdNamespace)
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
gw, err := cninetwork.CNIGateway()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts := fmt.Sprintf(`
|
||||
127.0.0.1 localhost
|
||||
%s faasd-provider`, gw)
|
||||
|
||||
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
||||
[]byte(`127.0.0.1 localhost
|
||||
172.19.0.1 faas-containerd`), 0644)
|
||||
[]byte(hosts), workingDirectoryPermission)
|
||||
|
||||
if writeHostsErr != nil {
|
||||
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
||||
}
|
||||
// os.Chown("hosts", 101, 101)
|
||||
|
||||
images := map[string]containerd.Image{}
|
||||
|
||||
for _, svc := range svcs {
|
||||
fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image)
|
||||
fmt.Printf("Preparing %s with image: %s\n", svc.Name, svc.Image)
|
||||
|
||||
img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter)
|
||||
r, err := reference.ParseNormalizedNamed(svc.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgRef := reference.TagNameOnly(r).String()
|
||||
|
||||
img, err := service.PrepareImage(ctx, s.client, imgRef, defaultSnapshotter, faasServicesPullAlways)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
images[svc.Name] = img
|
||||
size, _ := img.Size(ctx)
|
||||
fmt.Printf("Prepare done for: %s, %d bytes\n", svc.Image, size)
|
||||
fmt.Printf("Prepare done for: %s, %s\n", svc.Image, units.HumanSize(float64(size)))
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
fmt.Printf("Reconciling: %s\n", svc.Name)
|
||||
|
||||
containerErr := service.Remove(ctx, s.client, svc.Name)
|
||||
if containerErr != nil {
|
||||
return containerErr
|
||||
fmt.Printf("Removing old container for: %s\n", svc.Name)
|
||||
if err := service.Remove(ctx,
|
||||
s.client,
|
||||
svc.Name,
|
||||
removalGracePeriod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
order := buildDeploymentOrder(svcs)
|
||||
|
||||
for _, key := range order {
|
||||
|
||||
var svc *Service
|
||||
for _, s := range svcs {
|
||||
if s.Name == key {
|
||||
svc = &s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Starting: %s\n", svc.Name)
|
||||
|
||||
image := images[svc.Name]
|
||||
|
||||
@ -100,8 +161,29 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
Type: "bind",
|
||||
Options: []string{"rbind", "rw"},
|
||||
})
|
||||
}
|
||||
|
||||
// Only create directories, not files.
|
||||
// Some files don't have a suffix, such as secrets.
|
||||
if len(path.Ext(mnt.Src)) == 0 &&
|
||||
!strings.HasPrefix(mnt.Src, "/var/lib/faasd/secrets/") {
|
||||
// src is already prefixed with wd from an earlier step
|
||||
src := mnt.Src
|
||||
fmt.Printf("Creating local directory: %s\n", src)
|
||||
if err := os.MkdirAll(src, workingDirectoryPermission); err != nil {
|
||||
if !errors.Is(os.ErrExist, err) {
|
||||
fmt.Printf("Unable to create: %s, %s\n", src, err)
|
||||
}
|
||||
}
|
||||
if len(svc.User) > 0 {
|
||||
uid, err := strconv.Atoi(svc.User)
|
||||
if err == nil {
|
||||
if err := os.Chown(src, uid, -1); err != nil {
|
||||
fmt.Printf("Unable to chown: %s to %d, error: %s\n", src, uid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mounts = append(mounts, specs.Mount{
|
||||
@ -118,76 +200,75 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
|
||||
hook := func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
|
||||
if s.Hooks == nil {
|
||||
s.Hooks = &specs.Hooks{}
|
||||
}
|
||||
netnsPath, err := exec.LookPath("netns")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Hooks.Prestart = []specs.Hook{
|
||||
{
|
||||
Path: netnsPath,
|
||||
Args: []string{
|
||||
"netns",
|
||||
},
|
||||
Env: os.Environ(),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
if len(svc.User) > 0 {
|
||||
log.Printf("Running %s with user: %q", svc.Name, svc.User)
|
||||
}
|
||||
|
||||
newContainer, containerCreateErr := s.client.NewContainer(
|
||||
newContainer, err := s.client.NewContainer(
|
||||
ctx,
|
||||
svc.Name,
|
||||
containerd.WithImage(image),
|
||||
containerd.WithNewSnapshot(svc.Name+"-snapshot", image),
|
||||
containerd.WithNewSpec(oci.WithImageConfig(image),
|
||||
oci.WithHostname(svc.Name),
|
||||
withUserOrDefault(svc.User),
|
||||
oci.WithCapabilities(svc.Caps),
|
||||
oci.WithMounts(mounts),
|
||||
withOCIArgs(svc.Args),
|
||||
hook,
|
||||
oci.WithEnv(svc.Env)),
|
||||
)
|
||||
|
||||
if containerCreateErr != nil {
|
||||
log.Println(containerCreateErr)
|
||||
return containerCreateErr
|
||||
}
|
||||
|
||||
log.Printf("Created container %s\n", newContainer.ID())
|
||||
|
||||
task, err := newContainer.NewTask(ctx, cio.NewCreator(cio.WithStdio))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Printf("Error creating container: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ip := getIP(newContainer.ID(), task.Pid())
|
||||
log.Printf("Created container: %s\n", newContainer.ID())
|
||||
|
||||
hosts, _ := ioutil.ReadFile("hosts")
|
||||
task, err := newContainer.NewTask(ctx, cio.BinaryIO("/usr/local/bin/faasd", nil))
|
||||
if err != nil {
|
||||
log.Printf("Error creating task: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
labels := map[string]string{}
|
||||
_, err = cninetwork.CreateCNINetwork(ctx, s.cni, task, labels)
|
||||
if err != nil {
|
||||
log.Printf("Error creating CNI for %s: %s", svc.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
ip, err := cninetwork.GetIPAddress(svc.Name, task.Pid())
|
||||
if err != nil {
|
||||
log.Printf("Error getting IP for %s: %s", svc.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%s has IP: %s\n", newContainer.ID(), ip)
|
||||
|
||||
hosts, err := ioutil.ReadFile("hosts")
|
||||
if err != nil {
|
||||
log.Printf("Unable to read hosts file: %s\n", err.Error())
|
||||
}
|
||||
|
||||
hosts = []byte(string(hosts) + fmt.Sprintf(`
|
||||
%s %s
|
||||
`, ip, svc.Name))
|
||||
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
||||
|
||||
if writeErr != nil {
|
||||
log.Println("Error writing hosts file")
|
||||
if err := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission); err != nil {
|
||||
log.Printf("Error writing file: %s %s\n", "hosts", err)
|
||||
}
|
||||
// os.Chown("hosts", 101, 101)
|
||||
|
||||
exitStatusC, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
if _, err := task.Wait(ctx); err != nil {
|
||||
log.Printf("Task wait error: %s\n", err)
|
||||
return err
|
||||
}
|
||||
log.Println("Exited: ", exitStatusC)
|
||||
|
||||
if err := task.Start(ctx); err != nil {
|
||||
log.Println("Task err: ", err)
|
||||
log.Printf("Task: %s\tContainer: %s\n", task.ID(), newContainer.ID())
|
||||
// log.Println("Exited: ", exitStatusC)
|
||||
|
||||
if err = task.Start(ctx); err != nil {
|
||||
log.Printf("Task start error: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -195,37 +276,35 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIP(containerID string, taskPID uint32) string {
|
||||
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
||||
|
||||
peerIDs, err := weave.ConnectedToBridgeVethPeerIds("netns0")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
addrs, addrsErr := weave.GetNetDevsByVethPeerIds(int(taskPID), peerIDs)
|
||||
if addrsErr != nil {
|
||||
log.Fatal(addrsErr)
|
||||
}
|
||||
if len(addrs) > 0 {
|
||||
return addrs[0].CIDRs[0].IP.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
func (s *Supervisor) Close() {
|
||||
defer s.client.Close()
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Image string
|
||||
Env []string
|
||||
Name string
|
||||
Mounts []Mount
|
||||
Caps []string
|
||||
Args []string
|
||||
func (s *Supervisor) Remove(svcs []Service) error {
|
||||
ctx := namespaces.WithNamespace(context.Background(), FaasdNamespace)
|
||||
|
||||
for _, svc := range svcs {
|
||||
err := cninetwork.DeleteCNINetwork(ctx, s.cni, s.client, svc.Name)
|
||||
if err != nil {
|
||||
log.Printf("[Delete] error removing CNI network for %s, %s\n", svc.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := service.Remove(ctx, s.client, svc.Name, removalGracePeriod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Mount struct {
|
||||
Src string
|
||||
Dest string
|
||||
func withUserOrDefault(userstr string) oci.SpecOpts {
|
||||
if len(userstr) > 0 {
|
||||
return oci.WithUser(userstr)
|
||||
}
|
||||
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func withOCIArgs(args []string) oci.SpecOpts {
|
||||
@ -234,8 +313,140 @@ func withOCIArgs(args []string) oci.SpecOpts {
|
||||
}
|
||||
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ParseCompose converts a docker-compose Config into a service list that we can
|
||||
// pass to the supervisor client Start.
|
||||
//
|
||||
// The only anticipated error is a failure if the value mounts are not of type `bind`.
|
||||
func ParseCompose(config *compose.Config) ([]Service, error) {
|
||||
services := make([]Service, len(config.Services))
|
||||
for idx, s := range config.Services {
|
||||
// environment is a map[string]*string
|
||||
// but we want a []string
|
||||
|
||||
var env []string
|
||||
|
||||
envKeys := sortedEnvKeys(s.Environment)
|
||||
for _, name := range envKeys {
|
||||
value := s.Environment[name]
|
||||
if value == nil {
|
||||
env = append(env, fmt.Sprintf(`%s=""`, name))
|
||||
} else {
|
||||
env = append(env, fmt.Sprintf(`%s=%s`, name, *value))
|
||||
}
|
||||
}
|
||||
|
||||
var mounts []Mount
|
||||
for _, v := range s.Volumes {
|
||||
if v.Type != "bind" {
|
||||
return nil, errors.Errorf("unsupported volume mount type '%s' when parsing service '%s'", v.Type, s.Name)
|
||||
}
|
||||
mounts = append(mounts, Mount{
|
||||
Src: v.Source,
|
||||
Dest: v.Target,
|
||||
})
|
||||
}
|
||||
|
||||
services[idx] = Service{
|
||||
Name: s.Name,
|
||||
Image: s.Image,
|
||||
// ShellCommand is just an alias of string slice
|
||||
Args: []string(s.Command),
|
||||
Caps: s.CapAdd,
|
||||
Env: env,
|
||||
Mounts: mounts,
|
||||
DependsOn: s.DependsOn,
|
||||
User: s.User,
|
||||
Ports: convertPorts(s.Ports),
|
||||
}
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func convertPorts(ports []compose.ServicePortConfig) []ServicePort {
|
||||
servicePorts := []ServicePort{}
|
||||
for _, p := range ports {
|
||||
servicePorts = append(servicePorts, ServicePort{
|
||||
Port: p.Published,
|
||||
TargetPort: p.Target,
|
||||
HostIP: p.HostIP,
|
||||
})
|
||||
}
|
||||
|
||||
return servicePorts
|
||||
}
|
||||
|
||||
// LoadComposeFile is a helper method for loading a docker-compose file
|
||||
func LoadComposeFile(wd string, file string) (*compose.Config, error) {
|
||||
return LoadComposeFileWithArch(wd, file, env.GetClientArch)
|
||||
}
|
||||
|
||||
// LoadComposeFileWithArch is a helper method for loading a docker-compose file
|
||||
func LoadComposeFileWithArch(wd string, file string, archGetter ArchGetter) (*compose.Config, error) {
|
||||
|
||||
file = path.Join(wd, file)
|
||||
b, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := loader.ParseYAML(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archSuffix, err := GetArchSuffix(archGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []compose.ConfigFile
|
||||
files = append(files, compose.ConfigFile{Filename: file, Config: config})
|
||||
|
||||
return loader.Load(compose.ConfigDetails{
|
||||
WorkingDir: wd,
|
||||
ConfigFiles: files,
|
||||
Environment: map[string]string{
|
||||
"ARCH_SUFFIX": archSuffix,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func sortedEnvKeys(env map[string]*string) (keys []string) {
|
||||
for k := range env {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// ArchGetter provides client CPU architecture and
|
||||
// client OS
|
||||
type ArchGetter func() (string, string)
|
||||
|
||||
// GetArchSuffix provides client CPU architecture and
|
||||
// client OS from ArchGetter
|
||||
func GetArchSuffix(getClientArch ArchGetter) (suffix string, err error) {
|
||||
clientArch, clientOS := getClientArch()
|
||||
|
||||
if clientOS != "Linux" {
|
||||
return "", fmt.Errorf("you can only use faasd with Linux")
|
||||
}
|
||||
|
||||
switch clientArch {
|
||||
case "x86_64":
|
||||
// no suffix needed
|
||||
return "", nil
|
||||
case "armhf", "armv7l":
|
||||
return "-armhf", nil
|
||||
case "arm64", "aarch64":
|
||||
return "-arm64", nil
|
||||
default:
|
||||
// unknown, so use the default without suffix for now
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
262
pkg/supervisor_test.go
Normal file
262
pkg/supervisor_test.go
Normal file
@ -0,0 +1,262 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"path"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ParseCompose(t *testing.T) {
|
||||
|
||||
wd := "testdata"
|
||||
|
||||
want := map[string]Service{
|
||||
"basic-auth-plugin": {
|
||||
Name: "basic-auth-plugin",
|
||||
Image: "docker.io/openfaas/basic-auth-plugin:0.18.17",
|
||||
Env: []string{
|
||||
"pass_filename=basic-auth-password",
|
||||
"port=8080",
|
||||
"secret_mount_path=/run/secrets",
|
||||
"user_filename=basic-auth-user",
|
||||
},
|
||||
Mounts: []Mount{
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-password"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-password"),
|
||||
},
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-user"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
"nats": {
|
||||
Name: "nats",
|
||||
Image: "docker.io/library/nats-streaming:0.11.2",
|
||||
Args: []string{"/nats-streaming-server", "-m", "8222", "--store=memory", "--cluster_id=faas-cluster"},
|
||||
},
|
||||
"prometheus": {
|
||||
Name: "prometheus",
|
||||
Image: "docker.io/prom/prometheus:v2.14.0",
|
||||
Mounts: []Mount{
|
||||
{
|
||||
Src: path.Join(wd, "prometheus.yml"),
|
||||
Dest: "/etc/prometheus/prometheus.yml",
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
"gateway": {
|
||||
Name: "gateway",
|
||||
Env: []string{
|
||||
"auth_proxy_pass_body=false",
|
||||
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
|
||||
"basic_auth=true",
|
||||
"direct_functions=false",
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"functions_provider_url=http://faasd-provider:8081/",
|
||||
"read_timeout=60s",
|
||||
"scale_from_zero=true",
|
||||
"secret_mount_path=/run/secrets",
|
||||
"upstream_timeout=65s",
|
||||
"write_timeout=60s",
|
||||
},
|
||||
Image: "docker.io/openfaas/gateway:0.18.17",
|
||||
Mounts: []Mount{
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-password"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-password"),
|
||||
},
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-user"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
DependsOn: []string{"nats"},
|
||||
},
|
||||
"queue-worker": {
|
||||
Name: "queue-worker",
|
||||
Env: []string{
|
||||
"ack_wait=5m5s",
|
||||
"basic_auth=true",
|
||||
"faas_gateway_address=gateway",
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"gateway_invoke=true",
|
||||
"max_inflight=1",
|
||||
"secret_mount_path=/run/secrets",
|
||||
"write_debug=false",
|
||||
},
|
||||
Image: "docker.io/openfaas/queue-worker:0.11.2",
|
||||
Mounts: []Mount{
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-password"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-password"),
|
||||
},
|
||||
{
|
||||
Src: path.Join(wd, "secrets", "basic-auth-user"),
|
||||
Dest: path.Join("/run/secrets", "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
}
|
||||
|
||||
compose, err := LoadComposeFileWithArch(wd, "docker-compose.yaml", func() (string, string) { return "x86_64", "Linux" })
|
||||
if err != nil {
|
||||
t.Fatalf("can't read docker-compose file: %s", err)
|
||||
}
|
||||
|
||||
services, err := ParseCompose(compose)
|
||||
if err != nil {
|
||||
t.Fatalf("can't parse compose services: %s", err)
|
||||
}
|
||||
|
||||
if len(services) != len(want) {
|
||||
t.Fatalf("want: %d services, got: %d", len(want), len(services))
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
exp, ok := want[service.Name]
|
||||
|
||||
if service.Name == "gateway" {
|
||||
if len(service.DependsOn) == 0 {
|
||||
t.Fatalf("gateway should have at least one depends_on entry")
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("incorrect service: %s", service.Name)
|
||||
}
|
||||
|
||||
if service.Name != exp.Name {
|
||||
t.Fatalf("incorrect service Name:\n\twant: %s,\n\tgot: %s", exp.Name, service.Name)
|
||||
}
|
||||
|
||||
if service.Image != exp.Image {
|
||||
t.Fatalf("incorrect service Image:\n\twant: %s,\n\tgot: %s", exp.Image, service.Image)
|
||||
}
|
||||
|
||||
equalStringSlice(t, exp.Env, service.Env)
|
||||
equalStringSlice(t, exp.Caps, service.Caps)
|
||||
equalStringSlice(t, exp.Args, service.Args)
|
||||
|
||||
if !reflect.DeepEqual(exp.Mounts, service.Mounts) {
|
||||
t.Fatalf("incorrect service Mounts:\n\twant: %+v,\n\tgot: %+v", exp.Mounts, service.Mounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func equalStringSlice(t *testing.T, want, found []string) {
|
||||
t.Helper()
|
||||
if (want == nil) != (found == nil) {
|
||||
t.Fatalf("unexpected nil slice: want %+v, got %+v", want, found)
|
||||
}
|
||||
|
||||
if len(want) != len(found) {
|
||||
t.Fatalf("unequal slice length: want %+v, got %+v", want, found)
|
||||
}
|
||||
|
||||
for i := range want {
|
||||
if want[i] != found[i] {
|
||||
t.Fatalf("unexpected value at postition %d: want %s, got %s", i, want[i], found[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func equalMountSlice(t *testing.T, want, found []Mount) {
|
||||
t.Helper()
|
||||
if (want == nil) != (found == nil) {
|
||||
t.Fatalf("unexpected nil slice: want %+v, got %+v", want, found)
|
||||
}
|
||||
|
||||
if len(want) != len(found) {
|
||||
t.Fatalf("unequal slice length: want %+v, got %+v", want, found)
|
||||
}
|
||||
|
||||
for i := range want {
|
||||
if !reflect.DeepEqual(want[i], found[i]) {
|
||||
t.Fatalf("unexpected value at postition %d: want %s, got %s", i, want[i], found[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetArchSuffix(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
want string
|
||||
foundArch string
|
||||
foundOS string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "error if os is not linux",
|
||||
foundOS: "mac",
|
||||
err: "you can only use faasd with Linux",
|
||||
},
|
||||
{
|
||||
name: "x86 has no suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "x86_64",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "unknown arch has no suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "anything_else",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "armhf has armhf suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "armhf",
|
||||
want: "-armhf",
|
||||
},
|
||||
{
|
||||
name: "armv7l has armhf suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "armv7l",
|
||||
want: "-armhf",
|
||||
},
|
||||
{
|
||||
name: "arm64 has arm64 suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "arm64",
|
||||
want: "-arm64",
|
||||
},
|
||||
{
|
||||
name: "aarch64 has arm64 suffix",
|
||||
foundOS: "Linux",
|
||||
foundArch: "aarch64",
|
||||
want: "-arm64",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
suffix, err := GetArchSuffix(testArchGetter(tc.foundArch, tc.foundOS))
|
||||
if tc.err != "" && err == nil {
|
||||
t.Fatalf("want error %s but got nil", tc.err)
|
||||
} else if tc.err != "" && err.Error() != tc.err {
|
||||
t.Fatalf("want error %s, got %s", tc.err, err.Error())
|
||||
} else if tc.err == "" && err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
|
||||
if suffix != tc.want {
|
||||
t.Fatalf("want suffix %s, got %s", tc.want, suffix)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testArchGetter(arch, os string) ArchGetter {
|
||||
return func() (string, string) {
|
||||
return arch, os
|
||||
}
|
||||
}
|
@ -64,7 +64,11 @@ func DaemonReload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstallUnit(name string) error {
|
||||
func InstallUnit(name string, tokens map[string]string) error {
|
||||
if len(tokens["Cwd"]) == 0 {
|
||||
return fmt.Errorf("key Cwd expected in tokens parameter")
|
||||
}
|
||||
|
||||
tmplName := "./hack/" + name + ".service"
|
||||
tmpl, err := template.ParseFiles(tmplName)
|
||||
|
||||
@ -72,15 +76,9 @@ func InstallUnit(name string) error {
|
||||
return fmt.Errorf("error loading template %s, error %s", tmplName, err)
|
||||
}
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
var tpl bytes.Buffer
|
||||
userData := struct {
|
||||
Cwd string
|
||||
}{
|
||||
Cwd: wd,
|
||||
}
|
||||
|
||||
err = tmpl.Execute(&tpl, userData)
|
||||
err = tmpl.Execute(&tpl, tokens)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
98
pkg/testdata/docker-compose.yaml
vendored
Normal file
98
pkg/testdata/docker-compose.yaml
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
basic-auth-plugin:
|
||||
image: "docker.io/openfaas/basic-auth-plugin:0.18.17${ARCH_SUFFIX}"
|
||||
environment:
|
||||
- port=8080
|
||||
- secret_mount_path=/run/secrets
|
||||
- user_filename=basic-auth-user
|
||||
- pass_filename=basic-auth-password
|
||||
volumes:
|
||||
# we assume cwd == /var/lib/faasd
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-password
|
||||
target: /run/secrets/basic-auth-password
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-user
|
||||
target: /run/secrets/basic-auth-user
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
|
||||
nats:
|
||||
image: docker.io/library/nats-streaming:0.11.2
|
||||
command:
|
||||
- "/nats-streaming-server"
|
||||
- "-m"
|
||||
- "8222"
|
||||
- "--store=memory"
|
||||
- "--cluster_id=faas-cluster"
|
||||
ports:
|
||||
- "127.0.0.1:8222:8222"
|
||||
|
||||
prometheus:
|
||||
image: docker.io/prom/prometheus:v2.14.0
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./prometheus.yml
|
||||
target: /etc/prometheus/prometheus.yml
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
ports:
|
||||
- "127.0.0.1:9090:9090"
|
||||
|
||||
gateway:
|
||||
image: "docker.io/openfaas/gateway:0.18.17${ARCH_SUFFIX}"
|
||||
environment:
|
||||
- basic_auth=true
|
||||
- functions_provider_url=http://faasd-provider:8081/
|
||||
- direct_functions=false
|
||||
- read_timeout=60s
|
||||
- write_timeout=60s
|
||||
- upstream_timeout=65s
|
||||
- faas_nats_address=nats
|
||||
- faas_nats_port=4222
|
||||
- auth_proxy_url=http://basic-auth-plugin:8080/validate
|
||||
- auth_proxy_pass_body=false
|
||||
- secret_mount_path=/run/secrets
|
||||
- scale_from_zero=true
|
||||
volumes:
|
||||
# we assume cwd == /var/lib/faasd
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-password
|
||||
target: /run/secrets/basic-auth-password
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-user
|
||||
target: /run/secrets/basic-auth-user
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
depends_on:
|
||||
- basic-auth-plugin
|
||||
- nats
|
||||
- prometheus
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
queue-worker:
|
||||
image: docker.io/openfaas/queue-worker:0.11.2
|
||||
environment:
|
||||
- faas_nats_address=nats
|
||||
- faas_nats_port=4222
|
||||
- gateway_invoke=true
|
||||
- faas_gateway_address=gateway
|
||||
- ack_wait=5m5s
|
||||
- max_inflight=1
|
||||
- write_debug=false
|
||||
- basic_auth=true
|
||||
- secret_mount_path=/run/secrets
|
||||
volumes:
|
||||
# we assume cwd == /var/lib/faasd
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-password
|
||||
target: /run/secrets/basic-auth-password
|
||||
- type: bind
|
||||
source: ./secrets/basic-auth-user
|
||||
target: /run/secrets/basic-auth-user
|
||||
cap_add:
|
||||
- CAP_NET_RAW
|
||||
depends_on:
|
||||
- nats
|
@ -1,131 +0,0 @@
|
||||
// Copyright Weaveworks
|
||||
// github.com/weaveworks/weave/net
|
||||
|
||||
package weave
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
type Dev struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
MAC net.HardwareAddr `json:"MAC,omitempty"`
|
||||
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
|
||||
}
|
||||
|
||||
func linkToNetDev(link netlink.Link) (Dev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
|
||||
for _, addr := range addrs {
|
||||
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||
}
|
||||
return netDev, nil
|
||||
}
|
||||
|
||||
// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to
|
||||
// the given bridge. The peer index is used to query from a container netns
|
||||
// whether the container is connected to the bridge.
|
||||
func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) {
|
||||
var ids []int
|
||||
|
||||
br, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index {
|
||||
peerID := link.Attrs().ParentIndex
|
||||
if peerID == 0 {
|
||||
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||
// as fall-back, assume the peers are consecutive
|
||||
peerID = link.Attrs().Index - 1
|
||||
}
|
||||
ids = append(ids, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Lookup the weave interface of a container
|
||||
func GetWeaveNetDevs(processID int) ([]Dev, error) {
|
||||
peerIDs, err := ConnectedToBridgeVethPeerIds("weave")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetNetDevsByVethPeerIds(processID, peerIDs)
|
||||
}
|
||||
|
||||
func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
|
||||
// Bail out if this container is running in the root namespace
|
||||
netnsRoot, err := netns.GetFromPid(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||
}
|
||||
defer netnsRoot.Close()
|
||||
netnsContainer, err := netns.GetFromPid(processID)
|
||||
if err != nil {
|
||||
// Unable to find a namespace for this process - just return nothing
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||
}
|
||||
defer netnsContainer.Close()
|
||||
if netnsRoot.Equal(netnsContainer) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// convert list of peerIDs into a map for faster lookup
|
||||
indexes := make(map[int]struct{})
|
||||
for _, id := range peerIDs {
|
||||
indexes[id] = struct{}{}
|
||||
}
|
||||
|
||||
var netdevs []Dev
|
||||
err = WithNetNS(netnsContainer, func() error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if _, found := indexes[link.Attrs().Index]; found {
|
||||
netdev, err := linkToNetDev(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netdevs = append(netdevs, netdev)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return netdevs, err
|
||||
}
|
||||
|
||||
// Get the weave bridge interface.
|
||||
// NB: Should be called from the root network namespace.
|
||||
func GetBridgeNetDev(bridgeName string) (Dev, error) {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
return linkToNetDev(link)
|
||||
}
|
@ -26,3 +26,7 @@ scrape_configs:
|
||||
- job_name: 'gateway'
|
||||
static_configs:
|
||||
- targets: ['gateway:8082']
|
||||
|
||||
- job_name: 'provider'
|
||||
static_configs:
|
||||
- targets: ['faasd-provider:8081']
|
93
vendor/github.com/AdaLogics/go-fuzz-headers/README.md
generated
vendored
Normal file
93
vendor/github.com/AdaLogics/go-fuzz-headers/README.md
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
# go-fuzz-headers
|
||||
This repository contains various helper functions for go fuzzing. It is mostly used in combination with [go-fuzz](https://github.com/dvyukov/go-fuzz), but compatibility with fuzzing in the standard library will also be supported. Any coverage guided fuzzing engine that provides an array or slice of bytes can be used with go-fuzz-headers.
|
||||
|
||||
|
||||
## Usage
|
||||
Using go-fuzz-headers is easy. First create a new consumer with the bytes provided by the fuzzing engine:
|
||||
|
||||
```go
|
||||
import (
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
)
|
||||
data := []byte{'R', 'a', 'n', 'd', 'o', 'm'}
|
||||
f := fuzz.NewConsumer(data)
|
||||
|
||||
```
|
||||
|
||||
This creates a `Consumer` that consumes the bytes of the input as it uses them to fuzz different types.
|
||||
|
||||
After that, `f` can be used to easily create fuzzed instances of different types. Below are some examples:
|
||||
|
||||
### Structs
|
||||
One of the most useful features of go-fuzz-headers is its ability to fill structs with the data provided by the fuzzing engine. This is done with a single line:
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
p := Person{}
|
||||
// Fill p with values based on the data provided by the fuzzing engine:
|
||||
err := f.GenerateStruct(&p)
|
||||
```
|
||||
|
||||
This includes nested structs too. In this example, the fuzz Consumer will also insert values in `p.BestFriend`:
|
||||
```go
|
||||
type PersonI struct {
|
||||
Name string
|
||||
Age int
|
||||
BestFriend PersonII
|
||||
}
|
||||
type PersonII struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
p := PersonI{}
|
||||
err := f.GenerateStruct(&p)
|
||||
```
|
||||
|
||||
If the consumer should insert values for unexported fields as well as exported, this can be enabled with:
|
||||
|
||||
```go
|
||||
f.AllowUnexportedFields()
|
||||
```
|
||||
|
||||
...and disabled with:
|
||||
|
||||
```go
|
||||
f.DisallowUnexportedFields()
|
||||
```
|
||||
|
||||
### Other types:
|
||||
|
||||
Other useful APIs:
|
||||
|
||||
```go
|
||||
createdString, err := f.GetString() // Gets a string
|
||||
createdInt, err := f.GetInt() // Gets an integer
|
||||
createdByte, err := f.GetByte() // Gets a byte
|
||||
createdBytes, err := f.GetBytes() // Gets a byte slice
|
||||
createdBool, err := f.GetBool() // Gets a boolean
|
||||
err := f.FuzzMap(target_map) // Fills a map
|
||||
createdTarBytes, err := f.TarBytes() // Gets bytes of a valid tar archive
|
||||
err := f.CreateFiles(inThisDir) // Fills inThisDir with files
|
||||
createdString, err := f.GetStringFrom("anyCharInThisString", ofThisLength) // Gets a string that consists of chars from "anyCharInThisString" and has the exact length "ofThisLength"
|
||||
```
|
||||
|
||||
Most APIs are added as they are needed.
|
||||
|
||||
## Projects that use go-fuzz-headers
|
||||
- [runC](https://github.com/opencontainers/runc)
|
||||
- [Istio](https://github.com/istio/istio)
|
||||
- [Vitess](https://github.com/vitessio/vitess)
|
||||
- [Containerd](https://github.com/containerd/containerd)
|
||||
|
||||
Feel free to add your own project to the list, if you use go-fuzz-headers to fuzz it.
|
||||
|
||||
|
||||
|
||||
|
||||
## Status
|
||||
The project is under development and will be updated regularly.
|
||||
|
||||
## References
|
||||
go-fuzz-headers' approach to fuzzing structs is strongly inspired by [gofuzz](https://github.com/google/gofuzz).
|
899
vendor/github.com/AdaLogics/go-fuzz-headers/consumer.go
generated
vendored
Normal file
899
vendor/github.com/AdaLogics/go-fuzz-headers/consumer.go
generated
vendored
Normal file
@ -0,0 +1,899 @@
|
||||
// Copyright 2023 The go-fuzz-headers Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gofuzzheaders
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
)
|
||||
|
||||
var (
|
||||
MaxTotalLen uint32 = 2000000
|
||||
maxDepth = 100
|
||||
)
|
||||
|
||||
func SetMaxTotalLen(newLen uint32) {
|
||||
MaxTotalLen = newLen
|
||||
}
|
||||
|
||||
type ConsumeFuzzer struct {
|
||||
data []byte
|
||||
dataTotal uint32
|
||||
CommandPart []byte
|
||||
RestOfArray []byte
|
||||
NumberOfCalls int
|
||||
position uint32
|
||||
fuzzUnexportedFields bool
|
||||
curDepth int
|
||||
Funcs map[reflect.Type]reflect.Value
|
||||
}
|
||||
|
||||
func IsDivisibleBy(n int, divisibleby int) bool {
|
||||
return (n % divisibleby) == 0
|
||||
}
|
||||
|
||||
func NewConsumer(fuzzData []byte) *ConsumeFuzzer {
|
||||
return &ConsumeFuzzer{
|
||||
data: fuzzData,
|
||||
dataTotal: uint32(len(fuzzData)),
|
||||
Funcs: make(map[reflect.Type]reflect.Value),
|
||||
curDepth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) Split(minCalls, maxCalls int) error {
|
||||
if f.dataTotal == 0 {
|
||||
return errors.New("could not split")
|
||||
}
|
||||
numberOfCalls := int(f.data[0])
|
||||
if numberOfCalls < minCalls || numberOfCalls > maxCalls {
|
||||
return errors.New("bad number of calls")
|
||||
}
|
||||
if int(f.dataTotal) < numberOfCalls+numberOfCalls+1 {
|
||||
return errors.New("length of data does not match required parameters")
|
||||
}
|
||||
|
||||
// Define part 2 and 3 of the data array
|
||||
commandPart := f.data[1 : numberOfCalls+1]
|
||||
restOfArray := f.data[numberOfCalls+1:]
|
||||
|
||||
// Just a small check. It is necessary
|
||||
if len(commandPart) != numberOfCalls {
|
||||
return errors.New("length of commandPart does not match number of calls")
|
||||
}
|
||||
|
||||
// Check if restOfArray is divisible by numberOfCalls
|
||||
if !IsDivisibleBy(len(restOfArray), numberOfCalls) {
|
||||
return errors.New("length of commandPart does not match number of calls")
|
||||
}
|
||||
f.CommandPart = commandPart
|
||||
f.RestOfArray = restOfArray
|
||||
f.NumberOfCalls = numberOfCalls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) AllowUnexportedFields() {
|
||||
f.fuzzUnexportedFields = true
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) DisallowUnexportedFields() {
|
||||
f.fuzzUnexportedFields = false
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GenerateStruct(targetStruct interface{}) error {
|
||||
e := reflect.ValueOf(targetStruct).Elem()
|
||||
return f.fuzzStruct(e, false)
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) setCustom(v reflect.Value) error {
|
||||
// First: see if we have a fuzz function for it.
|
||||
doCustom, ok := f.Funcs[v.Type()]
|
||||
if !ok {
|
||||
return fmt.Errorf("could not find a custom function")
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
if !v.CanSet() {
|
||||
return fmt.Errorf("could not use a custom function")
|
||||
}
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
if !v.CanSet() {
|
||||
return fmt.Errorf("could not use a custom function")
|
||||
}
|
||||
v.Set(reflect.MakeMap(v.Type()))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("could not use a custom function")
|
||||
}
|
||||
|
||||
verr := doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{
|
||||
F: f,
|
||||
})})
|
||||
|
||||
// check if we return an error
|
||||
if verr[0].IsNil() {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("could not use a custom function")
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) fuzzStruct(e reflect.Value, customFunctions bool) error {
|
||||
if f.curDepth >= maxDepth {
|
||||
// return err or nil here?
|
||||
return nil
|
||||
}
|
||||
f.curDepth++
|
||||
defer func() { f.curDepth-- }()
|
||||
|
||||
// We check if we should check for custom functions
|
||||
if customFunctions && e.IsValid() && e.CanAddr() {
|
||||
err := f.setCustom(e.Addr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch e.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < e.NumField(); i++ {
|
||||
var v reflect.Value
|
||||
if !e.Field(i).CanSet() {
|
||||
if f.fuzzUnexportedFields {
|
||||
v = reflect.NewAt(e.Field(i).Type(), unsafe.Pointer(e.Field(i).UnsafeAddr())).Elem()
|
||||
}
|
||||
if err := f.fuzzStruct(v, customFunctions); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
v = e.Field(i)
|
||||
if err := f.fuzzStruct(v, customFunctions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
str, err := f.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetString(str)
|
||||
}
|
||||
case reflect.Slice:
|
||||
var maxElements uint32
|
||||
// Byte slices should not be restricted
|
||||
if e.Type().String() == "[]uint8" {
|
||||
maxElements = 10000000
|
||||
} else {
|
||||
maxElements = 50
|
||||
}
|
||||
|
||||
randQty, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numOfElements := randQty % maxElements
|
||||
if (f.dataTotal - f.position) < numOfElements {
|
||||
numOfElements = f.dataTotal - f.position
|
||||
}
|
||||
|
||||
uu := reflect.MakeSlice(e.Type(), int(numOfElements), int(numOfElements))
|
||||
|
||||
for i := 0; i < int(numOfElements); i++ {
|
||||
// If we have more than 10, then we can proceed with that.
|
||||
if err := f.fuzzStruct(uu.Index(i), customFunctions); err != nil {
|
||||
if i >= 10 {
|
||||
if e.CanSet() {
|
||||
e.Set(uu)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.Set(uu)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
newInt, err := f.GetUint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetUint(uint64(newInt))
|
||||
}
|
||||
case reflect.Uint32:
|
||||
newInt, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetUint(uint64(newInt))
|
||||
}
|
||||
case reflect.Uint64:
|
||||
newInt, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetUint(uint64(newInt))
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
newInt, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetInt(int64(newInt))
|
||||
}
|
||||
case reflect.Float32:
|
||||
newFloat, err := f.GetFloat32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetFloat(float64(newFloat))
|
||||
}
|
||||
case reflect.Float64:
|
||||
newFloat, err := f.GetFloat64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetFloat(float64(newFloat))
|
||||
}
|
||||
case reflect.Map:
|
||||
if e.CanSet() {
|
||||
e.Set(reflect.MakeMap(e.Type()))
|
||||
const maxElements = 50
|
||||
randQty, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numOfElements := randQty % maxElements
|
||||
for i := 0; i < numOfElements; i++ {
|
||||
key := reflect.New(e.Type().Key()).Elem()
|
||||
if err := f.fuzzStruct(key, customFunctions); err != nil {
|
||||
return err
|
||||
}
|
||||
val := reflect.New(e.Type().Elem()).Elem()
|
||||
if err = f.fuzzStruct(val, customFunctions); err != nil {
|
||||
return err
|
||||
}
|
||||
e.SetMapIndex(key, val)
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if e.CanSet() {
|
||||
e.Set(reflect.New(e.Type().Elem()))
|
||||
if err := f.fuzzStruct(e.Elem(), customFunctions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint8:
|
||||
b, err := f.GetByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.CanSet() {
|
||||
e.SetUint(uint64(b))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetStringArray() (reflect.Value, error) {
|
||||
// The max size of the array:
|
||||
const max uint32 = 20
|
||||
|
||||
arraySize := f.position
|
||||
if arraySize > max {
|
||||
arraySize = max
|
||||
}
|
||||
stringArray := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("string")), int(arraySize), int(arraySize))
|
||||
if f.position+arraySize >= f.dataTotal {
|
||||
return stringArray, errors.New("could not make string array")
|
||||
}
|
||||
|
||||
for i := 0; i < int(arraySize); i++ {
|
||||
stringSize := uint32(f.data[f.position])
|
||||
if f.position+stringSize >= f.dataTotal {
|
||||
return stringArray, nil
|
||||
}
|
||||
stringToAppend := string(f.data[f.position : f.position+stringSize])
|
||||
strVal := reflect.ValueOf(stringToAppend)
|
||||
stringArray = reflect.Append(stringArray, strVal)
|
||||
f.position += stringSize
|
||||
}
|
||||
return stringArray, nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetInt() (int, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return 0, errors.New("not enough bytes to create int")
|
||||
}
|
||||
returnInt := int(f.data[f.position])
|
||||
f.position++
|
||||
return returnInt, nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetByte() (byte, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return 0x00, errors.New("not enough bytes to get byte")
|
||||
}
|
||||
returnByte := f.data[f.position]
|
||||
f.position++
|
||||
return returnByte, nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetNBytes(numberOfBytes int) ([]byte, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to get byte")
|
||||
}
|
||||
returnBytes := make([]byte, 0, numberOfBytes)
|
||||
for i := 0; i < numberOfBytes; i++ {
|
||||
newByte, err := f.GetByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
returnBytes = append(returnBytes, newByte)
|
||||
}
|
||||
return returnBytes, nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetUint16() (uint16, error) {
|
||||
u16, err := f.GetNBytes(2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
littleEndian, err := f.GetBool()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if littleEndian {
|
||||
return binary.LittleEndian.Uint16(u16), nil
|
||||
}
|
||||
return binary.BigEndian.Uint16(u16), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetUint32() (uint32, error) {
|
||||
i, err := f.GetInt()
|
||||
if err != nil {
|
||||
return uint32(0), err
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetUint64() (uint64, error) {
|
||||
u64, err := f.GetNBytes(8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
littleEndian, err := f.GetBool()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if littleEndian {
|
||||
return binary.LittleEndian.Uint64(u64), nil
|
||||
}
|
||||
return binary.BigEndian.Uint64(u64), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetBytes() ([]byte, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
length, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
if f.position+length > MaxTotalLen {
|
||||
return nil, errors.New("created too large a string")
|
||||
}
|
||||
byteBegin := f.position - 1
|
||||
if byteBegin >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
if length == 0 {
|
||||
return nil, errors.New("zero-length is not supported")
|
||||
}
|
||||
if byteBegin+length >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
if byteBegin+length < byteBegin {
|
||||
return nil, errors.New("numbers overflow")
|
||||
}
|
||||
f.position = byteBegin + length
|
||||
return f.data[byteBegin:f.position], nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetString() (string, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
length, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
if f.position > MaxTotalLen {
|
||||
return "nil", errors.New("created too large a string")
|
||||
}
|
||||
byteBegin := f.position
|
||||
if byteBegin >= f.dataTotal {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
if byteBegin+length > f.dataTotal {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
if byteBegin > byteBegin+length {
|
||||
return "nil", errors.New("numbers overflow")
|
||||
}
|
||||
f.position = byteBegin + length
|
||||
return string(f.data[byteBegin:f.position]), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetBool() (bool, error) {
|
||||
if f.position >= f.dataTotal {
|
||||
return false, errors.New("not enough bytes to create bool")
|
||||
}
|
||||
if IsDivisibleBy(int(f.data[f.position]), 2) {
|
||||
f.position++
|
||||
return true, nil
|
||||
} else {
|
||||
f.position++
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) FuzzMap(m interface{}) error {
|
||||
return f.GenerateStruct(m)
|
||||
}
|
||||
|
||||
func returnTarBytes(buf []byte) ([]byte, error) {
|
||||
// Count files
|
||||
var fileCounter int
|
||||
tr := tar.NewReader(bytes.NewReader(buf))
|
||||
for {
|
||||
_, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileCounter++
|
||||
}
|
||||
if fileCounter >= 1 {
|
||||
return buf, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not enough files were created\n")
|
||||
}
|
||||
|
||||
func setTarHeaderFormat(hdr *tar.Header, f *ConsumeFuzzer) error {
|
||||
ind, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch ind % 4 {
|
||||
case 0:
|
||||
hdr.Format = tar.FormatUnknown
|
||||
case 1:
|
||||
hdr.Format = tar.FormatUSTAR
|
||||
case 2:
|
||||
hdr.Format = tar.FormatPAX
|
||||
case 3:
|
||||
hdr.Format = tar.FormatGNU
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTarHeaderTypeflag(hdr *tar.Header, f *ConsumeFuzzer) error {
|
||||
ind, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch ind % 13 {
|
||||
case 0:
|
||||
hdr.Typeflag = tar.TypeReg
|
||||
case 1:
|
||||
hdr.Typeflag = tar.TypeLink
|
||||
linkname, err := f.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Linkname = linkname
|
||||
case 2:
|
||||
hdr.Typeflag = tar.TypeSymlink
|
||||
linkname, err := f.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Linkname = linkname
|
||||
case 3:
|
||||
hdr.Typeflag = tar.TypeChar
|
||||
case 4:
|
||||
hdr.Typeflag = tar.TypeBlock
|
||||
case 5:
|
||||
hdr.Typeflag = tar.TypeDir
|
||||
case 6:
|
||||
hdr.Typeflag = tar.TypeFifo
|
||||
case 7:
|
||||
hdr.Typeflag = tar.TypeCont
|
||||
case 8:
|
||||
hdr.Typeflag = tar.TypeXHeader
|
||||
case 9:
|
||||
hdr.Typeflag = tar.TypeXGlobalHeader
|
||||
case 10:
|
||||
hdr.Typeflag = tar.TypeGNUSparse
|
||||
case 11:
|
||||
hdr.Typeflag = tar.TypeGNULongName
|
||||
case 12:
|
||||
hdr.Typeflag = tar.TypeGNULongLink
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tooSmallFileBody(length uint32) bool {
|
||||
if length < 2 {
|
||||
return true
|
||||
}
|
||||
if length < 4 {
|
||||
return true
|
||||
}
|
||||
if length < 10 {
|
||||
return true
|
||||
}
|
||||
if length < 100 {
|
||||
return true
|
||||
}
|
||||
if length < 500 {
|
||||
return true
|
||||
}
|
||||
if length < 1000 {
|
||||
return true
|
||||
}
|
||||
if length < 2000 {
|
||||
return true
|
||||
}
|
||||
if length < 4000 {
|
||||
return true
|
||||
}
|
||||
if length < 8000 {
|
||||
return true
|
||||
}
|
||||
if length < 16000 {
|
||||
return true
|
||||
}
|
||||
if length < 32000 {
|
||||
return true
|
||||
}
|
||||
if length < 64000 {
|
||||
return true
|
||||
}
|
||||
if length < 128000 {
|
||||
return true
|
||||
}
|
||||
if length < 264000 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) createTarFileBody() ([]byte, error) {
|
||||
length, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
|
||||
shouldUseLargeFileBody, err := f.GetBool()
|
||||
if err != nil {
|
||||
return nil, errors.New("not enough bytes to check long file body")
|
||||
}
|
||||
|
||||
if shouldUseLargeFileBody && tooSmallFileBody(length) {
|
||||
return nil, errors.New("File body was too small")
|
||||
}
|
||||
|
||||
// A bit of optimization to attempt to create a file body
|
||||
// when we don't have as many bytes left as "length"
|
||||
remainingBytes := f.dataTotal - f.position
|
||||
if remainingBytes == 0 {
|
||||
return nil, errors.New("created too large a string")
|
||||
}
|
||||
if f.position+length > MaxTotalLen {
|
||||
return nil, errors.New("created too large a string")
|
||||
}
|
||||
byteBegin := f.position
|
||||
if byteBegin >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
if length == 0 {
|
||||
return nil, errors.New("zero-length is not supported")
|
||||
}
|
||||
if byteBegin+length >= f.dataTotal {
|
||||
return nil, errors.New("not enough bytes to create byte array")
|
||||
}
|
||||
if byteBegin+length < byteBegin {
|
||||
return nil, errors.New("numbers overflow")
|
||||
}
|
||||
f.position = byteBegin + length
|
||||
return f.data[byteBegin:f.position], nil
|
||||
}
|
||||
|
||||
// getTarFileName is similar to GetString(), but creates string based
|
||||
// on the length of f.data to reduce the likelihood of overflowing
|
||||
// f.data.
|
||||
func (f *ConsumeFuzzer) getTarFilename() (string, error) {
|
||||
length, err := f.GetUint32()
|
||||
if err != nil {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
|
||||
// A bit of optimization to attempt to create a file name
|
||||
// when we don't have as many bytes left as "length"
|
||||
remainingBytes := f.dataTotal - f.position
|
||||
if remainingBytes == 0 {
|
||||
return "nil", errors.New("created too large a string")
|
||||
}
|
||||
if remainingBytes < 50 {
|
||||
length = length % remainingBytes
|
||||
} else if f.dataTotal < 500 {
|
||||
length = length % f.dataTotal
|
||||
}
|
||||
if f.position > MaxTotalLen {
|
||||
return "nil", errors.New("created too large a string")
|
||||
}
|
||||
byteBegin := f.position
|
||||
if byteBegin >= f.dataTotal {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
if byteBegin+length > f.dataTotal {
|
||||
return "nil", errors.New("not enough bytes to create string")
|
||||
}
|
||||
if byteBegin > byteBegin+length {
|
||||
return "nil", errors.New("numbers overflow")
|
||||
}
|
||||
f.position = byteBegin + length
|
||||
return string(f.data[byteBegin:f.position]), nil
|
||||
}
|
||||
|
||||
// TarBytes returns valid bytes for a tar archive
|
||||
func (f *ConsumeFuzzer) TarBytes() ([]byte, error) {
|
||||
numberOfFiles, err := f.GetInt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tw := tar.NewWriter(&buf)
|
||||
defer tw.Close()
|
||||
|
||||
const maxNoOfFiles = 1000
|
||||
for i := 0; i < numberOfFiles%maxNoOfFiles; i++ {
|
||||
filename, err := f.getTarFilename()
|
||||
if err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
filebody, err := f.createTarFileBody()
|
||||
if err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
sec, err := f.GetInt()
|
||||
if err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
nsec, err := f.GetInt()
|
||||
if err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
|
||||
hdr := &tar.Header{
|
||||
Name: filename,
|
||||
Size: int64(len(filebody)),
|
||||
Mode: 0o600,
|
||||
ModTime: time.Unix(int64(sec), int64(nsec)),
|
||||
}
|
||||
if err := setTarHeaderTypeflag(hdr, f); err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
if err := setTarHeaderFormat(hdr, f); err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
if _, err := tw.Write(filebody); err != nil {
|
||||
return returnTarBytes(buf.Bytes())
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// CreateFiles creates pseudo-random files in rootDir.
|
||||
// It creates subdirs and places the files there.
|
||||
// It is the callers responsibility to ensure that
|
||||
// rootDir exists.
|
||||
func (f *ConsumeFuzzer) CreateFiles(rootDir string) error {
|
||||
numberOfFiles, err := f.GetInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxNumberOfFiles := numberOfFiles % 4000 // This is completely arbitrary
|
||||
if maxNumberOfFiles == 0 {
|
||||
return errors.New("maxNumberOfFiles is nil")
|
||||
}
|
||||
|
||||
var noOfCreatedFiles int
|
||||
for i := 0; i < maxNumberOfFiles; i++ {
|
||||
// The file to create:
|
||||
fileName, err := f.GetString()
|
||||
if err != nil {
|
||||
if noOfCreatedFiles > 0 {
|
||||
// If files have been created, we don't return an error.
|
||||
break
|
||||
} else {
|
||||
return errors.New("could not get fileName")
|
||||
}
|
||||
}
|
||||
fullFilePath, err := securejoin.SecureJoin(rootDir, fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the subdirectory of the file
|
||||
if subDir := filepath.Dir(fileName); subDir != "" && subDir != "." {
|
||||
// create the dir first; avoid going outside the root dir
|
||||
if strings.Contains(subDir, "../") || (len(subDir) > 0 && subDir[0] == 47) || strings.Contains(subDir, "\\") {
|
||||
continue
|
||||
}
|
||||
dirPath, err := securejoin.SecureJoin(rootDir, subDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
err2 := os.MkdirAll(dirPath, 0o777)
|
||||
if err2 != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
fullFilePath, err = securejoin.SecureJoin(dirPath, fileName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Create symlink
|
||||
createSymlink, err := f.GetBool()
|
||||
if err != nil {
|
||||
if noOfCreatedFiles > 0 {
|
||||
break
|
||||
} else {
|
||||
return errors.New("could not create the symlink")
|
||||
}
|
||||
}
|
||||
if createSymlink {
|
||||
symlinkTarget, err := f.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Symlink(symlinkTarget, fullFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// stop loop here, since a symlink needs no further action
|
||||
noOfCreatedFiles++
|
||||
continue
|
||||
}
|
||||
// We create a normal file
|
||||
fileContents, err := f.GetBytes()
|
||||
if err != nil {
|
||||
if noOfCreatedFiles > 0 {
|
||||
break
|
||||
} else {
|
||||
return errors.New("could not create the file")
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(fullFilePath, fileContents, 0o666)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
noOfCreatedFiles++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStringFrom returns a string that can only consist of characters
|
||||
// included in possibleChars. It returns an error if the created string
|
||||
// does not have the specified length.
|
||||
func (f *ConsumeFuzzer) GetStringFrom(possibleChars string, length int) (string, error) {
|
||||
if (f.dataTotal - f.position) < uint32(length) {
|
||||
return "", errors.New("not enough bytes to create a string")
|
||||
}
|
||||
output := make([]byte, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
charIndex, err := f.GetInt()
|
||||
if err != nil {
|
||||
return string(output), err
|
||||
}
|
||||
output = append(output, possibleChars[charIndex%len(possibleChars)])
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetRune() ([]rune, error) {
|
||||
stringToConvert, err := f.GetString()
|
||||
if err != nil {
|
||||
return []rune("nil"), err
|
||||
}
|
||||
return []rune(stringToConvert), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetFloat32() (float32, error) {
|
||||
u32, err := f.GetNBytes(4)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
littleEndian, err := f.GetBool()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if littleEndian {
|
||||
u32LE := binary.LittleEndian.Uint32(u32)
|
||||
return math.Float32frombits(u32LE), nil
|
||||
}
|
||||
u32BE := binary.BigEndian.Uint32(u32)
|
||||
return math.Float32frombits(u32BE), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GetFloat64() (float64, error) {
|
||||
u64, err := f.GetNBytes(8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
littleEndian, err := f.GetBool()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if littleEndian {
|
||||
u64LE := binary.LittleEndian.Uint64(u64)
|
||||
return math.Float64frombits(u64LE), nil
|
||||
}
|
||||
u64BE := binary.BigEndian.Uint64(u64)
|
||||
return math.Float64frombits(u64BE), nil
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) CreateSlice(targetSlice interface{}) error {
|
||||
return f.GenerateStruct(targetSlice)
|
||||
}
|
62
vendor/github.com/AdaLogics/go-fuzz-headers/funcs.go
generated
vendored
Normal file
62
vendor/github.com/AdaLogics/go-fuzz-headers/funcs.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2023 The go-fuzz-headers Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gofuzzheaders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Continue struct {
|
||||
F *ConsumeFuzzer
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) AddFuncs(fuzzFuncs []interface{}) {
|
||||
for i := range fuzzFuncs {
|
||||
v := reflect.ValueOf(fuzzFuncs[i])
|
||||
if v.Kind() != reflect.Func {
|
||||
panic("Need only funcs!")
|
||||
}
|
||||
t := v.Type()
|
||||
if t.NumIn() != 2 || t.NumOut() != 1 {
|
||||
fmt.Println(t.NumIn(), t.NumOut())
|
||||
|
||||
panic("Need 2 in and 1 out params. In must be the type. Out must be an error")
|
||||
}
|
||||
argT := t.In(0)
|
||||
switch argT.Kind() {
|
||||
case reflect.Ptr, reflect.Map:
|
||||
default:
|
||||
panic("fuzzFunc must take pointer or map type")
|
||||
}
|
||||
if t.In(1) != reflect.TypeOf(Continue{}) {
|
||||
panic("fuzzFunc's second parameter must be type Continue")
|
||||
}
|
||||
f.Funcs[argT] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ConsumeFuzzer) GenerateWithCustom(targetStruct interface{}) error {
|
||||
e := reflect.ValueOf(targetStruct).Elem()
|
||||
return f.fuzzStruct(e, true)
|
||||
}
|
||||
|
||||
func (c Continue) GenerateStruct(targetStruct interface{}) error {
|
||||
return c.F.GenerateStruct(targetStruct)
|
||||
}
|
||||
|
||||
func (c Continue) GenerateStructWithCustom(targetStruct interface{}) error {
|
||||
return c.F.GenerateWithCustom(targetStruct)
|
||||
}
|
556
vendor/github.com/AdaLogics/go-fuzz-headers/sql.go
generated
vendored
Normal file
556
vendor/github.com/AdaLogics/go-fuzz-headers/sql.go
generated
vendored
Normal file
@ -0,0 +1,556 @@
|
||||
// Copyright 2023 The go-fuzz-headers Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gofuzzheaders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// returns a keyword by index
|
||||
func getKeyword(f *ConsumeFuzzer) (string, error) {
|
||||
index, err := f.GetInt()
|
||||
if err != nil {
|
||||
return keywords[0], err
|
||||
}
|
||||
for i, k := range keywords {
|
||||
if i == index {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
return keywords[0], fmt.Errorf("could not get a kw")
|
||||
}
|
||||
|
||||
// Simple utility function to check if a string
|
||||
// slice contains a string.
|
||||
func containsString(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// These keywords are used specifically for fuzzing Vitess
|
||||
var keywords = []string{
|
||||
"accessible", "action", "add", "after", "against", "algorithm",
|
||||
"all", "alter", "always", "analyze", "and", "as", "asc", "asensitive",
|
||||
"auto_increment", "avg_row_length", "before", "begin", "between",
|
||||
"bigint", "binary", "_binary", "_utf8mb4", "_utf8", "_latin1", "bit",
|
||||
"blob", "bool", "boolean", "both", "by", "call", "cancel", "cascade",
|
||||
"cascaded", "case", "cast", "channel", "change", "char", "character",
|
||||
"charset", "check", "checksum", "coalesce", "code", "collate", "collation",
|
||||
"column", "columns", "comment", "committed", "commit", "compact", "complete",
|
||||
"compressed", "compression", "condition", "connection", "constraint", "continue",
|
||||
"convert", "copy", "cume_dist", "substr", "substring", "create", "cross",
|
||||
"csv", "current_date", "current_time", "current_timestamp", "current_user",
|
||||
"cursor", "data", "database", "databases", "day", "day_hour", "day_microsecond",
|
||||
"day_minute", "day_second", "date", "datetime", "dec", "decimal", "declare",
|
||||
"default", "definer", "delay_key_write", "delayed", "delete", "dense_rank",
|
||||
"desc", "describe", "deterministic", "directory", "disable", "discard",
|
||||
"disk", "distinct", "distinctrow", "div", "double", "do", "drop", "dumpfile",
|
||||
"duplicate", "dynamic", "each", "else", "elseif", "empty", "enable",
|
||||
"enclosed", "encryption", "end", "enforced", "engine", "engines", "enum",
|
||||
"error", "escape", "escaped", "event", "exchange", "exclusive", "exists",
|
||||
"exit", "explain", "expansion", "export", "extended", "extract", "false",
|
||||
"fetch", "fields", "first", "first_value", "fixed", "float", "float4",
|
||||
"float8", "flush", "for", "force", "foreign", "format", "from", "full",
|
||||
"fulltext", "function", "general", "generated", "geometry", "geometrycollection",
|
||||
"get", "global", "gtid_executed", "grant", "group", "grouping", "groups",
|
||||
"group_concat", "having", "header", "high_priority", "hosts", "hour", "hour_microsecond",
|
||||
"hour_minute", "hour_second", "if", "ignore", "import", "in", "index", "indexes",
|
||||
"infile", "inout", "inner", "inplace", "insensitive", "insert", "insert_method",
|
||||
"int", "int1", "int2", "int3", "int4", "int8", "integer", "interval",
|
||||
"into", "io_after_gtids", "is", "isolation", "iterate", "invoker", "join",
|
||||
"json", "json_table", "key", "keys", "keyspaces", "key_block_size", "kill", "lag",
|
||||
"language", "last", "last_value", "last_insert_id", "lateral", "lead", "leading",
|
||||
"leave", "left", "less", "level", "like", "limit", "linear", "lines",
|
||||
"linestring", "load", "local", "localtime", "localtimestamp", "lock", "logs",
|
||||
"long", "longblob", "longtext", "loop", "low_priority", "manifest",
|
||||
"master_bind", "match", "max_rows", "maxvalue", "mediumblob", "mediumint",
|
||||
"mediumtext", "memory", "merge", "microsecond", "middleint", "min_rows", "minute",
|
||||
"minute_microsecond", "minute_second", "mod", "mode", "modify", "modifies",
|
||||
"multilinestring", "multipoint", "multipolygon", "month", "name",
|
||||
"names", "natural", "nchar", "next", "no", "none", "not", "no_write_to_binlog",
|
||||
"nth_value", "ntile", "null", "numeric", "of", "off", "offset", "on",
|
||||
"only", "open", "optimize", "optimizer_costs", "option", "optionally",
|
||||
"or", "order", "out", "outer", "outfile", "over", "overwrite", "pack_keys",
|
||||
"parser", "partition", "partitioning", "password", "percent_rank", "plugins",
|
||||
"point", "polygon", "precision", "primary", "privileges", "processlist",
|
||||
"procedure", "query", "quarter", "range", "rank", "read", "reads", "read_write",
|
||||
"real", "rebuild", "recursive", "redundant", "references", "regexp", "relay",
|
||||
"release", "remove", "rename", "reorganize", "repair", "repeat", "repeatable",
|
||||
"replace", "require", "resignal", "restrict", "return", "retry", "revert",
|
||||
"revoke", "right", "rlike", "rollback", "row", "row_format", "row_number",
|
||||
"rows", "s3", "savepoint", "schema", "schemas", "second", "second_microsecond",
|
||||
"security", "select", "sensitive", "separator", "sequence", "serializable",
|
||||
"session", "set", "share", "shared", "show", "signal", "signed", "slow",
|
||||
"smallint", "spatial", "specific", "sql", "sqlexception", "sqlstate",
|
||||
"sqlwarning", "sql_big_result", "sql_cache", "sql_calc_found_rows",
|
||||
"sql_no_cache", "sql_small_result", "ssl", "start", "starting",
|
||||
"stats_auto_recalc", "stats_persistent", "stats_sample_pages", "status",
|
||||
"storage", "stored", "straight_join", "stream", "system", "vstream",
|
||||
"table", "tables", "tablespace", "temporary", "temptable", "terminated",
|
||||
"text", "than", "then", "time", "timestamp", "timestampadd", "timestampdiff",
|
||||
"tinyblob", "tinyint", "tinytext", "to", "trailing", "transaction", "tree",
|
||||
"traditional", "trigger", "triggers", "true", "truncate", "uncommitted",
|
||||
"undefined", "undo", "union", "unique", "unlock", "unsigned", "update",
|
||||
"upgrade", "usage", "use", "user", "user_resources", "using", "utc_date",
|
||||
"utc_time", "utc_timestamp", "validation", "values", "variables", "varbinary",
|
||||
"varchar", "varcharacter", "varying", "vgtid_executed", "virtual", "vindex",
|
||||
"vindexes", "view", "vitess", "vitess_keyspaces", "vitess_metadata",
|
||||
"vitess_migration", "vitess_migrations", "vitess_replication_status",
|
||||
"vitess_shards", "vitess_tablets", "vschema", "warnings", "when",
|
||||
"where", "while", "window", "with", "without", "work", "write", "xor",
|
||||
"year", "year_month", "zerofill",
|
||||
}
|
||||
|
||||
// Keywords that could get an additional keyword
|
||||
var needCustomString = []string{
|
||||
"DISTINCTROW", "FROM", // Select keywords:
|
||||
"GROUP BY", "HAVING", "WINDOW",
|
||||
"FOR",
|
||||
"ORDER BY", "LIMIT",
|
||||
"INTO", "PARTITION", "AS", // Insert Keywords:
|
||||
"ON DUPLICATE KEY UPDATE",
|
||||
"WHERE", "LIMIT", // Delete keywords
|
||||
"INFILE", "INTO TABLE", "CHARACTER SET", // Load keywords
|
||||
"TERMINATED BY", "ENCLOSED BY",
|
||||
"ESCAPED BY", "STARTING BY",
|
||||
"TERMINATED BY", "STARTING BY",
|
||||
"IGNORE",
|
||||
"VALUE", "VALUES", // Replace tokens
|
||||
"SET", // Update tokens
|
||||
"ENGINE =", // Drop tokens
|
||||
"DEFINER =", "ON SCHEDULE", "RENAME TO", // Alter tokens
|
||||
"COMMENT", "DO", "INITIAL_SIZE = ", "OPTIONS",
|
||||
}
|
||||
|
||||
var alterTableTokens = [][]string{
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"CUSTOM_ALTTER_TABLE_OPTIONS"},
|
||||
{"PARTITION_OPTIONS_FOR_ALTER_TABLE"},
|
||||
}
|
||||
|
||||
var alterTokens = [][]string{
|
||||
{
|
||||
"DATABASE", "SCHEMA", "DEFINER = ", "EVENT", "FUNCTION", "INSTANCE",
|
||||
"LOGFILE GROUP", "PROCEDURE", "SERVER",
|
||||
},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{
|
||||
"ON SCHEDULE", "ON COMPLETION PRESERVE", "ON COMPLETION NOT PRESERVE",
|
||||
"ADD UNDOFILE", "OPTIONS",
|
||||
},
|
||||
{"RENAME TO", "INITIAL_SIZE = "},
|
||||
{"ENABLE", "DISABLE", "DISABLE ON SLAVE", "ENGINE"},
|
||||
{"COMMENT"},
|
||||
{"DO"},
|
||||
}
|
||||
|
||||
var setTokens = [][]string{
|
||||
{"CHARACTER SET", "CHARSET", "CUSTOM_FUZZ_STRING", "NAMES"},
|
||||
{"CUSTOM_FUZZ_STRING", "DEFAULT", "="},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
}
|
||||
|
||||
var dropTokens = [][]string{
|
||||
{"TEMPORARY", "UNDO"},
|
||||
{
|
||||
"DATABASE", "SCHEMA", "EVENT", "INDEX", "LOGFILE GROUP",
|
||||
"PROCEDURE", "FUNCTION", "SERVER", "SPATIAL REFERENCE SYSTEM",
|
||||
"TABLE", "TABLESPACE", "TRIGGER", "VIEW",
|
||||
},
|
||||
{"IF EXISTS"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"ON", "ENGINE = ", "RESTRICT", "CASCADE"},
|
||||
}
|
||||
|
||||
var renameTokens = [][]string{
|
||||
{"TABLE"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"TO"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
}
|
||||
|
||||
var truncateTokens = [][]string{
|
||||
{"TABLE"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
}
|
||||
|
||||
var createTokens = [][]string{
|
||||
{"OR REPLACE", "TEMPORARY", "UNDO"}, // For create spatial reference system
|
||||
{
|
||||
"UNIQUE", "FULLTEXT", "SPATIAL", "ALGORITHM = UNDEFINED", "ALGORITHM = MERGE",
|
||||
"ALGORITHM = TEMPTABLE",
|
||||
},
|
||||
{
|
||||
"DATABASE", "SCHEMA", "EVENT", "FUNCTION", "INDEX", "LOGFILE GROUP",
|
||||
"PROCEDURE", "SERVER", "SPATIAL REFERENCE SYSTEM", "TABLE", "TABLESPACE",
|
||||
"TRIGGER", "VIEW",
|
||||
},
|
||||
{"IF NOT EXISTS"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
}
|
||||
|
||||
/*
|
||||
// For future use.
|
||||
var updateTokens = [][]string{
|
||||
{"LOW_PRIORITY"},
|
||||
{"IGNORE"},
|
||||
{"SET"},
|
||||
{"WHERE"},
|
||||
{"ORDER BY"},
|
||||
{"LIMIT"},
|
||||
}
|
||||
*/
|
||||
|
||||
var replaceTokens = [][]string{
|
||||
{"LOW_PRIORITY", "DELAYED"},
|
||||
{"INTO"},
|
||||
{"PARTITION"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"VALUES", "VALUE"},
|
||||
}
|
||||
|
||||
var loadTokens = [][]string{
|
||||
{"DATA"},
|
||||
{"LOW_PRIORITY", "CONCURRENT", "LOCAL"},
|
||||
{"INFILE"},
|
||||
{"REPLACE", "IGNORE"},
|
||||
{"INTO TABLE"},
|
||||
{"PARTITION"},
|
||||
{"CHARACTER SET"},
|
||||
{"FIELDS", "COLUMNS"},
|
||||
{"TERMINATED BY"},
|
||||
{"OPTIONALLY"},
|
||||
{"ENCLOSED BY"},
|
||||
{"ESCAPED BY"},
|
||||
{"LINES"},
|
||||
{"STARTING BY"},
|
||||
{"TERMINATED BY"},
|
||||
{"IGNORE"},
|
||||
{"LINES", "ROWS"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
}
|
||||
|
||||
// These Are everything that comes after "INSERT"
|
||||
var insertTokens = [][]string{
|
||||
{"LOW_PRIORITY", "DELAYED", "HIGH_PRIORITY", "IGNORE"},
|
||||
{"INTO"},
|
||||
{"PARTITION"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"AS"},
|
||||
{"ON DUPLICATE KEY UPDATE"},
|
||||
}
|
||||
|
||||
// These are everything that comes after "SELECT"
|
||||
var selectTokens = [][]string{
|
||||
{"*", "CUSTOM_FUZZ_STRING", "DISTINCTROW"},
|
||||
{"HIGH_PRIORITY"},
|
||||
{"STRAIGHT_JOIN"},
|
||||
{"SQL_SMALL_RESULT", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT"},
|
||||
{"SQL_NO_CACHE", "SQL_CALC_FOUND_ROWS"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"FROM"},
|
||||
{"WHERE"},
|
||||
{"GROUP BY"},
|
||||
{"HAVING"},
|
||||
{"WINDOW"},
|
||||
{"ORDER BY"},
|
||||
{"LIMIT"},
|
||||
{"CUSTOM_FUZZ_STRING"},
|
||||
{"FOR"},
|
||||
}
|
||||
|
||||
// These are everything that comes after "DELETE"
|
||||
var deleteTokens = [][]string{
|
||||
{"LOW_PRIORITY", "QUICK", "IGNORE", "FROM", "AS"},
|
||||
{"PARTITION"},
|
||||
{"WHERE"},
|
||||
{"ORDER BY"},
|
||||
{"LIMIT"},
|
||||
}
|
||||
|
||||
var alter_table_options = []string{
|
||||
"ADD", "COLUMN", "FIRST", "AFTER", "INDEX", "KEY", "FULLTEXT", "SPATIAL",
|
||||
"CONSTRAINT", "UNIQUE", "FOREIGN KEY", "CHECK", "ENFORCED", "DROP", "ALTER",
|
||||
"NOT", "INPLACE", "COPY", "SET", "VISIBLE", "INVISIBLE", "DEFAULT", "CHANGE",
|
||||
"CHARACTER SET", "COLLATE", "DISABLE", "ENABLE", "KEYS", "TABLESPACE", "LOCK",
|
||||
"FORCE", "MODIFY", "SHARED", "EXCLUSIVE", "NONE", "ORDER BY", "RENAME COLUMN",
|
||||
"AS", "=", "ASC", "DESC", "WITH", "WITHOUT", "VALIDATION", "ADD PARTITION",
|
||||
"DROP PARTITION", "DISCARD PARTITION", "IMPORT PARTITION", "TRUNCATE PARTITION",
|
||||
"COALESCE PARTITION", "REORGANIZE PARTITION", "EXCHANGE PARTITION",
|
||||
"ANALYZE PARTITION", "CHECK PARTITION", "OPTIMIZE PARTITION", "REBUILD PARTITION",
|
||||
"REPAIR PARTITION", "REMOVE PARTITIONING", "USING", "BTREE", "HASH", "COMMENT",
|
||||
"KEY_BLOCK_SIZE", "WITH PARSER", "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG_ROW_LENGTH",
|
||||
"CHECKSUM", "INSERT_METHOD", "ROW_FORMAT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT",
|
||||
"COMPACT", "SECONDARY_ENGINE_ATTRIBUTE", "STATS_AUTO_RECALC", "STATS_PERSISTENT",
|
||||
"STATS_SAMPLE_PAGES", "ZLIB", "LZ4", "ENGINE_ATTRIBUTE", "KEY_BLOCK_SIZE", "MAX_ROWS",
|
||||
"MIN_ROWS", "PACK_KEYS", "PASSWORD", "COMPRESSION", "CONNECTION", "DIRECTORY",
|
||||
"DELAY_KEY_WRITE", "ENCRYPTION", "STORAGE", "DISK", "MEMORY", "UNION",
|
||||
}
|
||||
|
||||
// Creates an 'alter table' statement. 'alter table' is an exception
|
||||
// in that it has its own function. The majority of statements
|
||||
// are created by 'createStmt()'.
|
||||
func createAlterTableStmt(f *ConsumeFuzzer) (string, error) {
|
||||
maxArgs, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
maxArgs = maxArgs % 30
|
||||
if maxArgs == 0 {
|
||||
return "", fmt.Errorf("could not create alter table stmt")
|
||||
}
|
||||
|
||||
var stmt strings.Builder
|
||||
stmt.WriteString("ALTER TABLE ")
|
||||
for i := 0; i < maxArgs; i++ {
|
||||
// Calculate if we get existing token or custom string
|
||||
tokenType, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if tokenType%4 == 1 {
|
||||
customString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stmt.WriteString(" " + customString)
|
||||
} else {
|
||||
tokenIndex, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stmt.WriteString(" " + alter_table_options[tokenIndex%len(alter_table_options)])
|
||||
}
|
||||
}
|
||||
return stmt.String(), nil
|
||||
}
|
||||
|
||||
func chooseToken(tokens []string, f *ConsumeFuzzer) (string, error) {
|
||||
index, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var token strings.Builder
|
||||
token.WriteString(tokens[index%len(tokens)])
|
||||
if token.String() == "CUSTOM_FUZZ_STRING" {
|
||||
customFuzzString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return customFuzzString, nil
|
||||
}
|
||||
|
||||
// Check if token requires an argument
|
||||
if containsString(needCustomString, token.String()) {
|
||||
customFuzzString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token.WriteString(" " + customFuzzString)
|
||||
}
|
||||
return token.String(), nil
|
||||
}
|
||||
|
||||
var stmtTypes = map[string][][]string{
|
||||
"DELETE": deleteTokens,
|
||||
"INSERT": insertTokens,
|
||||
"SELECT": selectTokens,
|
||||
"LOAD": loadTokens,
|
||||
"REPLACE": replaceTokens,
|
||||
"CREATE": createTokens,
|
||||
"DROP": dropTokens,
|
||||
"RENAME": renameTokens,
|
||||
"TRUNCATE": truncateTokens,
|
||||
"SET": setTokens,
|
||||
"ALTER": alterTokens,
|
||||
"ALTER TABLE": alterTableTokens, // ALTER TABLE has its own set of tokens
|
||||
}
|
||||
|
||||
var stmtTypeEnum = map[int]string{
|
||||
0: "DELETE",
|
||||
1: "INSERT",
|
||||
2: "SELECT",
|
||||
3: "LOAD",
|
||||
4: "REPLACE",
|
||||
5: "CREATE",
|
||||
6: "DROP",
|
||||
7: "RENAME",
|
||||
8: "TRUNCATE",
|
||||
9: "SET",
|
||||
10: "ALTER",
|
||||
11: "ALTER TABLE",
|
||||
}
|
||||
|
||||
func createStmt(f *ConsumeFuzzer) (string, error) {
|
||||
stmtIndex, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stmtIndex = stmtIndex % len(stmtTypes)
|
||||
|
||||
queryType := stmtTypeEnum[stmtIndex]
|
||||
tokens := stmtTypes[queryType]
|
||||
|
||||
// We have custom creator for ALTER TABLE
|
||||
if queryType == "ALTER TABLE" {
|
||||
query, err := createAlterTableStmt(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// Here we are creating a query that is not
|
||||
// an 'alter table' query. For available
|
||||
// queries, see "stmtTypes"
|
||||
|
||||
// First specify the first query keyword:
|
||||
var query strings.Builder
|
||||
query.WriteString(queryType)
|
||||
|
||||
// Next create the args for the
|
||||
queryArgs, err := createStmtArgs(tokens, f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.WriteString(" " + queryArgs)
|
||||
return query.String(), nil
|
||||
}
|
||||
|
||||
// Creates the arguments of a statements. In a select statement
|
||||
// that would be everything after "select".
|
||||
func createStmtArgs(tokenslice [][]string, f *ConsumeFuzzer) (string, error) {
|
||||
var query, token strings.Builder
|
||||
|
||||
// We go through the tokens in the tokenslice,
|
||||
// create the respective token and add it to
|
||||
// "query"
|
||||
for _, tokens := range tokenslice {
|
||||
// For extra randomization, the fuzzer can
|
||||
// choose to not include this token.
|
||||
includeThisToken, err := f.GetBool()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !includeThisToken {
|
||||
continue
|
||||
}
|
||||
|
||||
// There may be several tokens to choose from:
|
||||
if len(tokens) > 1 {
|
||||
chosenToken, err := chooseToken(tokens, f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.WriteString(" " + chosenToken)
|
||||
} else {
|
||||
token.WriteString(tokens[0])
|
||||
|
||||
// In case the token is "CUSTOM_FUZZ_STRING"
|
||||
// we will then create a non-structured string
|
||||
if token.String() == "CUSTOM_FUZZ_STRING" {
|
||||
customFuzzString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.WriteString(" " + customFuzzString)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if token requires an argument.
|
||||
// Tokens that take an argument can be found
|
||||
// in 'needCustomString'. If so, we add a
|
||||
// non-structured string to the token.
|
||||
if containsString(needCustomString, token.String()) {
|
||||
customFuzzString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token.WriteString(fmt.Sprintf(" %s", customFuzzString))
|
||||
}
|
||||
query.WriteString(fmt.Sprintf(" %s", token.String()))
|
||||
}
|
||||
}
|
||||
return query.String(), nil
|
||||
}
|
||||
|
||||
// Creates a semi-structured query. It creates a string
|
||||
// that is a combination of the keywords and random strings.
|
||||
func createQuery(f *ConsumeFuzzer) (string, error) {
|
||||
queryLen, err := f.GetInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
maxLen := queryLen % 60
|
||||
if maxLen == 0 {
|
||||
return "", fmt.Errorf("could not create a query")
|
||||
}
|
||||
var query strings.Builder
|
||||
for i := 0; i < maxLen; i++ {
|
||||
// Get a new token:
|
||||
useKeyword, err := f.GetBool()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if useKeyword {
|
||||
keyword, err := getKeyword(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.WriteString(" " + keyword)
|
||||
} else {
|
||||
customString, err := f.GetString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.WriteString(" " + customString)
|
||||
}
|
||||
}
|
||||
if query.String() == "" {
|
||||
return "", fmt.Errorf("could not create a query")
|
||||
}
|
||||
return query.String(), nil
|
||||
}
|
||||
|
||||
// GetSQLString is the API that users interact with.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := NewConsumer(data)
|
||||
// sqlString, err := f.GetSQLString()
|
||||
func (f *ConsumeFuzzer) GetSQLString() (string, error) {
|
||||
var query string
|
||||
veryStructured, err := f.GetBool()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if veryStructured {
|
||||
query, err = createStmt(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
query, err = createQuery(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return query, nil
|
||||
}
|
207
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/f.go
generated
vendored
Normal file
207
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/f.go
generated
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type F struct {
|
||||
Data []byte
|
||||
T *T
|
||||
FuzzFunc func(*T, any)
|
||||
}
|
||||
|
||||
func (f *F) CleanupTempDirs() {
|
||||
f.T.CleanupTempDirs()
|
||||
}
|
||||
|
||||
func (f *F) Add(args ...any) {}
|
||||
func (c *F) Cleanup(f func()) {}
|
||||
func (c *F) Error(args ...any) {}
|
||||
func (c *F) Errorf(format string, args ...any) {}
|
||||
func (f *F) Fail() {}
|
||||
func (c *F) FailNow() {}
|
||||
func (c *F) Failed() bool { return false }
|
||||
func (c *F) Fatal(args ...any) {}
|
||||
func (c *F) Fatalf(format string, args ...any) {}
|
||||
func (f *F) Fuzz(ff any) {
|
||||
// we are assuming that ff is a func.
|
||||
// TODO: Add a check for UX purposes
|
||||
|
||||
fn := reflect.ValueOf(ff)
|
||||
fnType := fn.Type()
|
||||
var types []reflect.Type
|
||||
for i := 1; i < fnType.NumIn(); i++ {
|
||||
t := fnType.In(i)
|
||||
|
||||
types = append(types, t)
|
||||
}
|
||||
args := []reflect.Value{reflect.ValueOf(f.T)}
|
||||
fuzzConsumer := fuzz.NewConsumer(f.Data)
|
||||
for _, v := range types {
|
||||
switch v.String() {
|
||||
case "[]uint8":
|
||||
b, err := fuzzConsumer.GetBytes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newBytes := reflect.New(v)
|
||||
newBytes.Elem().SetBytes(b)
|
||||
args = append(args, newBytes.Elem())
|
||||
case "string":
|
||||
s, err := fuzzConsumer.GetString()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newString := reflect.New(v)
|
||||
newString.Elem().SetString(s)
|
||||
args = append(args, newString.Elem())
|
||||
case "int":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newInt := reflect.New(v)
|
||||
newInt.Elem().SetInt(int64(randInt))
|
||||
args = append(args, newInt.Elem())
|
||||
case "int8":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newInt := reflect.New(v)
|
||||
newInt.Elem().SetInt(int64(randInt))
|
||||
args = append(args, newInt.Elem())
|
||||
case "int16":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newInt := reflect.New(v)
|
||||
newInt.Elem().SetInt(int64(randInt))
|
||||
args = append(args, newInt.Elem())
|
||||
case "int32":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newInt := reflect.New(v)
|
||||
newInt.Elem().SetInt(int64(randInt))
|
||||
args = append(args, newInt.Elem())
|
||||
case "int64":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newInt := reflect.New(v)
|
||||
newInt.Elem().SetInt(int64(randInt))
|
||||
args = append(args, newInt.Elem())
|
||||
case "uint":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUint := reflect.New(v)
|
||||
newUint.Elem().SetUint(uint64(randInt))
|
||||
args = append(args, newUint.Elem())
|
||||
case "uint8":
|
||||
randInt, err := fuzzConsumer.GetInt()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUint := reflect.New(v)
|
||||
newUint.Elem().SetUint(uint64(randInt))
|
||||
args = append(args, newUint.Elem())
|
||||
case "uint16":
|
||||
randInt, err := fuzzConsumer.GetUint16()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUint16 := reflect.New(v)
|
||||
newUint16.Elem().SetUint(uint64(randInt))
|
||||
args = append(args, newUint16.Elem())
|
||||
case "uint32":
|
||||
randInt, err := fuzzConsumer.GetUint32()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUint32 := reflect.New(v)
|
||||
newUint32.Elem().SetUint(uint64(randInt))
|
||||
args = append(args, newUint32.Elem())
|
||||
case "uint64":
|
||||
randInt, err := fuzzConsumer.GetUint64()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUint64 := reflect.New(v)
|
||||
newUint64.Elem().SetUint(uint64(randInt))
|
||||
args = append(args, newUint64.Elem())
|
||||
case "rune":
|
||||
randRune, err := fuzzConsumer.GetRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newRune := reflect.New(v)
|
||||
newRune.Elem().Set(reflect.ValueOf(randRune))
|
||||
args = append(args, newRune.Elem())
|
||||
case "float32":
|
||||
randFloat, err := fuzzConsumer.GetFloat32()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newFloat := reflect.New(v)
|
||||
newFloat.Elem().Set(reflect.ValueOf(randFloat))
|
||||
args = append(args, newFloat.Elem())
|
||||
case "float64":
|
||||
randFloat, err := fuzzConsumer.GetFloat64()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newFloat := reflect.New(v)
|
||||
newFloat.Elem().Set(reflect.ValueOf(randFloat))
|
||||
args = append(args, newFloat.Elem())
|
||||
case "bool":
|
||||
randBool, err := fuzzConsumer.GetBool()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newBool := reflect.New(v)
|
||||
newBool.Elem().Set(reflect.ValueOf(randBool))
|
||||
args = append(args, newBool.Elem())
|
||||
default:
|
||||
fmt.Println(v.String())
|
||||
}
|
||||
}
|
||||
fn.Call(args)
|
||||
}
|
||||
func (f *F) Helper() {}
|
||||
func (c *F) Log(args ...any) {
|
||||
fmt.Println(args...)
|
||||
}
|
||||
func (c *F) Logf(format string, args ...any) {
|
||||
fmt.Println(format, args)
|
||||
}
|
||||
func (c *F) Name() string { return "libFuzzer" }
|
||||
func (c *F) Setenv(key, value string) {}
|
||||
func (c *F) Skip(args ...any) {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
func (c *F) SkipNow() {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
func (c *F) Skipf(format string, args ...any) {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
func (f *F) Skipped() bool { return false }
|
||||
|
||||
func (f *F) TempDir() string {
|
||||
dir, err := os.MkdirTemp("", "fuzzdir-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.T.TempDirs = append(f.T.TempDirs, dir)
|
||||
|
||||
return dir
|
||||
}
|
129
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/t.go
generated
vendored
Normal file
129
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/t.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// T can be used to terminate the current fuzz iteration
|
||||
// without terminating the whole fuzz run. To do so, simply
|
||||
// panic with the text "GO-FUZZ-BUILD-PANIC" and the fuzzer
|
||||
// will recover.
|
||||
type T struct {
|
||||
TempDirs []string
|
||||
}
|
||||
|
||||
func NewT() *T {
|
||||
tempDirs := make([]string, 0)
|
||||
return &T{TempDirs: tempDirs}
|
||||
}
|
||||
|
||||
func unsupportedApi(name string) string {
|
||||
plsOpenIss := "Please open an issue https://github.com/AdamKorcz/go-118-fuzz-build if you need this feature."
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("%s is not supported when fuzzing in libFuzzer mode\n.", name))
|
||||
b.WriteString(plsOpenIss)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t *T) Cleanup(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (t *T) Deadline() (deadline time.Time, ok bool) {
|
||||
panic(unsupportedApi("t.Deadline()"))
|
||||
}
|
||||
|
||||
func (t *T) Error(args ...any) {
|
||||
fmt.Println(args...)
|
||||
panic("error")
|
||||
}
|
||||
|
||||
func (t *T) Errorf(format string, args ...any) {
|
||||
fmt.Printf(format+"\n", args...)
|
||||
panic("errorf")
|
||||
}
|
||||
|
||||
func (t *T) Fail() {
|
||||
panic("Called T.Fail()")
|
||||
}
|
||||
|
||||
func (t *T) FailNow() {
|
||||
panic("Called T.Fail()")
|
||||
panic(unsupportedApi("t.FailNow()"))
|
||||
}
|
||||
|
||||
func (t *T) Failed() bool {
|
||||
panic(unsupportedApi("t.Failed()"))
|
||||
}
|
||||
|
||||
func (t *T) Fatal(args ...any) {
|
||||
fmt.Println(args...)
|
||||
panic("fatal")
|
||||
}
|
||||
func (t *T) Fatalf(format string, args ...any) {
|
||||
fmt.Printf(format+"\n", args...)
|
||||
panic("fatal")
|
||||
}
|
||||
func (t *T) Helper() {
|
||||
// We can't support it, but it also just impacts how failures are reported, so we can ignore it
|
||||
}
|
||||
func (t *T) Log(args ...any) {
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
func (t *T) Logf(format string, args ...any) {
|
||||
fmt.Println(format)
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
func (t *T) Name() string {
|
||||
return "libFuzzer"
|
||||
}
|
||||
|
||||
func (t *T) Parallel() {
|
||||
panic(unsupportedApi("t.Parallel()"))
|
||||
}
|
||||
func (t *T) Run(name string, f func(t *T)) bool {
|
||||
panic(unsupportedApi("t.Run()"))
|
||||
}
|
||||
|
||||
func (t *T) Setenv(key, value string) {
|
||||
|
||||
}
|
||||
|
||||
func (t *T) Skip(args ...any) {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
func (t *T) SkipNow() {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
|
||||
// Is not really supported. We just skip instead
|
||||
// of printing any message. A log message can be
|
||||
// added if need be.
|
||||
func (t *T) Skipf(format string, args ...any) {
|
||||
panic("GO-FUZZ-BUILD-PANIC")
|
||||
}
|
||||
func (t *T) Skipped() bool {
|
||||
panic(unsupportedApi("t.Skipped()"))
|
||||
}
|
||||
func (t *T) TempDir() string {
|
||||
dir, err := os.MkdirTemp("", "fuzzdir-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.TempDirs = append(t.TempDirs, dir)
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func (t *T) CleanupTempDirs() {
|
||||
if len(t.TempDirs) > 0 {
|
||||
for _, tempDir := range t.TempDirs {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
}
|
||||
}
|
42
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/unsupported_funcs.go
generated
vendored
Normal file
42
vendor/github.com/AdamKorcz/go-118-fuzz-build/testing/unsupported_funcs.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func AllocsPerRun(runs int, f func()) (avg float64) {
|
||||
panic(unsupportedApi("testing.AllocsPerRun"))
|
||||
}
|
||||
func CoverMode() string {
|
||||
panic(unsupportedApi("testing.CoverMode"))
|
||||
}
|
||||
func Coverage() float64 {
|
||||
panic(unsupportedApi("testing.Coverage"))
|
||||
}
|
||||
func Init() {
|
||||
panic(unsupportedApi("testing.Init"))
|
||||
|
||||
}
|
||||
func RegisterCover(c testing.Cover) {
|
||||
panic(unsupportedApi("testing.RegisterCover"))
|
||||
}
|
||||
func RunExamples(matchString func(pat, str string) (bool, error), examples []testing.InternalExample) (ok bool) {
|
||||
panic(unsupportedApi("testing.RunExamples"))
|
||||
}
|
||||
|
||||
func RunTests(matchString func(pat, str string) (bool, error), tests []testing.InternalTest) (ok bool) {
|
||||
panic(unsupportedApi("testing.RunTests"))
|
||||
}
|
||||
|
||||
func Short() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func Verbose() bool {
|
||||
panic(unsupportedApi("testing.Verbose"))
|
||||
}
|
||||
|
||||
type M struct {}
|
||||
func (m *M) Run() (code int) {
|
||||
panic("testing.M is not support in libFuzzer Mode")
|
||||
}
|
1
vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
9
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
9
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
@ -1 +1,10 @@
|
||||
.vscode/
|
||||
|
||||
*.exe
|
||||
|
||||
# testing
|
||||
testdata
|
||||
|
||||
# go workspaces
|
||||
go.work
|
||||
go.work.sum
|
||||
|
144
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
Normal file
144
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
run:
|
||||
skip-dirs:
|
||||
- pkg/etw/sample
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# style
|
||||
- containedctx # struct contains a context
|
||||
- dupl # duplicate code
|
||||
- errname # erorrs are named correctly
|
||||
- goconst # strings that should be constants
|
||||
- godot # comments end in a period
|
||||
- misspell
|
||||
- nolintlint # "//nolint" directives are properly explained
|
||||
- revive # golint replacement
|
||||
- stylecheck # golint replacement, less configurable than revive
|
||||
- unconvert # unnecessary conversions
|
||||
- wastedassign
|
||||
|
||||
# bugs, performance, unused, etc ...
|
||||
- contextcheck # function uses a non-inherited context
|
||||
- errorlint # errors not wrapped for 1.13
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- gofmt # files are gofmt'ed
|
||||
- gosec # security
|
||||
- nestif # deeply nested ifs
|
||||
- nilerr # returns nil even with non-nil error
|
||||
- prealloc # slices that can be pre-allocated
|
||||
- structcheck # unused struct fields
|
||||
- unparam # unused function params
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# err is very often shadowed in nested scopes
|
||||
- linters:
|
||||
- govet
|
||||
text: '^shadow: declaration of "err" shadows declaration'
|
||||
|
||||
# ignore long lines for skip autogen directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errcheck` should provide explanation"
|
||||
source: '^\s*defer '
|
||||
|
||||
# allow unjustified ignores of error lints for io.EOF
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errorlint` should provide explanation"
|
||||
source: '[=|!]= io.EOF'
|
||||
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
# struct order is often for Win32 compat
|
||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||
- fieldalignment
|
||||
check-shadowing: true
|
||||
nolintlint:
|
||||
allow-leading-space: false
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
revive:
|
||||
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
||||
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
||||
enable-all-rules:
|
||||
true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
# rules with required arguments
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: banned-characters
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: file-header
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: max-public-structs
|
||||
disabled: true
|
||||
# geneally annoying rules
|
||||
- name: add-constant # complains about any and all strings and integers
|
||||
disabled: true
|
||||
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
||||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
- 140
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- []
|
||||
- - CID
|
||||
- CRI
|
||||
- CTRD
|
||||
- DACL
|
||||
- DLL
|
||||
- DOS
|
||||
- ETW
|
||||
- FSCTL
|
||||
- GCS
|
||||
- GMSA
|
||||
- HCS
|
||||
- HV
|
||||
- IO
|
||||
- LCOW
|
||||
- LDAP
|
||||
- LPAC
|
||||
- LTSC
|
||||
- MMIO
|
||||
- NT
|
||||
- OCI
|
||||
- PMEM
|
||||
- PWSH
|
||||
- RX
|
||||
- SACl
|
||||
- SID
|
||||
- SMB
|
||||
- TX
|
||||
- VHD
|
||||
- VHDX
|
||||
- VMID
|
||||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
stylecheck:
|
||||
checks:
|
||||
- "all"
|
||||
- "-ST1003" # use revive's var naming
|
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @microsoft/containerplat
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user