mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-18 12:06:36 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@alexellis
|
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!--- 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 -->
|
||||||
|
|
||||||
|
## Possible Solution
|
||||||
|
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!--- or ideas how to implement the addition or 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.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||||
|
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||||
|
|
||||||
|
## Your Environment
|
||||||
|
|
||||||
|
* OS and architecture:
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go version
|
||||||
|
|
||||||
|
containerd -version
|
||||||
|
|
||||||
|
uname -a
|
||||||
|
|
||||||
|
cat /etc/os-release
|
||||||
|
```
|
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.
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ hosts
|
|||||||
|
|
||||||
basic-auth-user
|
basic-auth-user
|
||||||
basic-auth-password
|
basic-auth-password
|
||||||
|
/bin
|
||||||
|
/secrets
|
||||||
|
16
.travis.yml
16
.travis.yml
@ -1,9 +1,19 @@
|
|||||||
sudo: required
|
sudo: required
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- '1.12'
|
- '1.13'
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- runc
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make dist
|
- make dist
|
||||||
|
- make prepare-test
|
||||||
|
- make test-e2e
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
api_key:
|
api_key:
|
||||||
@ -16,5 +26,5 @@ deploy:
|
|||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
|
|
||||||
env:
|
env: GO111MODULE=off
|
||||||
- GO111MODULE=off
|
|
||||||
|
137
Gopkg.lock
generated
137
Gopkg.lock
generated
@ -13,7 +13,7 @@
|
|||||||
version = "v0.4.14"
|
version = "v0.4.14"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:b28f788c0be42a6d26f07b282c5ff5f814ab7ad5833810ef0bc5f56fb9bedf11"
|
digest = "1:f06a14a8b60a7a9cdbf14ed52272faf4ff5de4ed7c784ff55b64995be98ac59f"
|
||||||
name = "github.com/Microsoft/hcsshim"
|
name = "github.com/Microsoft/hcsshim"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
@ -33,6 +33,7 @@
|
|||||||
"internal/timeout",
|
"internal/timeout",
|
||||||
"internal/vmcompute",
|
"internal/vmcompute",
|
||||||
"internal/wclayer",
|
"internal/wclayer",
|
||||||
|
"osversion",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "9e921883ac929bbe515b39793ece99ce3a9d7706"
|
revision = "9e921883ac929bbe515b39793ece99ce3a9d7706"
|
||||||
@ -54,7 +55,7 @@
|
|||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:386ca0ac781cc1b630b3ed21725759770174140164b3faf3810e6ed6366a970b"
|
digest = "1:cf83a14c8042951b0dcd74758fc32258111ecc7838cbdf5007717172cab9ca9b"
|
||||||
name = "github.com/containerd/containerd"
|
name = "github.com/containerd/containerd"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
@ -102,6 +103,7 @@
|
|||||||
"remotes/docker/schema1",
|
"remotes/docker/schema1",
|
||||||
"rootfs",
|
"rootfs",
|
||||||
"runtime/linux/runctypes",
|
"runtime/linux/runctypes",
|
||||||
|
"runtime/v2/logging",
|
||||||
"runtime/v2/runc/options",
|
"runtime/v2/runc/options",
|
||||||
"snapshots",
|
"snapshots",
|
||||||
"snapshots/proxy",
|
"snapshots/proxy",
|
||||||
@ -113,10 +115,11 @@
|
|||||||
version = "v1.3.2"
|
version = "v1.3.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:7e9da25c7a952c63e31ed367a88eede43224b0663b58eb452870787d8ddb6c70"
|
digest = "1:e4414857969cfbe45c7dab0a012aad4855bf7167c25d672a182cb18676424a0c"
|
||||||
name = "github.com/containerd/continuity"
|
name = "github.com/containerd/continuity"
|
||||||
packages = [
|
packages = [
|
||||||
"fs",
|
"fs",
|
||||||
|
"pathdriver",
|
||||||
"syscallx",
|
"syscallx",
|
||||||
"sysx",
|
"sysx",
|
||||||
]
|
]
|
||||||
@ -130,6 +133,14 @@
|
|||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13"
|
revision = "bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:2301a9a859e3b0946e2ddd6961ba6faf6857e6e68bc9293db758dbe3b17cc35e"
|
||||||
|
name = "github.com/containerd/go-cni"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "c154a49e2c754b83ebfb12ebf1362213b94d23e6"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:6d66a41dbbc6819902f1589d0550bc01c18032c0a598a7cd656731e6df73861b"
|
digest = "1:6d66a41dbbc6819902f1589d0550bc01c18032c0a598a7cd656731e6df73861b"
|
||||||
name = "github.com/containerd/ttrpc"
|
name = "github.com/containerd/ttrpc"
|
||||||
@ -144,6 +155,42 @@
|
|||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "a93fcdb778cd272c6e9b3028b2f42d813e785d40"
|
revision = "a93fcdb778cd272c6e9b3028b2f42d813e785d40"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:1a07bbfee1d0534e8dda4773948e6dcd3a061ea7ab047ce04619476946226483"
|
||||||
|
name = "github.com/containernetworking/cni"
|
||||||
|
packages = [
|
||||||
|
"libcni",
|
||||||
|
"pkg/invoke",
|
||||||
|
"pkg/types",
|
||||||
|
"pkg/types/020",
|
||||||
|
"pkg/types/current",
|
||||||
|
"pkg/version",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "4cfb7b568922a3c79a23e438dc52fe537fc9687e"
|
||||||
|
version = "v0.7.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:bcf36df8d43860bfde913d008301aef27c6e9a303582118a837c4a34c0d18167"
|
||||||
|
name = "github.com/coreos/go-systemd"
|
||||||
|
packages = ["journal"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "d3cd4ed1dbcf5835feba465b180436db54f20228"
|
||||||
|
version = "v21"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:92ebc9c068ab8e3fff03a58694ee33830964f6febd0130069aadce328802de14"
|
||||||
|
name = "github.com/docker/cli"
|
||||||
|
packages = [
|
||||||
|
"cli/config",
|
||||||
|
"cli/config/configfile",
|
||||||
|
"cli/config/credentials",
|
||||||
|
"cli/config/types",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "99c5edceb48d64c1aa5d09b8c9c499d431d98bb9"
|
||||||
|
version = "v19.03.5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:e495f9f1fb2bae55daeb76e099292054fe1f734947274b3cfc403ccda595d55a"
|
digest = "1:e495f9f1fb2bae55daeb76e099292054fe1f734947274b3cfc403ccda595d55a"
|
||||||
name = "github.com/docker/distribution"
|
name = "github.com/docker/distribution"
|
||||||
@ -155,6 +202,30 @@
|
|||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "0d3efadf0154c2b8a4e7b6621fff9809655cc580"
|
revision = "0d3efadf0154c2b8a4e7b6621fff9809655cc580"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:10f9c98f627e9697ec23b7973a683324f1d901dd9bace4a71405c0b2ec554303"
|
||||||
|
name = "github.com/docker/docker"
|
||||||
|
packages = [
|
||||||
|
"pkg/homedir",
|
||||||
|
"pkg/idtools",
|
||||||
|
"pkg/mount",
|
||||||
|
"pkg/system",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "ea84732a77251e0d7af278e2b7df1d6a59fca46b"
|
||||||
|
version = "v19.03.5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:9f3f49b4e32d3da2dd6ed07cc568627b53cc80205c0dcf69f4091f027416cb60"
|
||||||
|
name = "github.com/docker/docker-credential-helpers"
|
||||||
|
packages = [
|
||||||
|
"client",
|
||||||
|
"credentials",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "54f0238b6bf101fc3ad3b34114cb5520beb562f5"
|
||||||
|
version = "v0.6.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:0938aba6e09d72d48db029d44dcfa304851f52e2d67cda920436794248e92793"
|
digest = "1:0938aba6e09d72d48db029d44dcfa304851f52e2d67cda920436794248e92793"
|
||||||
name = "github.com/docker/go-events"
|
name = "github.com/docker/go-events"
|
||||||
@ -162,6 +233,14 @@
|
|||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "9461782956ad83b30282bf90e31fa6a70c255ba9"
|
revision = "9461782956ad83b30282bf90e31fa6a70c255ba9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:e95ef557dc3120984bb66b385ae01b4bb8ff56bcde28e7b0d1beed0cccc4d69f"
|
||||||
|
name = "github.com/docker/go-units"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "519db1ee28dcc9fd2474ae59fca29a810482bfb1"
|
||||||
|
version = "v0.4.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:fa6faf4a2977dc7643de38ae599a95424d82f8ffc184045510737010a82c4ecd"
|
digest = "1:fa6faf4a2977dc7643de38ae599a95424d82f8ffc184045510737010a82c4ecd"
|
||||||
name = "github.com/gogo/googleapis"
|
name = "github.com/gogo/googleapis"
|
||||||
@ -204,6 +283,14 @@
|
|||||||
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
|
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
|
||||||
version = "v1.3.2"
|
version = "v1.3.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296"
|
||||||
|
name = "github.com/gorilla/mux"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15"
|
||||||
|
version = "v1.7.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
@ -265,6 +352,29 @@
|
|||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "29686dbc5559d93fb1ef402eeda3e35c38d75af4"
|
revision = "29686dbc5559d93fb1ef402eeda3e35c38d75af4"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:cdf3df431e70077f94e14a99305808e3d13e96262b4686154970f448f7248842"
|
||||||
|
name = "github.com/openfaas/faas"
|
||||||
|
packages = ["gateway/requests"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "80b6976c106370a7081b2f8e9099a6ea9638e1f3"
|
||||||
|
version = "0.18.10"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:7a20be0bdfb2c05a4a7b955cb71645fe2983aa3c0bbae10d6bba3e2dd26ddd0d"
|
||||||
|
name = "github.com/openfaas/faas-provider"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"auth",
|
||||||
|
"httputil",
|
||||||
|
"logs",
|
||||||
|
"proxy",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "8f7c35975e1b2bf8286c2f90ee51633eec427491"
|
||||||
|
version = "0.14.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
|
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
@ -314,15 +424,15 @@
|
|||||||
revision = "d98352740cb2c55f81556b63d4a1ec64c5a319c2"
|
revision = "d98352740cb2c55f81556b63d4a1ec64c5a319c2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:2d9d06cb9d46dacfdbb45f8575b39fc0126d083841a29d4fbf8d97708f43107e"
|
digest = "1:1314b5ef1c0b25257ea02e454291bf042478a48407cfe3ffea7e20323bbf5fdf"
|
||||||
name = "github.com/vishvananda/netlink"
|
name = "github.com/vishvananda/netlink"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
"nl",
|
"nl",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752"
|
revision = "f049be6f391489d3f374498fe0c8df8449258372"
|
||||||
version = "v1.0.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -472,8 +582,23 @@
|
|||||||
"github.com/containerd/containerd/errdefs",
|
"github.com/containerd/containerd/errdefs",
|
||||||
"github.com/containerd/containerd/namespaces",
|
"github.com/containerd/containerd/namespaces",
|
||||||
"github.com/containerd/containerd/oci",
|
"github.com/containerd/containerd/oci",
|
||||||
|
"github.com/containerd/containerd/remotes",
|
||||||
|
"github.com/containerd/containerd/remotes/docker",
|
||||||
|
"github.com/containerd/containerd/runtime/v2/logging",
|
||||||
|
"github.com/containerd/go-cni",
|
||||||
|
"github.com/coreos/go-systemd/journal",
|
||||||
|
"github.com/docker/cli/cli/config",
|
||||||
|
"github.com/docker/cli/cli/config/configfile",
|
||||||
|
"github.com/docker/distribution/reference",
|
||||||
|
"github.com/gorilla/mux",
|
||||||
"github.com/morikuni/aec",
|
"github.com/morikuni/aec",
|
||||||
"github.com/opencontainers/runtime-spec/specs-go",
|
"github.com/opencontainers/runtime-spec/specs-go",
|
||||||
|
"github.com/openfaas/faas-provider",
|
||||||
|
"github.com/openfaas/faas-provider/logs",
|
||||||
|
"github.com/openfaas/faas-provider/proxy",
|
||||||
|
"github.com/openfaas/faas-provider/types",
|
||||||
|
"github.com/openfaas/faas/gateway/requests",
|
||||||
|
"github.com/pkg/errors",
|
||||||
"github.com/sethvargo/go-password/password",
|
"github.com/sethvargo/go-password/password",
|
||||||
"github.com/spf13/cobra",
|
"github.com/spf13/cobra",
|
||||||
"github.com/vishvananda/netlink",
|
"github.com/vishvananda/netlink",
|
||||||
|
26
Gopkg.toml
26
Gopkg.toml
@ -1,3 +1,7 @@
|
|||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/containerd/containerd"
|
name = "github.com/containerd/containerd"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@ -18,10 +22,26 @@
|
|||||||
name = "github.com/alexellis/go-execute"
|
name = "github.com/alexellis/go-execute"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
||||||
[prune]
|
[[constraint]]
|
||||||
go-tests = true
|
name = "github.com/gorilla/mux"
|
||||||
unused-packages = true
|
version = "1.7.3"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/openfaas/faas"
|
||||||
|
version = "0.18.7"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/sethvargo/go-password"
|
name = "github.com/sethvargo/go-password"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/containerd/go-cni"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/openfaas/faas-provider"
|
||||||
|
version = "0.14.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/docker/cli"
|
||||||
|
version = "19.3.5"
|
||||||
|
37
Makefile
37
Makefile
@ -1,6 +1,9 @@
|
|||||||
Version := $(shell git describe --tags --dirty)
|
Version := $(shell git describe --tags --dirty)
|
||||||
GitCommit := $(shell git rev-parse HEAD)
|
GitCommit := $(shell git rev-parse HEAD)
|
||||||
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
||||||
|
CONTAINERD_VER := 1.3.2
|
||||||
|
CNI_VERSION := v0.8.5
|
||||||
|
ARCH := amd64
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: local
|
all: local
|
||||||
@ -13,3 +16,37 @@ dist:
|
|||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
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=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 GOARCH=arm64 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-arm64
|
||||||
|
|
||||||
|
.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.3.2/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 $(GOPATH)/src/github.com/openfaas/faasd/bin/faasd /usr/local/bin/
|
||||||
|
cd $(GOPATH)/src/github.com/openfaas/faasd/ && 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
|
||||||
|
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 1
|
||||||
|
/usr/local/bin/faas-cli logs figlet --follow=false | grep Forking
|
||||||
|
261
README.md
261
README.md
@ -1,158 +1,165 @@
|
|||||||
# faasd - serverless with containerd
|
# faasd - serverless with containerd and CNI 🐳
|
||||||
|
|
||||||
[](https://travis-ci.com/alexellis/faasd)
|
[](https://travis-ci.com/openfaas/faasd)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
[](https://www.openfaas.com)
|
||||||
|
|
||||||
faasd is a Golang supervisor that bundles OpenFaaS for use with containerd instead of a container orchestrator like Kubernetes or Docker Swarm.
|
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling.
|
||||||
|
|
||||||
## About faasd:
|
## About faasd
|
||||||
|
|
||||||
* faasd is a single Golang binary
|
* is a single Golang binary
|
||||||
* faasd is multi-arch, so works on `x86_64`, armhf and arm64
|
* can be set-up and left alone to run your applications
|
||||||
* faasd downloads, starts and supervises the core components to run OpenFaaS
|
* is multi-arch, so works on Intel `x86_64` and ARM out the box
|
||||||
|
* uses the same core components and ecosystem of OpenFaaS
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> Demo of faasd running in KVM
|
||||||
|
|
||||||
## What does faasd deploy?
|
## What does faasd deploy?
|
||||||
|
|
||||||
* [faas-containerd](https://github.com/alexellis/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)
|
* [Prometheus](https://github.com/prometheus/prometheus) - for monitoring of services, metrics, scaling and dashboards
|
||||||
* [the OpenFaaS gateway](https://github.com/openfaas/faas/tree/master/gateway)
|
* [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
|
||||||
|
|
||||||
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.
|
You'll also need:
|
||||||
|
|
||||||
### faas-containerd supports:
|
* [CNI](https://github.com/containernetworking/plugins)
|
||||||
|
* [containerd](https://github.com/containerd/containerd)
|
||||||
|
* [runc](https://github.com/opencontainers/runc)
|
||||||
|
|
||||||
* `faas list`
|
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.
|
||||||
* `faas describe`
|
|
||||||
* `faas deploy --update=true --replace=false`
|
|
||||||
* `faas invoke`
|
|
||||||
* `faas invoke --async`
|
|
||||||
|
|
||||||
Other operations are pending development in the provider.
|
## Tutorials
|
||||||
|
|
||||||
### Pre-reqs
|
### Get started on DigitalOcean, or any other IaaS
|
||||||
|
|
||||||
* Linux - ideally Ubuntu, which is used for testing
|
If your IaaS supports `user_data` aka "cloud-init", then this guide is for you. If not, then checkout the approach and feel free to run each step manually.
|
||||||
* 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`
|
* [Build a Serverless appliance with faasd](https://blog.alexellis.io/deploy-serverless-faasd-with-cloud-init/)
|
||||||
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
|
||||||
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
### Run locally on MacOS, Linux, or Windows with Multipass.run
|
||||||
|
|
||||||
|
* [Get up and running with your own faasd installation on your Mac/Ubuntu or Windows with cloud-config](https://gist.github.com/alexellis/6d297e678c9243d326c151028a3ad7b9)
|
||||||
|
|
||||||
|
### Get started on armhf / Raspberry Pi
|
||||||
|
|
||||||
|
You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular Linux VM/VPS host.
|
||||||
|
|
||||||
|
* [faasd - lightweight Serverless for your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
|
||||||
|
|
||||||
|
### Terraform for DigitalOcean
|
||||||
|
|
||||||
|
Automate everything within < 60 seconds and get a public URL and IP address back. Customise as required, or adapt to your preferred cloud such as AWS EC2.
|
||||||
|
|
||||||
|
* [Provision faasd 0.7.5 on DigitalOcean with Terraform 0.12.0](https://gist.github.com/alexellis/fd618bd2f957eb08c44d086ef2fc3906)
|
||||||
|
|
||||||
|
### A note on private repos / registries
|
||||||
|
|
||||||
|
To use private image repos, `~/.docker/config.json` needs to be copied to `/var/lib/faasd/.docker/config.json`.
|
||||||
|
|
||||||
|
If you'd like to set up your own private registry, [see this tutorial](https://blog.alexellis.io/get-a-tls-enabled-docker-registry-in-5-minutes/).
|
||||||
|
|
||||||
|
Beware that running `docker login` on MacOS and Windows may create an empty file with your credentials stored in the system helper.
|
||||||
|
|
||||||
|
Alternatively, use you can use the `registry-login` command from the OpenFaaS Cloud bootstrap tool (ofc-bootstrap):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sLSf https://raw.githubusercontent.com/openfaas-incubator/ofc-bootstrap/master/get.sh | sudo sh
|
||||||
|
|
||||||
|
ofc-bootstrap registry-login --username <your-registry-username> --password-stdin
|
||||||
|
# (the enter your password and hit return)
|
||||||
|
```
|
||||||
|
The file will be created in `./credentials/`
|
||||||
|
|
||||||
|
### Logs for functions
|
||||||
|
|
||||||
|
You can view the logs of functions using `journalctl`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -t openfaas-fn:FUNCTION_NAME
|
||||||
|
|
||||||
|
|
||||||
|
faas-cli store deploy figlet
|
||||||
|
journalctl -t openfaas-fn:figlet -f &
|
||||||
|
echo logs | faas-cli invoke figlet
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual / developer instructions
|
||||||
|
|
||||||
|
See [here for manual / developer instructions](docs/DEV.md)
|
||||||
|
|
||||||
|
## Getting help
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
The [OpenFaaS docs](https://docs.openfaas.com/) provide a wealth of information and are kept up to date with new features.
|
||||||
|
|
||||||
|
### Function and template store
|
||||||
|
|
||||||
|
For community functions see `faas-cli store --help`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Workshop
|
||||||
|
|
||||||
|
[The OpenFaaS workshop](https://github.com/openfaas/workshop/) is a set of 12 self-paced labs and provides a great starting point
|
||||||
|
|
||||||
|
### Community support
|
||||||
|
|
||||||
|
An active community of almost 3000 users awaits you on Slack. Over 250 of those users are also contributors and help maintain the code.
|
||||||
|
|
||||||
|
* [Join Slack](https://slack.openfaas.io/)
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|
||||||
|
### Supported operations
|
||||||
|
|
||||||
|
* `faas login`
|
||||||
|
* `faas up`
|
||||||
|
* `faas list`
|
||||||
|
* `faas describe`
|
||||||
|
* `faas deploy --update=true --replace=false`
|
||||||
|
* `faas invoke --async`
|
||||||
|
* `faas invoke`
|
||||||
|
* `faas rm`
|
||||||
|
* `faas store list/deploy/inspect`
|
||||||
|
* `faas version`
|
||||||
|
* `faas namespace`
|
||||||
|
* `faas secret`
|
||||||
|
* `faas logs`
|
||||||
|
|
||||||
|
Scale from and to zero is also supported. On a Dell XPS with a small, pre-pulled image unpausing an existing task took 0.19s and starting a task for a killed function took 0.39s. There may be further optimizations to be gained.
|
||||||
|
|
||||||
|
Other operations are pending development in the provider such as:
|
||||||
|
|
||||||
|
* `faas auth` - supported for Basic Authentication, but OAuth2 & OIDC require a patch
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
Pending:
|
Pending:
|
||||||
|
|
||||||
* [ ] Use CNI to create network namespaces and adapters
|
* [ ] Add support for using container images in third-party public registries
|
||||||
|
* [ ] Add support for using container images in private third-party registries
|
||||||
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
* [ ] 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)
|
* [ ] 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
|
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||||
|
* [ ] 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
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
|
|
||||||
|
* [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] 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] Add queue-worker and NATS
|
||||||
* [x] Create faasd.service and faas-containerd.service
|
* [x] Create faasd.service and faasd-provider.service
|
||||||
* [x] Self-install / create systemd service via `faasd install`
|
* [x] Self-install / create systemd service via `faasd install`
|
||||||
* [x] Restart containers upon restart of faasd
|
* [x] Restart containers upon restart of faasd
|
||||||
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
||||||
* [x] Determine armhf/arm64 containers to run for gateway
|
* [x] Determine armhf/arm64 containers to run for gateway
|
||||||
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faasd-provider HTTP API
|
||||||
|
* [x] Setup custom working directory for faasd `/var/lib/faasd/`
|
||||||
## Hacking (build from source)
|
* [x] Use CNI to create network namespaces and adapters
|
||||||
|
|
||||||
First run faas-containerd
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd $GOPATH/src/github.com/alexellis/faas-containerd
|
|
||||||
|
|
||||||
# You'll need to install containerd and its pre-reqs first
|
|
||||||
# https://github.com/alexellis/faas-containerd/
|
|
||||||
|
|
||||||
sudo ./faas-containerd
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run faasd, which brings up the gateway and Prometheus as containers
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd $GOPATH/src/github.com/alexellis/faasd
|
|
||||||
go build
|
|
||||||
|
|
||||||
# Install with systemd
|
|
||||||
# sudo ./faasd install
|
|
||||||
|
|
||||||
# Or run interactively
|
|
||||||
# sudo ./faasd up
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build and run (binaries)
|
|
||||||
|
|
||||||
```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"
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
```
|
|
||||||
|
|
||||||
### At run-time
|
|
||||||
|
|
||||||
Look in `hosts` in the current working folder to get the IP for the gateway or Prometheus
|
|
||||||
|
|
||||||
```sh
|
|
||||||
127.0.0.1 localhost
|
|
||||||
172.19.0.1 faas-containerd
|
|
||||||
172.19.0.2 prometheus
|
|
||||||
|
|
||||||
172.19.0.3 gateway
|
|
||||||
172.19.0.4 nats
|
|
||||||
172.19.0.5 queue-worker
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://172.19.0.2:9090/targets
|
|
||||||
|
|
||||||
* faas-containerd runs on 172.19.0.1:8081
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
|
28
cloud-config.txt
Normal file
28
cloud-config.txt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#cloud-config
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
|
||||||
|
|
||||||
|
package_update: true
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- runc
|
||||||
|
|
||||||
|
runcmd:
|
||||||
|
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.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.3.2/containerd.service | tee /etc/systemd/system/containerd.service
|
||||||
|
- systemctl daemon-reload && systemctl start containerd
|
||||||
|
- systemctl enable 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.8.5/cni-plugins-linux-amd64-v0.8.5.tgz | tar -xz -C /opt/cni/bin
|
||||||
|
- mkdir -p /go/src/github.com/openfaas/
|
||||||
|
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
||||||
|
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.7.7/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
|
||||||
|
- 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)
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
systemd "github.com/alexellis/faasd/pkg/systemd"
|
systemd "github.com/openfaas/faasd/pkg/systemd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -16,29 +18,48 @@ var installCmd = &cobra.Command{
|
|||||||
RunE: runInstall,
|
RunE: runInstall,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const workingDirectoryPermission = 0644
|
||||||
|
|
||||||
|
const faasdwd = "/var/lib/faasd"
|
||||||
|
|
||||||
|
const faasdProviderWd = "/var/lib/faasd-provider"
|
||||||
|
|
||||||
func runInstall(_ *cobra.Command, _ []string) error {
|
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("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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = binExists("/usr/local/bin/", "netns")
|
err = systemd.InstallUnit("faasd", map[string]string{"Cwd": faasdwd})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = systemd.InstallUnit("faas-containerd")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = systemd.InstallUnit("faasd")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -48,7 +69,7 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = systemd.Enable("faas-containerd")
|
err = systemd.Enable("faasd-provider")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -58,7 +79,7 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = systemd.Start("faas-containerd")
|
err = systemd.Start("faasd-provider")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -68,6 +89,9 @@ func runInstall(_ *cobra.Command, _ []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(`Login with:
|
||||||
|
sudo cat /var/lib/faasd/secrets/basic-auth-password | faas-cli login -s`)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,3 +102,33 @@ func binExists(folder, name string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
115
cmd/provider.go
Normal file
115
cmd/provider.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
userSecretPath := path.Join(wd, "secrets")
|
||||||
|
|
||||||
|
bootstrapHandlers := types.FaaSHandlers{
|
||||||
|
FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver),
|
||||||
|
DeleteHandler: handlers.MakeDeleteHandler(client, cni),
|
||||||
|
DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath, alwaysPull),
|
||||||
|
FunctionReader: handlers.MakeReadHandler(client),
|
||||||
|
ReplicaReader: handlers.MakeReplicaReaderHandler(client),
|
||||||
|
ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni),
|
||||||
|
UpdateHandler: handlers.MakeUpdateHandler(client, cni, userSecretPath, alwaysPull),
|
||||||
|
HealthHandler: func(w http.ResponseWriter, r *http.Request) {},
|
||||||
|
InfoHandler: handlers.MakeInfoHandler(Version, GitCommit),
|
||||||
|
ListNamespaceHandler: listNamespaces(),
|
||||||
|
SecretHandler: handlers.MakeSecretHandler(client, userSecretPath),
|
||||||
|
LogHandler: logs.NewLogHandlerFunc(faasdlogs.New(), config.ReadTimeout),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Listening on TCP port: %d\n", *config.TCPPort)
|
||||||
|
bootstrap.Serve(&bootstrapHandlers, config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
func listNamespaces() func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
list := []string{""}
|
||||||
|
out, _ := json.Marshal(list)
|
||||||
|
w.Write(out)
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,12 @@ func init() {
|
|||||||
rootCommand.AddCommand(versionCmd)
|
rootCommand.AddCommand(versionCmd)
|
||||||
rootCommand.AddCommand(upCmd)
|
rootCommand.AddCommand(upCmd)
|
||||||
rootCommand.AddCommand(installCmd)
|
rootCommand.AddCommand(installCmd)
|
||||||
|
rootCommand.AddCommand(makeProviderCmd())
|
||||||
|
rootCommand.AddCommand(collectCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootCommand() *cobra.Command {
|
||||||
|
return rootCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
86
cmd/up.go
86
cmd/up.go
@ -14,8 +14,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/pkg"
|
|
||||||
"github.com/alexellis/k3sup/pkg/env"
|
"github.com/alexellis/k3sup/pkg/env"
|
||||||
|
"github.com/openfaas/faasd/pkg"
|
||||||
"github.com/sethvargo/go-password/password"
|
"github.com/sethvargo/go-password/password"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -26,6 +26,8 @@ var upCmd = &cobra.Command{
|
|||||||
RunE: runUp,
|
RunE: runUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containerSecretMountDir = "/run/secrets"
|
||||||
|
|
||||||
func runUp(_ *cobra.Command, _ []string) error {
|
func runUp(_ *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
clientArch, clientOS := env.GetClientArch()
|
clientArch, clientOS := env.GetClientArch()
|
||||||
@ -47,9 +49,8 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
clientSuffix = "-arm64"
|
clientSuffix = "-arm64"
|
||||||
}
|
}
|
||||||
|
|
||||||
authFileErr := errors.Wrap(makeBasicAuthFiles(), "Could not create gateway auth files")
|
if basicAuthErr := makeBasicAuthFiles(path.Join(path.Join(faasdwd, "secrets"))); basicAuthErr != nil {
|
||||||
if authFileErr != nil {
|
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||||
return authFileErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services := makeServiceDefinitions(clientSuffix)
|
services := makeServiceDefinitions(clientSuffix)
|
||||||
@ -76,6 +77,7 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
|
|
||||||
shutdownTimeout := time.Second * 1
|
shutdownTimeout := time.Second * 1
|
||||||
timeout := time.Second * 60
|
timeout := time.Second * 60
|
||||||
|
proxyDoneCh := make(chan bool)
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -91,14 +93,18 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close proxy
|
||||||
|
proxyDoneCh <- true
|
||||||
time.AfterFunc(shutdownTimeout, func() {
|
time.AfterFunc(shutdownTimeout, func() {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gatewayURLChan := make(chan string, 1)
|
gatewayURLChan := make(chan string, 1)
|
||||||
proxy := pkg.NewProxy(timeout)
|
proxyPort := 8080
|
||||||
go proxy.Start(gatewayURLChan)
|
proxy := pkg.NewProxy(proxyPort, timeout)
|
||||||
|
go proxy.Start(gatewayURLChan, proxyDoneCh)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
@ -110,6 +116,7 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
log.Println(fileErr)
|
log.Println(fileErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host := ""
|
host := ""
|
||||||
lines := strings.Split(string(fileData), "\n")
|
lines := strings.Split(string(fileData), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@ -118,7 +125,7 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("[up] Sending %s to proxy\n", host)
|
log.Printf("[up] Sending %s to proxy\n", host)
|
||||||
gatewayURLChan <- host
|
gatewayURLChan <- host + ":8080"
|
||||||
close(gatewayURLChan)
|
close(gatewayURLChan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -126,8 +133,8 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeBasicAuthFiles() error {
|
func makeBasicAuthFiles(wd string) error {
|
||||||
wd, _ := os.Getwd()
|
|
||||||
pwdFile := wd + "/basic-auth-password"
|
pwdFile := wd + "/basic-auth-password"
|
||||||
authPassword, err := password.Generate(63, 10, 0, false, true)
|
authPassword, err := password.Generate(63, 10, 0, false, true)
|
||||||
|
|
||||||
@ -156,7 +163,7 @@ func makeFile(filePath, fileContents string) error {
|
|||||||
return nil
|
return nil
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
log.Printf("Writing to: %q\n", filePath)
|
log.Printf("Writing to: %q\n", filePath)
|
||||||
return ioutil.WriteFile(filePath, []byte(fileContents), 0644)
|
return ioutil.WriteFile(filePath, []byte(fileContents), workingDirectoryPermission)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -165,55 +172,53 @@ func makeFile(filePath, fileContents string) error {
|
|||||||
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
secretMountDir := "/run/secrets"
|
|
||||||
|
|
||||||
return []pkg.Service{
|
return []pkg.Service{
|
||||||
pkg.Service{
|
{
|
||||||
Name: "basic-auth-plugin",
|
Name: "basic-auth-plugin",
|
||||||
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
|
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"port=8080",
|
"port=8080",
|
||||||
"secret_mount_path=" + secretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
"user_filename=basic-auth-user",
|
"user_filename=basic-auth-user",
|
||||||
"pass_filename=basic-auth-password",
|
"pass_filename=basic-auth-password",
|
||||||
},
|
},
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-password"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
|
||||||
},
|
},
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-user"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
Caps: []string{"CAP_NET_RAW"},
|
||||||
Args: nil,
|
Args: nil,
|
||||||
},
|
},
|
||||||
pkg.Service{
|
{
|
||||||
Name: "nats",
|
Name: "nats",
|
||||||
Env: []string{""},
|
Env: []string{""},
|
||||||
Image: "docker.io/library/nats-streaming:0.11.2",
|
Image: "docker.io/library/nats-streaming:0.11.2",
|
||||||
Caps: []string{},
|
Caps: []string{},
|
||||||
Args: []string{"/nats-streaming-server", "-m", "8222", "--store=memory", "--cluster_id=faas-cluster"},
|
Args: []string{"/nats-streaming-server", "-m", "8222", "--store=memory", "--cluster_id=faas-cluster"},
|
||||||
},
|
},
|
||||||
pkg.Service{
|
{
|
||||||
Name: "prometheus",
|
Name: "prometheus",
|
||||||
Env: []string{},
|
Env: []string{},
|
||||||
Image: "docker.io/prom/prometheus:v2.14.0",
|
Image: "docker.io/prom/prometheus:v2.14.0",
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "prometheus.yml"),
|
Src: path.Join(wd, "prometheus.yml"),
|
||||||
Dest: "/etc/prometheus/prometheus.yml",
|
Dest: "/etc/prometheus/prometheus.yml",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
Caps: []string{"CAP_NET_RAW"},
|
||||||
},
|
},
|
||||||
pkg.Service{
|
{
|
||||||
Name: "gateway",
|
Name: "gateway",
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"basic_auth=true",
|
"basic_auth=true",
|
||||||
"functions_provider_url=http://faas-containerd:8081/",
|
"functions_provider_url=http://faasd-provider:8081/",
|
||||||
"direct_functions=false",
|
"direct_functions=false",
|
||||||
"read_timeout=60s",
|
"read_timeout=60s",
|
||||||
"write_timeout=60s",
|
"write_timeout=60s",
|
||||||
@ -222,22 +227,23 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"faas_nats_port=4222",
|
"faas_nats_port=4222",
|
||||||
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
|
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
|
||||||
"auth_proxy_pass_body=false",
|
"auth_proxy_pass_body=false",
|
||||||
"secret_mount_path=" + secretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
|
"scale_from_zero=true",
|
||||||
},
|
},
|
||||||
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-password"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
|
||||||
},
|
},
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-user"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
Caps: []string{"CAP_NET_RAW"},
|
||||||
},
|
},
|
||||||
pkg.Service{
|
{
|
||||||
Name: "queue-worker",
|
Name: "queue-worker",
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"faas_nats_address=nats",
|
"faas_nats_address=nats",
|
||||||
@ -248,17 +254,17 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"max_inflight=1",
|
"max_inflight=1",
|
||||||
"write_debug=false",
|
"write_debug=false",
|
||||||
"basic_auth=true",
|
"basic_auth=true",
|
||||||
"secret_mount_path=" + secretMountDir,
|
"secret_mount_path=" + containerSecretMountDir,
|
||||||
},
|
},
|
||||||
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
||||||
Mounts: []pkg.Mount{
|
Mounts: []pkg.Mount{
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-password"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
|
||||||
},
|
},
|
||||||
pkg.Mount{
|
{
|
||||||
Src: path.Join(wd, "basic-auth-user"),
|
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
|
||||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
Caps: []string{"CAP_NET_RAW"},
|
||||||
|
309
docs/DEV.md
Normal file
309
docs/DEV.md
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
## Manual installation of faasd for development
|
||||||
|
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
### Pre-reqs
|
||||||
|
|
||||||
|
* 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 v1.3.2](https://github.com/containerd/containerd) and the [CNI plugins v0.8.5](https://github.com/containernetworking/plugins)
|
||||||
|
|
||||||
|
[faas-cli](https://github.com/openfaas/faas-cli) is optional, but recommended.
|
||||||
|
|
||||||
|
### Get containerd
|
||||||
|
|
||||||
|
You have three options - binaries for PC, binaries for armhf, or build from source.
|
||||||
|
|
||||||
|
* Install containerd `x86_64` only
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export VER=1.3.2
|
||||||
|
curl -sLSf https://github.com/containerd/containerd/releases/download/v$VER/containerd-$VER.linux-amd64.tar.gz > /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.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSL https://github.com/alexellis/containerd-armhf/releases/download/v1.3.2/containerd.tgz | sudo tar -xvz --strip-components=2 -C /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
* Or clone / build / install [containerd](https://github.com/containerd/containerd) from source:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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.3.2
|
||||||
|
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
containerd --version
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Ensure containerd is running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sLS https://raw.githubusercontent.com/containerd/containerd/master/containerd.service > /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:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo containerd &
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Enable forwarding
|
||||||
|
|
||||||
|
> This is required to allow containers in containerd to access the Internet via your computer's primary network interface.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the setting permanent:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hacking (build from source)
|
||||||
|
|
||||||
|
#### Get build packages
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.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
|
||||||
|
```
|
||||||
|
|
||||||
|
You should also add the following to `~/.bash_profile`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export GOPATH=$HOME/go/
|
||||||
|
export PATH=$PATH:/usr/local/go/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Or on Raspberry Pi (armhf)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -SLsf https://dl.google.com/go/go1.13.6.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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export ARCH=amd64
|
||||||
|
export CNI_VERSION=v0.8.5
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Clone faasd and its systemd unit files
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd $GOPATH/src/github.com/openfaas/faasd
|
||||||
|
cd faasd
|
||||||
|
make local
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build and run `faasd` (binaries)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# For x86_64
|
||||||
|
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd" \
|
||||||
|
-o "/usr/local/bin/faasd" \
|
||||||
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
|
# armhf
|
||||||
|
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd-armhf" \
|
||||||
|
-o "/usr/local/bin/faasd" \
|
||||||
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
|
# arm64
|
||||||
|
sudo curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.0/faasd-arm64" \
|
||||||
|
-o "/usr/local/bin/faasd" \
|
||||||
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install `faasd`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Install with systemd
|
||||||
|
sudo cp bin/faasd /usr/local/bin
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo journalctl -u faasd
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see output like:
|
||||||
|
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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
|
@ -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
|
|
14
hack/faasd-provider.service
Normal file
14
hack/faasd-provider.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=faasd-provider
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
MemoryLimit=500M
|
||||||
|
Environment="secret_mount_path={{.SecretMountPath}}"
|
||||||
|
Environment="basic_auth=true"
|
||||||
|
ExecStart=/usr/local/bin/faasd provider
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10s
|
||||||
|
WorkingDirectory={{.Cwd}}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=faasd
|
Description=faasd
|
||||||
After=faas-containerd.service
|
After=faasd-provider.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
MemoryLimit=500M
|
MemoryLimit=500M
|
||||||
|
18
main.go
18
main.go
@ -1,9 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/cmd"
|
"github.com/openfaas/faasd/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These values will be injected into these variables at the build time.
|
// These values will be injected into these variables at the build time.
|
||||||
@ -15,6 +16,21 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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 {
|
if err := cmd.Execute(Version, GitCommit); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
228
pkg/cninetwork/cni_network.go
Normal file
228
pkg/cninetwork/cni_network.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package cninetwork
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
// CNIResultsDir is the directory CNI stores allocated IP for containers
|
||||||
|
CNIResultsDir = "/var/lib/cni/results"
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
"routes": [
|
||||||
|
{ "dst": "0.0.0.0/0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "firewall"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`, defaultNetworkName, defaultBridgeName, defaultSubnet)
|
||||||
|
|
||||||
|
// 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}))
|
||||||
|
|
||||||
|
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 of the created container
|
||||||
|
func GetIPAddress(result *gocni.CNIResult, task containerd.Task) (net.IP, error) {
|
||||||
|
// Get the IP of the created interface
|
||||||
|
var ip net.IP
|
||||||
|
for ifName, config := range result.Interfaces {
|
||||||
|
if config.Sandbox == netNamespace(task) {
|
||||||
|
for _, ipConfig := range config.IPConfigs {
|
||||||
|
if ifName != "lo" && ipConfig.IP.To4() != nil {
|
||||||
|
ip = ipConfig.IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip == nil {
|
||||||
|
return nil, fmt.Errorf("unable to get IP address for: %s", task.ID())
|
||||||
|
}
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIPfromPID(pid int) (*net.IP, error) {
|
||||||
|
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
||||||
|
|
||||||
|
peerIDs, err := ConnectedToBridgeVethPeerIds(defaultBridgeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find peers on: %s %s", defaultBridgeName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, addrsErr := GetNetDevsByVethPeerIds(pid, peerIDs)
|
||||||
|
if addrsErr != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find address for veth pair using: %v %s", peerIDs, addrsErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) > 0 && len(addrs[0].CIDRs) > 0 {
|
||||||
|
return &addrs[0].CIDRs[0].IP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no IP found for function")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
// Copyright Weaveworks
|
// Copyright Weaveworks
|
||||||
// github.com/weaveworks/weave/net
|
// github.com/weaveworks/weave/net
|
||||||
|
|
||||||
package weave
|
package cninetwork
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright Weaveworks
|
// Copyright Weaveworks
|
||||||
// github.com/weaveworks/weave/net
|
// github.com/weaveworks/weave/net
|
||||||
|
|
||||||
package weave
|
package cninetwork
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
6
pkg/contants.go
Normal file
6
pkg/contants.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FunctionNamespace is the default containerd namespace functions are created
|
||||||
|
FunctionNamespace = "openfaas-fn"
|
||||||
|
)
|
188
pkg/logs/requestor.go
Normal file
188
pkg/logs/requestor.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
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, 100)
|
||||||
|
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> \
|
||||||
|
// --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP
|
||||||
|
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.FunctionNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// without this sleep the channel seems to get stuck. This results in either no log messages
|
||||||
|
// being read by the Handler _or_ the messages are read but only flushed when the request
|
||||||
|
// timesout
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
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("expected Name %s, got %s", expectedEntry.Name, entry.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Namespace != expectedEntry.Namespace {
|
||||||
|
t.Fatalf("expected Namespace %s, got %s", expectedEntry.Namespace, entry.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Timestamp != expectedEntry.Timestamp {
|
||||||
|
t.Fatalf("expected Timestamp %s, got %s", expectedEntry.Timestamp, entry.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Text != expectedEntry.Text {
|
||||||
|
t.Fatalf("expected Text %s, got %s", 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 --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
||||||
|
now.UTC().Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := buildCmd(ctx, req).String()
|
||||||
|
|
||||||
|
if !strings.Contains(cmd, "journalctl") {
|
||||||
|
t.Fatalf("expected journalctl cmd, got cmd %s", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(cmd, expectedArgs) {
|
||||||
|
t.Fatalf("expected arg %s,\ngot cmd %s", expectedArgs, cmd)
|
||||||
|
}
|
||||||
|
}
|
35
pkg/provider/config/read.go
Normal file
35
pkg/provider/config/read.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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.EnableHealth = true
|
||||||
|
config.ReadTimeout = serviceTimeout
|
||||||
|
config.WriteTimeout = serviceTimeout
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
73
pkg/provider/handlers/delete.go
Normal file
73
pkg/provider/handlers/delete.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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/gateway/requests"
|
||||||
|
|
||||||
|
faasd "github.com/openfaas/faasd/pkg"
|
||||||
|
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{}
|
||||||
|
err := json.Unmarshal(body, &req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Delete] error parsing input: %s\n", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.FunctionName
|
||||||
|
|
||||||
|
function, err := GetFunction(client, name)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("service %s not found", name)
|
||||||
|
log.Printf("[Delete] %s\n", msg)
|
||||||
|
http.Error(w, msg, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), faasd.FunctionNamespace)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containerErr := service.Remove(ctx, client, name)
|
||||||
|
if containerErr != nil {
|
||||||
|
log.Printf("[Delete] error removing %s, %s\n", name, containerErr)
|
||||||
|
http.Error(w, containerErr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[Delete] deleted %s\n", name)
|
||||||
|
}
|
||||||
|
}
|
207
pkg/provider/handlers/deploy.go
Normal file
207
pkg/provider/handlers/deploy.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/cio"
|
||||||
|
"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"
|
||||||
|
faasd "github.com/openfaas/faasd/pkg"
|
||||||
|
cninetwork "github.com/openfaas/faasd/pkg/cninetwork"
|
||||||
|
"github.com/openfaas/faasd/pkg/service"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateSecrets(secretMountPath, req.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.Service
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), faasd.FunctionNamespace)
|
||||||
|
|
||||||
|
deployErr := deploy(ctx, req, client, cni, secretMountPath, alwaysPull)
|
||||||
|
if deployErr != nil {
|
||||||
|
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
|
||||||
|
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) error {
|
||||||
|
r, err := reference.ParseNormalizedNamed(req.Image)
|
||||||
|
if err != nil {
|
||||||
|
return 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 errors.Wrapf(err, "unable to pull image %s", imgRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := image.Size(ctx)
|
||||||
|
log.Printf("Deploy %s size: %d\n", image.Name(), size)
|
||||||
|
|
||||||
|
envs := prepareEnv(req.EnvProcess, req.EnvVars)
|
||||||
|
mounts := getMounts()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
container, err := client.NewContainer(
|
||||||
|
ctx,
|
||||||
|
name,
|
||||||
|
containerd.WithImage(image),
|
||||||
|
containerd.WithSnapshotter(snapshotter),
|
||||||
|
containerd.WithNewSnapshot(name+"-snapshot", image),
|
||||||
|
containerd.WithNewSpec(oci.WithImageConfig(image),
|
||||||
|
oci.WithCapabilities([]string{"CAP_NET_RAW"}),
|
||||||
|
oci.WithMounts(mounts),
|
||||||
|
oci.WithEnv(envs)),
|
||||||
|
containerd.WithContainerLabels(*req.Labels),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create container: %s, error: %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createTask(ctx, client, container, cni)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTask(ctx context.Context, client *containerd.Client, container containerd.Container, cni gocni.CNI) error {
|
||||||
|
|
||||||
|
name := container.ID()
|
||||||
|
// task, taskErr := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
|
||||||
|
|
||||||
|
task, taskErr := container.NewTask(ctx, cio.BinaryIO("/usr/local/bin/faasd", nil))
|
||||||
|
|
||||||
|
if taskErr != nil {
|
||||||
|
return fmt.Errorf("unable to start task: %s, error: %s", name, taskErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Container ID: %s\tTask ID %s:\tTask PID: %d\t\n", name, task.ID(), task.Pid())
|
||||||
|
|
||||||
|
labels := map[string]string{}
|
||||||
|
network, err := cninetwork.CreateCNINetwork(ctx, cni, task, labels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := cninetwork.GetIPAddress(network, task)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("%s has IP: %s.\n", name, ip.String())
|
||||||
|
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMounts() []specs.Mount {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
mounts := []specs.Mount{}
|
||||||
|
mounts = append(mounts, specs.Mount{
|
||||||
|
Destination: "/etc/resolv.conf",
|
||||||
|
Type: "bind",
|
||||||
|
Source: path.Join(wd, "resolv.conf"),
|
||||||
|
Options: []string{"rbind", "ro"},
|
||||||
|
})
|
||||||
|
|
||||||
|
mounts = append(mounts, specs.Mount{
|
||||||
|
Destination: "/etc/hosts",
|
||||||
|
Type: "bind",
|
||||||
|
Source: path.Join(wd, "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
|
||||||
|
}
|
91
pkg/provider/handlers/functions.go
Normal file
91
pkg/provider/handlers/functions.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/namespaces"
|
||||||
|
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||||
|
|
||||||
|
faasd "github.com/openfaas/faasd/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Function struct {
|
||||||
|
name string
|
||||||
|
namespace string
|
||||||
|
image string
|
||||||
|
pid uint32
|
||||||
|
replicas int
|
||||||
|
IP string
|
||||||
|
labels map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFunctions returns a map of all functions with running tasks on namespace
|
||||||
|
func ListFunctions(client *containerd.Client) (map[string]Function, error) {
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), faasd.FunctionNamespace)
|
||||||
|
functions := make(map[string]Function)
|
||||||
|
|
||||||
|
containers, _ := client.Containers(ctx)
|
||||||
|
for _, k := range containers {
|
||||||
|
name := k.ID()
|
||||||
|
f, err := GetFunction(client, name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
functions[name] = f
|
||||||
|
}
|
||||||
|
return functions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFunction returns a function that matches name
|
||||||
|
func GetFunction(client *containerd.Client, name string) (Function, error) {
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), faasd.FunctionNamespace)
|
||||||
|
c, err := client.LoadContainer(ctx, name)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
image, _ := c.Image(ctx)
|
||||||
|
|
||||||
|
containerName := c.ID()
|
||||||
|
labels, labelErr := c.Labels(ctx)
|
||||||
|
if labelErr != nil {
|
||||||
|
log.Printf("cannot list container %s labels: %s", containerName, labelErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
f := Function{
|
||||||
|
name: containerName,
|
||||||
|
namespace: faasd.FunctionNamespace,
|
||||||
|
image: image.Name(),
|
||||||
|
labels: labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 %s", name, err)
|
||||||
|
}
|
||||||
|
if svc.Status == "running" {
|
||||||
|
replicas = 1
|
||||||
|
f.pid = task.Pid()
|
||||||
|
|
||||||
|
// Get container IP address
|
||||||
|
ip, err := cninetwork.GetIPfromPID(int(task.Pid()))
|
||||||
|
if err != nil {
|
||||||
|
return Function{}, err
|
||||||
|
}
|
||||||
|
f.IP = ip.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replicas = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
f.replicas = replicas
|
||||||
|
return f, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
return Function{}, fmt.Errorf("unable to find function: %s, error %s", name, err)
|
||||||
|
}
|
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.InfoResponse{
|
||||||
|
Orchestration: OrchestrationIdentifier,
|
||||||
|
Provider: ProviderName,
|
||||||
|
Version: types.ProviderVersion{
|
||||||
|
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.InfoResponse{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error unmarshalling the response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Provider != ProviderName {
|
||||||
|
t.Fatalf("expected provider %q, got %q", ProviderName, resp.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
39
pkg/provider/handlers/invoke_resolver.go
Normal file
39
pkg/provider/handlers/invoke_resolver.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
log.Printf("Resolve: %q\n", functionName)
|
||||||
|
|
||||||
|
function, err := GetFunction(i.client, functionName)
|
||||||
|
if err != nil {
|
||||||
|
return url.URL{}, fmt.Errorf("%s not found", functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
39
pkg/provider/handlers/read.go
Normal file
39
pkg/provider/handlers/read.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
|
||||||
|
res := []types.FunctionStatus{}
|
||||||
|
funcs, err := ListFunctions(client)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[Read] error listing functions. Error: %s\n", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, function := range funcs {
|
||||||
|
res = append(res, types.FunctionStatus{
|
||||||
|
Name: function.name,
|
||||||
|
Image: function.image,
|
||||||
|
Replicas: uint64(function.replicas),
|
||||||
|
Namespace: function.namespace,
|
||||||
|
Labels: &function.labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(res)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(body)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
35
pkg/provider/handlers/replicas.go
Normal file
35
pkg/provider/handlers/replicas.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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"]
|
||||||
|
|
||||||
|
if f, err := GetFunction(client, functionName); err == nil {
|
||||||
|
found := types.FunctionStatus{
|
||||||
|
Name: functionName,
|
||||||
|
AvailableReplicas: uint64(f.replicas),
|
||||||
|
Replicas: uint64(f.replicas),
|
||||||
|
Namespace: f.namespace,
|
||||||
|
Labels: &f.labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
functionBytes, _ := json.Marshal(found)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(functionBytes)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
pkg/provider/handlers/scale.go
Normal file
103
pkg/provider/handlers/scale.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"
|
||||||
|
faasd "github.com/openfaas/faasd/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.ServiceName
|
||||||
|
|
||||||
|
if _, err := GetFunction(client, name); 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(), faasd.FunctionNamespace)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
taskExists := true
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Replicas > 0 {
|
||||||
|
if taskExists {
|
||||||
|
if status, statusErr := task.Status(ctx); statusErr == nil {
|
||||||
|
if status.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deployErr := createTask(ctx, client, ctr, cni)
|
||||||
|
if deployErr != nil {
|
||||||
|
log.Printf("[Scale] error deploying %s, error: %s\n", name, deployErr)
|
||||||
|
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if taskExists {
|
||||||
|
if status, statusErr := task.Status(ctx); statusErr == nil {
|
||||||
|
if status.Status == containerd.Running {
|
||||||
|
if pauseErr := task.Pause(ctx); pauseErr != nil {
|
||||||
|
log.Printf("[Scale] error pausing task %s, error: %s\n", name, pauseErr)
|
||||||
|
http.Error(w, pauseErr.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
105
pkg/provider/handlers/secret.go
Normal file
105
pkg/provider/handlers/secret.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/openfaas/faas-provider/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secretFilePermission = 0644
|
||||||
|
|
||||||
|
func MakeSecretHandler(c *containerd.Client, 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(c, w, r, mountPath)
|
||||||
|
case http.MethodPost:
|
||||||
|
createSecret(c, w, r, mountPath)
|
||||||
|
case http.MethodPut:
|
||||||
|
createSecret(c, w, r, mountPath)
|
||||||
|
case http.MethodDelete:
|
||||||
|
deleteSecret(c, w, r, mountPath)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listSecrets(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||||
|
files, err := ioutil.ReadDir(mountPath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := []types.Secret{}
|
||||||
|
for _, f := range files {
|
||||||
|
secrets = append(secrets, types.Secret{Name: f.Name()})
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesOut, _ := json.Marshal(secrets)
|
||||||
|
w.Write(bytesOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSecret(c *containerd.Client, 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 = ioutil.WriteFile(path.Join(mountPath, secret.Name), []byte(secret.Value), secretFilePermission)
|
||||||
|
|
||||||
|
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)
|
||||||
|
return secret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteSecret(c *containerd.Client, 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 = 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
|
||||||
|
}
|
||||||
|
}
|
81
pkg/provider/handlers/update.go
Normal file
81
pkg/provider/handlers/update.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
faasd "github.com/openfaas/faasd/pkg"
|
||||||
|
"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
|
||||||
|
|
||||||
|
function, err := GetFunction(client, name)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("service %s not found", name)
|
||||||
|
log.Printf("[Update] %s\n", msg)
|
||||||
|
http.Error(w, msg, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateSecrets(secretMountPath, req.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := namespaces.WithNamespace(context.Background(), faasd.FunctionNamespace)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
containerErr := service.Remove(ctx, client, name)
|
||||||
|
if containerErr != nil {
|
||||||
|
log.Printf("[Update] error removing %s, %s\n", name, containerErr)
|
||||||
|
http.Error(w, containerErr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deployErr := deploy(ctx, req, client, cni, secretMountPath, alwaysPull)
|
||||||
|
if deployErr != nil {
|
||||||
|
log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr)
|
||||||
|
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
pkg/proxy.go
123
pkg/proxy.go
@ -1,6 +1,7 @@
|
|||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,83 +11,58 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProxy(timeout time.Duration) *Proxy {
|
func NewProxy(port int, timeout time.Duration) *Proxy {
|
||||||
|
|
||||||
return &Proxy{
|
return &Proxy{
|
||||||
|
Port: port,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) Start(gatewayChan chan string) error {
|
func (p *Proxy) Start(gatewayChan chan string, done chan bool) error {
|
||||||
tcp := 8080
|
tcp := p.Port
|
||||||
|
|
||||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}
|
}
|
||||||
|
ps := proxyState{
|
||||||
data := struct{ host string }{
|
Host: "",
|
||||||
host: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data.host = <-gatewayChan
|
ps.Host = <-gatewayChan
|
||||||
|
|
||||||
log.Printf("Starting faasd proxy on %d\n", tcp)
|
log.Printf("Starting faasd proxy on %d\n", tcp)
|
||||||
|
|
||||||
fmt.Printf("Gateway: %s\n", data.host)
|
fmt.Printf("Gateway: %s\n", ps.Host)
|
||||||
|
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", tcp),
|
Addr: fmt.Sprintf(":%d", tcp),
|
||||||
ReadTimeout: p.Timeout,
|
ReadTimeout: p.Timeout,
|
||||||
WriteTimeout: p.Timeout,
|
WriteTimeout: p.Timeout,
|
||||||
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Handler: http.HandlerFunc(makeProxy(&ps)),
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
copyHeaders(upReq.Header, &r.Header)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
copyHeaders(w.Header(), &upRes.Header)
|
|
||||||
|
|
||||||
w.WriteHeader(upRes.StatusCode)
|
|
||||||
io.Copy(w, upRes.Body)
|
|
||||||
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.ListenAndServe()
|
go func() {
|
||||||
|
log.Printf("[proxy] Begin listen on %d\n", p.Port)
|
||||||
|
if err := s.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
log.Printf("Error ListenAndServe: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("[proxy] Wait for done")
|
||||||
|
<-done
|
||||||
|
log.Println("[proxy] Done received")
|
||||||
|
if err := s.Shutdown(context.Background()); err != nil {
|
||||||
|
log.Printf("[proxy] Error in Shutdown: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyHeaders clones the header values from the source into the destination.
|
// copyHeaders clones the header values from the source into the destination.
|
||||||
@ -97,3 +73,50 @@ func copyHeaders(destination http.Header, source *http.Header) {
|
|||||||
destination[k] = vClone
|
destination[k] = vClone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type proxyState struct {
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeProxy(ps *proxyState) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
query := ""
|
||||||
|
if len(r.URL.RawQuery) > 0 {
|
||||||
|
query = "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream := fmt.Sprintf("http://%s%s%s", ps.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)
|
||||||
|
|
||||||
|
copyHeaders(upReq.Header, &r.Header)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeaders(w.Header(), &upRes.Header)
|
||||||
|
|
||||||
|
w.WriteHeader(upRes.StatusCode)
|
||||||
|
io.Copy(w, upRes.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
73
pkg/proxy_test.go
Normal file
73
pkg/proxy_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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)
|
||||||
|
upstream := 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 upstream.Close()
|
||||||
|
port := 8080
|
||||||
|
proxy := NewProxy(port, time.Second*1)
|
||||||
|
|
||||||
|
gwChan := make(chan string, 1)
|
||||||
|
doneCh := make(chan bool)
|
||||||
|
|
||||||
|
go proxy.Start(gwChan, doneCh)
|
||||||
|
|
||||||
|
u, _ := url.Parse(upstream.URL)
|
||||||
|
log.Println("Host", u.Host)
|
||||||
|
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
|
||||||
|
}()
|
||||||
|
}
|
@ -4,14 +4,23 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"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"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// dockerConfigDir contains "config.json"
|
||||||
|
const dockerConfigDir = "/var/lib/faasd/.docker/"
|
||||||
|
|
||||||
// Remove removes a container
|
// 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) error {
|
||||||
|
|
||||||
@ -53,8 +62,11 @@ func Remove(ctx context.Context, client *containerd.Client, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// From Stellar
|
// Adapted from Stellar - https://github.com/stellar
|
||||||
func killTask(ctx context.Context, task containerd.Task) error {
|
func killTask(ctx context.Context, task containerd.Task) error {
|
||||||
|
|
||||||
|
killTimeout := 30 * time.Second
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
var err error
|
var err error
|
||||||
@ -69,11 +81,12 @@ func killTask(ctx context.Context, task containerd.Task) error {
|
|||||||
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
|
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
|
||||||
log.Printf("error killing container task: %s", err)
|
log.Printf("error killing container task: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-wait:
|
case <-wait:
|
||||||
task.Delete(ctx)
|
task.Delete(ctx)
|
||||||
return
|
return
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(killTimeout):
|
||||||
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
||||||
log.Printf("error force killing container task: %s", err)
|
log.Printf("error force killing container task: %s", err)
|
||||||
}
|
}
|
||||||
@ -86,20 +99,71 @@ func killTask(ctx context.Context, task containerd.Task) error {
|
|||||||
return err
|
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
|
||||||
|
}
|
||||||
|
authOpts := []docker.AuthorizerOpt{docker.WithAuthCreds(credFunc)}
|
||||||
|
authorizer := docker.NewDockerAuthorizer(authOpts...)
|
||||||
|
opts := docker.ResolverOptions{
|
||||||
|
Hosts: docker.ConfigureDefaultRegistries(docker.WithAuthorizer(authorizer)),
|
||||||
|
}
|
||||||
|
return docker.NewResolver(opts), nil
|
||||||
|
}
|
||||||
|
|
||||||
var empty containerd.Image
|
func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string, pullAlways bool) (containerd.Image, error) {
|
||||||
image, err := client.GetImage(ctx, imageName)
|
var (
|
||||||
if err != nil {
|
empty containerd.Image
|
||||||
if !errdefs.IsNotFound(err) {
|
resolver remotes.Resolver
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, stErr := os.Stat(filepath.Join(dockerConfigDir, config.ConfigFileName)); stErr == 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(stErr) {
|
||||||
|
return empty, stErr
|
||||||
|
}
|
||||||
|
|
||||||
|
var image containerd.Image
|
||||||
|
if pullAlways {
|
||||||
|
img, err := pullImage(ctx, client, resolver, imageName)
|
||||||
|
if err != nil {
|
||||||
return empty, err
|
return empty, err
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := client.Pull(ctx, imageName, containerd.WithPullUnpack)
|
|
||||||
if err != nil {
|
|
||||||
return empty, fmt.Errorf("cannot pull: %s", err)
|
|
||||||
}
|
|
||||||
image = img
|
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)
|
unpacked, err := image.IsUnpacked(ctx, snapshotter)
|
||||||
@ -115,3 +179,21 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna
|
|||||||
|
|
||||||
return image, nil
|
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,73 +6,90 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/pkg/service"
|
|
||||||
"github.com/alexellis/faasd/pkg/weave"
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/cio"
|
"github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
gocni "github.com/containerd/go-cni"
|
||||||
|
"github.com/openfaas/faasd/pkg/cninetwork"
|
||||||
|
"github.com/openfaas/faasd/pkg/service"
|
||||||
|
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/oci"
|
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultSnapshotter = "overlayfs"
|
const (
|
||||||
|
defaultSnapshotter = "overlayfs"
|
||||||
|
workingDirectoryPermission = 0644
|
||||||
|
// faasdNamespace is the containerd namespace services are created
|
||||||
|
faasdNamespace = "default"
|
||||||
|
faasServicesPullAlways = false
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Image string
|
||||||
|
Env []string
|
||||||
|
Name string
|
||||||
|
Mounts []Mount
|
||||||
|
Caps []string
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mount struct {
|
||||||
|
Src string
|
||||||
|
Dest string
|
||||||
|
}
|
||||||
|
|
||||||
type Supervisor struct {
|
type Supervisor struct {
|
||||||
client *containerd.Client
|
client *containerd.Client
|
||||||
|
cni gocni.CNI
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSupervisor(sock string) (*Supervisor, error) {
|
func NewSupervisor(sock string) (*Supervisor, error) {
|
||||||
client, err := containerd.New(sock)
|
client, err := containerd.New(sock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cni, err := cninetwork.InitNetwork()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Supervisor{
|
return &Supervisor{
|
||||||
client: client,
|
client: client,
|
||||||
|
cni: cni,
|
||||||
}, nil
|
}, 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 {
|
func (s *Supervisor) Start(svcs []Service) error {
|
||||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
ctx := namespaces.WithNamespace(context.Background(), faasdNamespace)
|
||||||
|
|
||||||
wd, _ := os.Getwd()
|
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"),
|
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
||||||
[]byte(`127.0.0.1 localhost
|
[]byte(hosts), workingDirectoryPermission)
|
||||||
172.19.0.1 faas-containerd`), 0644)
|
|
||||||
|
|
||||||
if writeHostsErr != nil {
|
if writeHostsErr != nil {
|
||||||
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
||||||
}
|
}
|
||||||
// os.Chown("hosts", 101, 101)
|
|
||||||
|
|
||||||
images := map[string]containerd.Image{}
|
images := map[string]containerd.Image{}
|
||||||
|
|
||||||
for _, svc := range svcs {
|
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)
|
img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter, faasServicesPullAlways)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -118,27 +135,6 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
Options: []string{"rbind", "ro"},
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
newContainer, containerCreateErr := s.client.NewContainer(
|
newContainer, containerCreateErr := s.client.NewContainer(
|
||||||
ctx,
|
ctx,
|
||||||
svc.Name,
|
svc.Name,
|
||||||
@ -148,7 +144,6 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
oci.WithCapabilities(svc.Caps),
|
oci.WithCapabilities(svc.Caps),
|
||||||
oci.WithMounts(mounts),
|
oci.WithMounts(mounts),
|
||||||
withOCIArgs(svc.Args),
|
withOCIArgs(svc.Args),
|
||||||
hook,
|
|
||||||
oci.WithEnv(svc.Env)),
|
oci.WithEnv(svc.Env)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -165,14 +160,25 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := getIP(newContainer.ID(), task.Pid())
|
labels := map[string]string{}
|
||||||
|
network, err := cninetwork.CreateCNINetwork(ctx, s.cni, task, labels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := cninetwork.GetIPAddress(network, task)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("%s has IP: %s\n", newContainer.ID(), ip.String())
|
||||||
|
|
||||||
hosts, _ := ioutil.ReadFile("hosts")
|
hosts, _ := ioutil.ReadFile("hosts")
|
||||||
|
|
||||||
hosts = []byte(string(hosts) + fmt.Sprintf(`
|
hosts = []byte(string(hosts) + fmt.Sprintf(`
|
||||||
%s %s
|
%s %s
|
||||||
`, ip, svc.Name))
|
`, ip, svc.Name))
|
||||||
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
writeErr := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission)
|
||||||
|
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
||||||
@ -197,37 +203,26 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIP(containerID string, taskPID uint32) string {
|
func (s *Supervisor) Close() {
|
||||||
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
defer s.client.Close()
|
||||||
|
|
||||||
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 ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
func (s *Supervisor) Remove(svcs []Service) error {
|
||||||
Image string
|
ctx := namespaces.WithNamespace(context.Background(), faasdNamespace)
|
||||||
Env []string
|
|
||||||
Name string
|
|
||||||
Mounts []Mount
|
|
||||||
Caps []string
|
|
||||||
Args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mount struct {
|
for _, svc := range svcs {
|
||||||
Src string
|
err := cninetwork.DeleteCNINetwork(ctx, s.cni, s.client, svc.Name)
|
||||||
Dest string
|
if err != nil {
|
||||||
|
log.Printf("[Delete] error removing CNI network for %s, %s\n", svc.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = service.Remove(ctx, s.client, svc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func withOCIArgs(args []string) oci.SpecOpts {
|
func withOCIArgs(args []string) oci.SpecOpts {
|
||||||
@ -236,8 +231,6 @@ func withOCIArgs(args []string) oci.SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
|
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,11 @@ func DaemonReload() error {
|
|||||||
return nil
|
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"
|
tmplName := "./hack/" + name + ".service"
|
||||||
tmpl, err := template.ParseFiles(tmplName)
|
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)
|
return fmt.Errorf("error loading template %s, error %s", tmplName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wd, _ := os.Getwd()
|
|
||||||
var tpl bytes.Buffer
|
var tpl bytes.Buffer
|
||||||
userData := struct {
|
|
||||||
Cwd string
|
|
||||||
}{
|
|
||||||
Cwd: wd,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tmpl.Execute(&tpl, userData)
|
err = tmpl.Execute(&tpl, tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
57
vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
57
vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package osversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OSVersion is a wrapper for Windows version information
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||||
|
type OSVersion struct {
|
||||||
|
Version uint32
|
||||||
|
MajorVersion uint8
|
||||||
|
MinorVersion uint8
|
||||||
|
Build uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
|
||||||
|
type osVersionInfoEx struct {
|
||||||
|
OSVersionInfoSize uint32
|
||||||
|
MajorVersion uint32
|
||||||
|
MinorVersion uint32
|
||||||
|
BuildNumber uint32
|
||||||
|
PlatformID uint32
|
||||||
|
CSDVersion [128]uint16
|
||||||
|
ServicePackMajor uint16
|
||||||
|
ServicePackMinor uint16
|
||||||
|
SuiteMask uint16
|
||||||
|
ProductType byte
|
||||||
|
Reserve byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the operating system version on Windows.
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Get() OSVersion {
|
||||||
|
var err error
|
||||||
|
osv := OSVersion{}
|
||||||
|
osv.Version, err = windows.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
// GetVersion never fails.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||||
|
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||||
|
osv.Build = uint16(osv.Version >> 16)
|
||||||
|
return osv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build gets the build-number on Windows
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Build() uint16 {
|
||||||
|
return Get().Build
|
||||||
|
}
|
||||||
|
|
||||||
|
func (osv OSVersion) ToString() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||||
|
}
|
23
vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
23
vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package osversion
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
|
||||||
|
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
|
||||||
|
RS1 = 14393
|
||||||
|
|
||||||
|
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
|
||||||
|
// corresponds to Windows 10 (Creators Update).
|
||||||
|
RS2 = 15063
|
||||||
|
|
||||||
|
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
|
||||||
|
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
|
||||||
|
RS3 = 16299
|
||||||
|
|
||||||
|
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
|
||||||
|
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
|
||||||
|
RS4 = 17134
|
||||||
|
|
||||||
|
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
|
||||||
|
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
|
||||||
|
RS5 = 17763
|
||||||
|
)
|
77
vendor/github.com/containerd/containerd/runtime/v2/logging/logging.go
generated
vendored
Normal file
77
vendor/github.com/containerd/containerd/runtime/v2/logging/logging.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config of the container logs
|
||||||
|
type Config struct {
|
||||||
|
ID string
|
||||||
|
Namespace string
|
||||||
|
Stdout io.Reader
|
||||||
|
Stderr io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFunc is implemented by custom v2 logging binaries
|
||||||
|
type LoggerFunc func(context.Context, *Config, func() error) error
|
||||||
|
|
||||||
|
// Run the logging driver
|
||||||
|
func Run(fn LoggerFunc) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
config := &Config{
|
||||||
|
ID: os.Getenv("CONTAINER_ID"),
|
||||||
|
Namespace: os.Getenv("CONTAINER_NAMESPACE"),
|
||||||
|
Stdout: os.NewFile(3, "CONTAINER_STDOUT"),
|
||||||
|
Stderr: os.NewFile(4, "CONTAINER_STDERR"),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
s = make(chan os.Signal, 32)
|
||||||
|
errCh = make(chan error, 1)
|
||||||
|
wait = os.NewFile(5, "CONTAINER_WAIT")
|
||||||
|
)
|
||||||
|
signal.Notify(s, unix.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := fn(ctx, config, wait.Close); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s:
|
||||||
|
cancel()
|
||||||
|
case err := <-errCh:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
vendor/github.com/containerd/continuity/pathdriver/path_driver.go
generated
vendored
Normal file
101
vendor/github.com/containerd/continuity/pathdriver/path_driver.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 pathdriver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathDriver provides all of the path manipulation functions in a common
|
||||||
|
// interface. The context should call these and never use the `filepath`
|
||||||
|
// package or any other package to manipulate paths.
|
||||||
|
type PathDriver interface {
|
||||||
|
Join(paths ...string) string
|
||||||
|
IsAbs(path string) bool
|
||||||
|
Rel(base, target string) (string, error)
|
||||||
|
Base(path string) string
|
||||||
|
Dir(path string) string
|
||||||
|
Clean(path string) string
|
||||||
|
Split(path string) (dir, file string)
|
||||||
|
Separator() byte
|
||||||
|
Abs(path string) (string, error)
|
||||||
|
Walk(string, filepath.WalkFunc) error
|
||||||
|
FromSlash(path string) string
|
||||||
|
ToSlash(path string) string
|
||||||
|
Match(pattern, name string) (matched bool, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathDriver is a simple default implementation calls the filepath package.
|
||||||
|
type pathDriver struct{}
|
||||||
|
|
||||||
|
// LocalPathDriver is the exported pathDriver struct for convenience.
|
||||||
|
var LocalPathDriver PathDriver = &pathDriver{}
|
||||||
|
|
||||||
|
func (*pathDriver) Join(paths ...string) string {
|
||||||
|
return filepath.Join(paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) IsAbs(path string) bool {
|
||||||
|
return filepath.IsAbs(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Rel(base, target string) (string, error) {
|
||||||
|
return filepath.Rel(base, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Base(path string) string {
|
||||||
|
return filepath.Base(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Dir(path string) string {
|
||||||
|
return filepath.Dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Clean(path string) string {
|
||||||
|
return filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Split(path string) (dir, file string) {
|
||||||
|
return filepath.Split(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Separator() byte {
|
||||||
|
return filepath.Separator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Abs(path string) (string, error) {
|
||||||
|
return filepath.Abs(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that filepath.Walk calls os.Stat, so if the context wants to
|
||||||
|
// to call Driver.Stat() for Walk, they need to create a new struct that
|
||||||
|
// overrides this method.
|
||||||
|
func (*pathDriver) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return filepath.Walk(root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) FromSlash(path string) string {
|
||||||
|
return filepath.FromSlash(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) ToSlash(path string) string {
|
||||||
|
return filepath.ToSlash(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathDriver) Match(pattern, name string) (bool, error) {
|
||||||
|
return filepath.Match(pattern, name)
|
||||||
|
}
|
23
vendor/github.com/containerd/go-cni/.travis.yml
generated
vendored
Normal file
23
vendor/github.com/containerd/go-cni/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.12.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
go_import_path: github.com/containerd/go-cni
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d
|
||||||
|
- env GO111MODULE=off go get -u github.com/vbatts/git-validation
|
||||||
|
- env GO111MODULE=off go get -u github.com/kunalkushwaha/ltag
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||||
|
|
||||||
|
script:
|
||||||
|
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||||
|
- ../project/script/validate/fileheader ../project/
|
||||||
|
- env GO111MODULE=on ../project/script/validate/vendor
|
||||||
|
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
201
vendor/github.com/containerd/go-cni/LICENSE
generated
vendored
Normal file
201
vendor/github.com/containerd/go-cni/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
60
vendor/github.com/containerd/go-cni/README.md
generated
vendored
Normal file
60
vendor/github.com/containerd/go-cni/README.md
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
[](https://travis-ci.org/containerd/go-cni) [](https://godoc.org/github.com/containerd/go-cni)
|
||||||
|
|
||||||
|
# go-cni
|
||||||
|
|
||||||
|
A generic CNI library to provide APIs for CNI plugin interactions. The library provides APIs to:
|
||||||
|
|
||||||
|
- Load CNI network config from different sources
|
||||||
|
- Setup networks for container namespace
|
||||||
|
- Remove networks from container namespace
|
||||||
|
- Query status of CNI network plugin initialization
|
||||||
|
|
||||||
|
go-cni aims to support plugins that implement [Container Network Interface](https://github.com/containernetworking/cni)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
id := "123456"
|
||||||
|
netns := "/proc/9999/ns/net"
|
||||||
|
defaultIfName := "eth0"
|
||||||
|
// Initialize library
|
||||||
|
l = gocni.New(gocni.WithMinNetworkCount(2),
|
||||||
|
gocni.WithPluginConfDir("/etc/mycni/net.d"),
|
||||||
|
gocni.WithPluginDir([]string{"/opt/mycni/bin", "/opt/cni/bin"}),
|
||||||
|
gocni.WithDefaultIfName(defaultIfName))
|
||||||
|
|
||||||
|
// Load the cni configuration
|
||||||
|
err:= l.Load(gocni.WithLoNetwork, gocni.WithDefaultConf)
|
||||||
|
if err != nil{
|
||||||
|
log.Errorf("failed to load cni configuration: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup network for namespace.
|
||||||
|
labels := map[string]string{
|
||||||
|
"K8S_POD_NAMESPACE": "namespace1",
|
||||||
|
"K8S_POD_NAME": "pod1",
|
||||||
|
"K8S_POD_INFRA_CONTAINER_ID": id,
|
||||||
|
}
|
||||||
|
result, err := l.Setup(id, netns, gocni.WithLabels(labels))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to setup network for namespace %q: %v",id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IP of the default interface
|
||||||
|
IP := result.Interfaces[defaultIfName].IPConfigs[0].IP.String()
|
||||||
|
fmt.Printf("IP of the default interface %s:%s", defaultIfName, IP)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project details
|
||||||
|
|
||||||
|
The go-cni is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||||
|
As a containerd sub-project, you will find the:
|
||||||
|
|
||||||
|
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||||
|
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||||
|
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
220
vendor/github.com/containerd/go-cni/cni.go
generated
vendored
Normal file
220
vendor/github.com/containerd/go-cni/cni.go
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CNI interface {
|
||||||
|
// Setup setup the network for the namespace
|
||||||
|
Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*CNIResult, error)
|
||||||
|
// Remove tears down the network of the namespace.
|
||||||
|
Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
|
||||||
|
// Load loads the cni network config
|
||||||
|
Load(opts ...CNIOpt) error
|
||||||
|
// Status checks the status of the cni initialization
|
||||||
|
Status() error
|
||||||
|
// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
|
||||||
|
GetConfig() *ConfigResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigResult struct {
|
||||||
|
PluginDirs []string
|
||||||
|
PluginConfDir string
|
||||||
|
PluginMaxConfNum int
|
||||||
|
Prefix string
|
||||||
|
Networks []*ConfNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfNetwork struct {
|
||||||
|
Config *NetworkConfList
|
||||||
|
IFName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList
|
||||||
|
type NetworkConfList struct {
|
||||||
|
Name string
|
||||||
|
CNIVersion string
|
||||||
|
Plugins []*NetworkConf
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig
|
||||||
|
type NetworkConf struct {
|
||||||
|
Network *types.NetConf
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
type libcni struct {
|
||||||
|
config
|
||||||
|
|
||||||
|
cniConfig cnilibrary.CNI
|
||||||
|
networkCount int // minimum network plugin configurations needed to initialize cni
|
||||||
|
networks []*Network
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCNIConfig() *libcni {
|
||||||
|
return &libcni{
|
||||||
|
config: config{
|
||||||
|
pluginDirs: []string{DefaultCNIDir},
|
||||||
|
pluginConfDir: DefaultNetDir,
|
||||||
|
pluginMaxConfNum: DefaultMaxConfNum,
|
||||||
|
prefix: DefaultPrefix,
|
||||||
|
},
|
||||||
|
cniConfig: &cnilibrary.CNIConfig{
|
||||||
|
Path: []string{DefaultCNIDir},
|
||||||
|
},
|
||||||
|
networkCount: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new libcni instance.
|
||||||
|
func New(config ...CNIOpt) (CNI, error) {
|
||||||
|
cni := defaultCNIConfig()
|
||||||
|
var err error
|
||||||
|
for _, c := range config {
|
||||||
|
if err = c(cni); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cni, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the latest config from cni config files.
|
||||||
|
func (c *libcni) Load(opts ...CNIOpt) error {
|
||||||
|
var err error
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
// Reset the networks on a load operation to ensure
|
||||||
|
// config happens on a clean slate
|
||||||
|
c.reset()
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
if err = o(c); err != nil {
|
||||||
|
return errors.Wrapf(ErrLoad, fmt.Sprintf("cni config load failed: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status returns the status of CNI initialization.
|
||||||
|
func (c *libcni) Status() error {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
if len(c.networks) < c.networkCount {
|
||||||
|
return ErrCNINotInitialized
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Networks returns all the configured networks.
|
||||||
|
// NOTE: Caller MUST NOT modify anything in the returned array.
|
||||||
|
func (c *libcni) Networks() []*Network {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
return append([]*Network{}, c.networks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup setups the network in the namespace
|
||||||
|
func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*CNIResult, error) {
|
||||||
|
if err := c.Status(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ns, err := newNamespace(id, path, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var results []*current.Result
|
||||||
|
for _, network := range c.Networks() {
|
||||||
|
r, err := network.Attach(ctx, ns)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, r)
|
||||||
|
}
|
||||||
|
return c.GetCNIResultFromResults(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the network config from the namespace
|
||||||
|
func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
|
||||||
|
if err := c.Status(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ns, err := newNamespace(id, path, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, network := range c.Networks() {
|
||||||
|
if err := network.Remove(ctx, ns); err != nil {
|
||||||
|
// Based on CNI spec v0.7.0, empty network namespace is allowed to
|
||||||
|
// do best effort cleanup. However, it is not handled consistently
|
||||||
|
// right now:
|
||||||
|
// https://github.com/containernetworking/plugins/issues/210
|
||||||
|
// TODO(random-liu): Remove the error handling when the issue is
|
||||||
|
// fixed and the CNI spec v0.6.0 support is deprecated.
|
||||||
|
if path == "" && strings.Contains(err.Error(), "no such file or directory") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
|
||||||
|
func (c *libcni) GetConfig() *ConfigResult {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
r := &ConfigResult{
|
||||||
|
PluginDirs: c.config.pluginDirs,
|
||||||
|
PluginConfDir: c.config.pluginConfDir,
|
||||||
|
PluginMaxConfNum: c.config.pluginMaxConfNum,
|
||||||
|
Prefix: c.config.prefix,
|
||||||
|
}
|
||||||
|
for _, network := range c.networks {
|
||||||
|
conf := &NetworkConfList{
|
||||||
|
Name: network.config.Name,
|
||||||
|
CNIVersion: network.config.CNIVersion,
|
||||||
|
Source: string(network.config.Bytes),
|
||||||
|
}
|
||||||
|
for _, plugin := range network.config.Plugins {
|
||||||
|
conf.Plugins = append(conf.Plugins, &NetworkConf{
|
||||||
|
Network: plugin.Network,
|
||||||
|
Source: string(plugin.Bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r.Networks = append(r.Networks, &ConfNetwork{
|
||||||
|
Config: conf,
|
||||||
|
IFName: network.ifName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *libcni) reset() {
|
||||||
|
c.networks = nil
|
||||||
|
}
|
55
vendor/github.com/containerd/go-cni/errors.go
generated
vendored
Normal file
55
vendor/github.com/containerd/go-cni/errors.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCNINotInitialized = errors.New("cni plugin not initialized")
|
||||||
|
ErrInvalidConfig = errors.New("invalid cni config")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrRead = errors.New("failed to read config file")
|
||||||
|
ErrInvalidResult = errors.New("invalid result")
|
||||||
|
ErrLoad = errors.New("failed to load cni config")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsCNINotInitialized returns true if the error is due to cni config not being initialized
|
||||||
|
func IsCNINotInitialized(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrCNINotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvalidConfig returns true if the error is invalid cni config
|
||||||
|
func IsInvalidConfig(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound returns true if the error is due to a missing config or result
|
||||||
|
func IsNotFound(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReadFailure return true if the error is a config read failure
|
||||||
|
func IsReadFailure(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrRead
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvalidResult return true if the error is due to invalid cni result
|
||||||
|
func IsInvalidResult(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrInvalidResult
|
||||||
|
}
|
14
vendor/github.com/containerd/go-cni/go.mod
generated
vendored
Normal file
14
vendor/github.com/containerd/go-cni/go.mod
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module github.com/containerd/go-cni
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/containernetworking/cni v0.7.1
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||||
|
github.com/onsi/gomega v1.7.1 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f // indirect
|
||||||
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.12
|
39
vendor/github.com/containerd/go-cni/go.sum
generated
vendored
Normal file
39
vendor/github.com/containerd/go-cni/go.sum
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
|
||||||
|
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||||
|
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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||||
|
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/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/stretchr/objx v0.0.0-20180129172003-8a3f7159479f h1:SrOsK2rwonEK9IsdNEU61zcTdKW68/PuV9wuHHpqngk=
|
||||||
|
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d h1:YCdGqZILKLGzbyEYbdau30JBEXbKaKYmkBDU5JUW3D0=
|
||||||
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
41
vendor/github.com/containerd/go-cni/helper.go
generated
vendored
Normal file
41
vendor/github.com/containerd/go-cni/helper.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateInterfaceConfig(ipConf *current.IPConfig, ifs int) error {
|
||||||
|
if ipConf == nil {
|
||||||
|
return fmt.Errorf("invalid IP configuration (nil)")
|
||||||
|
}
|
||||||
|
if ipConf.Interface != nil && *ipConf.Interface > ifs {
|
||||||
|
return fmt.Errorf("invalid IP configuration (interface number %d is > number of interfaces %d)", *ipConf.Interface, ifs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIfName(prefix string, i int) string {
|
||||||
|
return fmt.Sprintf("%s%d", prefix, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultInterface(prefix string) string {
|
||||||
|
return getIfName(prefix, 0)
|
||||||
|
}
|
77
vendor/github.com/containerd/go-cni/namespace.go
generated
vendored
Normal file
77
vendor/github.com/containerd/go-cni/namespace.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Network struct {
|
||||||
|
cni cnilibrary.CNI
|
||||||
|
config *cnilibrary.NetworkConfigList
|
||||||
|
ifName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Network) Attach(ctx context.Context, ns *Namespace) (*current.Result, error) {
|
||||||
|
r, err := n.cni.AddNetworkList(ctx, n.config, ns.config(n.ifName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return current.NewResultFromResult(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Network) Remove(ctx context.Context, ns *Namespace) error {
|
||||||
|
return n.cni.DelNetworkList(ctx, n.config, ns.config(n.ifName))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namespace struct {
|
||||||
|
id string
|
||||||
|
path string
|
||||||
|
capabilityArgs map[string]interface{}
|
||||||
|
args map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNamespace(id, path string, opts ...NamespaceOpts) (*Namespace, error) {
|
||||||
|
ns := &Namespace{
|
||||||
|
id: id,
|
||||||
|
path: path,
|
||||||
|
capabilityArgs: make(map[string]interface{}),
|
||||||
|
args: make(map[string]string),
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ns); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *Namespace) config(ifName string) *cnilibrary.RuntimeConf {
|
||||||
|
c := &cnilibrary.RuntimeConf{
|
||||||
|
ContainerID: ns.id,
|
||||||
|
NetNS: ns.path,
|
||||||
|
IfName: ifName,
|
||||||
|
}
|
||||||
|
for k, v := range ns.args {
|
||||||
|
c.Args = append(c.Args, [2]string{k, v})
|
||||||
|
}
|
||||||
|
c.CapabilityArgs = ns.capabilityArgs
|
||||||
|
return c
|
||||||
|
}
|
75
vendor/github.com/containerd/go-cni/namespace_opts.go
generated
vendored
Normal file
75
vendor/github.com/containerd/go-cni/namespace_opts.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
type NamespaceOpts func(s *Namespace) error
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
func WithCapabilityPortMap(portMapping []PortMapping) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.capabilityArgs["portMappings"] = portMapping
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCapabilityIPRanges(ipRanges []IPRanges) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.capabilityArgs["ipRanges"] = ipRanges
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCapabilityBandWitdh adds support for traffic shaping:
|
||||||
|
// https://github.com/heptio/cni-plugins/tree/master/plugins/meta/bandwidth
|
||||||
|
func WithCapabilityBandWidth(bandWidth BandWidth) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.capabilityArgs["bandwidth"] = bandWidth
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCapabilityDNS adds support for dns
|
||||||
|
func WithCapabilityDNS(dns DNS) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.capabilityArgs["dns"] = dns
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCapability(name string, capability interface{}) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.capabilityArgs[name] = capability
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args
|
||||||
|
func WithLabels(labels map[string]string) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
for k, v := range labels {
|
||||||
|
c.args[k] = v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithArgs(k, v string) NamespaceOpts {
|
||||||
|
return func(c *Namespace) error {
|
||||||
|
c.args[k] = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
263
vendor/github.com/containerd/go-cni/opts.go
generated
vendored
Normal file
263
vendor/github.com/containerd/go-cni/opts.go
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CNIOpt func(c *libcni) error
|
||||||
|
|
||||||
|
// WithInterfacePrefix sets the prefix for network interfaces
|
||||||
|
// e.g. eth or wlan
|
||||||
|
func WithInterfacePrefix(prefix string) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
c.prefix = prefix
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPluginDir can be used to set the locations of
|
||||||
|
// the cni plugin binaries
|
||||||
|
func WithPluginDir(dirs []string) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
c.pluginDirs = dirs
|
||||||
|
c.cniConfig = &cnilibrary.CNIConfig{Path: dirs}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPluginConfDir can be used to configure the
|
||||||
|
// cni configuration directory.
|
||||||
|
func WithPluginConfDir(dir string) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
c.pluginConfDir = dir
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPluginMaxConfNum can be used to configure the
|
||||||
|
// max cni plugin config file num.
|
||||||
|
func WithPluginMaxConfNum(max int) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
c.pluginMaxConfNum = max
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinNetworkCount can be used to configure the
|
||||||
|
// minimum networks to be configured and initialized
|
||||||
|
// for the status to report success. By default its 1.
|
||||||
|
func WithMinNetworkCount(count int) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
c.networkCount = count
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLoNetwork can be used to load the loopback
|
||||||
|
// network config.
|
||||||
|
func WithLoNetwork(c *libcni) error {
|
||||||
|
loConfig, _ := cnilibrary.ConfListFromBytes([]byte(`{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "cni-loopback",
|
||||||
|
"plugins": [{
|
||||||
|
"type": "loopback"
|
||||||
|
}]
|
||||||
|
}`))
|
||||||
|
|
||||||
|
c.networks = append(c.networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: loConfig,
|
||||||
|
ifName: "lo",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConf can be used to load config directly
|
||||||
|
// from byte.
|
||||||
|
func WithConf(bytes []byte) CNIOpt {
|
||||||
|
return WithConfIndex(bytes, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfIndex can be used to load config directly
|
||||||
|
// from byte and set the interface name's index.
|
||||||
|
func WithConfIndex(bytes []byte, index int) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
conf, err := cnilibrary.ConfFromBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
confList, err := cnilibrary.ConfListFromConf(conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.networks = append(c.networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: confList,
|
||||||
|
ifName: getIfName(c.prefix, index),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfFile can be used to load network config
|
||||||
|
// from an .conf file. Supported with absolute fileName
|
||||||
|
// with path only.
|
||||||
|
func WithConfFile(fileName string) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
conf, err := cnilibrary.ConfFromFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// upconvert to conf list
|
||||||
|
confList, err := cnilibrary.ConfListFromConf(conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.networks = append(c.networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: confList,
|
||||||
|
ifName: getIfName(c.prefix, 0),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfListBytes can be used to load network config list directly
|
||||||
|
// from byte
|
||||||
|
func WithConfListBytes(bytes []byte) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
confList, err := cnilibrary.ConfListFromBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i := len(c.networks)
|
||||||
|
c.networks = append(c.networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: confList,
|
||||||
|
ifName: getIfName(c.prefix, i),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfListFile can be used to load network config
|
||||||
|
// from an .conflist file. Supported with absolute fileName
|
||||||
|
// with path only.
|
||||||
|
func WithConfListFile(fileName string) CNIOpt {
|
||||||
|
return func(c *libcni) error {
|
||||||
|
confList, err := cnilibrary.ConfListFromFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i := len(c.networks)
|
||||||
|
c.networks = append(c.networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: confList,
|
||||||
|
ifName: getIfName(c.prefix, i),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultConf can be used to detect the default network
|
||||||
|
// config file from the configured cni config directory and load
|
||||||
|
// it.
|
||||||
|
// Since the CNI spec does not specify a way to detect default networks,
|
||||||
|
// the convention chosen is - the first network configuration in the sorted
|
||||||
|
// list of network conf files as the default network.
|
||||||
|
func WithDefaultConf(c *libcni) error {
|
||||||
|
return loadFromConfDir(c, c.pluginMaxConfNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAllConf can be used to detect all network config
|
||||||
|
// files from the configured cni config directory and load
|
||||||
|
// them.
|
||||||
|
func WithAllConf(c *libcni) error {
|
||||||
|
return loadFromConfDir(c, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFromConfDir detects network config files from the
|
||||||
|
// configured cni config directory and load them. max is
|
||||||
|
// the maximum network config to load (max i<= 0 means no limit).
|
||||||
|
func loadFromConfDir(c *libcni, max int) error {
|
||||||
|
files, err := cnilibrary.ConfFiles(c.pluginConfDir, []string{".conf", ".conflist", ".json"})
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrapf(ErrRead, "failed to read config file: %v", err)
|
||||||
|
case len(files) == 0:
|
||||||
|
return errors.Wrapf(ErrCNINotInitialized, "no network config found in %s", c.pluginConfDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// files contains the network config files associated with cni network.
|
||||||
|
// Use lexicographical way as a defined order for network config files.
|
||||||
|
sort.Strings(files)
|
||||||
|
// Since the CNI spec does not specify a way to detect default networks,
|
||||||
|
// the convention chosen is - the first network configuration in the sorted
|
||||||
|
// list of network conf files as the default network and choose the default
|
||||||
|
// interface provided during init as the network interface for this default
|
||||||
|
// network. For every other network use a generated interface id.
|
||||||
|
i := 0
|
||||||
|
var networks []*Network
|
||||||
|
for _, confFile := range files {
|
||||||
|
var confList *cnilibrary.NetworkConfigList
|
||||||
|
if strings.HasSuffix(confFile, ".conflist") {
|
||||||
|
confList, err = cnilibrary.ConfListFromFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config list file %s: %v", confFile, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conf, err := cnilibrary.ConfFromFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config file %s: %v", confFile, err)
|
||||||
|
}
|
||||||
|
// Ensure the config has a "type" so we know what plugin to run.
|
||||||
|
// Also catches the case where somebody put a conflist into a conf file.
|
||||||
|
if conf.Network.Type == "" {
|
||||||
|
return errors.Wrapf(ErrInvalidConfig, "network type not found in %s", confFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
confList, err = cnilibrary.ConfListFromConf(conf)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(ErrInvalidConfig, "failed to convert CNI config file %s to CNI config list: %v", confFile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(confList.Plugins) == 0 {
|
||||||
|
return errors.Wrapf(ErrInvalidConfig, "CNI config list in config file %s has no networks, skipping", confFile)
|
||||||
|
|
||||||
|
}
|
||||||
|
networks = append(networks, &Network{
|
||||||
|
cni: c.cniConfig,
|
||||||
|
config: confList,
|
||||||
|
ifName: getIfName(c.prefix, i),
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
if i == max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(networks) == 0 {
|
||||||
|
return errors.Wrapf(ErrCNINotInitialized, "no valid networks found in %s", c.pluginDirs)
|
||||||
|
}
|
||||||
|
c.networks = append(c.networks, networks...)
|
||||||
|
return nil
|
||||||
|
}
|
106
vendor/github.com/containerd/go-cni/result.go
generated
vendored
Normal file
106
vendor/github.com/containerd/go-cni/result.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPConfig struct {
|
||||||
|
IP net.IP
|
||||||
|
Gateway net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type CNIResult struct {
|
||||||
|
Interfaces map[string]*Config
|
||||||
|
DNS []types.DNS
|
||||||
|
Routes []*types.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
IPConfigs []*IPConfig
|
||||||
|
Mac string
|
||||||
|
Sandbox string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCNIResultFromResults returns a structured data containing the
|
||||||
|
// interface configuration for each of the interfaces created in the namespace.
|
||||||
|
// Conforms with
|
||||||
|
// Result:
|
||||||
|
// a) Interfaces list. Depending on the plugin, this can include the sandbox
|
||||||
|
// (eg, container or hypervisor) interface name and/or the host interface
|
||||||
|
// name, the hardware addresses of each interface, and details about the
|
||||||
|
// sandbox (if any) the interface is in.
|
||||||
|
// b) IP configuration assigned to each interface. The IPv4 and/or IPv6 addresses,
|
||||||
|
// gateways, and routes assigned to sandbox and/or host interfaces.
|
||||||
|
// c) DNS information. Dictionary that includes DNS information for nameservers,
|
||||||
|
// domain, search domains and options.
|
||||||
|
func (c *libcni) GetCNIResultFromResults(results []*current.Result) (*CNIResult, error) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
r := &CNIResult{
|
||||||
|
Interfaces: make(map[string]*Config),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugins may not need to return Interfaces in result if
|
||||||
|
// if there are no multiple interfaces created. In that case
|
||||||
|
// all configs should be applied against default interface
|
||||||
|
r.Interfaces[defaultInterface(c.prefix)] = &Config{}
|
||||||
|
|
||||||
|
// Walk through all the results
|
||||||
|
for _, result := range results {
|
||||||
|
// Walk through all the interface in each result
|
||||||
|
for _, intf := range result.Interfaces {
|
||||||
|
r.Interfaces[intf.Name] = &Config{
|
||||||
|
Mac: intf.Mac,
|
||||||
|
Sandbox: intf.Sandbox,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Walk through all the IPs in the result and attach it to corresponding
|
||||||
|
// interfaces
|
||||||
|
for _, ipConf := range result.IPs {
|
||||||
|
if err := validateInterfaceConfig(ipConf, len(result.Interfaces)); err != nil {
|
||||||
|
return nil, errors.Wrapf(ErrInvalidResult, "invalid interface config: %v", err)
|
||||||
|
}
|
||||||
|
name := c.getInterfaceName(result.Interfaces, ipConf)
|
||||||
|
r.Interfaces[name].IPConfigs = append(r.Interfaces[name].IPConfigs,
|
||||||
|
&IPConfig{IP: ipConf.Address.IP, Gateway: ipConf.Gateway})
|
||||||
|
}
|
||||||
|
r.DNS = append(r.DNS, result.DNS)
|
||||||
|
r.Routes = append(r.Routes, result.Routes...)
|
||||||
|
}
|
||||||
|
if _, ok := r.Interfaces[defaultInterface(c.prefix)]; !ok {
|
||||||
|
return nil, errors.Wrapf(ErrNotFound, "default network not found for: %s", defaultInterface(c.prefix))
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceName returns the interface name if the plugins
|
||||||
|
// return the result with associated interfaces. If interface
|
||||||
|
// is not present then default interface name is used
|
||||||
|
func (c *libcni) getInterfaceName(interfaces []*current.Interface,
|
||||||
|
ipConf *current.IPConfig) string {
|
||||||
|
if ipConf.Interface != nil {
|
||||||
|
return interfaces[*ipConf.Interface].Name
|
||||||
|
}
|
||||||
|
return defaultInterface(c.prefix)
|
||||||
|
}
|
78
vendor/github.com/containerd/go-cni/testutils.go
generated
vendored
Normal file
78
vendor/github.com/containerd/go-cni/testutils.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeTmpDir(prefix string) (string, error) {
|
||||||
|
tmpDir, err := ioutil.TempDir(os.TempDir(), prefix)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return tmpDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFakeCNIConfig(t *testing.T) (string, string) {
|
||||||
|
cniDir, err := makeTmpDir("fakecni")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create plugin config dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cniConfDir := path.Join(cniDir, "net.d")
|
||||||
|
err = os.MkdirAll(cniConfDir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create network config dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conf")
|
||||||
|
f1, err := os.Create(networkConfig1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create network config %v: %v", f1, err)
|
||||||
|
}
|
||||||
|
networkConfig2 := path.Join(cniConfDir, "mocknetwork2.conf")
|
||||||
|
f2, err := os.Create(networkConfig2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create network config %v: %v", f2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg1 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin1", "fakecni")
|
||||||
|
_, err = f1.WriteString(cfg1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write network config file %v: %v", f1, err)
|
||||||
|
}
|
||||||
|
f1.Close()
|
||||||
|
cfg2 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin2", "fakecni")
|
||||||
|
_, err = f2.WriteString(cfg2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to write network config file %v: %v", f2, err)
|
||||||
|
}
|
||||||
|
f2.Close()
|
||||||
|
return cniDir, cniConfDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func tearDownCNIConfig(t *testing.T, confDir string) {
|
||||||
|
err := os.RemoveAll(confDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to cleanup CNI configs: %v", err)
|
||||||
|
}
|
||||||
|
}
|
65
vendor/github.com/containerd/go-cni/types.go
generated
vendored
Normal file
65
vendor/github.com/containerd/go-cni/types.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd 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 cni
|
||||||
|
|
||||||
|
const (
|
||||||
|
CNIPluginName = "cni"
|
||||||
|
DefaultNetDir = "/etc/cni/net.d"
|
||||||
|
DefaultCNIDir = "/opt/cni/bin"
|
||||||
|
DefaultMaxConfNum = 1
|
||||||
|
VendorCNIDirTemplate = "%s/opt/%s/bin"
|
||||||
|
DefaultPrefix = "eth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
pluginDirs []string
|
||||||
|
pluginConfDir string
|
||||||
|
pluginMaxConfNum int
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortMapping struct {
|
||||||
|
HostPort int32
|
||||||
|
ContainerPort int32
|
||||||
|
Protocol string
|
||||||
|
HostIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRanges struct {
|
||||||
|
Subnet string
|
||||||
|
RangeStart string
|
||||||
|
RangeEnd string
|
||||||
|
Gateway string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BandWidth defines the ingress/egress rate and burst limits
|
||||||
|
type BandWidth struct {
|
||||||
|
IngressRate uint64
|
||||||
|
IngressBurst uint64
|
||||||
|
EgressRate uint64
|
||||||
|
EgressBurst uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS defines the dns config
|
||||||
|
type DNS struct {
|
||||||
|
// List of DNS servers of the cluster.
|
||||||
|
Servers []string
|
||||||
|
// List of DNS search domains of the cluster.
|
||||||
|
Searches []string
|
||||||
|
// List of DNS options.
|
||||||
|
Options []string
|
||||||
|
}
|
202
vendor/github.com/containernetworking/cni/LICENSE
generated
vendored
Normal file
202
vendor/github.com/containernetworking/cni/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
497
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
Normal file
497
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
// Copyright 2015 CNI 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 libcni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CacheDir = "/var/lib/cni"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A RuntimeConf holds the arguments to one invocation of a CNI plugin
|
||||||
|
// excepting the network configuration, with the nested exception that
|
||||||
|
// the `runtimeConfig` from the network configuration is included
|
||||||
|
// here.
|
||||||
|
type RuntimeConf struct {
|
||||||
|
ContainerID string
|
||||||
|
NetNS string
|
||||||
|
IfName string
|
||||||
|
Args [][2]string
|
||||||
|
// A dictionary of capability-specific data passed by the runtime
|
||||||
|
// to plugins as top-level keys in the 'runtimeConfig' dictionary
|
||||||
|
// of the plugin's stdin data. libcni will ensure that only keys
|
||||||
|
// in this map which match the capabilities of the plugin are passed
|
||||||
|
// to the plugin
|
||||||
|
CapabilityArgs map[string]interface{}
|
||||||
|
|
||||||
|
// A cache directory in which to library data. Defaults to CacheDir
|
||||||
|
CacheDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkConfig struct {
|
||||||
|
Network *types.NetConf
|
||||||
|
Bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkConfigList struct {
|
||||||
|
Name string
|
||||||
|
CNIVersion string
|
||||||
|
DisableCheck bool
|
||||||
|
Plugins []*NetworkConfig
|
||||||
|
Bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type CNI interface {
|
||||||
|
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||||
|
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
|
||||||
|
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
|
||||||
|
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||||
|
|
||||||
|
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||||
|
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||||
|
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||||
|
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||||
|
|
||||||
|
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
|
||||||
|
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CNIConfig struct {
|
||||||
|
Path []string
|
||||||
|
exec invoke.Exec
|
||||||
|
}
|
||||||
|
|
||||||
|
// CNIConfig implements the CNI interface
|
||||||
|
var _ CNI = &CNIConfig{}
|
||||||
|
|
||||||
|
// NewCNIConfig returns a new CNIConfig object that will search for plugins
|
||||||
|
// in the given paths and use the given exec interface to run those plugins,
|
||||||
|
// or if the exec interface is not given, will use a default exec handler.
|
||||||
|
func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
|
||||||
|
return &CNIConfig{
|
||||||
|
Path: path,
|
||||||
|
exec: exec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
inject := map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"cniVersion": cniVersion,
|
||||||
|
}
|
||||||
|
// Add previous plugin result
|
||||||
|
if prevResult != nil {
|
||||||
|
inject["prevResult"] = prevResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure every config uses the same name and version
|
||||||
|
orig, err = InjectConf(orig, inject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return injectRuntimeConfig(orig, rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function takes a libcni RuntimeConf structure and injects values into
|
||||||
|
// a "runtimeConfig" dictionary in the CNI network configuration JSON that
|
||||||
|
// will be passed to the plugin on stdin.
|
||||||
|
//
|
||||||
|
// Only "capabilities arguments" passed by the runtime are currently injected.
|
||||||
|
// These capabilities arguments are filtered through the plugin's advertised
|
||||||
|
// capabilities from its config JSON, and any keys in the CapabilityArgs
|
||||||
|
// matching plugin capabilities are added to the "runtimeConfig" dictionary
|
||||||
|
// sent to the plugin via JSON on stdin. For example, if the plugin's
|
||||||
|
// capabilities include "portMappings", and the CapabilityArgs map includes a
|
||||||
|
// "portMappings" key, that key and its value are added to the "runtimeConfig"
|
||||||
|
// dictionary to be passed to the plugin's stdin.
|
||||||
|
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
rc := make(map[string]interface{})
|
||||||
|
for capability, supported := range orig.Network.Capabilities {
|
||||||
|
if !supported {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if data, ok := rt.CapabilityArgs[capability]; ok {
|
||||||
|
rc[capability] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rc) > 0 {
|
||||||
|
orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return orig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure we have a usable exec if the CNIConfig was not given one
|
||||||
|
func (c *CNIConfig) ensureExec() invoke.Exec {
|
||||||
|
if c.exec == nil {
|
||||||
|
c.exec = &invoke.DefaultExec{
|
||||||
|
RawExec: &invoke.RawExec{Stderr: os.Stderr},
|
||||||
|
PluginDecoder: version.PluginDecoder{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.exec
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResultCacheFilePath(netName string, rt *RuntimeConf) string {
|
||||||
|
cacheDir := rt.CacheDir
|
||||||
|
if cacheDir == "" {
|
||||||
|
cacheDir = CacheDir
|
||||||
|
}
|
||||||
|
return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error {
|
||||||
|
data, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fname := getResultCacheFilePath(netName, rt)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(fname, data, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delCachedResult(netName string, rt *RuntimeConf) error {
|
||||||
|
fname := getResultCacheFilePath(netName, rt)
|
||||||
|
return os.Remove(fname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
fname := getResultCacheFilePath(netName, rt)
|
||||||
|
data, err := ioutil.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore read errors; the cached result may not exist on-disk
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the version of the cached result
|
||||||
|
decoder := version.ConfigDecoder{}
|
||||||
|
resultCniVersion, err := decoder.Decode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we can understand the result
|
||||||
|
result, err := version.NewResult(resultCniVersion, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the config version to ensure plugins get prevResult
|
||||||
|
// in the same version as the config. The cached result version
|
||||||
|
// should match the config version unless the config was changed
|
||||||
|
// while the container was running.
|
||||||
|
result, err = result.GetAsVersion(cniVersion)
|
||||||
|
if err != nil && resultCniVersion != cniVersion {
|
||||||
|
return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkListCachedResult returns the cached Result of the previous
|
||||||
|
// previous AddNetworkList() operation for a network list, or an error.
|
||||||
|
func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
return getCachedResult(list.Name, list.CNIVersion, rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkCachedResult returns the cached Result of the previous
|
||||||
|
// previous AddNetwork() operation for a network, or an error.
|
||||||
|
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
return getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
c.ensureExec()
|
||||||
|
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetworkList executes a sequence of plugins with the ADD command
|
||||||
|
func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
var err error
|
||||||
|
var result types.Result
|
||||||
|
for _, net := range list.Plugins {
|
||||||
|
result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = setCachedResult(result, list.Name, rt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||||
|
c.ensureExec()
|
||||||
|
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckNetworkList executes a sequence of plugins with the CHECK command
|
||||||
|
func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
|
||||||
|
// CHECK was added in CNI spec version 0.4.0 and higher
|
||||||
|
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !gtet {
|
||||||
|
return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if list.DisableCheck {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, net := range list.Plugins {
|
||||||
|
if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||||
|
c.ensureExec()
|
||||||
|
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelNetworkList executes a sequence of plugins with the DEL command
|
||||||
|
func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
|
||||||
|
var cachedResult types.Result
|
||||||
|
|
||||||
|
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
|
||||||
|
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if gtet {
|
||||||
|
cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(list.Plugins) - 1; i >= 0; i-- {
|
||||||
|
net := list.Plugins[i]
|
||||||
|
if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = delCachedResult(list.Name, rt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetwork executes the plugin with the ADD command
|
||||||
|
func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||||
|
result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = setCachedResult(result, net.Network.Name, rt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckNetwork executes the plugin with the CHECK command
|
||||||
|
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
|
||||||
|
// CHECK was added in CNI spec version 0.4.0 and higher
|
||||||
|
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !gtet {
|
||||||
|
return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
|
||||||
|
}
|
||||||
|
return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelNetwork executes the plugin with the DEL command
|
||||||
|
func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
|
||||||
|
var cachedResult types.Result
|
||||||
|
|
||||||
|
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
|
||||||
|
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if gtet {
|
||||||
|
cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = delCachedResult(net.Network.Name, rt)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNetworkList checks that a configuration is reasonably valid.
|
||||||
|
// - all the specified plugins exist on disk
|
||||||
|
// - every plugin supports the desired version.
|
||||||
|
//
|
||||||
|
// Returns a list of all capabilities supported by the configuration, or error
|
||||||
|
func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) {
|
||||||
|
version := list.CNIVersion
|
||||||
|
|
||||||
|
// holding map for seen caps (in case of duplicates)
|
||||||
|
caps := map[string]interface{}{}
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
for _, net := range list.Plugins {
|
||||||
|
if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
for c, enabled := range net.Network.Capabilities {
|
||||||
|
if !enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
caps[c] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, fmt.Errorf("%v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make caps list
|
||||||
|
cc := make([]string, 0, len(caps))
|
||||||
|
for c := range caps {
|
||||||
|
cc = append(cc, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNetwork checks that a configuration is reasonably valid.
|
||||||
|
// It uses the same logic as ValidateNetworkList)
|
||||||
|
// Returns a list of capabilities
|
||||||
|
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) {
|
||||||
|
caps := []string{}
|
||||||
|
for c, ok := range net.Network.Capabilities {
|
||||||
|
if ok {
|
||||||
|
caps = append(caps, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return caps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatePlugin checks that an individual plugin's configuration is sane
|
||||||
|
func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
|
||||||
|
pluginPath, err := invoke.FindInPath(pluginName, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, vers := range vi.SupportedVersions() {
|
||||||
|
if vers == expectedVersion {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionInfo reports which versions of the CNI spec are supported by
|
||||||
|
// the given plugin.
|
||||||
|
func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) {
|
||||||
|
c.ensureExec()
|
||||||
|
pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====
|
||||||
|
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||||
|
return &invoke.Args{
|
||||||
|
Command: action,
|
||||||
|
ContainerID: rt.ContainerID,
|
||||||
|
NetNS: rt.NetNS,
|
||||||
|
PluginArgs: rt.Args,
|
||||||
|
IfName: rt.IfName,
|
||||||
|
Path: strings.Join(c.Path, string(os.PathListSeparator)),
|
||||||
|
}
|
||||||
|
}
|
268
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
Normal file
268
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
// Copyright 2015 CNI 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 libcni
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotFoundError struct {
|
||||||
|
Dir string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoConfigsFoundError struct {
|
||||||
|
Dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NoConfigsFoundError) Error() string {
|
||||||
|
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||||
|
conf := &NetworkConfig{Bytes: bytes}
|
||||||
|
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||||
|
}
|
||||||
|
if conf.Network.Type == "" {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration: missing 'type'")
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||||
|
bytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
return ConfFromBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||||
|
rawList := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(bytes, &rawList); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawName, ok := rawList["name"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: no name")
|
||||||
|
}
|
||||||
|
name, ok := rawName.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cniVersion string
|
||||||
|
rawVersion, ok := rawList["cniVersion"]
|
||||||
|
if ok {
|
||||||
|
cniVersion, ok = rawVersion.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disableCheck := false
|
||||||
|
if rawDisableCheck, ok := rawList["disableCheck"]; ok {
|
||||||
|
disableCheck, ok = rawDisableCheck.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list := &NetworkConfigList{
|
||||||
|
Name: name,
|
||||||
|
DisableCheck: disableCheck,
|
||||||
|
CNIVersion: cniVersion,
|
||||||
|
Bytes: bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
var plugins []interface{}
|
||||||
|
plug, ok := rawList["plugins"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
|
||||||
|
}
|
||||||
|
plugins, ok = plug.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
|
||||||
|
}
|
||||||
|
if len(plugins) == 0 {
|
||||||
|
return nil, fmt.Errorf("error parsing configuration list: no plugins in list")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, conf := range plugins {
|
||||||
|
newBytes, err := json.Marshal(conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
|
||||||
|
}
|
||||||
|
netConf, err := ConfFromBytes(newBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
|
||||||
|
}
|
||||||
|
list.Plugins = append(list.Plugins, netConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||||
|
bytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
return ConfListFromBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfFiles(dir string, extensions []string) ([]string, error) {
|
||||||
|
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
switch {
|
||||||
|
case err == nil: // break
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
confFiles := []string{}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileExt := filepath.Ext(f.Name())
|
||||||
|
for _, ext := range extensions {
|
||||||
|
if fileExt == ext {
|
||||||
|
confFiles = append(confFiles, filepath.Join(dir, f.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return confFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||||
|
files, err := ConfFiles(dir, []string{".conf", ".json"})
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
case len(files) == 0:
|
||||||
|
return nil, NoConfigsFoundError{Dir: dir}
|
||||||
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
for _, confFile := range files {
|
||||||
|
conf, err := ConfFromFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conf.Network.Name == name {
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, NotFoundError{dir, name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
||||||
|
files, err := ConfFiles(dir, []string{".conflist"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
for _, confFile := range files {
|
||||||
|
conf, err := ConfListFromFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conf.Name == name {
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and load a network configuration file (instead of list)
|
||||||
|
// from the same name, then upconvert.
|
||||||
|
singleConf, err := LoadConf(dir, name)
|
||||||
|
if err != nil {
|
||||||
|
// A little extra logic so the error makes sense
|
||||||
|
if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
|
||||||
|
// Config lists found but no config files found
|
||||||
|
return nil, NotFoundError{dir, name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ConfListFromConf(singleConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
|
||||||
|
config := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal(original.Bytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range newValues {
|
||||||
|
if key == "" {
|
||||||
|
return nil, fmt.Errorf("keys cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == nil {
|
||||||
|
return nil, fmt.Errorf("key '%s' value must not be nil", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
config[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
newBytes, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfFromBytes(newBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
|
||||||
|
// with the single network as the only entry in the list.
|
||||||
|
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
|
||||||
|
// Re-deserialize the config's json, then make a raw map configlist.
|
||||||
|
// This may seem a bit strange, but it's to make the Bytes fields
|
||||||
|
// actually make sense. Otherwise, the generated json is littered with
|
||||||
|
// golang default values.
|
||||||
|
|
||||||
|
rawConfig := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawConfigList := map[string]interface{}{
|
||||||
|
"name": original.Network.Name,
|
||||||
|
"cniVersion": original.Network.CNIVersion,
|
||||||
|
"plugins": []interface{}{rawConfig},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(rawConfigList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ConfListFromBytes(b)
|
||||||
|
}
|
128
vendor/github.com/containernetworking/cni/pkg/invoke/args.go
generated
vendored
Normal file
128
vendor/github.com/containernetworking/cni/pkg/invoke/args.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2015 CNI 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CNIArgs interface {
|
||||||
|
// For use with os/exec; i.e., return nil to inherit the
|
||||||
|
// environment from this process
|
||||||
|
// For use in delegation; inherit the environment from this
|
||||||
|
// process and allow overrides
|
||||||
|
AsEnv() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type inherited struct{}
|
||||||
|
|
||||||
|
var inheritArgsFromEnv inherited
|
||||||
|
|
||||||
|
func (_ *inherited) AsEnv() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArgsFromEnv() CNIArgs {
|
||||||
|
return &inheritArgsFromEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
type Args struct {
|
||||||
|
Command string
|
||||||
|
ContainerID string
|
||||||
|
NetNS string
|
||||||
|
PluginArgs [][2]string
|
||||||
|
PluginArgsStr string
|
||||||
|
IfName string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args implements the CNIArgs interface
|
||||||
|
var _ CNIArgs = &Args{}
|
||||||
|
|
||||||
|
func (args *Args) AsEnv() []string {
|
||||||
|
env := os.Environ()
|
||||||
|
pluginArgsStr := args.PluginArgsStr
|
||||||
|
if pluginArgsStr == "" {
|
||||||
|
pluginArgsStr = stringify(args.PluginArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicated values which come first will be overrided, so we must put the
|
||||||
|
// custom values in the end to avoid being overrided by the process environments.
|
||||||
|
env = append(env,
|
||||||
|
"CNI_COMMAND="+args.Command,
|
||||||
|
"CNI_CONTAINERID="+args.ContainerID,
|
||||||
|
"CNI_NETNS="+args.NetNS,
|
||||||
|
"CNI_ARGS="+pluginArgsStr,
|
||||||
|
"CNI_IFNAME="+args.IfName,
|
||||||
|
"CNI_PATH="+args.Path,
|
||||||
|
)
|
||||||
|
return dedupEnv(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// taken from rkt/networking/net_plugin.go
|
||||||
|
func stringify(pluginArgs [][2]string) string {
|
||||||
|
entries := make([]string, len(pluginArgs))
|
||||||
|
|
||||||
|
for i, kv := range pluginArgs {
|
||||||
|
entries[i] = strings.Join(kv[:], "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(entries, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateArgs implements the CNIArgs interface
|
||||||
|
// used for delegation to inherit from environments
|
||||||
|
// and allow some overrides like CNI_COMMAND
|
||||||
|
var _ CNIArgs = &DelegateArgs{}
|
||||||
|
|
||||||
|
type DelegateArgs struct {
|
||||||
|
Command string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DelegateArgs) AsEnv() []string {
|
||||||
|
env := os.Environ()
|
||||||
|
|
||||||
|
// The custom values should come in the end to override the existing
|
||||||
|
// process environment of the same key.
|
||||||
|
env = append(env,
|
||||||
|
"CNI_COMMAND="+d.Command,
|
||||||
|
)
|
||||||
|
return dedupEnv(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
|
||||||
|
// Items not of the normal environment "key=value" form are preserved unchanged.
|
||||||
|
func dedupEnv(env []string) []string {
|
||||||
|
out := make([]string, 0, len(env))
|
||||||
|
envMap := map[string]string{}
|
||||||
|
|
||||||
|
for _, kv := range env {
|
||||||
|
// find the first "=" in environment, if not, just keep it
|
||||||
|
eq := strings.Index(kv, "=")
|
||||||
|
if eq < 0 {
|
||||||
|
out = append(out, kv)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
envMap[kv[:eq]] = kv[eq+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range envMap {
|
||||||
|
out = append(out, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
80
vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go
generated
vendored
Normal file
80
vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2016 CNI 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
|
||||||
|
if exec == nil {
|
||||||
|
exec = defaultExec
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
|
||||||
|
pluginPath, err := exec.FindInPath(delegatePlugin, paths)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginPath, exec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateAdd calls the given delegate plugin with the CNI ADD action and
|
||||||
|
// JSON configuration
|
||||||
|
func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
|
||||||
|
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
|
||||||
|
return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateCheck calls the given delegate plugin with the CNI CHECK action and
|
||||||
|
// JSON configuration
|
||||||
|
func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
|
||||||
|
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
|
||||||
|
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateDel calls the given delegate plugin with the CNI DEL action and
|
||||||
|
// JSON configuration
|
||||||
|
func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
|
||||||
|
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegateDel will override the original CNI_COMMAND env from process with DEL
|
||||||
|
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return CNIArgs used by delegation
|
||||||
|
func delegateArgs(action string) *DelegateArgs {
|
||||||
|
return &DelegateArgs{
|
||||||
|
Command: action,
|
||||||
|
}
|
||||||
|
}
|
144
vendor/github.com/containernetworking/cni/pkg/invoke/exec.go
generated
vendored
Normal file
144
vendor/github.com/containernetworking/cni/pkg/invoke/exec.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2015 CNI 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exec is an interface encapsulates all operations that deal with finding
|
||||||
|
// and executing a CNI plugin. Tests may provide a fake implementation
|
||||||
|
// to avoid writing fake plugins to temporary directories during the test.
|
||||||
|
type Exec interface {
|
||||||
|
ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error)
|
||||||
|
FindInPath(plugin string, paths []string) (string, error)
|
||||||
|
Decode(jsonBytes []byte) (version.PluginInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For example, a testcase could pass an instance of the following fakeExec
|
||||||
|
// object to ExecPluginWithResult() to verify the incoming stdin and environment
|
||||||
|
// and provide a tailored response:
|
||||||
|
//
|
||||||
|
//import (
|
||||||
|
// "encoding/json"
|
||||||
|
// "path"
|
||||||
|
// "strings"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//type fakeExec struct {
|
||||||
|
// version.PluginDecoder
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||||
|
// net := &types.NetConf{}
|
||||||
|
// err := json.Unmarshal(stdinData, net)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to unmarshal configuration: %v", err)
|
||||||
|
// }
|
||||||
|
// pluginName := path.Base(pluginPath)
|
||||||
|
// if pluginName != net.Type {
|
||||||
|
// return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type)
|
||||||
|
// }
|
||||||
|
// for _, e := range environ {
|
||||||
|
// // Check environment for forced failure request
|
||||||
|
// parts := strings.Split(e, "=")
|
||||||
|
// if len(parts) > 0 && parts[0] == "FAIL" {
|
||||||
|
// return nil, fmt.Errorf("failed to execute plugin %s", pluginName)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
|
||||||
|
// if len(paths) > 0 {
|
||||||
|
// return path.Join(paths[0], plugin), nil
|
||||||
|
// }
|
||||||
|
// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
|
||||||
|
if exec == nil {
|
||||||
|
exec = defaultExec
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin must return result in same version as specified in netconf
|
||||||
|
versionDecoder := &version.ConfigDecoder{}
|
||||||
|
confVersion, err := versionDecoder.Decode(netconf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return version.NewResult(confVersion, stdoutBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {
|
||||||
|
if exec == nil {
|
||||||
|
exec = defaultExec
|
||||||
|
}
|
||||||
|
_, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionInfo returns the version information available about the plugin.
|
||||||
|
// For recent-enough plugins, it uses the information returned by the VERSION
|
||||||
|
// command. For older plugins which do not recognize that command, it reports
|
||||||
|
// version 0.1.0
|
||||||
|
func GetVersionInfo(ctx context.Context, pluginPath string, exec Exec) (version.PluginInfo, error) {
|
||||||
|
if exec == nil {
|
||||||
|
exec = defaultExec
|
||||||
|
}
|
||||||
|
args := &Args{
|
||||||
|
Command: "VERSION",
|
||||||
|
|
||||||
|
// set fake values required by plugins built against an older version of skel
|
||||||
|
NetNS: "dummy",
|
||||||
|
IfName: "dummy",
|
||||||
|
Path: "dummy",
|
||||||
|
}
|
||||||
|
stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
|
||||||
|
stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, stdin, args.AsEnv())
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "unknown CNI_COMMAND: VERSION" {
|
||||||
|
return version.PluginSupports("0.1.0"), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.Decode(stdoutBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultExec is an object that implements the Exec interface which looks
|
||||||
|
// for and executes plugins from disk.
|
||||||
|
type DefaultExec struct {
|
||||||
|
*RawExec
|
||||||
|
version.PluginDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultExec implements the Exec interface
|
||||||
|
var _ Exec = &DefaultExec{}
|
||||||
|
|
||||||
|
var defaultExec = &DefaultExec{
|
||||||
|
RawExec: &RawExec{Stderr: os.Stderr},
|
||||||
|
}
|
43
vendor/github.com/containernetworking/cni/pkg/invoke/find.go
generated
vendored
Normal file
43
vendor/github.com/containernetworking/cni/pkg/invoke/find.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2015 CNI 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindInPath returns the full path of the plugin by searching in the provided path
|
||||||
|
func FindInPath(plugin string, paths []string) (string, error) {
|
||||||
|
if plugin == "" {
|
||||||
|
return "", fmt.Errorf("no plugin name provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return "", fmt.Errorf("no paths provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
for _, fe := range ExecutableFileExtensions {
|
||||||
|
fullpath := filepath.Join(path, plugin) + fe
|
||||||
|
if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() {
|
||||||
|
return fullpath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
|
||||||
|
}
|
20
vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go
generated
vendored
Normal file
20
vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2016 CNI 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.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package invoke
|
||||||
|
|
||||||
|
// Valid file extensions for plugin executables.
|
||||||
|
var ExecutableFileExtensions = []string{""}
|
18
vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go
generated
vendored
Normal file
18
vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2016 CNI 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 invoke
|
||||||
|
|
||||||
|
// Valid file extensions for plugin executables.
|
||||||
|
var ExecutableFileExtensions = []string{".exe", ""}
|
62
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
generated
vendored
Normal file
62
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2016 CNI 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 invoke
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawExec struct {
|
||||||
|
Stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
c := exec.CommandContext(ctx, pluginPath)
|
||||||
|
c.Env = environ
|
||||||
|
c.Stdin = bytes.NewBuffer(stdinData)
|
||||||
|
c.Stdout = stdout
|
||||||
|
c.Stderr = e.Stderr
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return nil, pluginErr(err, stdout.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginErr(err error, output []byte) error {
|
||||||
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
|
emsg := types.Error{}
|
||||||
|
if len(output) == 0 {
|
||||||
|
emsg.Msg = "netplugin failed with no error message"
|
||||||
|
} else if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||||
|
emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||||
|
}
|
||||||
|
return &emsg
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
|
||||||
|
return FindInPath(plugin, paths)
|
||||||
|
}
|
140
vendor/github.com/containernetworking/cni/pkg/types/020/types.go
generated
vendored
Normal file
140
vendor/github.com/containernetworking/cni/pkg/types/020/types.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2016 CNI 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 types020
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ImplementedSpecVersion string = "0.2.0"
|
||||||
|
|
||||||
|
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
|
||||||
|
|
||||||
|
// Compatibility types for CNI version 0.1.0 and 0.2.0
|
||||||
|
|
||||||
|
func NewResult(data []byte) (types.Result, error) {
|
||||||
|
result := &Result{}
|
||||||
|
if err := json.Unmarshal(data, result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetResult(r types.Result) (*Result, error) {
|
||||||
|
// We expect version 0.1.0/0.2.0 results
|
||||||
|
result020, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result, ok := result020.(*Result)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||||
|
type Result struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
|
IP4 *IPConfig `json:"ip4,omitempty"`
|
||||||
|
IP6 *IPConfig `json:"ip6,omitempty"`
|
||||||
|
DNS types.DNS `json:"dns,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) Version() string {
|
||||||
|
return ImplementedSpecVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||||
|
for _, supportedVersion := range SupportedVersions {
|
||||||
|
if version == supportedVersion {
|
||||||
|
r.CNIVersion = version
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) Print() error {
|
||||||
|
return r.PrintTo(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) PrintTo(writer io.Writer) error {
|
||||||
|
data, err := json.MarshalIndent(r, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
|
||||||
|
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
|
||||||
|
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
|
||||||
|
func (r *Result) String() string {
|
||||||
|
var str string
|
||||||
|
if r.IP4 != nil {
|
||||||
|
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
|
||||||
|
}
|
||||||
|
if r.IP6 != nil {
|
||||||
|
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPConfig contains values necessary to configure an interface
|
||||||
|
type IPConfig struct {
|
||||||
|
IP net.IPNet
|
||||||
|
Gateway net.IP
|
||||||
|
Routes []types.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||||
|
// for our custom IPNet type
|
||||||
|
|
||||||
|
// JSON (un)marshallable types
|
||||||
|
type ipConfig struct {
|
||||||
|
IP types.IPNet `json:"ip"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty"`
|
||||||
|
Routes []types.Route `json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
ipc := ipConfig{
|
||||||
|
IP: types.IPNet(c.IP),
|
||||||
|
Gateway: c.Gateway,
|
||||||
|
Routes: c.Routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(ipc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||||
|
ipc := ipConfig{}
|
||||||
|
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.IP = net.IPNet(ipc.IP)
|
||||||
|
c.Gateway = ipc.Gateway
|
||||||
|
c.Routes = ipc.Routes
|
||||||
|
return nil
|
||||||
|
}
|
112
vendor/github.com/containernetworking/cni/pkg/types/args.go
generated
vendored
Normal file
112
vendor/github.com/containernetworking/cni/pkg/types/args.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2015 CNI 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 types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshallableBool typedef for builtin bool
|
||||||
|
// because builtin type's methods can't be declared
|
||||||
|
type UnmarshallableBool bool
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
// Returns boolean true if the string is "1" or "[Tt]rue"
|
||||||
|
// Returns boolean false if the string is "0" or "[Ff]alse"
|
||||||
|
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
|
||||||
|
s := strings.ToLower(string(data))
|
||||||
|
switch s {
|
||||||
|
case "1", "true":
|
||||||
|
*b = true
|
||||||
|
case "0", "false":
|
||||||
|
*b = false
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshallableString typedef for builtin string
|
||||||
|
type UnmarshallableString string
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
// Returns the string
|
||||||
|
func (s *UnmarshallableString) UnmarshalText(data []byte) error {
|
||||||
|
*s = UnmarshallableString(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonArgs contains the IgnoreUnknown argument
|
||||||
|
// and must be embedded by all Arg structs
|
||||||
|
type CommonArgs struct {
|
||||||
|
IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyField is a helper function to receive Values
|
||||||
|
// Values that represent a pointer to a struct
|
||||||
|
func GetKeyField(keyString string, v reflect.Value) reflect.Value {
|
||||||
|
return v.Elem().FieldByName(keyString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalableArgsError is used to indicate error unmarshalling args
|
||||||
|
// from the args-string in the form "K=V;K2=V2;..."
|
||||||
|
type UnmarshalableArgsError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadArgs parses args from a string in the form "K=V;K2=V2;..."
|
||||||
|
func LoadArgs(args string, container interface{}) error {
|
||||||
|
if args == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
containerValue := reflect.ValueOf(container)
|
||||||
|
|
||||||
|
pairs := strings.Split(args, ";")
|
||||||
|
unknownArgs := []string{}
|
||||||
|
for _, pair := range pairs {
|
||||||
|
kv := strings.Split(pair, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return fmt.Errorf("ARGS: invalid pair %q", pair)
|
||||||
|
}
|
||||||
|
keyString := kv[0]
|
||||||
|
valueString := kv[1]
|
||||||
|
keyField := GetKeyField(keyString, containerValue)
|
||||||
|
if !keyField.IsValid() {
|
||||||
|
unknownArgs = append(unknownArgs, pair)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyFieldIface := keyField.Addr().Interface()
|
||||||
|
u, ok := keyFieldIface.(encoding.TextUnmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return UnmarshalableArgsError{fmt.Errorf(
|
||||||
|
"ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler",
|
||||||
|
keyString, reflect.TypeOf(keyFieldIface))}
|
||||||
|
}
|
||||||
|
err := u.UnmarshalText([]byte(valueString))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool()
|
||||||
|
if len(unknownArgs) > 0 && !isIgnoreUnknown {
|
||||||
|
return fmt.Errorf("ARGS: unknown args %q", unknownArgs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
293
vendor/github.com/containernetworking/cni/pkg/types/current/types.go
generated
vendored
Normal file
293
vendor/github.com/containernetworking/cni/pkg/types/current/types.go
generated
vendored
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
// Copyright 2016 CNI 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 current
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ImplementedSpecVersion string = "0.4.0"
|
||||||
|
|
||||||
|
var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
|
||||||
|
|
||||||
|
func NewResult(data []byte) (types.Result, error) {
|
||||||
|
result := &Result{}
|
||||||
|
if err := json.Unmarshal(data, result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetResult(r types.Result) (*Result, error) {
|
||||||
|
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result, ok := resultCurrent.(*Result)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultConverters = []struct {
|
||||||
|
versions []string
|
||||||
|
convert func(types.Result) (*Result, error)
|
||||||
|
}{
|
||||||
|
{types020.SupportedVersions, convertFrom020},
|
||||||
|
{SupportedVersions, convertFrom030},
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFrom020(result types.Result) (*Result, error) {
|
||||||
|
oldResult, err := types020.GetResult(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newResult := &Result{
|
||||||
|
CNIVersion: ImplementedSpecVersion,
|
||||||
|
DNS: oldResult.DNS,
|
||||||
|
Routes: []*types.Route{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldResult.IP4 != nil {
|
||||||
|
newResult.IPs = append(newResult.IPs, &IPConfig{
|
||||||
|
Version: "4",
|
||||||
|
Address: oldResult.IP4.IP,
|
||||||
|
Gateway: oldResult.IP4.Gateway,
|
||||||
|
})
|
||||||
|
for _, route := range oldResult.IP4.Routes {
|
||||||
|
newResult.Routes = append(newResult.Routes, &types.Route{
|
||||||
|
Dst: route.Dst,
|
||||||
|
GW: route.GW,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldResult.IP6 != nil {
|
||||||
|
newResult.IPs = append(newResult.IPs, &IPConfig{
|
||||||
|
Version: "6",
|
||||||
|
Address: oldResult.IP6.IP,
|
||||||
|
Gateway: oldResult.IP6.Gateway,
|
||||||
|
})
|
||||||
|
for _, route := range oldResult.IP6.Routes {
|
||||||
|
newResult.Routes = append(newResult.Routes, &types.Route{
|
||||||
|
Dst: route.Dst,
|
||||||
|
GW: route.GW,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFrom030(result types.Result) (*Result, error) {
|
||||||
|
newResult, ok := result.(*Result)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to convert result")
|
||||||
|
}
|
||||||
|
newResult.CNIVersion = ImplementedSpecVersion
|
||||||
|
return newResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultFromResult(result types.Result) (*Result, error) {
|
||||||
|
version := result.Version()
|
||||||
|
for _, converter := range resultConverters {
|
||||||
|
for _, supportedVersion := range converter.versions {
|
||||||
|
if version == supportedVersion {
|
||||||
|
return converter.convert(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||||
|
type Result struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
|
Interfaces []*Interface `json:"interfaces,omitempty"`
|
||||||
|
IPs []*IPConfig `json:"ips,omitempty"`
|
||||||
|
Routes []*types.Route `json:"routes,omitempty"`
|
||||||
|
DNS types.DNS `json:"dns,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the older 0.2.0 CNI spec Result type
|
||||||
|
func (r *Result) convertTo020() (*types020.Result, error) {
|
||||||
|
oldResult := &types020.Result{
|
||||||
|
CNIVersion: types020.ImplementedSpecVersion,
|
||||||
|
DNS: r.DNS,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range r.IPs {
|
||||||
|
// Only convert the first IP address of each version as 0.2.0
|
||||||
|
// and earlier cannot handle multiple IP addresses
|
||||||
|
if ip.Version == "4" && oldResult.IP4 == nil {
|
||||||
|
oldResult.IP4 = &types020.IPConfig{
|
||||||
|
IP: ip.Address,
|
||||||
|
Gateway: ip.Gateway,
|
||||||
|
}
|
||||||
|
} else if ip.Version == "6" && oldResult.IP6 == nil {
|
||||||
|
oldResult.IP6 = &types020.IPConfig{
|
||||||
|
IP: ip.Address,
|
||||||
|
Gateway: ip.Gateway,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldResult.IP4 != nil && oldResult.IP6 != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range r.Routes {
|
||||||
|
is4 := route.Dst.IP.To4() != nil
|
||||||
|
if is4 && oldResult.IP4 != nil {
|
||||||
|
oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
|
||||||
|
Dst: route.Dst,
|
||||||
|
GW: route.GW,
|
||||||
|
})
|
||||||
|
} else if !is4 && oldResult.IP6 != nil {
|
||||||
|
oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
|
||||||
|
Dst: route.Dst,
|
||||||
|
GW: route.GW,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldResult.IP4 == nil && oldResult.IP6 == nil {
|
||||||
|
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) Version() string {
|
||||||
|
return ImplementedSpecVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||||
|
switch version {
|
||||||
|
case "0.3.0", "0.3.1", ImplementedSpecVersion:
|
||||||
|
r.CNIVersion = version
|
||||||
|
return r, nil
|
||||||
|
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
|
||||||
|
return r.convertTo020()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) Print() error {
|
||||||
|
return r.PrintTo(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) PrintTo(writer io.Writer) error {
|
||||||
|
data, err := json.MarshalIndent(r, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
|
||||||
|
// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
|
||||||
|
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
|
||||||
|
func (r *Result) String() string {
|
||||||
|
var str string
|
||||||
|
if len(r.Interfaces) > 0 {
|
||||||
|
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
|
||||||
|
}
|
||||||
|
if len(r.IPs) > 0 {
|
||||||
|
str += fmt.Sprintf("IP:%+v, ", r.IPs)
|
||||||
|
}
|
||||||
|
if len(r.Routes) > 0 {
|
||||||
|
str += fmt.Sprintf("Routes:%+v, ", r.Routes)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert this old version result to the current CNI version result
|
||||||
|
func (r *Result) Convert() (*Result, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface contains values about the created interfaces
|
||||||
|
type Interface struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Mac string `json:"mac,omitempty"`
|
||||||
|
Sandbox string `json:"sandbox,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Interface) String() string {
|
||||||
|
return fmt.Sprintf("%+v", *i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns a pointer to the int value passed in. Used to
|
||||||
|
// set the IPConfig.Interface field.
|
||||||
|
func Int(v int) *int {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPConfig contains values necessary to configure an IP address on an interface
|
||||||
|
type IPConfig struct {
|
||||||
|
// IP version, either "4" or "6"
|
||||||
|
Version string
|
||||||
|
// Index into Result structs Interfaces list
|
||||||
|
Interface *int
|
||||||
|
Address net.IPNet
|
||||||
|
Gateway net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IPConfig) String() string {
|
||||||
|
return fmt.Sprintf("%+v", *i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON (un)marshallable types
|
||||||
|
type ipConfig struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Interface *int `json:"interface,omitempty"`
|
||||||
|
Address types.IPNet `json:"address"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
ipc := ipConfig{
|
||||||
|
Version: c.Version,
|
||||||
|
Interface: c.Interface,
|
||||||
|
Address: types.IPNet(c.Address),
|
||||||
|
Gateway: c.Gateway,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(ipc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPConfig) UnmarshalJSON(data []byte) error {
|
||||||
|
ipc := ipConfig{}
|
||||||
|
if err := json.Unmarshal(data, &ipc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Version = ipc.Version
|
||||||
|
c.Interface = ipc.Interface
|
||||||
|
c.Address = net.IPNet(ipc.Address)
|
||||||
|
c.Gateway = ipc.Gateway
|
||||||
|
return nil
|
||||||
|
}
|
199
vendor/github.com/containernetworking/cni/pkg/types/types.go
generated
vendored
Normal file
199
vendor/github.com/containernetworking/cni/pkg/types/types.go
generated
vendored
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Copyright 2015 CNI 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 types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// like net.IPNet but adds JSON marshalling and unmarshalling
|
||||||
|
type IPNet net.IPNet
|
||||||
|
|
||||||
|
// ParseCIDR takes a string like "10.2.3.1/24" and
|
||||||
|
// return IPNet with "10.2.3.1" and /24 mask
|
||||||
|
func ParseCIDR(s string) (*net.IPNet, error) {
|
||||||
|
ip, ipn, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipn.IP = ip
|
||||||
|
return ipn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n IPNet) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal((*net.IPNet)(&n).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *IPNet) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, err := ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = IPNet(*tmp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetConf describes a network.
|
||||||
|
type NetConf struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
|
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Capabilities map[string]bool `json:"capabilities,omitempty"`
|
||||||
|
IPAM IPAM `json:"ipam,omitempty"`
|
||||||
|
DNS DNS `json:"dns"`
|
||||||
|
|
||||||
|
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||||
|
PrevResult Result `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPAM struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetConfList describes an ordered list of networks.
|
||||||
|
type NetConfList struct {
|
||||||
|
CNIVersion string `json:"cniVersion,omitempty"`
|
||||||
|
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
DisableCheck bool `json:"disableCheck,omitempty"`
|
||||||
|
Plugins []*NetConf `json:"plugins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultFactoryFunc func([]byte) (Result, error)
|
||||||
|
|
||||||
|
// Result is an interface that provides the result of plugin execution
|
||||||
|
type Result interface {
|
||||||
|
// The highest CNI specification result version the result supports
|
||||||
|
// without having to convert
|
||||||
|
Version() string
|
||||||
|
|
||||||
|
// Returns the result converted into the requested CNI specification
|
||||||
|
// result version, or an error if conversion failed
|
||||||
|
GetAsVersion(version string) (Result, error)
|
||||||
|
|
||||||
|
// Prints the result in JSON format to stdout
|
||||||
|
Print() error
|
||||||
|
|
||||||
|
// Prints the result in JSON format to provided writer
|
||||||
|
PrintTo(writer io.Writer) error
|
||||||
|
|
||||||
|
// Returns a JSON string representation of the result
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintResult(result Result, version string) error {
|
||||||
|
newResult, err := result.GetAsVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return newResult.Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS contains values interesting for DNS resolvers
|
||||||
|
type DNS struct {
|
||||||
|
Nameservers []string `json:"nameservers,omitempty"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
Search []string `json:"search,omitempty"`
|
||||||
|
Options []string `json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Dst net.IPNet
|
||||||
|
GW net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) String() string {
|
||||||
|
return fmt.Sprintf("%+v", *r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Well known error codes
|
||||||
|
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||||
|
const (
|
||||||
|
ErrUnknown uint = iota // 0
|
||||||
|
ErrIncompatibleCNIVersion // 1
|
||||||
|
ErrUnsupportedField // 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Code uint `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
details := ""
|
||||||
|
if e.Details != "" {
|
||||||
|
details = fmt.Sprintf("; %v", e.Details)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v%v", e.Msg, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Print() error {
|
||||||
|
return prettyPrint(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||||
|
// for our custom IPNet type
|
||||||
|
|
||||||
|
// JSON (un)marshallable types
|
||||||
|
type route struct {
|
||||||
|
Dst IPNet `json:"dst"`
|
||||||
|
GW net.IP `json:"gw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) UnmarshalJSON(data []byte) error {
|
||||||
|
rt := route{}
|
||||||
|
if err := json.Unmarshal(data, &rt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Dst = net.IPNet(rt.Dst)
|
||||||
|
r.GW = rt.GW
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Route) MarshalJSON() ([]byte, error) {
|
||||||
|
rt := route{
|
||||||
|
Dst: IPNet(r.Dst),
|
||||||
|
GW: r.GW,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prettyPrint(obj interface{}) error {
|
||||||
|
data, err := json.MarshalIndent(obj, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotImplementedError is used to indicate that a method is not implemented for the given platform
|
||||||
|
var NotImplementedError = errors.New("Not Implemented")
|
37
vendor/github.com/containernetworking/cni/pkg/version/conf.go
generated
vendored
Normal file
37
vendor/github.com/containernetworking/cni/pkg/version/conf.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2016 CNI 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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigDecoder can decode the CNI version available in network config data
|
||||||
|
type ConfigDecoder struct{}
|
||||||
|
|
||||||
|
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
|
||||||
|
var conf struct {
|
||||||
|
CNIVersion string `json:"cniVersion"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(jsonBytes, &conf)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("decoding version from network config: %s", err)
|
||||||
|
}
|
||||||
|
if conf.CNIVersion == "" {
|
||||||
|
return "0.1.0", nil
|
||||||
|
}
|
||||||
|
return conf.CNIVersion, nil
|
||||||
|
}
|
144
vendor/github.com/containernetworking/cni/pkg/version/plugin.go
generated
vendored
Normal file
144
vendor/github.com/containernetworking/cni/pkg/version/plugin.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2016 CNI 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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PluginInfo reports information about CNI versioning
|
||||||
|
type PluginInfo interface {
|
||||||
|
// SupportedVersions returns one or more CNI spec versions that the plugin
|
||||||
|
// supports. If input is provided in one of these versions, then the plugin
|
||||||
|
// promises to use the same CNI version in its response
|
||||||
|
SupportedVersions() []string
|
||||||
|
|
||||||
|
// Encode writes this CNI version information as JSON to the given Writer
|
||||||
|
Encode(io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginInfo struct {
|
||||||
|
CNIVersion_ string `json:"cniVersion"`
|
||||||
|
SupportedVersions_ []string `json:"supportedVersions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// pluginInfo implements the PluginInfo interface
|
||||||
|
var _ PluginInfo = &pluginInfo{}
|
||||||
|
|
||||||
|
func (p *pluginInfo) Encode(w io.Writer) error {
|
||||||
|
return json.NewEncoder(w).Encode(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pluginInfo) SupportedVersions() []string {
|
||||||
|
return p.SupportedVersions_
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginSupports returns a new PluginInfo that will report the given versions
|
||||||
|
// as supported
|
||||||
|
func PluginSupports(supportedVersions ...string) PluginInfo {
|
||||||
|
if len(supportedVersions) < 1 {
|
||||||
|
panic("programmer error: you must support at least one version")
|
||||||
|
}
|
||||||
|
return &pluginInfo{
|
||||||
|
CNIVersion_: Current(),
|
||||||
|
SupportedVersions_: supportedVersions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginDecoder can decode the response returned by a plugin's VERSION command
|
||||||
|
type PluginDecoder struct{}
|
||||||
|
|
||||||
|
func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
|
||||||
|
var info pluginInfo
|
||||||
|
err := json.Unmarshal(jsonBytes, &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding version info: %s", err)
|
||||||
|
}
|
||||||
|
if info.CNIVersion_ == "" {
|
||||||
|
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
|
||||||
|
}
|
||||||
|
if len(info.SupportedVersions_) == 0 {
|
||||||
|
if info.CNIVersion_ == "0.2.0" {
|
||||||
|
return PluginSupports("0.1.0", "0.2.0"), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("decoding version info: missing field supportedVersions")
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major,
|
||||||
|
// minor, and micro numbers or returns an error
|
||||||
|
func ParseVersion(version string) (int, int, int, error) {
|
||||||
|
var major, minor, micro int
|
||||||
|
if version == "" {
|
||||||
|
return -1, -1, -1, fmt.Errorf("invalid version %q: the version is empty", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) >= 4 {
|
||||||
|
return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
major, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %v", parts[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
minor, err = strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %v", parts[1], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
micro, err = strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %v", parts[2], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return major, minor, micro, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro
|
||||||
|
// numbers, and compares them to determine whether the first version is greater
|
||||||
|
// than or equal to the second
|
||||||
|
func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
|
||||||
|
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstMajor > secondMajor {
|
||||||
|
return true, nil
|
||||||
|
} else if firstMajor == secondMajor {
|
||||||
|
if firstMinor > secondMinor {
|
||||||
|
return true, nil
|
||||||
|
} else if firstMinor == secondMinor && firstMicro >= secondMicro {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
49
vendor/github.com/containernetworking/cni/pkg/version/reconcile.go
generated
vendored
Normal file
49
vendor/github.com/containernetworking/cni/pkg/version/reconcile.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2016 CNI 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 version
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ErrorIncompatible struct {
|
||||||
|
Config string
|
||||||
|
Supported []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorIncompatible) Details() string {
|
||||||
|
return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorIncompatible) Error() string {
|
||||||
|
return fmt.Sprintf("incompatible CNI versions: %s", e.Details())
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reconciler struct{}
|
||||||
|
|
||||||
|
func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible {
|
||||||
|
return r.CheckRaw(configVersion, pluginInfo.SupportedVersions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible {
|
||||||
|
for _, supportedVersion := range supportedVersions {
|
||||||
|
if configVersion == supportedVersion {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ErrorIncompatible{
|
||||||
|
Config: configVersion,
|
||||||
|
Supported: supportedVersions,
|
||||||
|
}
|
||||||
|
}
|
83
vendor/github.com/containernetworking/cni/pkg/version/version.go
generated
vendored
Normal file
83
vendor/github.com/containernetworking/cni/pkg/version/version.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2016 CNI 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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/020"
|
||||||
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current reports the version of the CNI spec implemented by this library
|
||||||
|
func Current() string {
|
||||||
|
return "0.4.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||||
|
// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0
|
||||||
|
// library ought to work correctly with a plugin that reports support for
|
||||||
|
// Legacy versions.
|
||||||
|
//
|
||||||
|
// Any future CNI spec versions which meet this definition should be added to
|
||||||
|
// this list.
|
||||||
|
var Legacy = PluginSupports("0.1.0", "0.2.0")
|
||||||
|
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0")
|
||||||
|
|
||||||
|
var resultFactories = []struct {
|
||||||
|
supportedVersions []string
|
||||||
|
newResult types.ResultFactoryFunc
|
||||||
|
}{
|
||||||
|
{current.SupportedVersions, current.NewResult},
|
||||||
|
{types020.SupportedVersions, types020.NewResult},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds a Result object matching the requested version (if any) and asks
|
||||||
|
// that object to parse the plugin result, returning an error if parsing failed.
|
||||||
|
func NewResult(version string, resultBytes []byte) (types.Result, error) {
|
||||||
|
reconciler := &Reconciler{}
|
||||||
|
for _, resultFactory := range resultFactories {
|
||||||
|
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
|
||||||
|
if err == nil {
|
||||||
|
// Result supports this version
|
||||||
|
return resultFactory.newResult(resultBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unsupported CNI result version %q", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePrevResult parses a prevResult in a NetConf structure and sets
|
||||||
|
// the NetConf's PrevResult member to the parsed Result object.
|
||||||
|
func ParsePrevResult(conf *types.NetConf) error {
|
||||||
|
if conf.RawPrevResult == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not serialize prevResult: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.RawPrevResult = nil
|
||||||
|
conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse prevResult: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
191
vendor/github.com/coreos/go-systemd/LICENSE
generated
vendored
Normal file
191
vendor/github.com/coreos/go-systemd/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and
|
||||||
|
distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||||
|
owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
For the purposes of this definition, "control" means (i) the power, direct or
|
||||||
|
indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||||
|
permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including
|
||||||
|
but not limited to software source code, documentation source, and configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or
|
||||||
|
translation of a Source form, including but not limited to compiled object code,
|
||||||
|
generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||||
|
available under the License, as indicated by a copyright notice that is included
|
||||||
|
in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||||
|
is based on (or derived from) the Work and for which the editorial revisions,
|
||||||
|
annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works
|
||||||
|
shall not include works that remain separable from, or merely link (or bind by
|
||||||
|
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version
|
||||||
|
of the Work and any modifications or additions to that Work or Derivative Works
|
||||||
|
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||||
|
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||||
|
on behalf of the copyright owner. For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems, and
|
||||||
|
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||||
|
the purpose of discussing and improving the Work, but excluding communication
|
||||||
|
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||||
|
owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||||
|
of whom a Contribution has been received by Licensor and subsequently
|
||||||
|
incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable (except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||||
|
such license applies only to those patent claims licensable by such Contributor
|
||||||
|
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||||
|
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||||
|
Contribution incorporated within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under this License
|
||||||
|
for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||||
|
in any medium, with or without modifications, and in Source or Object form,
|
||||||
|
provided that You meet the following conditions:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||||
|
all copyright, patent, trademark, and attribution notices from the Source form
|
||||||
|
of the Work, excluding those notices that do not pertain to any part of the
|
||||||
|
Derivative Works; and
|
||||||
|
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||||
|
Derivative Works that You distribute must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||||
|
following places: within a NOTICE text file distributed as part of the
|
||||||
|
Derivative Works; within the Source form or documentation, if provided along
|
||||||
|
with the Derivative Works; or, within a display generated by the Derivative
|
||||||
|
Works, if and wherever such third-party notices normally appear. The contents of
|
||||||
|
the NOTICE file are for informational purposes only and do not modify the
|
||||||
|
License. You may add Your own attribution notices within Derivative Works that
|
||||||
|
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||||
|
provided that such additional attribution notices cannot be construed as
|
||||||
|
modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide
|
||||||
|
additional or different license terms and conditions for use, reproduction, or
|
||||||
|
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||||
|
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||||
|
with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||||
|
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||||
|
conditions of this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||||
|
any separate license agreement you may have executed with Licensor regarding
|
||||||
|
such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names, trademarks,
|
||||||
|
service marks, or product names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||||
|
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||||
|
including, without limitation, any warranties or conditions of TITLE,
|
||||||
|
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||||
|
solely responsible for determining the appropriateness of using or
|
||||||
|
redistributing the Work and assume any risks associated with Your exercise of
|
||||||
|
permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including negligence),
|
||||||
|
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||||
|
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special, incidental,
|
||||||
|
or consequential damages of any character arising as a result of this License or
|
||||||
|
out of the use or inability to use the Work (including but not limited to
|
||||||
|
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has
|
||||||
|
been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||||
|
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||||
|
other liability obligations and/or rights consistent with this License. However,
|
||||||
|
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||||
|
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||||
|
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason of your
|
||||||
|
accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate
|
||||||
|
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||||
|
identifying information. (Don't include the brackets!) The text should be
|
||||||
|
enclosed in the appropriate comment syntax for the file format. We also
|
||||||
|
recommend that a file or class name and description of purpose be included on
|
||||||
|
the same "printed page" as the copyright notice for easier identification within
|
||||||
|
third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
5
vendor/github.com/coreos/go-systemd/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/NOTICE
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CoreOS Project
|
||||||
|
Copyright 2018 CoreOS, Inc
|
||||||
|
|
||||||
|
This product includes software developed at CoreOS, Inc.
|
||||||
|
(http://www.coreos.com/).
|
225
vendor/github.com/coreos/go-systemd/journal/journal.go
generated
vendored
Normal file
225
vendor/github.com/coreos/go-systemd/journal/journal.go
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
// Copyright 2015 CoreOS, Inc.
|
||||||
|
//
|
||||||
|
// 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 journal provides write bindings to the local systemd journal.
|
||||||
|
// It is implemented in pure Go and connects to the journal directly over its
|
||||||
|
// unix socket.
|
||||||
|
//
|
||||||
|
// To read from the journal, see the "sdjournal" package, which wraps the
|
||||||
|
// sd-journal a C API.
|
||||||
|
//
|
||||||
|
// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
|
||||||
|
package journal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Priority of a journal message
|
||||||
|
type Priority int
|
||||||
|
|
||||||
|
const (
|
||||||
|
PriEmerg Priority = iota
|
||||||
|
PriAlert
|
||||||
|
PriCrit
|
||||||
|
PriErr
|
||||||
|
PriWarning
|
||||||
|
PriNotice
|
||||||
|
PriInfo
|
||||||
|
PriDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// This can be overridden at build-time:
|
||||||
|
// https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable
|
||||||
|
journalSocket = "/run/systemd/journal/socket"
|
||||||
|
|
||||||
|
// unixConnPtr atomically holds the local unconnected Unix-domain socket.
|
||||||
|
// Concrete safe pointer type: *net.UnixConn
|
||||||
|
unixConnPtr unsafe.Pointer
|
||||||
|
// onceConn ensures that unixConnPtr is initialized exactly once.
|
||||||
|
onceConn sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
onceConn.Do(initConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled checks whether the local systemd journal is available for logging.
|
||||||
|
func Enabled() bool {
|
||||||
|
onceConn.Do(initConn)
|
||||||
|
|
||||||
|
if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.Dial("unixgram", journalSocket); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to the local systemd journal. vars is a map of journald
|
||||||
|
// fields to values. Fields must be composed of uppercase letters, numbers,
|
||||||
|
// and underscores, but must not start with an underscore. Within these
|
||||||
|
// restrictions, any arbitrary field name may be used. Some names have special
|
||||||
|
// significance: see the journalctl documentation
|
||||||
|
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
|
||||||
|
// for more details. vars may be nil.
|
||||||
|
func Send(message string, priority Priority, vars map[string]string) error {
|
||||||
|
conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
|
||||||
|
if conn == nil {
|
||||||
|
return errors.New("could not initialize socket to journald")
|
||||||
|
}
|
||||||
|
|
||||||
|
socketAddr := &net.UnixAddr{
|
||||||
|
Name: journalSocket,
|
||||||
|
Net: "unixgram",
|
||||||
|
}
|
||||||
|
|
||||||
|
data := new(bytes.Buffer)
|
||||||
|
appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
|
||||||
|
appendVariable(data, "MESSAGE", message)
|
||||||
|
for k, v := range vars {
|
||||||
|
appendVariable(data, k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !isSocketSpaceError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large log entry, send it via tempfile and ancillary-fd.
|
||||||
|
file, err := tempFd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = io.Copy(file, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rights := syscall.UnixRights(int(file.Fd()))
|
||||||
|
_, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prints a message to the local systemd journal using Send().
|
||||||
|
func Print(priority Priority, format string, a ...interface{}) error {
|
||||||
|
return Send(fmt.Sprintf(format, a...), priority, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendVariable(w io.Writer, name, value string) {
|
||||||
|
if err := validVarName(name); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(value, '\n') {
|
||||||
|
/* When the value contains a newline, we write:
|
||||||
|
* - the variable name, followed by a newline
|
||||||
|
* - the size (in 64bit little endian format)
|
||||||
|
* - the data, followed by a newline
|
||||||
|
*/
|
||||||
|
fmt.Fprintln(w, name)
|
||||||
|
binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||||
|
fmt.Fprintln(w, value)
|
||||||
|
} else {
|
||||||
|
/* just write the variable and value all on one line */
|
||||||
|
fmt.Fprintf(w, "%s=%s\n", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validVarName validates a variable name to make sure journald will accept it.
|
||||||
|
// The variable name must be in uppercase and consist only of characters,
|
||||||
|
// numbers and underscores, and may not begin with an underscore:
|
||||||
|
// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
|
||||||
|
func validVarName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("Empty variable name")
|
||||||
|
} else if name[0] == '_' {
|
||||||
|
return errors.New("Variable name begins with an underscore")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range name {
|
||||||
|
if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
|
||||||
|
return errors.New("Variable name contains invalid characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSocketSpaceError checks whether the error is signaling
|
||||||
|
// an "overlarge message" condition.
|
||||||
|
func isSocketSpaceError(err error) bool {
|
||||||
|
opErr, ok := err.(*net.OpError)
|
||||||
|
if !ok || opErr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sysErr, ok := opErr.Err.(*os.SyscallError)
|
||||||
|
if !ok || sysErr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS
|
||||||
|
}
|
||||||
|
|
||||||
|
// tempFd creates a temporary, unlinked file under `/dev/shm`.
|
||||||
|
func tempFd() (*os.File, error) {
|
||||||
|
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = syscall.Unlink(file.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initConn initializes the global `unixConnPtr` socket.
|
||||||
|
// It is meant to be called exactly once, at program startup.
|
||||||
|
func initConn() {
|
||||||
|
autobind, err := net.ResolveUnixAddr("unixgram", "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, err := net.ListenUnixgram("unixgram", autobind)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer(&unixConnPtr, unsafe.Pointer(sock))
|
||||||
|
}
|
716
vendor/github.com/docker/cli/AUTHORS
generated
vendored
Normal file
716
vendor/github.com/docker/cli/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,716 @@
|
|||||||
|
# This file lists all individuals having contributed content to the repository.
|
||||||
|
# For how it is generated, see `scripts/docs/generate-authors.sh`.
|
||||||
|
|
||||||
|
Aanand Prasad <aanand.prasad@gmail.com>
|
||||||
|
Aaron L. Xu <liker.xu@foxmail.com>
|
||||||
|
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||||
|
Aaron.L.Xu <likexu@harmonycloud.cn>
|
||||||
|
Abdur Rehman <abdur_rehman@mentor.com>
|
||||||
|
Abhinandan Prativadi <abhi@docker.com>
|
||||||
|
Abin Shahab <ashahab@altiscale.com>
|
||||||
|
Ace Tang <aceapril@126.com>
|
||||||
|
Addam Hardy <addam.hardy@gmail.com>
|
||||||
|
Adolfo Ochagavía <aochagavia92@gmail.com>
|
||||||
|
Adrien Duermael <adrien@duermael.com>
|
||||||
|
Adrien Folie <folie.adrien@gmail.com>
|
||||||
|
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||||
|
Aidan Feldman <aidan.feldman@gmail.com>
|
||||||
|
Aidan Hobson Sayers <aidanhs@cantab.net>
|
||||||
|
AJ Bowen <aj@gandi.net>
|
||||||
|
Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
|
||||||
|
Akim Demaille <akim.demaille@docker.com>
|
||||||
|
Alan Thompson <cloojure@gmail.com>
|
||||||
|
Albert Callarisa <shark234@gmail.com>
|
||||||
|
Aleksa Sarai <asarai@suse.de>
|
||||||
|
Alessandro Boch <aboch@tetrationanalytics.com>
|
||||||
|
Alex Mavrogiannis <alex.mavrogiannis@docker.com>
|
||||||
|
Alex Mayer <amayer5125@gmail.com>
|
||||||
|
Alexander Boyd <alex@opengroove.org>
|
||||||
|
Alexander Larsson <alexl@redhat.com>
|
||||||
|
Alexander Morozov <lk4d4@docker.com>
|
||||||
|
Alexander Ryabov <i@sepa.spb.ru>
|
||||||
|
Alexandre González <agonzalezro@gmail.com>
|
||||||
|
Alfred Landrum <alfred.landrum@docker.com>
|
||||||
|
Alicia Lauerman <alicia@eta.im>
|
||||||
|
Allen Sun <allensun.shl@alibaba-inc.com>
|
||||||
|
Alvin Deng <alvin.q.deng@utexas.edu>
|
||||||
|
Amen Belayneh <amenbelayneh@gmail.com>
|
||||||
|
Amir Goldstein <amir73il@aquasec.com>
|
||||||
|
Amit Krishnan <amit.krishnan@oracle.com>
|
||||||
|
Amit Shukla <amit.shukla@docker.com>
|
||||||
|
Amy Lindburg <amy.lindburg@docker.com>
|
||||||
|
Anda Xu <anda.xu@docker.com>
|
||||||
|
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||||
|
Andreas Köhler <andi5.py@gmx.net>
|
||||||
|
Andrew France <andrew@avito.co.uk>
|
||||||
|
Andrew Hsu <andrewhsu@docker.com>
|
||||||
|
Andrew Macpherson <hopscotch23@gmail.com>
|
||||||
|
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
||||||
|
Andrew Po <absourd.noise@gmail.com>
|
||||||
|
Andrey Petrov <andrey.petrov@shazow.net>
|
||||||
|
André Martins <aanm90@gmail.com>
|
||||||
|
Andy Goldstein <agoldste@redhat.com>
|
||||||
|
Andy Rothfusz <github@developersupport.net>
|
||||||
|
Anil Madhavapeddy <anil@recoil.org>
|
||||||
|
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||||
|
Anne Henmi <anne.henmi@docker.com>
|
||||||
|
Anton Polonskiy <anton.polonskiy@gmail.com>
|
||||||
|
Antonio Murdaca <antonio.murdaca@gmail.com>
|
||||||
|
Antonis Kalipetis <akalipetis@gmail.com>
|
||||||
|
Anusha Ragunathan <anusha.ragunathan@docker.com>
|
||||||
|
Ao Li <la9249@163.com>
|
||||||
|
Arash Deshmeh <adeshmeh@ca.ibm.com>
|
||||||
|
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||||
|
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
||||||
|
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||||
|
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||||
|
Barnaby Gray <barnaby@pickle.me.uk>
|
||||||
|
Bastiaan Bakker <bbakker@xebia.com>
|
||||||
|
BastianHofmann <bastianhofmann@me.com>
|
||||||
|
Ben Bonnefoy <frenchben@docker.com>
|
||||||
|
Ben Creasy <ben@bencreasy.com>
|
||||||
|
Ben Firshman <ben@firshman.co.uk>
|
||||||
|
Benjamin Boudreau <boudreau.benjamin@gmail.com>
|
||||||
|
Benoit Sigoure <tsunanet@gmail.com>
|
||||||
|
Bhumika Bayani <bhumikabayani@gmail.com>
|
||||||
|
Bill Wang <ozbillwang@gmail.com>
|
||||||
|
Bin Liu <liubin0329@gmail.com>
|
||||||
|
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||||
|
Boaz Shuster <ripcurld.github@gmail.com>
|
||||||
|
Bogdan Anton <contact@bogdananton.ro>
|
||||||
|
Boris Pruessmann <boris@pruessmann.org>
|
||||||
|
Bradley Cicenas <bradley.cicenas@gmail.com>
|
||||||
|
Brandon Mitchell <git@bmitch.net>
|
||||||
|
Brandon Philips <brandon.philips@coreos.com>
|
||||||
|
Brent Salisbury <brent.salisbury@docker.com>
|
||||||
|
Bret Fisher <bret@bretfisher.com>
|
||||||
|
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||||
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
|
Bryan Bess <squarejaw@bsbess.com>
|
||||||
|
Bryan Boreham <bjboreham@gmail.com>
|
||||||
|
Bryan Murphy <bmurphy1976@gmail.com>
|
||||||
|
bryfry <bryon.fryer@gmail.com>
|
||||||
|
Cameron Spear <cameronspear@gmail.com>
|
||||||
|
Cao Weiwei <cao.weiwei30@zte.com.cn>
|
||||||
|
Carlo Mion <mion00@gmail.com>
|
||||||
|
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||||
|
Ce Gao <ce.gao@outlook.com>
|
||||||
|
Cedric Davies <cedricda@microsoft.com>
|
||||||
|
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||||
|
Chad Faragher <wyckster@hotmail.com>
|
||||||
|
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
||||||
|
Charles Chan <charleswhchan@users.noreply.github.com>
|
||||||
|
Charles Law <claw@conduce.com>
|
||||||
|
Charles Smith <charles.smith@docker.com>
|
||||||
|
Charlie Drage <charlie@charliedrage.com>
|
||||||
|
ChaYoung You <yousbe@gmail.com>
|
||||||
|
Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||||
|
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||||
|
Chen Mingjie <chenmingjie0828@163.com>
|
||||||
|
Chen Qiu <cheney-90@hotmail.com>
|
||||||
|
Chris Gavin <chris@chrisgavin.me>
|
||||||
|
Chris Gibson <chris@chrisg.io>
|
||||||
|
Chris McKinnel <chrismckinnel@gmail.com>
|
||||||
|
Chris Snow <chsnow123@gmail.com>
|
||||||
|
Chris Weyl <cweyl@alumni.drew.edu>
|
||||||
|
Christian Persson <saser@live.se>
|
||||||
|
Christian Stefanescu <st.chris@gmail.com>
|
||||||
|
Christophe Robin <crobin@nekoo.com>
|
||||||
|
Christophe Vidal <kriss@krizalys.com>
|
||||||
|
Christopher Biscardi <biscarch@sketcht.com>
|
||||||
|
Christopher Crone <christopher.crone@docker.com>
|
||||||
|
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||||
|
Christy Norman <christy@linux.vnet.ibm.com>
|
||||||
|
Chun Chen <ramichen@tencent.com>
|
||||||
|
Clinton Kitson <clintonskitson@gmail.com>
|
||||||
|
Coenraad Loubser <coenraad@wish.org.za>
|
||||||
|
Colin Hebert <hebert.colin@gmail.com>
|
||||||
|
Collin Guarino <collin.guarino@gmail.com>
|
||||||
|
Colm Hally <colmhally@gmail.com>
|
||||||
|
Corey Farrell <git@cfware.com>
|
||||||
|
Corey Quon <corey.quon@docker.com>
|
||||||
|
Craig Wilhite <crwilhit@microsoft.com>
|
||||||
|
Cristian Staretu <cristian.staretu@gmail.com>
|
||||||
|
Daehyeok Mun <daehyeok@gmail.com>
|
||||||
|
Dafydd Crosby <dtcrsby@gmail.com>
|
||||||
|
dalanlan <dalanlan925@gmail.com>
|
||||||
|
Damien Nadé <github@livna.org>
|
||||||
|
Dan Cotora <dan@bluevision.ro>
|
||||||
|
Daniel Dao <dqminh@cloudflare.com>
|
||||||
|
Daniel Farrell <dfarrell@redhat.com>
|
||||||
|
Daniel Gasienica <daniel@gasienica.ch>
|
||||||
|
Daniel Goosen <daniel.goosen@surveysampling.com>
|
||||||
|
Daniel Hiltgen <daniel.hiltgen@docker.com>
|
||||||
|
Daniel J Walsh <dwalsh@redhat.com>
|
||||||
|
Daniel Nephin <dnephin@docker.com>
|
||||||
|
Daniel Norberg <dano@spotify.com>
|
||||||
|
Daniel Watkins <daniel@daniel-watkins.co.uk>
|
||||||
|
Daniel Zhang <jmzwcn@gmail.com>
|
||||||
|
Danny Berger <dpb587@gmail.com>
|
||||||
|
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||||
|
Darren Stahl <darst@microsoft.com>
|
||||||
|
Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
|
||||||
|
Dave Goodchild <buddhamagnet@gmail.com>
|
||||||
|
Dave Henderson <dhenderson@gmail.com>
|
||||||
|
Dave Tucker <dt@docker.com>
|
||||||
|
David Beitey <david@davidjb.com>
|
||||||
|
David Calavera <david.calavera@gmail.com>
|
||||||
|
David Cramer <davcrame@cisco.com>
|
||||||
|
David Dooling <dooling@gmail.com>
|
||||||
|
David Gageot <david@gageot.net>
|
||||||
|
David Lechner <david@lechnology.com>
|
||||||
|
David Scott <dave@recoil.org>
|
||||||
|
David Sheets <dsheets@docker.com>
|
||||||
|
David Williamson <david.williamson@docker.com>
|
||||||
|
David Xia <dxia@spotify.com>
|
||||||
|
David Young <yangboh@cn.ibm.com>
|
||||||
|
Deng Guangxing <dengguangxing@huawei.com>
|
||||||
|
Denis Defreyne <denis@soundcloud.com>
|
||||||
|
Denis Gladkikh <denis@gladkikh.email>
|
||||||
|
Denis Ollier <larchunix@users.noreply.github.com>
|
||||||
|
Dennis Docter <dennis@d23.nl>
|
||||||
|
Derek McGowan <derek@mcgstyle.net>
|
||||||
|
Deshi Xiao <dxiao@redhat.com>
|
||||||
|
Dharmit Shah <shahdharmit@gmail.com>
|
||||||
|
Dhawal Yogesh Bhanushali <dbhanushali@vmware.com>
|
||||||
|
Dieter Reuter <dieter.reuter@me.com>
|
||||||
|
Dima Stopel <dima@twistlock.com>
|
||||||
|
Dimitry Andric <d.andric@activevideo.com>
|
||||||
|
Ding Fei <dingfei@stars.org.cn>
|
||||||
|
Diogo Monica <diogo@docker.com>
|
||||||
|
Dmitry Gusev <dmitry.gusev@gmail.com>
|
||||||
|
Dmitry Smirnov <onlyjob@member.fsf.org>
|
||||||
|
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
||||||
|
Don Kjer <don.kjer@gmail.com>
|
||||||
|
Dong Chen <dongluo.chen@docker.com>
|
||||||
|
Doug Davis <dug@us.ibm.com>
|
||||||
|
Drew Erny <drew.erny@docker.com>
|
||||||
|
Ed Costello <epc@epcostello.com>
|
||||||
|
Elango Sivanandam <elango.siva@docker.com>
|
||||||
|
Eli Uriegas <eli.uriegas@docker.com>
|
||||||
|
Eli Uriegas <seemethere101@gmail.com>
|
||||||
|
Elias Faxö <elias.faxo@tre.se>
|
||||||
|
Elliot Luo <956941328@qq.com>
|
||||||
|
Eric Curtin <ericcurtin17@gmail.com>
|
||||||
|
Eric G. Noriega <enoriega@vizuri.com>
|
||||||
|
Eric Rosenberg <ehaydenr@gmail.com>
|
||||||
|
Eric Sage <eric.david.sage@gmail.com>
|
||||||
|
Eric-Olivier Lamey <eo@lamey.me>
|
||||||
|
Erica Windisch <erica@windisch.us>
|
||||||
|
Erik Hollensbe <github@hollensbe.org>
|
||||||
|
Erik St. Martin <alakriti@gmail.com>
|
||||||
|
Essam A. Hassan <es.hassan187@gmail.com>
|
||||||
|
Ethan Haynes <ethanhaynes@alumni.harvard.edu>
|
||||||
|
Euan Kemp <euank@euank.com>
|
||||||
|
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||||
|
Evan Allrich <evan@unguku.com>
|
||||||
|
Evan Hazlett <ejhazlett@gmail.com>
|
||||||
|
Evan Krall <krall@yelp.com>
|
||||||
|
Evelyn Xu <evelynhsu21@gmail.com>
|
||||||
|
Everett Toews <everett.toews@rackspace.com>
|
||||||
|
Fabio Falci <fabiofalci@gmail.com>
|
||||||
|
Fabrizio Soppelsa <fsoppelsa@mirantis.com>
|
||||||
|
Felix Hupfeld <felix@quobyte.com>
|
||||||
|
Felix Rabe <felix@rabe.io>
|
||||||
|
Filip Jareš <filipjares@gmail.com>
|
||||||
|
Flavio Crisciani <flavio.crisciani@docker.com>
|
||||||
|
Florian Klein <florian.klein@free.fr>
|
||||||
|
Foysal Iqbal <foysal.iqbal.fb@gmail.com>
|
||||||
|
François Scala <francois.scala@swiss-as.com>
|
||||||
|
Fred Lifton <fred.lifton@docker.com>
|
||||||
|
Frederic Hemberger <mail@frederic-hemberger.de>
|
||||||
|
Frederick F. Kautz IV <fkautz@redhat.com>
|
||||||
|
Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
|
||||||
|
Frieder Bluemle <frieder.bluemle@gmail.com>
|
||||||
|
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||||
|
Gaetan de Villele <gdevillele@gmail.com>
|
||||||
|
Gang Qiao <qiaohai8866@gmail.com>
|
||||||
|
Gary Schaetz <gary@schaetzkc.com>
|
||||||
|
Genki Takiuchi <genki@s21g.com>
|
||||||
|
George MacRorie <gmacr31@gmail.com>
|
||||||
|
George Xie <georgexsh@gmail.com>
|
||||||
|
Gianluca Borello <g.borello@gmail.com>
|
||||||
|
Gildas Cuisinier <gildas.cuisinier@gcuisinier.net>
|
||||||
|
Gou Rao <gou@portworx.com>
|
||||||
|
Grant Reaber <grant.reaber@gmail.com>
|
||||||
|
Greg Pflaum <gpflaum@users.noreply.github.com>
|
||||||
|
Guilhem Lettron <guilhem+github@lettron.fr>
|
||||||
|
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||||
|
Guillaume Le Floch <glfloch@gmail.com>
|
||||||
|
gwx296173 <gaojing3@huawei.com>
|
||||||
|
Günther Jungbluth <gunther@gameslabs.net>
|
||||||
|
Hakan Özler <hakan.ozler@kodcu.com>
|
||||||
|
Hao Zhang <21521210@zju.edu.cn>
|
||||||
|
Harald Albers <github@albersweb.de>
|
||||||
|
Harold Cooper <hrldcpr@gmail.com>
|
||||||
|
Harry Zhang <harryz@hyper.sh>
|
||||||
|
He Simei <hesimei@zju.edu.cn>
|
||||||
|
Helen Xie <chenjg@harmonycloud.cn>
|
||||||
|
Henning Sprang <henning.sprang@gmail.com>
|
||||||
|
Henry N <henrynmail-github@yahoo.de>
|
||||||
|
Hernan Garcia <hernandanielg@gmail.com>
|
||||||
|
Hongbin Lu <hongbin034@gmail.com>
|
||||||
|
Hu Keping <hukeping@huawei.com>
|
||||||
|
Huayi Zhang <irachex@gmail.com>
|
||||||
|
huqun <huqun@zju.edu.cn>
|
||||||
|
Huu Nguyen <huu@prismskylabs.com>
|
||||||
|
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||||
|
Ian Campbell <ian.campbell@docker.com>
|
||||||
|
Ian Philpot <ian.philpot@microsoft.com>
|
||||||
|
Ignacio Capurro <icapurrofagian@gmail.com>
|
||||||
|
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||||
|
Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||||
|
Ilya Sotkov <ilya@sotkov.com>
|
||||||
|
Ioan Eugen Stan <eu@ieugen.ro>
|
||||||
|
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||||
|
Ivan Grcic <igrcic@gmail.com>
|
||||||
|
Ivan Markin <sw@nogoegst.net>
|
||||||
|
Jacob Atzen <jacob@jacobatzen.dk>
|
||||||
|
Jacob Tomlinson <jacob@tom.linson.uk>
|
||||||
|
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||||
|
Jake Lambert <jake.lambert@volusion.com>
|
||||||
|
Jake Sanders <jsand@google.com>
|
||||||
|
James Nesbitt <james.nesbitt@wunderkraut.com>
|
||||||
|
James Turnbull <james@lovedthanlost.net>
|
||||||
|
Jamie Hannaford <jamie@limetree.org>
|
||||||
|
Jan Koprowski <jan.koprowski@gmail.com>
|
||||||
|
Jan Pazdziora <jpazdziora@redhat.com>
|
||||||
|
Jan-Jaap Driessen <janjaapdriessen@gmail.com>
|
||||||
|
Jana Radhakrishnan <mrjana@docker.com>
|
||||||
|
Jared Hocutt <jaredh@netapp.com>
|
||||||
|
Jasmine Hegman <jasmine@jhegman.com>
|
||||||
|
Jason Heiss <jheiss@aput.net>
|
||||||
|
Jason Plum <jplum@devonit.com>
|
||||||
|
Jay Kamat <github@jgkamat.33mail.com>
|
||||||
|
Jean Rouge <rougej+github@gmail.com>
|
||||||
|
Jean-Christophe Sirot <jean-christophe.sirot@docker.com>
|
||||||
|
Jean-Pierre Huynh <jean-pierre.huynh@ounet.fr>
|
||||||
|
Jeff Lindsay <progrium@gmail.com>
|
||||||
|
Jeff Nickoloff <jeff.nickoloff@gmail.com>
|
||||||
|
Jeff Silberman <jsilberm@gmail.com>
|
||||||
|
Jeremy Chambers <jeremy@thehipbot.com>
|
||||||
|
Jeremy Unruh <jeremybunruh@gmail.com>
|
||||||
|
Jeremy Yallop <yallop@docker.com>
|
||||||
|
Jeroen Franse <jeroenfranse@gmail.com>
|
||||||
|
Jesse Adametz <jesseadametz@gmail.com>
|
||||||
|
Jessica Frazelle <jessfraz@google.com>
|
||||||
|
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||||
|
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
|
||||||
|
Jie Luo <luo612@zju.edu.cn>
|
||||||
|
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||||
|
Jim Galasyn <jim.galasyn@docker.com>
|
||||||
|
Jimmy Leger <jimmy.leger@gmail.com>
|
||||||
|
Jimmy Song <rootsongjc@gmail.com>
|
||||||
|
jimmyxian <jimmyxian2004@yahoo.com.cn>
|
||||||
|
Jintao Zhang <zhangjintao9020@gmail.com>
|
||||||
|
Joao Fernandes <joao.fernandes@docker.com>
|
||||||
|
Joe Doliner <jdoliner@pachyderm.io>
|
||||||
|
Joe Gordon <joe.gordon0@gmail.com>
|
||||||
|
Joel Handwell <joelhandwell@gmail.com>
|
||||||
|
Joey Geiger <jgeiger@gmail.com>
|
||||||
|
Joffrey F <joffrey@docker.com>
|
||||||
|
Johan Euphrosine <proppy@google.com>
|
||||||
|
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||||
|
John Feminella <jxf@jxf.me>
|
||||||
|
John Harris <john@johnharris.io>
|
||||||
|
John Howard (VM) <John.Howard@microsoft.com>
|
||||||
|
John Laswell <john.n.laswell@gmail.com>
|
||||||
|
John Maguire <jmaguire@duosecurity.com>
|
||||||
|
John Mulhausen <john@docker.com>
|
||||||
|
John Starks <jostarks@microsoft.com>
|
||||||
|
John Stephens <johnstep@docker.com>
|
||||||
|
John Tims <john.k.tims@gmail.com>
|
||||||
|
John V. Martinez <jvmatl@gmail.com>
|
||||||
|
John Willis <john.willis@docker.com>
|
||||||
|
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||||
|
Jonathan Lee <jonjohn1232009@gmail.com>
|
||||||
|
Jonathan Lomas <jonathan@floatinglomas.ca>
|
||||||
|
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||||
|
Jonh Wendell <jonh.wendell@redhat.com>
|
||||||
|
Jordan Jennings <jjn2009@gmail.com>
|
||||||
|
Joseph Kern <jkern@semafour.net>
|
||||||
|
Josh Bodah <jb3689@yahoo.com>
|
||||||
|
Josh Chorlton <jchorlton@gmail.com>
|
||||||
|
Josh Hawn <josh.hawn@docker.com>
|
||||||
|
Josh Horwitz <horwitz@addthis.com>
|
||||||
|
Josh Soref <jsoref@gmail.com>
|
||||||
|
Julien Barbier <write0@gmail.com>
|
||||||
|
Julien Kassar <github@kassisol.com>
|
||||||
|
Julien Maitrehenry <julien.maitrehenry@me.com>
|
||||||
|
Justas Brazauskas <brazauskasjustas@gmail.com>
|
||||||
|
Justin Cormack <justin.cormack@docker.com>
|
||||||
|
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||||
|
Justyn Temme <justyntemme@gmail.com>
|
||||||
|
Jyrki Puttonen <jyrkiput@gmail.com>
|
||||||
|
Jérémie Drouet <jeremie.drouet@gmail.com>
|
||||||
|
Jérôme Petazzoni <jerome.petazzoni@docker.com>
|
||||||
|
Jörg Thalheim <joerg@higgsboson.tk>
|
||||||
|
Kai Blin <kai@samba.org>
|
||||||
|
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||||
|
Kara Alexandra <kalexandra@us.ibm.com>
|
||||||
|
Kareem Khazem <karkhaz@karkhaz.com>
|
||||||
|
Karthik Nayak <Karthik.188@gmail.com>
|
||||||
|
Kat Samperi <kat.samperi@gmail.com>
|
||||||
|
Katie McLaughlin <katie@glasnt.com>
|
||||||
|
Ke Xu <leonhartx.k@gmail.com>
|
||||||
|
Kei Ohmura <ohmura.kei@gmail.com>
|
||||||
|
Keith Hudgins <greenman@greenman.org>
|
||||||
|
Ken Cochrane <kencochrane@gmail.com>
|
||||||
|
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||||
|
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||||
|
Kevin Burke <kev@inburke.com>
|
||||||
|
Kevin Feyrer <kevin.feyrer@btinternet.com>
|
||||||
|
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||||
|
Kevin Kirsche <Kev.Kirsche+GitHub@gmail.com>
|
||||||
|
Kevin Meredith <kevin.m.meredith@gmail.com>
|
||||||
|
Kevin Richardson <kevin@kevinrichardson.co>
|
||||||
|
khaled souf <khaled.souf@gmail.com>
|
||||||
|
Kim Eik <kim@heldig.org>
|
||||||
|
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||||
|
Kotaro Yoshimatsu <kotaro.yoshimatsu@gmail.com>
|
||||||
|
Krasi Georgiev <krasi@vip-consult.solutions>
|
||||||
|
Kris-Mikael Krister <krismikael@protonmail.com>
|
||||||
|
Kun Zhang <zkazure@gmail.com>
|
||||||
|
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
|
||||||
|
Kyle Spiers <kyle@spiers.me>
|
||||||
|
Lachlan Cooper <lachlancooper@gmail.com>
|
||||||
|
Lai Jiangshan <jiangshanlai@gmail.com>
|
||||||
|
Lars Kellogg-Stedman <lars@redhat.com>
|
||||||
|
Laura Frank <ljfrank@gmail.com>
|
||||||
|
Laurent Erignoux <lerignoux@gmail.com>
|
||||||
|
Lee Gaines <eightlimbed@gmail.com>
|
||||||
|
Lei Jitang <leijitang@huawei.com>
|
||||||
|
Lennie <github@consolejunkie.net>
|
||||||
|
Leo Gallucci <elgalu3@gmail.com>
|
||||||
|
Lewis Daly <lewisdaly@me.com>
|
||||||
|
Li Yi <denverdino@gmail.com>
|
||||||
|
Li Yi <weiyuan.yl@alibaba-inc.com>
|
||||||
|
Liang-Chi Hsieh <viirya@gmail.com>
|
||||||
|
Lifubang <lifubang@acmcoder.com>
|
||||||
|
Lihua Tang <lhtang@alauda.io>
|
||||||
|
Lily Guo <lily.guo@docker.com>
|
||||||
|
Lin Lu <doraalin@163.com>
|
||||||
|
Linus Heckemann <lheckemann@twig-world.com>
|
||||||
|
Liping Xue <lipingxue@gmail.com>
|
||||||
|
Liron Levin <liron@twistlock.com>
|
||||||
|
liwenqi <vikilwq@zju.edu.cn>
|
||||||
|
lixiaobing10051267 <li.xiaobing1@zte.com.cn>
|
||||||
|
Lloyd Dewolf <foolswisdom@gmail.com>
|
||||||
|
Lorenzo Fontana <lo@linux.com>
|
||||||
|
Louis Opter <kalessin@kalessin.fr>
|
||||||
|
Luca Favatella <luca.favatella@erlang-solutions.com>
|
||||||
|
Luca Marturana <lucamarturana@gmail.com>
|
||||||
|
Lucas Chan <lucas-github@lucaschan.com>
|
||||||
|
Luka Hartwig <mail@lukahartwig.de>
|
||||||
|
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
|
||||||
|
Lydell Manganti <LydellManganti@users.noreply.github.com>
|
||||||
|
Lénaïc Huard <lhuard@amadeus.com>
|
||||||
|
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
|
||||||
|
Mabin <bin.ma@huawei.com>
|
||||||
|
Madhav Puri <madhav.puri@gmail.com>
|
||||||
|
Madhu Venugopal <madhu@socketplane.io>
|
||||||
|
Malte Janduda <mail@janduda.net>
|
||||||
|
Manjunath A Kumatagi <mkumatag@in.ibm.com>
|
||||||
|
Mansi Nahar <mmn4185@rit.edu>
|
||||||
|
mapk0y <mapk0y@gmail.com>
|
||||||
|
Marc Bihlmaier <marc.bihlmaier@reddoxx.com>
|
||||||
|
Marco Mariani <marco.mariani@alterway.fr>
|
||||||
|
Marco Vedovati <mvedovati@suse.com>
|
||||||
|
Marcus Martins <marcus@docker.com>
|
||||||
|
Marianna Tessel <mtesselh@gmail.com>
|
||||||
|
Marius Sturm <marius@graylog.com>
|
||||||
|
Mark Oates <fl0yd@me.com>
|
||||||
|
Marsh Macy <marsma@microsoft.com>
|
||||||
|
Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
|
||||||
|
Mary Anthony <mary.anthony@docker.com>
|
||||||
|
Mason Fish <mason.fish@docker.com>
|
||||||
|
Mason Malone <mason.malone@gmail.com>
|
||||||
|
Mateusz Major <apkd@users.noreply.github.com>
|
||||||
|
Mathieu Champlon <mathieu.champlon@docker.com>
|
||||||
|
Matt Gucci <matt9ucci@gmail.com>
|
||||||
|
Matt Robenolt <matt@ydekproductions.com>
|
||||||
|
Matteo Orefice <matteo.orefice@bites4bits.software>
|
||||||
|
Matthew Heon <mheon@redhat.com>
|
||||||
|
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||||
|
Mauro Porras P <mauroporrasp@gmail.com>
|
||||||
|
Max Shytikov <mshytikov@gmail.com>
|
||||||
|
Maxime Petazzoni <max@signalfuse.com>
|
||||||
|
Mei ChunTao <mei.chuntao@zte.com.cn>
|
||||||
|
Micah Zoltu <micah@newrelic.com>
|
||||||
|
Michael A. Smith <michael@smith-li.com>
|
||||||
|
Michael Bridgen <mikeb@squaremobius.net>
|
||||||
|
Michael Crosby <michael@docker.com>
|
||||||
|
Michael Friis <friism@gmail.com>
|
||||||
|
Michael Irwin <mikesir87@gmail.com>
|
||||||
|
Michael Käufl <docker@c.michael-kaeufl.de>
|
||||||
|
Michael Prokop <github@michael-prokop.at>
|
||||||
|
Michael Scharf <github@scharf.gr>
|
||||||
|
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||||
|
Michael Steinert <mike.steinert@gmail.com>
|
||||||
|
Michael West <mwest@mdsol.com>
|
||||||
|
Michal Minář <miminar@redhat.com>
|
||||||
|
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||||
|
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com>
|
||||||
|
Mihai Borobocea <MihaiBorob@gmail.com>
|
||||||
|
Mihuleacc Sergiu <mihuleac.sergiu@gmail.com>
|
||||||
|
Mike Brown <brownwm@us.ibm.com>
|
||||||
|
Mike Casas <mkcsas0@gmail.com>
|
||||||
|
Mike Danese <mikedanese@google.com>
|
||||||
|
Mike Dillon <mike@embody.org>
|
||||||
|
Mike Goelzer <mike.goelzer@docker.com>
|
||||||
|
Mike MacCana <mike.maccana@gmail.com>
|
||||||
|
mikelinjie <294893458@qq.com>
|
||||||
|
Mikhail Vasin <vasin@cloud-tv.ru>
|
||||||
|
Milind Chawre <milindchawre@gmail.com>
|
||||||
|
Mindaugas Rukas <momomg@gmail.com>
|
||||||
|
Misty Stanley-Jones <misty@docker.com>
|
||||||
|
Mohammad Banikazemi <mb@us.ibm.com>
|
||||||
|
Mohammed Aaqib Ansari <maaquib@gmail.com>
|
||||||
|
Mohini Anne Dsouza <mohini3917@gmail.com>
|
||||||
|
Moorthy RS <rsmoorthy@gmail.com>
|
||||||
|
Morgan Bauer <mbauer@us.ibm.com>
|
||||||
|
Moysés Borges <moysesb@gmail.com>
|
||||||
|
Mrunal Patel <mrunalp@gmail.com>
|
||||||
|
muicoder <muicoder@gmail.com>
|
||||||
|
Muthukumar R <muthur@gmail.com>
|
||||||
|
Máximo Cuadros <mcuadros@gmail.com>
|
||||||
|
Mårten Cassel <marten.cassel@gmail.com>
|
||||||
|
Nace Oroz <orkica@gmail.com>
|
||||||
|
Nahum Shalman <nshalman@omniti.com>
|
||||||
|
Nalin Dahyabhai <nalin@redhat.com>
|
||||||
|
Nao YONASHIRO <owan.orisano@gmail.com>
|
||||||
|
Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
|
||||||
|
Natalie Parker <nparker@omnifone.com>
|
||||||
|
Nate Brennand <nate.brennand@clever.com>
|
||||||
|
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||||
|
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||||
|
Nathan McCauley <nathan.mccauley@docker.com>
|
||||||
|
Neil Peterson <neilpeterson@outlook.com>
|
||||||
|
Nick Adcock <nick.adcock@docker.com>
|
||||||
|
Nico Stapelbroek <nstapelbroek@gmail.com>
|
||||||
|
Nicola Kabar <nicolaka@gmail.com>
|
||||||
|
Nicolas Borboën <ponsfrilus@gmail.com>
|
||||||
|
Nicolas De Loof <nicolas.deloof@gmail.com>
|
||||||
|
Nikhil Chawla <chawlanikhil24@gmail.com>
|
||||||
|
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
|
||||||
|
Nikolay Milovanov <nmil@itransformers.net>
|
||||||
|
Nir Soffer <nsoffer@redhat.com>
|
||||||
|
Nishant Totla <nishanttotla@gmail.com>
|
||||||
|
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
|
||||||
|
Noah Treuhaft <noah.treuhaft@docker.com>
|
||||||
|
O.S. Tezer <ostezer@gmail.com>
|
||||||
|
ohmystack <jun.jiang02@ele.me>
|
||||||
|
Olle Jonsson <olle.jonsson@gmail.com>
|
||||||
|
Olli Janatuinen <olli.janatuinen@gmail.com>
|
||||||
|
Otto Kekäläinen <otto@seravo.fi>
|
||||||
|
Ovidio Mallo <ovidio.mallo@gmail.com>
|
||||||
|
Pascal Borreli <pascal@borreli.com>
|
||||||
|
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||||
|
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||||
|
Patrick Lang <plang@microsoft.com>
|
||||||
|
Paul <paul9869@gmail.com>
|
||||||
|
Paul Kehrer <paul.l.kehrer@gmail.com>
|
||||||
|
Paul Lietar <paul@lietar.net>
|
||||||
|
Paul Weaver <pauweave@cisco.com>
|
||||||
|
Pavel Pospisil <pospispa@gmail.com>
|
||||||
|
Paweł Szczekutowicz <pszczekutowicz@gmail.com>
|
||||||
|
Peeyush Gupta <gpeeyush@linux.vnet.ibm.com>
|
||||||
|
Per Lundberg <per.lundberg@ecraft.com>
|
||||||
|
Peter Edge <peter.edge@gmail.com>
|
||||||
|
Peter Hsu <shhsu@microsoft.com>
|
||||||
|
Peter Jaffe <pjaffe@nevo.com>
|
||||||
|
Peter Kehl <peter.kehl@gmail.com>
|
||||||
|
Peter Nagy <xificurC@gmail.com>
|
||||||
|
Peter Salvatore <peter@psftw.com>
|
||||||
|
Peter Waller <p@pwaller.net>
|
||||||
|
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||||
|
Philip Alexander Etling <paetling@gmail.com>
|
||||||
|
Philipp Gillé <philipp.gille@gmail.com>
|
||||||
|
Philipp Schmied <pschmied@schutzwerk.com>
|
||||||
|
pidster <pid@pidster.com>
|
||||||
|
pixelistik <pixelistik@users.noreply.github.com>
|
||||||
|
Pratik Karki <prertik@outlook.com>
|
||||||
|
Prayag Verma <prayag.verma@gmail.com>
|
||||||
|
Preston Cowley <preston.cowley@sony.com>
|
||||||
|
Pure White <daniel48@126.com>
|
||||||
|
Qiang Huang <h.huangqiang@huawei.com>
|
||||||
|
Qinglan Peng <qinglanpeng@zju.edu.cn>
|
||||||
|
qudongfang <qudongfang@gmail.com>
|
||||||
|
Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
|
||||||
|
Ray Tsang <rayt@google.com>
|
||||||
|
Reficul <xuzhenglun@gmail.com>
|
||||||
|
Remy Suen <remy.suen@gmail.com>
|
||||||
|
Renaud Gaubert <rgaubert@nvidia.com>
|
||||||
|
Ricardo N Feliciano <FelicianoTech@gmail.com>
|
||||||
|
Rich Moyse <rich@moyse.us>
|
||||||
|
Richard Mathie <richard.mathie@amey.co.uk>
|
||||||
|
Richard Scothern <richard.scothern@gmail.com>
|
||||||
|
Rick Wieman <git@rickw.nl>
|
||||||
|
Ritesh H Shukla <sritesh@vmware.com>
|
||||||
|
Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
|
||||||
|
Robert Wallis <smilingrob@gmail.com>
|
||||||
|
Robin Naundorf <r.naundorf@fh-muenster.de>
|
||||||
|
Robin Speekenbrink <robin@kingsquare.nl>
|
||||||
|
Rodolfo Ortiz <rodolfo.ortiz@definityfirst.com>
|
||||||
|
Rogelio Canedo <rcanedo@mappy.priv>
|
||||||
|
Roland Kammerer <roland.kammerer@linbit.com>
|
||||||
|
Roman Dudin <katrmr@gmail.com>
|
||||||
|
Rory Hunter <roryhunter2@gmail.com>
|
||||||
|
Ross Boucher <rboucher@gmail.com>
|
||||||
|
Rubens Figueiredo <r.figueiredo.52@gmail.com>
|
||||||
|
Rui Cao <ruicao@alauda.io>
|
||||||
|
Ryan Belgrave <rmb1993@gmail.com>
|
||||||
|
Ryan Detzel <ryan.detzel@gmail.com>
|
||||||
|
Ryan Stelly <ryan.stelly@live.com>
|
||||||
|
Ryan Wilson-Perkin <ryanwilsonperkin@gmail.com>
|
||||||
|
Ryan Zhang <ryan.zhang@docker.com>
|
||||||
|
Sainath Grandhi <sainath.grandhi@intel.com>
|
||||||
|
Sakeven Jiang <jc5930@sina.cn>
|
||||||
|
Sally O'Malley <somalley@redhat.com>
|
||||||
|
Sam Neirinck <sam@samneirinck.com>
|
||||||
|
Sambuddha Basu <sambuddhabasu1@gmail.com>
|
||||||
|
Sami Tabet <salph.tabet@gmail.com>
|
||||||
|
Samuel Karp <skarp@amazon.com>
|
||||||
|
Santhosh Manohar <santhosh@docker.com>
|
||||||
|
Scott Brenner <scott@scottbrenner.me>
|
||||||
|
Scott Collier <emailscottcollier@gmail.com>
|
||||||
|
Sean Christopherson <sean.j.christopherson@intel.com>
|
||||||
|
Sean Rodman <srodman7689@gmail.com>
|
||||||
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
|
Sergey Tryuber <Sergeant007@users.noreply.github.com>
|
||||||
|
Serhat Gülçiçek <serhat25@gmail.com>
|
||||||
|
Sevki Hasirci <s@sevki.org>
|
||||||
|
Shaun Kaasten <shaunk@gmail.com>
|
||||||
|
Sheng Yang <sheng@yasker.org>
|
||||||
|
Shijiang Wei <mountkin@gmail.com>
|
||||||
|
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||||
|
Shoubhik Bose <sbose78@gmail.com>
|
||||||
|
Shukui Yang <yangshukui@huawei.com>
|
||||||
|
Sian Lerk Lau <kiawin@gmail.com>
|
||||||
|
Sidhartha Mani <sidharthamn@gmail.com>
|
||||||
|
sidharthamani <sid@rancher.com>
|
||||||
|
Silvin Lubecki <silvin.lubecki@docker.com>
|
||||||
|
Simei He <hesimei@zju.edu.cn>
|
||||||
|
Simon Ferquel <simon.ferquel@docker.com>
|
||||||
|
Sindhu S <sindhus@live.in>
|
||||||
|
Slava Semushin <semushin@redhat.com>
|
||||||
|
Solomon Hykes <solomon@docker.com>
|
||||||
|
Song Gao <song@gao.io>
|
||||||
|
Spencer Brown <spencer@spencerbrown.org>
|
||||||
|
squeegels <1674195+squeegels@users.noreply.github.com>
|
||||||
|
Srini Brahmaroutu <srbrahma@us.ibm.com>
|
||||||
|
Stefan S. <tronicum@user.github.com>
|
||||||
|
Stefan Scherer <stefan.scherer@docker.com>
|
||||||
|
Stefan Weil <sw@weilnetz.de>
|
||||||
|
Stephane Jeandeaux <stephane.jeandeaux@gmail.com>
|
||||||
|
Stephen Day <stevvooe@gmail.com>
|
||||||
|
Stephen Rust <srust@blockbridge.com>
|
||||||
|
Steve Durrheimer <s.durrheimer@gmail.com>
|
||||||
|
Steve Richards <steve.richards@docker.com>
|
||||||
|
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||||
|
Subhajit Ghosh <isubuz.g@gmail.com>
|
||||||
|
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||||
|
Sune Keller <absukl@almbrand.dk>
|
||||||
|
Sungwon Han <sungwon.han@navercorp.com>
|
||||||
|
Sunny Gogoi <indiasuny000@gmail.com>
|
||||||
|
Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
|
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||||
|
Sébastien HOUZÉ <cto@verylastroom.com>
|
||||||
|
T K Sourabh <sourabhtk37@gmail.com>
|
||||||
|
TAGOMORI Satoshi <tagomoris@gmail.com>
|
||||||
|
taiji-tech <csuhqg@foxmail.com>
|
||||||
|
Taylor Jones <monitorjbl@gmail.com>
|
||||||
|
Tejaswini Duggaraju <naduggar@microsoft.com>
|
||||||
|
Thatcher Peskens <thatcher@docker.com>
|
||||||
|
Thomas Gazagnaire <thomas@gazagnaire.org>
|
||||||
|
Thomas Krzero <thomas.kovatchitch@gmail.com>
|
||||||
|
Thomas Leonard <thomas.leonard@docker.com>
|
||||||
|
Thomas Léveil <thomasleveil@gmail.com>
|
||||||
|
Thomas Riccardi <thomas@deepomatic.com>
|
||||||
|
Thomas Swift <tgs242@gmail.com>
|
||||||
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
|
Tianyi Wang <capkurmagati@gmail.com>
|
||||||
|
Tibor Vass <teabee89@gmail.com>
|
||||||
|
Tim Dettrick <t.dettrick@uq.edu.au>
|
||||||
|
Tim Hockin <thockin@google.com>
|
||||||
|
Tim Smith <timbot@google.com>
|
||||||
|
Tim Waugh <twaugh@redhat.com>
|
||||||
|
Tim Wraight <tim.wraight@tangentlabs.co.uk>
|
||||||
|
timfeirg <kkcocogogo@gmail.com>
|
||||||
|
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||||
|
Tobias Bradtke <webwurst@gmail.com>
|
||||||
|
Tobias Gesellchen <tobias@gesellix.de>
|
||||||
|
Todd Whiteman <todd.whiteman@joyent.com>
|
||||||
|
Tom Denham <tom@tomdee.co.uk>
|
||||||
|
Tom Fotherby <tom+github@peopleperhour.com>
|
||||||
|
Tom Klingenberg <tklingenberg@lastflood.net>
|
||||||
|
Tom Milligan <code@tommilligan.net>
|
||||||
|
Tom X. Tobin <tomxtobin@tomxtobin.com>
|
||||||
|
Tomas Tomecek <ttomecek@redhat.com>
|
||||||
|
Tomasz Kopczynski <tomek@kopczynski.net.pl>
|
||||||
|
Tomáš Hrčka <thrcka@redhat.com>
|
||||||
|
Tony Abboud <tdabboud@hotmail.com>
|
||||||
|
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Trapier Marshall <trapier.marshall@docker.com>
|
||||||
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Tristan Carel <tristan@cogniteev.com>
|
||||||
|
Tycho Andersen <tycho@docker.com>
|
||||||
|
Tycho Andersen <tycho@tycho.ws>
|
||||||
|
uhayate <uhayate.gong@daocloud.io>
|
||||||
|
Ulysses Souza <ulysses.souza@docker.com>
|
||||||
|
Umesh Yadav <umesh4257@gmail.com>
|
||||||
|
Valentin Lorentz <progval+git@progval.net>
|
||||||
|
Veres Lajos <vlajos@gmail.com>
|
||||||
|
Victor Vieux <victor.vieux@docker.com>
|
||||||
|
Victoria Bialas <victoria.bialas@docker.com>
|
||||||
|
Viktor Stanchev <me@viktorstanchev.com>
|
||||||
|
Vimal Raghubir <vraghubir0418@gmail.com>
|
||||||
|
Vincent Batts <vbatts@redhat.com>
|
||||||
|
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||||
|
Vincent Demeester <vincent.demeester@docker.com>
|
||||||
|
Vincent Woo <me@vincentwoo.com>
|
||||||
|
Vishnu Kannan <vishnuk@google.com>
|
||||||
|
Vivek Goyal <vgoyal@redhat.com>
|
||||||
|
Wang Jie <wangjie5@chinaskycloud.com>
|
||||||
|
Wang Lei <wanglei@tenxcloud.com>
|
||||||
|
Wang Long <long.wanglong@huawei.com>
|
||||||
|
Wang Ping <present.wp@icloud.com>
|
||||||
|
Wang Xing <hzwangxing@corp.netease.com>
|
||||||
|
Wang Yuexiao <wang.yuexiao@zte.com.cn>
|
||||||
|
Wataru Ishida <ishida.wataru@lab.ntt.co.jp>
|
||||||
|
Wayne Song <wsong@docker.com>
|
||||||
|
Wen Cheng Ma <wenchma@cn.ibm.com>
|
||||||
|
Wenzhi Liang <wenzhi.liang@gmail.com>
|
||||||
|
Wes Morgan <cap10morgan@gmail.com>
|
||||||
|
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||||
|
William Henry <whenry@redhat.com>
|
||||||
|
Xianglin Gao <xlgao@zju.edu.cn>
|
||||||
|
Xiaodong Zhang <a4012017@sina.com>
|
||||||
|
Xiaoxi He <xxhe@alauda.io>
|
||||||
|
Xinbo Weng <xihuanbo_0521@zju.edu.cn>
|
||||||
|
Xuecong Liao <satorulogic@gmail.com>
|
||||||
|
Yan Feng <yanfeng2@huawei.com>
|
||||||
|
Yanqiang Miao <miao.yanqiang@zte.com.cn>
|
||||||
|
Yassine Tijani <yasstij11@gmail.com>
|
||||||
|
Yi EungJun <eungjun.yi@navercorp.com>
|
||||||
|
Ying Li <ying.li@docker.com>
|
||||||
|
Yong Tang <yong.tang.github@outlook.com>
|
||||||
|
Yosef Fertel <yfertel@gmail.com>
|
||||||
|
Yu Peng <yu.peng36@zte.com.cn>
|
||||||
|
Yuan Sun <sunyuan3@huawei.com>
|
||||||
|
Yue Zhang <zy675793960@yeah.net>
|
||||||
|
Yunxiang Huang <hyxqshk@vip.qq.com>
|
||||||
|
Zachary Romero <zacromero3@gmail.com>
|
||||||
|
zebrilee <zebrilee@gmail.com>
|
||||||
|
Zhang Kun <zkazure@gmail.com>
|
||||||
|
Zhang Wei <zhangwei555@huawei.com>
|
||||||
|
Zhang Wentao <zhangwentao234@huawei.com>
|
||||||
|
ZhangHang <stevezhang2014@gmail.com>
|
||||||
|
zhenghenghuo <zhenghenghuo@zju.edu.cn>
|
||||||
|
Zhou Hao <zhouhao@cn.fujitsu.com>
|
||||||
|
Zhoulin Xie <zhoulin.xie@daocloud.io>
|
||||||
|
Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
|
||||||
|
Álex González <agonzalezro@gmail.com>
|
||||||
|
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
||||||
|
Átila Camurça Alves <camurca.home@gmail.com>
|
||||||
|
徐俊杰 <paco.xu@daocloud.io>
|
191
vendor/github.com/docker/cli/LICENSE
generated
vendored
Normal file
191
vendor/github.com/docker/cli/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2013-2017 Docker, Inc.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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.
|
19
vendor/github.com/docker/cli/NOTICE
generated
vendored
Normal file
19
vendor/github.com/docker/cli/NOTICE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Docker
|
||||||
|
Copyright 2012-2017 Docker, Inc.
|
||||||
|
|
||||||
|
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||||
|
|
||||||
|
This product contains software (https://github.com/kr/pty) developed
|
||||||
|
by Keith Rarick, licensed under the MIT License.
|
||||||
|
|
||||||
|
The following is courtesy of our legal counsel:
|
||||||
|
|
||||||
|
|
||||||
|
Use and transfer of Docker may be subject to certain restrictions by the
|
||||||
|
United States and other governments.
|
||||||
|
It is your responsibility to ensure that your use and/or transfer does not
|
||||||
|
violate applicable laws.
|
||||||
|
|
||||||
|
For more information, please see https://www.bis.doc.gov
|
||||||
|
|
||||||
|
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
136
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
Normal file
136
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/config/credentials"
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConfigFileName is the name of config file
|
||||||
|
ConfigFileName = "config.json"
|
||||||
|
configFileDir = ".docker"
|
||||||
|
oldConfigfile = ".dockercfg"
|
||||||
|
contextsDir = "contexts"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configDir = os.Getenv("DOCKER_CONFIG")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if configDir == "" {
|
||||||
|
configDir = filepath.Join(homedir.Get(), configFileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir returns the directory the configuration file is stored in
|
||||||
|
func Dir() string {
|
||||||
|
return configDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextStoreDir returns the directory the docker contexts are stored in
|
||||||
|
func ContextStoreDir() string {
|
||||||
|
return filepath.Join(Dir(), contextsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDir sets the directory the configuration file is stored in
|
||||||
|
func SetDir(dir string) {
|
||||||
|
configDir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path to a file relative to the config dir
|
||||||
|
func Path(p ...string) (string, error) {
|
||||||
|
path := filepath.Join(append([]string{Dir()}, p...)...)
|
||||||
|
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
|
||||||
|
return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir())
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
|
||||||
|
// a non-nested reader
|
||||||
|
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||||
|
configFile := configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
}
|
||||||
|
err := configFile.LegacyLoadFromReader(configData)
|
||||||
|
return &configFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
||||||
|
// a reader
|
||||||
|
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||||
|
configFile := configfile.ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
}
|
||||||
|
err := configFile.LoadFromReader(configData)
|
||||||
|
return &configFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads the configuration files in the given directory, and sets up
|
||||||
|
// the auth config information and returns values.
|
||||||
|
// FIXME: use the internal golang config parser
|
||||||
|
func Load(configDir string) (*configfile.ConfigFile, error) {
|
||||||
|
if configDir == "" {
|
||||||
|
configDir = Dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(configDir, ConfigFileName)
|
||||||
|
configFile := configfile.New(filename)
|
||||||
|
|
||||||
|
// Try happy path first - latest config file
|
||||||
|
if _, err := os.Stat(filename); err == nil {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return configFile, errors.Wrap(err, filename)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = configFile.LoadFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, filename)
|
||||||
|
}
|
||||||
|
return configFile, err
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
// if file is there but we can't stat it for any reason other
|
||||||
|
// than it doesn't exist then stop
|
||||||
|
return configFile, errors.Wrap(err, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't find latest config file so check for the old one
|
||||||
|
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
||||||
|
if _, err := os.Stat(confFile); err != nil {
|
||||||
|
return configFile, nil //missing file is not an error
|
||||||
|
}
|
||||||
|
file, err := os.Open(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return configFile, errors.Wrap(err, filename)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = configFile.LegacyLoadFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return configFile, errors.Wrap(err, filename)
|
||||||
|
}
|
||||||
|
return configFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||||
|
// an initialized ConfigFile struct if none is found.
|
||||||
|
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
|
||||||
|
configFile, err := Load(Dir())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
|
||||||
|
}
|
||||||
|
if !configFile.ContainsAuth() {
|
||||||
|
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
|
||||||
|
}
|
||||||
|
return configFile
|
||||||
|
}
|
385
vendor/github.com/docker/cli/cli/config/configfile/file.go
generated
vendored
Normal file
385
vendor/github.com/docker/cli/cli/config/configfile/file.go
generated
vendored
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
package configfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/config/credentials"
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This constant is only used for really old config files when the
|
||||||
|
// URL wasn't saved as part of the config file and it was just
|
||||||
|
// assumed to be this value.
|
||||||
|
defaultIndexServer = "https://index.docker.io/v1/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigFile ~/.docker/config.json file info
|
||||||
|
type ConfigFile struct {
|
||||||
|
AuthConfigs map[string]types.AuthConfig `json:"auths"`
|
||||||
|
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||||
|
PsFormat string `json:"psFormat,omitempty"`
|
||||||
|
ImagesFormat string `json:"imagesFormat,omitempty"`
|
||||||
|
NetworksFormat string `json:"networksFormat,omitempty"`
|
||||||
|
PluginsFormat string `json:"pluginsFormat,omitempty"`
|
||||||
|
VolumesFormat string `json:"volumesFormat,omitempty"`
|
||||||
|
StatsFormat string `json:"statsFormat,omitempty"`
|
||||||
|
DetachKeys string `json:"detachKeys,omitempty"`
|
||||||
|
CredentialsStore string `json:"credsStore,omitempty"`
|
||||||
|
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||||
|
Filename string `json:"-"` // Note: for internal use only
|
||||||
|
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
||||||
|
ServicesFormat string `json:"servicesFormat,omitempty"`
|
||||||
|
TasksFormat string `json:"tasksFormat,omitempty"`
|
||||||
|
SecretFormat string `json:"secretFormat,omitempty"`
|
||||||
|
ConfigFormat string `json:"configFormat,omitempty"`
|
||||||
|
NodesFormat string `json:"nodesFormat,omitempty"`
|
||||||
|
PruneFilters []string `json:"pruneFilters,omitempty"`
|
||||||
|
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
|
||||||
|
Experimental string `json:"experimental,omitempty"`
|
||||||
|
StackOrchestrator string `json:"stackOrchestrator,omitempty"`
|
||||||
|
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
|
||||||
|
CurrentContext string `json:"currentContext,omitempty"`
|
||||||
|
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
|
||||||
|
Plugins map[string]map[string]string `json:"plugins,omitempty"`
|
||||||
|
Aliases map[string]string `json:"aliases,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyConfig contains proxy configuration settings
|
||||||
|
type ProxyConfig struct {
|
||||||
|
HTTPProxy string `json:"httpProxy,omitempty"`
|
||||||
|
HTTPSProxy string `json:"httpsProxy,omitempty"`
|
||||||
|
NoProxy string `json:"noProxy,omitempty"`
|
||||||
|
FTPProxy string `json:"ftpProxy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KubernetesConfig contains Kubernetes orchestrator settings
|
||||||
|
type KubernetesConfig struct {
|
||||||
|
AllNamespaces string `json:"allNamespaces,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes an empty configuration file for the given filename 'fn'
|
||||||
|
func New(fn string) *ConfigFile {
|
||||||
|
return &ConfigFile{
|
||||||
|
AuthConfigs: make(map[string]types.AuthConfig),
|
||||||
|
HTTPHeaders: make(map[string]string),
|
||||||
|
Filename: fn,
|
||||||
|
Plugins: make(map[string]map[string]string),
|
||||||
|
Aliases: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
||||||
|
// auth config information with given directory and populates the receiver object
|
||||||
|
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
||||||
|
b, err := ioutil.ReadAll(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
|
||||||
|
arr := strings.Split(string(b), "\n")
|
||||||
|
if len(arr) < 2 {
|
||||||
|
return errors.Errorf("The Auth config file is empty")
|
||||||
|
}
|
||||||
|
authConfig := types.AuthConfig{}
|
||||||
|
origAuth := strings.Split(arr[0], " = ")
|
||||||
|
if len(origAuth) != 2 {
|
||||||
|
return errors.Errorf("Invalid Auth config file")
|
||||||
|
}
|
||||||
|
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.ServerAddress = defaultIndexServer
|
||||||
|
configFile.AuthConfigs[defaultIndexServer] = authConfig
|
||||||
|
} else {
|
||||||
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
|
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.Auth = ""
|
||||||
|
authConfig.ServerAddress = k
|
||||||
|
configFile.AuthConfigs[k] = authConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromReader reads the configuration data given and sets up the auth config
|
||||||
|
// information with given directory and populates the receiver object
|
||||||
|
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||||
|
if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for addr, ac := range configFile.AuthConfigs {
|
||||||
|
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ac.Auth = ""
|
||||||
|
ac.ServerAddress = addr
|
||||||
|
configFile.AuthConfigs[addr] = ac
|
||||||
|
}
|
||||||
|
return checkKubernetesConfiguration(configFile.Kubernetes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsAuth returns whether there is authentication configured
|
||||||
|
// in this file or not.
|
||||||
|
func (configFile *ConfigFile) ContainsAuth() bool {
|
||||||
|
return configFile.CredentialsStore != "" ||
|
||||||
|
len(configFile.CredentialHelpers) > 0 ||
|
||||||
|
len(configFile.AuthConfigs) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthConfigs returns the mapping of repo to auth configuration
|
||||||
|
func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
|
||||||
|
return configFile.AuthConfigs
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToWriter encodes and writes out all the authorization information to
|
||||||
|
// the given writer
|
||||||
|
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||||
|
// Encode sensitive data into a new/temp struct
|
||||||
|
tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
|
||||||
|
for k, authConfig := range configFile.AuthConfigs {
|
||||||
|
authCopy := authConfig
|
||||||
|
// encode and save the authstring, while blanking out the original fields
|
||||||
|
authCopy.Auth = encodeAuth(&authCopy)
|
||||||
|
authCopy.Username = ""
|
||||||
|
authCopy.Password = ""
|
||||||
|
authCopy.ServerAddress = ""
|
||||||
|
tmpAuthConfigs[k] = authCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAuthConfigs := configFile.AuthConfigs
|
||||||
|
configFile.AuthConfigs = tmpAuthConfigs
|
||||||
|
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(configFile, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save encodes and writes out all the authorization information
|
||||||
|
func (configFile *ConfigFile) Save() error {
|
||||||
|
if configFile.Filename == "" {
|
||||||
|
return errors.Errorf("Can't save config with empty filename")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(configFile.Filename)
|
||||||
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configFile.SaveToWriter(temp)
|
||||||
|
temp.Close()
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(temp.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(temp.Name(), configFile.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and
|
||||||
|
// then checking this against any environment variables provided to the container
|
||||||
|
func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*string) map[string]*string {
|
||||||
|
var cfgKey string
|
||||||
|
|
||||||
|
if _, ok := configFile.Proxies[host]; !ok {
|
||||||
|
cfgKey = "default"
|
||||||
|
} else {
|
||||||
|
cfgKey = host
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configFile.Proxies[cfgKey]
|
||||||
|
permitted := map[string]*string{
|
||||||
|
"HTTP_PROXY": &config.HTTPProxy,
|
||||||
|
"HTTPS_PROXY": &config.HTTPSProxy,
|
||||||
|
"NO_PROXY": &config.NoProxy,
|
||||||
|
"FTP_PROXY": &config.FTPProxy,
|
||||||
|
}
|
||||||
|
m := runOpts
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[string]*string)
|
||||||
|
}
|
||||||
|
for k := range permitted {
|
||||||
|
if *permitted[k] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := m[k]; !ok {
|
||||||
|
m[k] = permitted[k]
|
||||||
|
}
|
||||||
|
if _, ok := m[strings.ToLower(k)]; !ok {
|
||||||
|
m[strings.ToLower(k)] = permitted[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeAuth creates a base64 encoded string to containing authorization information
|
||||||
|
func encodeAuth(authConfig *types.AuthConfig) string {
|
||||||
|
if authConfig.Username == "" && authConfig.Password == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
authStr := authConfig.Username + ":" + authConfig.Password
|
||||||
|
msg := []byte(authStr)
|
||||||
|
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||||
|
base64.StdEncoding.Encode(encoded, msg)
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAuth decodes a base64 encoded string and returns username and password
|
||||||
|
func decodeAuth(authStr string) (string, string, error) {
|
||||||
|
if authStr == "" {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||||
|
decoded := make([]byte, decLen)
|
||||||
|
authByte := []byte(authStr)
|
||||||
|
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if n > decLen {
|
||||||
|
return "", "", errors.Errorf("Something went wrong decoding auth config")
|
||||||
|
}
|
||||||
|
arr := strings.SplitN(string(decoded), ":", 2)
|
||||||
|
if len(arr) != 2 {
|
||||||
|
return "", "", errors.Errorf("Invalid auth configuration file")
|
||||||
|
}
|
||||||
|
password := strings.Trim(arr[1], "\x00")
|
||||||
|
return arr[0], password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCredentialsStore returns a new credentials store from the settings in the
|
||||||
|
// configuration file
|
||||||
|
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||||
|
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
|
||||||
|
return newNativeStore(configFile, helper)
|
||||||
|
}
|
||||||
|
return credentials.NewFileStore(configFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// var for unit testing.
|
||||||
|
var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||||
|
return credentials.NewNativeStore(configFile, helperSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthConfig for a repository from the credential store
|
||||||
|
func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
|
||||||
|
return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfiguredCredentialStore returns the credential helper configured for the
|
||||||
|
// given registry, the default credsStore, or the empty string if neither are
|
||||||
|
// configured.
|
||||||
|
func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string {
|
||||||
|
if c.CredentialHelpers != nil && registryHostname != "" {
|
||||||
|
if helper, exists := c.CredentialHelpers[registryHostname]; exists {
|
||||||
|
return helper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.CredentialsStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllCredentials returns all of the credentials stored in all of the
|
||||||
|
// configured credential stores.
|
||||||
|
func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||||
|
auths := make(map[string]types.AuthConfig)
|
||||||
|
addAll := func(from map[string]types.AuthConfig) {
|
||||||
|
for reg, ac := range from {
|
||||||
|
auths[reg] = ac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultStore := configFile.GetCredentialsStore("")
|
||||||
|
newAuths, err := defaultStore.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addAll(newAuths)
|
||||||
|
|
||||||
|
// Auth configs from a registry-specific helper should override those from the default store.
|
||||||
|
for registryHostname := range configFile.CredentialHelpers {
|
||||||
|
newAuth, err := configFile.GetAuthConfig(registryHostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
auths[registryHostname] = newAuth
|
||||||
|
}
|
||||||
|
return auths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilename returns the file name that this config file is based on.
|
||||||
|
func (configFile *ConfigFile) GetFilename() string {
|
||||||
|
return configFile.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginConfig retrieves the requested option for the given plugin.
|
||||||
|
func (configFile *ConfigFile) PluginConfig(pluginname, option string) (string, bool) {
|
||||||
|
if configFile.Plugins == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
pluginConfig, ok := configFile.Plugins[pluginname]
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
value, ok := pluginConfig[option]
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPluginConfig sets the option to the given value for the given
|
||||||
|
// plugin. Passing a value of "" will remove the option. If removing
|
||||||
|
// the final config item for a given plugin then also cleans up the
|
||||||
|
// overall plugin entry.
|
||||||
|
func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string) {
|
||||||
|
if configFile.Plugins == nil {
|
||||||
|
configFile.Plugins = make(map[string]map[string]string)
|
||||||
|
}
|
||||||
|
pluginConfig, ok := configFile.Plugins[pluginname]
|
||||||
|
if !ok {
|
||||||
|
pluginConfig = make(map[string]string)
|
||||||
|
configFile.Plugins[pluginname] = pluginConfig
|
||||||
|
}
|
||||||
|
if value != "" {
|
||||||
|
pluginConfig[option] = value
|
||||||
|
} else {
|
||||||
|
delete(pluginConfig, option)
|
||||||
|
}
|
||||||
|
if len(pluginConfig) == 0 {
|
||||||
|
delete(configFile.Plugins, pluginname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkKubernetesConfiguration(kubeConfig *KubernetesConfig) error {
|
||||||
|
if kubeConfig == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch kubeConfig.AllNamespaces {
|
||||||
|
case "":
|
||||||
|
case "enabled":
|
||||||
|
case "disabled":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid 'kubernetes.allNamespaces' value, should be 'enabled' or 'disabled': %s", kubeConfig.AllNamespaces)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
17
vendor/github.com/docker/cli/cli/config/credentials/credentials.go
generated
vendored
Normal file
17
vendor/github.com/docker/cli/cli/config/credentials/credentials.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store is the interface that any credentials store must implement.
|
||||||
|
type Store interface {
|
||||||
|
// Erase removes credentials from the store for a given server.
|
||||||
|
Erase(serverAddress string) error
|
||||||
|
// Get retrieves credentials from the store for a given server.
|
||||||
|
Get(serverAddress string) (types.AuthConfig, error)
|
||||||
|
// GetAll retrieves all the credentials from the store.
|
||||||
|
GetAll() (map[string]types.AuthConfig, error)
|
||||||
|
// Store saves credentials in the store.
|
||||||
|
Store(authConfig types.AuthConfig) error
|
||||||
|
}
|
21
vendor/github.com/docker/cli/cli/config/credentials/default_store.go
generated
vendored
Normal file
21
vendor/github.com/docker/cli/cli/config/credentials/default_store.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectDefaultStore return the default credentials store for the platform if
|
||||||
|
// the store executable is available.
|
||||||
|
func DetectDefaultStore(store string) string {
|
||||||
|
platformDefault := defaultCredentialsStore()
|
||||||
|
|
||||||
|
// user defined or no default for platform
|
||||||
|
if store != "" || platformDefault == "" {
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err == nil {
|
||||||
|
return platformDefault
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
5
vendor/github.com/docker/cli/cli/config/credentials/default_store_darwin.go
generated
vendored
Normal file
5
vendor/github.com/docker/cli/cli/config/credentials/default_store_darwin.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
func defaultCredentialsStore() string {
|
||||||
|
return "osxkeychain"
|
||||||
|
}
|
13
vendor/github.com/docker/cli/cli/config/credentials/default_store_linux.go
generated
vendored
Normal file
13
vendor/github.com/docker/cli/cli/config/credentials/default_store_linux.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultCredentialsStore() string {
|
||||||
|
if _, err := exec.LookPath("pass"); err == nil {
|
||||||
|
return "pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "secretservice"
|
||||||
|
}
|
7
vendor/github.com/docker/cli/cli/config/credentials/default_store_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/docker/cli/cli/config/credentials/default_store_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !windows,!darwin,!linux
|
||||||
|
|
||||||
|
package credentials
|
||||||
|
|
||||||
|
func defaultCredentialsStore() string {
|
||||||
|
return ""
|
||||||
|
}
|
5
vendor/github.com/docker/cli/cli/config/credentials/default_store_windows.go
generated
vendored
Normal file
5
vendor/github.com/docker/cli/cli/config/credentials/default_store_windows.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
func defaultCredentialsStore() string {
|
||||||
|
return "wincred"
|
||||||
|
}
|
81
vendor/github.com/docker/cli/cli/config/credentials/file_store.go
generated
vendored
Normal file
81
vendor/github.com/docker/cli/cli/config/credentials/file_store.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type store interface {
|
||||||
|
Save() error
|
||||||
|
GetAuthConfigs() map[string]types.AuthConfig
|
||||||
|
GetFilename() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileStore implements a credentials store using
|
||||||
|
// the docker configuration file to keep the credentials in plain text.
|
||||||
|
type fileStore struct {
|
||||||
|
file store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStore creates a new file credentials store.
|
||||||
|
func NewFileStore(file store) Store {
|
||||||
|
return &fileStore{file: file}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase removes the given credentials from the file store.
|
||||||
|
func (c *fileStore) Erase(serverAddress string) error {
|
||||||
|
delete(c.file.GetAuthConfigs(), serverAddress)
|
||||||
|
return c.file.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials for a specific server from the file store.
|
||||||
|
func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
authConfig, ok := c.file.GetAuthConfigs()[serverAddress]
|
||||||
|
if !ok {
|
||||||
|
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||||
|
// them to the new format and testing
|
||||||
|
for r, ac := range c.file.GetAuthConfigs() {
|
||||||
|
if serverAddress == ConvertToHostname(r) {
|
||||||
|
return ac, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig = types.AuthConfig{}
|
||||||
|
}
|
||||||
|
return authConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||||
|
return c.file.GetAuthConfigs(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the given credentials in the file store.
|
||||||
|
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||||
|
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
|
||||||
|
return c.file.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileStore) GetFilename() string {
|
||||||
|
return c.file.GetFilename()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileStore) IsFileStore() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToHostname converts a registry url which has http|https prepended
|
||||||
|
// to just an hostname.
|
||||||
|
// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies.
|
||||||
|
func ConvertToHostname(url string) string {
|
||||||
|
stripped := url
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
stripped = strings.TrimPrefix(url, "http://")
|
||||||
|
} else if strings.HasPrefix(url, "https://") {
|
||||||
|
stripped = strings.TrimPrefix(url, "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
nameParts := strings.SplitN(stripped, "/", 2)
|
||||||
|
|
||||||
|
return nameParts[0]
|
||||||
|
}
|
143
vendor/github.com/docker/cli/cli/config/credentials/native_store.go
generated
vendored
Normal file
143
vendor/github.com/docker/cli/cli/config/credentials/native_store.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/cli/cli/config/types"
|
||||||
|
"github.com/docker/docker-credential-helpers/client"
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
remoteCredentialsPrefix = "docker-credential-"
|
||||||
|
tokenUsername = "<token>"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nativeStore implements a credentials store
|
||||||
|
// using native keychain to keep credentials secure.
|
||||||
|
// It piggybacks into a file store to keep users' emails.
|
||||||
|
type nativeStore struct {
|
||||||
|
programFunc client.ProgramFunc
|
||||||
|
fileStore Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNativeStore creates a new native store that
|
||||||
|
// uses a remote helper program to manage credentials.
|
||||||
|
func NewNativeStore(file store, helperSuffix string) Store {
|
||||||
|
name := remoteCredentialsPrefix + helperSuffix
|
||||||
|
return &nativeStore{
|
||||||
|
programFunc: client.NewShellProgramFunc(name),
|
||||||
|
fileStore: NewFileStore(file),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase removes the given credentials from the native store.
|
||||||
|
func (c *nativeStore) Erase(serverAddress string) error {
|
||||||
|
if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to plain text store to remove email
|
||||||
|
return c.fileStore.Erase(serverAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves credentials for a specific server from the native store.
|
||||||
|
func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
// load user email if it exist or an empty auth config.
|
||||||
|
auth, _ := c.fileStore.Get(serverAddress)
|
||||||
|
|
||||||
|
creds, err := c.getCredentialsFromStore(serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return auth, err
|
||||||
|
}
|
||||||
|
auth.Username = creds.Username
|
||||||
|
auth.IdentityToken = creds.IdentityToken
|
||||||
|
auth.Password = creds.Password
|
||||||
|
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll retrieves all the credentials from the native store.
|
||||||
|
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||||
|
auths, err := c.listCredentialsInStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emails are only stored in the file store.
|
||||||
|
// This call can be safely eliminated when emails are removed.
|
||||||
|
fileConfigs, _ := c.fileStore.GetAll()
|
||||||
|
|
||||||
|
authConfigs := make(map[string]types.AuthConfig)
|
||||||
|
for registry := range auths {
|
||||||
|
creds, err := c.getCredentialsFromStore(registry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ac := fileConfigs[registry] // might contain Email
|
||||||
|
ac.Username = creds.Username
|
||||||
|
ac.Password = creds.Password
|
||||||
|
ac.IdentityToken = creds.IdentityToken
|
||||||
|
authConfigs[registry] = ac
|
||||||
|
}
|
||||||
|
|
||||||
|
return authConfigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the given credentials in the file store.
|
||||||
|
func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
||||||
|
if err := c.storeCredentialsInStore(authConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authConfig.Username = ""
|
||||||
|
authConfig.Password = ""
|
||||||
|
authConfig.IdentityToken = ""
|
||||||
|
|
||||||
|
// Fallback to old credential in plain text to save only the email
|
||||||
|
return c.fileStore.Store(authConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeCredentialsInStore executes the command to store the credentials in the native store.
|
||||||
|
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
||||||
|
creds := &credentials.Credentials{
|
||||||
|
ServerURL: config.ServerAddress,
|
||||||
|
Username: config.Username,
|
||||||
|
Secret: config.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IdentityToken != "" {
|
||||||
|
creds.Username = tokenUsername
|
||||||
|
creds.Secret = config.IdentityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Store(c.programFunc, creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCredentialsFromStore executes the command to get the credentials from the native store.
|
||||||
|
func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
||||||
|
var ret types.AuthConfig
|
||||||
|
|
||||||
|
creds, err := client.Get(c.programFunc, serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
if credentials.IsErrCredentialsNotFound(err) {
|
||||||
|
// do not return an error if the credentials are not
|
||||||
|
// in the keychain. Let docker ask for new credentials.
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds.Username == tokenUsername {
|
||||||
|
ret.IdentityToken = creds.Secret
|
||||||
|
} else {
|
||||||
|
ret.Password = creds.Secret
|
||||||
|
ret.Username = creds.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.ServerAddress = serverAddress
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listCredentialsInStore returns a listing of stored credentials as a map of
|
||||||
|
// URL -> username.
|
||||||
|
func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
||||||
|
return client.List(c.programFunc)
|
||||||
|
}
|
22
vendor/github.com/docker/cli/cli/config/types/authconfig.go
generated
vendored
Normal file
22
vendor/github.com/docker/cli/cli/config/types/authconfig.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// AuthConfig contains authorization information for connecting to a Registry
|
||||||
|
type AuthConfig struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Auth string `json:"auth,omitempty"`
|
||||||
|
|
||||||
|
// Email is an optional value associated with the username.
|
||||||
|
// This field is deprecated and will be removed in a later
|
||||||
|
// version of docker.
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
|
||||||
|
ServerAddress string `json:"serveraddress,omitempty"`
|
||||||
|
|
||||||
|
// IdentityToken is used to authenticate the user and get
|
||||||
|
// an access token for the registry.
|
||||||
|
IdentityToken string `json:"identitytoken,omitempty"`
|
||||||
|
|
||||||
|
// RegistryToken is a bearer token to be sent to a registry
|
||||||
|
RegistryToken string `json:"registrytoken,omitempty"`
|
||||||
|
}
|
15
vendor/github.com/docker/cli/scripts/docs/generate-authors.sh
generated
vendored
Executable file
15
vendor/github.com/docker/cli/scripts/docs/generate-authors.sh
generated
vendored
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$(readlink -f "${BASH_SOURCE[*]}")")/../.."
|
||||||
|
|
||||||
|
# see also ".mailmap" for how email addresses and names are deduplicated
|
||||||
|
|
||||||
|
{
|
||||||
|
cat <<-'EOH'
|
||||||
|
# This file lists all individuals having contributed content to the repository.
|
||||||
|
# For how it is generated, see `scripts/docs/generate-authors.sh`.
|
||||||
|
EOH
|
||||||
|
echo
|
||||||
|
git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf
|
||||||
|
} > AUTHORS
|
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2016 David Calavera
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
121
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
121
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isValidCredsMessage checks if 'msg' contains invalid credentials error message.
|
||||||
|
// It returns whether the logs are free of invalid credentials errors and the error if it isn't.
|
||||||
|
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername.
|
||||||
|
func isValidCredsMessage(msg string) error {
|
||||||
|
if credentials.IsCredentialsMissingServerURLMessage(msg) {
|
||||||
|
return credentials.NewErrCredentialsMissingServerURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
if credentials.IsCredentialsMissingUsernameMessage(msg) {
|
||||||
|
return credentials.NewErrCredentialsMissingUsername()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store uses an external program to save credentials.
|
||||||
|
func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||||
|
cmd := program("store")
|
||||||
|
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Input(buffer)
|
||||||
|
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t := strings.TrimSpace(string(out))
|
||||||
|
|
||||||
|
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||||
|
err = isValidErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get executes an external program to get the credentials from a native store.
|
||||||
|
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
|
||||||
|
cmd := program("get")
|
||||||
|
cmd.Input(strings.NewReader(serverURL))
|
||||||
|
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t := strings.TrimSpace(string(out))
|
||||||
|
|
||||||
|
if credentials.IsErrCredentialsNotFoundMessage(t) {
|
||||||
|
return nil, credentials.NewErrCredentialsNotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||||
|
err = isValidErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &credentials.Credentials{
|
||||||
|
ServerURL: serverURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase executes a program to remove the server credentials from the native store.
|
||||||
|
func Erase(program ProgramFunc, serverURL string) error {
|
||||||
|
cmd := program("erase")
|
||||||
|
cmd.Input(strings.NewReader(serverURL))
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t := strings.TrimSpace(string(out))
|
||||||
|
|
||||||
|
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||||
|
err = isValidErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List executes a program to list server credentials in the native store.
|
||||||
|
func List(program ProgramFunc) (map[string]string, error) {
|
||||||
|
cmd := program("list")
|
||||||
|
cmd.Input(strings.NewReader("unused"))
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t := strings.TrimSpace(string(out))
|
||||||
|
|
||||||
|
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||||
|
err = isValidErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp map[string]string
|
||||||
|
if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user