Compare commits
323 Commits
Author | SHA1 | Date |
---|---|---|
|
ee7c96f201 | |
|
d0bdb01ee9 | |
|
8338cebec2 | |
|
44a77d77e9 | |
|
277757c333 | |
|
0b7ae2f1b1 | |
|
40b0a08c95 | |
|
7f701df6ab | |
|
a28faab926 | |
|
3f7369aeb9 | |
|
097592d34b | |
|
d2f3f46deb | |
|
3578abe586 | |
|
5f84615a5d | |
|
3b4dfc5dff | |
|
a845cc88d2 | |
|
4c9ae43c0e | |
|
be3f5a9b2d | |
|
f4f2dc7430 | |
|
81ed2d06d6 | |
|
00f4094a12 | |
|
f0289faa47 | |
|
81d15e90c3 | |
|
309b6bbc17 | |
|
692efbd2a7 | |
|
6a68851f26 | |
|
d5c71ad528 | |
|
587837eeda | |
|
425425837e | |
|
c0cc785dbc | |
|
9f73d4da82 | |
|
e82d9969cf | |
|
2c926b5f17 | |
|
ed014fe9dc | |
|
db70b6d343 | |
|
fac588ba52 | |
|
6e57c2ee5d | |
|
499596af1b | |
|
73debca5ce | |
|
9d422db72c | |
|
c28331408d | |
|
1125b83816 | |
|
529bccb346 | |
|
267db7092b | |
|
cebd7dff3a | |
|
6ef571535d | |
|
02ffd013fe | |
|
1e7858f987 | |
|
c04330ee9e | |
|
52babb0dd3 | |
|
9f32fa36f7 | |
|
f5e23f52c0 | |
|
85241009c7 | |
|
e34763a7ec | |
|
f85b8fe31f | |
|
3d7be1bae9 | |
|
df52e9460d | |
|
047255eeed | |
|
0137b32398 | |
|
dd65e2d527 | |
|
bbae2202a6 | |
|
0036b628e1 | |
|
83287db830 | |
|
a67dabb3d0 | |
|
951d8d0e05 | |
|
f846cb72b0 | |
|
e4b261e1a2 | |
|
829ae61c58 | |
|
86c37cb0a2 | |
|
56f11c6c0c | |
|
8eb1091d78 | |
|
a448e71e98 | |
|
2e9688f3b4 | |
|
1c8da137b2 | |
|
c45adb66fe | |
|
563cf56c27 | |
|
2d04079cfe | |
|
845a73789a | |
|
00576a2e55 | |
|
a4d4a0d1ff | |
|
06d9dec402 | |
|
d9f3a7bb2b | |
|
c822f1331b | |
|
163db33480 | |
|
94fc2b1545 | |
|
3c4079b3db | |
|
b62753aa2b | |
|
1557aeb57c | |
|
dc24f70105 | |
|
8ddb290852 | |
|
d1276f1ed4 | |
|
66c292a7c5 | |
|
2317778d4d | |
|
a6a989119a | |
|
1362169a1f | |
|
f51f8eafe2 | |
|
f8d7b81f43 | |
|
6bac4ee028 | |
|
91db252b9d | |
|
d2bbac8e63 | |
|
0e4c0e225b | |
|
2cec989c82 | |
|
4caff13753 | |
|
9c19f13738 | |
|
d654dde532 | |
|
b9a9324609 | |
|
4a0bccb249 | |
|
efffbeeef4 | |
|
93878e6530 | |
|
26cfdbe95b | |
|
cf9ca9e003 | |
|
baaee10d93 | |
|
cd1b855eae | |
|
c18965d032 | |
|
911855c357 | |
|
8640c3ff16 | |
|
58eb836866 | |
|
6ed65ac99d | |
|
f67e207467 | |
|
17331add54 | |
|
cfdc39b16b | |
|
509c4908cf | |
|
5968d61a1e | |
|
94fd332ecd | |
|
0c68fe12dc | |
|
a6eaa01146 | |
|
58553d6cdb | |
|
c9fdf812d2 | |
|
82b44c5282 | |
|
29ba516d90 | |
|
b7d75ff4da | |
|
4683716a59 | |
|
2cfe79728d | |
|
91629d5751 | |
|
b15c73737e | |
|
e86c9ca58f | |
|
02155e0c8c | |
|
d9f1e675b7 | |
|
8fdefe9412 | |
|
743a44d404 | |
|
004b09b6a6 | |
|
902534de61 | |
|
11077bcc03 | |
|
0021b93858 | |
|
f6506e215f | |
|
3072cfe946 | |
|
3bbe370e68 | |
|
44a6eef2b2 | |
|
837171b012 | |
|
215259b17d | |
|
4a096f6be2 | |
|
ca96f4ca96 | |
|
a899051e44 | |
|
c768dcb129 | |
|
622537633d | |
|
e34f9d2bff | |
|
e255525e4d | |
|
2161bf8c24 | |
|
a8fdbf9c40 | |
|
dc0779e8ce | |
|
b6608f8c87 | |
|
15612fccaa | |
|
10ec024835 | |
|
cfb0b54407 | |
|
c9cfcaa2c7 | |
|
db718fc6d5 | |
|
4093160909 | |
|
9302e5ff67 | |
|
9a71f523e2 | |
|
05f80e4fd9 | |
|
f8aa587bba | |
|
aef15f60b3 | |
|
6626820a52 | |
|
435fcb1414 | |
|
52b1cb11a5 | |
|
d4c7848c2a | |
|
d42556829e | |
|
1ffb655895 | |
|
cb0d26535b | |
|
420e5949b0 | |
|
024c57f43f | |
|
0821ff55fc | |
|
121798a08d | |
|
f89a8c6a3e | |
|
da8672c859 | |
|
c5603949c1 | |
|
8ac5c8a7f1 | |
|
ed461a9b0e | |
|
5fa3464d59 | |
|
6a43fb5511 | |
|
45761d9522 | |
|
aeb1d8ebf8 | |
|
dd2d40c4f6 | |
|
97954935c5 | |
|
0a26996875 | |
|
cc036171cc | |
|
55be4ccd0d | |
|
f024754da8 | |
|
e18f6322ff | |
|
6e5ac36b42 | |
|
58488a6214 | |
|
e629d106b4 | |
|
62709e0a59 | |
|
76aaefb2cb | |
|
04dce8c7d3 | |
|
307ed01652 | |
|
be9139d588 | |
|
30fd41546b | |
|
dc22d0462d | |
|
69967699d7 | |
|
08fb4605fc | |
|
dbf33e2c59 | |
|
8dba3824fb | |
|
40fa4795fb | |
|
a710a7b4c1 | |
|
3363d14368 | |
|
1c7c696779 | |
|
812a8cfa8a | |
|
58b77bdf71 | |
|
3ec19197ef | |
|
55fe94e885 | |
|
940e662699 | |
|
99eac24911 | |
|
08f8596ff3 | |
|
1054f8ead7 | |
|
f32e3df6fb | |
|
c9114c284a | |
|
54f1587d67 | |
|
bb46e27465 | |
|
e6dea17746 | |
|
aba8f8b1fb | |
|
4b46fe6d73 | |
|
2f6d8b1930 | |
|
35efaabf93 | |
|
e4a9f965d0 | |
|
96a1883818 | |
|
3e49ce16b5 | |
|
96425dafeb | |
|
b92c836f3a | |
|
269bf6103d | |
|
48fac6a491 | |
|
798e63d83f | |
|
4756a3616c | |
|
cff680034b | |
|
10f777cb7d | |
|
9070cb3eec | |
|
1d9f32dc0e | |
|
21cd5f0c1f | |
|
c3625975b3 | |
|
6a92df875a | |
|
42f24747b7 | |
|
5725f27869 | |
|
2e4887bf8a | |
|
1694fd7b57 | |
|
c7f5f70554 | |
|
5608690f77 | |
|
699ad28878 | |
|
30e06a8b76 | |
|
0dfe02421e | |
|
a9562466f1 | |
|
63a3bca188 | |
|
b277ec1bc2 | |
|
34a8a464e1 | |
|
76bf3de7f8 | |
|
b4e078af7a | |
|
4feedb9dde | |
|
57cf1cee7d | |
|
4fdc5f62c9 | |
|
3a8fc073b5 | |
|
f30a8249fd | |
|
c63d850d60 | |
|
37ce8d6fbe | |
|
9546b704c8 | |
|
7ba00c594c | |
|
3a13f6854d | |
|
05ea7269e1 | |
|
16f664ce57 | |
|
c92642bda0 | |
|
67ec90448e | |
|
e33f449ec9 | |
|
0555966685 | |
|
8b25a7bf9e | |
|
8ad568b543 | |
|
ea59a90103 | |
|
00169178ed | |
|
c2f6afa41f | |
|
9c075a3e40 | |
|
23b841a269 | |
|
e00ed00197 | |
|
ad59be0959 | |
|
b5ab16f010 | |
|
e32b586636 | |
|
296290a913 | |
|
a199e6aa75 | |
|
a5c35b1d17 | |
|
ada67263b1 | |
|
15391a8c4a | |
|
eec3755640 | |
|
216a7099cb | |
|
867451c7a0 | |
|
77cd8fe3d6 | |
|
75e7788841 | |
|
679ed9d310 | |
|
6d8228e1d3 | |
|
c7357803b1 | |
|
b678c260c4 | |
|
62e54113f4 | |
|
e781c94eea | |
|
d1e1ae3c34 | |
|
7555ca3250 | |
|
6823eba0ec | |
|
3805b13a3b | |
|
0050bfa528 | |
|
6fc72a49e9 | |
|
90311ea2b0 | |
|
e91547363e | |
|
f9b5c9b162 | |
|
17b2a04fa4 | |
|
e5c65a5bf3 | |
|
6b482fdc76 | |
|
65bf6884d1 | |
|
a7cceb916d | |
|
93a7425495 |
|
@ -1,17 +0,0 @@
|
|||
clone_folder: c:\gopath\src\github.com\containernetworking\cni
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
|
||||
install:
|
||||
- echo %PATH%
|
||||
- echo %GOPATH%
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- go get -t ./...
|
||||
- go test -v ./...
|
|
@ -0,0 +1,2 @@
|
|||
# Don't rewrite line endings
|
||||
*.go -text
|
|
@ -0,0 +1,7 @@
|
|||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
@ -0,0 +1,11 @@
|
|||
name: 'Re-Test'
|
||||
description: 'Re-Runs the last workflow for a PR'
|
||||
inputs:
|
||||
token:
|
||||
description: 'GitHub API Token'
|
||||
required: true
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
if ! jq -e '.issue.pull_request' ${GITHUB_EVENT_PATH}; then
|
||||
echo "Not a PR... Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$(jq -r '.comment.body' ${GITHUB_EVENT_PATH})" != "/retest" ]; then
|
||||
echo "Nothing to do... Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PR_URL=$(jq -r '.issue.pull_request.url' ${GITHUB_EVENT_PATH})
|
||||
|
||||
curl --request GET \
|
||||
--url "${PR_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json" > pr.json
|
||||
|
||||
ACTOR=$(jq -r '.user.login' pr.json)
|
||||
BRANCH=$(jq -r '.head.ref' pr.json)
|
||||
|
||||
curl --request GET \
|
||||
--url "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?event=pull_request&actor=${ACTOR}&branch=${BRANCH}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
|
||||
|
||||
RERUN_URL=$(jq -r '.rerun_url' run.json)
|
||||
|
||||
curl --request POST \
|
||||
--url "${RERUN_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json"
|
||||
|
||||
|
||||
REACTION_URL="$(jq -r '.comment.url' ${GITHUB_EVENT_PATH})/reactions"
|
||||
|
||||
curl --request POST \
|
||||
--url "${REACTION_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "accept: application/vnd.github.squirrel-girl-preview+json" \
|
||||
--header "content-type: application/json" \
|
||||
--data '{ "content" : "rocket" }'
|
|
@ -0,0 +1,27 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/.github/actions/retest-action"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
golang:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/plugins/debug"
|
||||
schedule:
|
||||
interval: "weekly"
|
|
@ -0,0 +1,17 @@
|
|||
name: commands
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
retest:
|
||||
if: github.repository == 'containernetworking/cni'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
|
@ -0,0 +1,40 @@
|
|||
name: Scorecard supply-chain security
|
||||
on:
|
||||
branch_protection_rule:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: 29 15 * * 0
|
||||
permissions: read-all
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
permissions:
|
||||
id-token: write
|
||||
security-events: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7
|
||||
with:
|
||||
sarif_file: results.sarif
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
name: test
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.22"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1
|
||||
with:
|
||||
format: auto
|
||||
config_file: .yamllint.yaml
|
||||
|
||||
- uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
|
||||
with:
|
||||
args: --verbose
|
||||
version: v1.57.1
|
||||
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Build on all supported architectures
|
||||
run: |
|
||||
set -e
|
||||
for arch in ${LINUX_ARCHES}; do
|
||||
echo "Building for arch $arch"
|
||||
GOARCH=$arch go build ./...
|
||||
done
|
||||
|
||||
test-linux:
|
||||
name: Run tests on Linux amd64
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Install test binaries
|
||||
run: |
|
||||
go install github.com/mattn/goveralls@v0.0.12
|
||||
go install github.com/modocache/gover@latest
|
||||
|
||||
- name: test
|
||||
run: COVERALLS=1 ./test.sh
|
||||
|
||||
- env:
|
||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Send coverage to coveralls
|
||||
run: |
|
||||
PATH=$PATH:$(go env GOPATH)/bin
|
||||
gover
|
||||
goveralls -coverprofile=gover.coverprofile -service=github
|
||||
|
||||
test-win:
|
||||
name: Build and run tests on Windows
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: test
|
||||
run: bash ./test.sh
|
|
@ -1,6 +1,5 @@
|
|||
.idea/
|
||||
bin/
|
||||
gopath/
|
||||
*.sw[ponm]
|
||||
.vagrant
|
||||
release-*
|
||||
cnitool/cnitool
|
|
@ -0,0 +1,30 @@
|
|||
linters:
|
||||
enable:
|
||||
- contextcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gci
|
||||
- ginkgolinter
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- nonamedreturns
|
||||
- predeclared
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/containernetworking)
|
||||
|
||||
run:
|
||||
timeout: 5m
|
38
.travis.yml
38
.travis.yml
|
@ -1,38 +0,0 @@
|
|||
language: go
|
||||
dist: bionic
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get -t ./...
|
||||
|
||||
script:
|
||||
- >
|
||||
if [ "${TARGET}" == "amd64" ]; then
|
||||
GOARCH="${TARGET}" ./test.sh;
|
||||
else
|
||||
GOARCH="${TARGET}" go list ./... | xargs -n1 go build -v -o /dev/null
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
git:
|
||||
depth: 9999999
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
document-start: disable
|
||||
line-length: disable
|
||||
truthy:
|
||||
ignore: |
|
||||
.github/workflows/*.yml
|
||||
.github/workflows/*.yaml
|
|
@ -1,3 +1,3 @@
|
|||
# Community Code of Conduct
|
||||
|
||||
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
|
||||
|
|
|
@ -34,7 +34,7 @@ are very busy and read the mailing lists.
|
|||
|
||||
This is a rough outline of how to prepare a contribution:
|
||||
|
||||
- Create a topic branch from where you want to base your work (usually branched from master).
|
||||
- Create a topic branch from where you want to base your work (usually branched from main).
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
|
|
|
@ -10,7 +10,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
|
|||
|
||||
## Plugins
|
||||
* Plugin authors should aim to support these conventions where it makes sense for their plugin. This means they are more likely to "just work" with a wider range of runtimes.
|
||||
* Plugins should accept arguments according to these conventions if they implement the same basic functionality as other plugins. If plugins have shared functionality that isn't coverered by these conventions then a PR should be opened against this document.
|
||||
* Plugins should accept arguments according to these conventions if they implement the same basic functionality as other plugins. If plugins have shared functionality that isn't covered by these conventions then a PR should be opened against this document.
|
||||
|
||||
## Runtimes
|
||||
* Runtime authors should follow these conventions if they want to pass additional information to plugins. This will allow the extra information to be consumed by the widest range of plugins.
|
||||
|
@ -20,7 +20,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
|
|||
Additional conventions can be created by creating PRs which modify this document.
|
||||
|
||||
## Dynamic Plugin specific fields (Capabilities / Runtime Configuration)
|
||||
[Plugin specific fields](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
|
||||
[Plugin specific fields](SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
|
||||
> Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the args field may be used to pass arbitrary data which may be ignored by plugins.
|
||||
|
||||
A plugin can define any additional fields it needs to work properly. It should return an error if it can't act on fields that were expected or where the field values were malformed.
|
||||
|
@ -32,7 +32,7 @@ This method of passing information to a plugin is recommended when the following
|
|||
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section. Plugins can request
|
||||
that the runtime insert this dynamic configuration by explicitly listing their `capabilities` in the network configuration.
|
||||
|
||||
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists).
|
||||
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](SPEC.md#network-configuration-lists).
|
||||
```json
|
||||
{
|
||||
"name" : "ExamplePlugin",
|
||||
|
@ -61,13 +61,15 @@ But the runtime would fill in the mappings so the plugin itself would receive so
|
|||
| ip ranges | Dynamically configure the IP range(s) for address allocation. Runtimes that manage IP pools, but not individual IP addresses, can pass these to plugins. | `ipRanges` | The same as the `ranges` key for `host-local` - a list of lists of subnets. The outer list is the number of IPs to allocate, and the inner list is a pool of subnets for each allocation. <br/><pre>[<br/> [<br/> { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" } <br/> ]<br/>]</pre> | none | CNI `host-local` plugin |
|
||||
| bandwidth limits | Dynamically configure interface bandwidth limits | `bandwidth` | Desired bandwidth limits. Rates are in bits per second, burst values are in bits. <pre> { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } </pre> | none | CNI `bandwidth` plugin |
|
||||
| dns | Dynamically configure dns according to runtime | `dns` | Dictionary containing a list of `servers` (string entries), a list of `searches` (string entries), a list of `options` (string entries). <pre>{ <br> "searches" : [ "internal.yoyodyne.net", "corp.tyrell.net" ] <br> "servers": [ "8.8.8.8", "10.0.0.10" ] <br />} </pre> | kubernetes | CNI `win-bridge` plugin, CNI `win-overlay` plugin |
|
||||
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (string entries). <pre> [ "10.10.0.1/24", "3ffe:ffff:0:01ff::1/64" ] </pre> | none | CNI `static` plugin |
|
||||
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (\<ip\>\[/\<prefix\>\]). <pre> [ "192.168.0.1", 10.10.0.1/24", "3ffe:ffff:0:01ff::2", "3ffe:ffff:0:01ff::1/64" ] </pre> The plugin may require the IP address to include a prefix length. | none | CNI `static` plugin, CNI `host-local` plugin |
|
||||
| mac | Dynamically assign MAC. Runtime can pass this to plugins which need MAC as input. | `mac` | `MAC` (string entry). <pre> "c2:11:22:33:44:55" </pre> | none | CNI `tuning` plugin |
|
||||
| infiniband guid | Dynamically assign Infiniband GUID to network interface. Runtime can pass this to plugins which need Infiniband GUID as input. | `infinibandGUID` | `GUID` (string entry). <pre> "c2:11:22:33:44:55:66:77" </pre> | none | CNI [`ib-sriov-cni`](https://github.com/Mellanox/ib-sriov-cni) plugin |
|
||||
| device id | Provide device identifier which is associated with the network to allow the CNI plugin to perform device dependent network configurations. | `deviceID` | `deviceID` (string entry). <pre> "0000:04:00.5" </pre> | none | CNI `host-local` plugin |
|
||||
| device id | Provide device identifier which is associated with the network to allow the CNI plugin to perform device dependent network configurations. | `deviceID` | `deviceID` (string entry). <pre> "0000:04:00.5" </pre> | none | CNI `host-device` plugin |
|
||||
| aliases | Provide a list of names that will be mapped to the IP addresses assigned to this interface. Other containers on the same network may use one of these names to access the container.| `aliases` | List of `alias` (string entry). <pre> ["my-container", "primary-db"] </pre> | none | CNI `alias` plugin |
|
||||
| cgroup path | Provide the cgroup path for pod as requested by CNI plugins. | `cgroupPath` | `cgroupPath` (string entry). <pre>"/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-burstable.slice/kubelet-kubepods-burstable-pod28ce45bc_63f8_48a3_a99b_cfb9e63c856c.slice" </pre> | none | CNI `host-local` plugin |
|
||||
|
||||
## "args" in network config
|
||||
`args` in [network config](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) were introduced as an optional field into the `0.2.0` release of the CNI spec. The first CNI code release that it appeared in was `v0.4.0`.
|
||||
`args` in [network config](SPEC.md#network-configuration) were reserved as a field in the `0.2.0` release of the CNI spec.
|
||||
> args (dictionary): Optional additional arguments provided by the container runtime. For example a dictionary of labels could be passed to CNI plugins by adding them to a labels field under args.
|
||||
|
||||
`args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support.
|
||||
|
@ -79,7 +81,7 @@ This method of passing information to a plugin is recommended when the informati
|
|||
The conventions documented here are all namespaced under `cni` so they don't conflict with any existing `args`.
|
||||
|
||||
For example:
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"cniVersion":"0.2.0",
|
||||
"name":"net",
|
||||
|
@ -88,9 +90,9 @@ For example:
|
|||
"labels": [{"key": "app", "value": "myapp"}]
|
||||
}
|
||||
},
|
||||
<REST OF CNI CONFIG HERE>
|
||||
// <REST OF CNI CONFIG HERE>
|
||||
"ipam":{
|
||||
<IPAM CONFIG HERE>
|
||||
// <IPAM CONFIG HERE>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -98,7 +100,7 @@ For example:
|
|||
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
|
||||
| ----- | ------ | ------------ | ----------------------- | ---------------------- |
|
||||
| labels | Pass`key=value` labels to plugins | <pre>"labels" : [<br /> { "key" : "app", "value" : "myapp" },<br /> { "key" : "env", "value" : "prod" }<br />] </pre> | none | none |
|
||||
| ips | Request static IPs | Spec:<pre>"ips": ["\<ip\>[/\<prefix\>]", ...]</pre>Examples:<pre>"ips": ["10.2.2.42/24", "2001:db8::5"]</pre>The plugin may require the IP address to include a prefix length. | none | host-local |
|
||||
| ips | Request specific IPs | Spec:<pre>"ips": ["\<ip\>[/\<prefix\>]", ...]</pre>Examples:<pre>"ips": ["10.2.2.42/24", "2001:db8::5"]</pre> The plugin may require the IP address to include a prefix length. | none | host-local, static |
|
||||
|
||||
## CNI_ARGS
|
||||
CNI_ARGS formed part of the original CNI spec and have been present since the initial release.
|
||||
|
@ -108,7 +110,7 @@ The use of `CNI_ARGS` is deprecated and "args" should be used instead. If a runt
|
|||
|
||||
| Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
|
||||
| ------ | ------ | ---------------- | ----------------------- | ---------------------- |
|
||||
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre>The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local (since version v0.2.0) supports the field for IPv4 only - [documentation](https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#supported-arguments).|
|
||||
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre> The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local, static |
|
||||
|
||||
## Chained Plugins
|
||||
If plugins are agnostic about the type of interface created, they SHOULD work in a chained mode and configure existing interfaces. Plugins MAY also create the desired interface when not run in a chain.
|
||||
|
|
|
@ -1,12 +1,45 @@
|
|||
# How to upgrade to CNI Specification v0.3.1
|
||||
# How to Upgrade to CNI Specification v1.0
|
||||
|
||||
CNI v1.0 has the following changes:
|
||||
|
||||
- non-List configurations are removed
|
||||
- the `version` field in the `interfaces` array was redundant and is removed
|
||||
|
||||
## libcni Changes in CNI v1.0
|
||||
|
||||
**`/pkg/types/current` no longer exists**
|
||||
|
||||
This means that runtimes need to explicitly select a version they support.
|
||||
This reduces code breakage when revendoring cni into other projects and
|
||||
returns the decision on which CNI Spec versions a plugin supports to the
|
||||
plugin's authors.
|
||||
|
||||
For example, your Go imports might look like
|
||||
|
||||
```go
|
||||
import (
|
||||
cniv1 "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
# Changes in CNI v0.4
|
||||
|
||||
CNI v0.4 has the following important changes:
|
||||
|
||||
- A new verb, "CHECK", was added. Runtimes can now ask plugins to verify the status of a container's attachment
|
||||
- A new configuration flag, `disableCheck`, which indicates to the runtime that configuration should not be CHECK'ed
|
||||
|
||||
No changes were made to the result type.
|
||||
|
||||
|
||||
# How to upgrade to CNI Specification v0.3.0 and later
|
||||
|
||||
The 0.3.0 specification contained a small error. The Result structure's `ip` field should have been renamed to `ips` to be consistent with the IPAM result structure definition; this rename was missed when updating the Result to accommodate multiple IP addresses and interfaces. All first-party CNI plugins (bridge, host-local, etc) were updated to use `ips` (and thus be inconsistent with the 0.3.0 specification) and most other plugins have not been updated to the 0.3.0 specification yet, so few (if any) users should be impacted by this change.
|
||||
|
||||
The 0.3.1 specification corrects the Result structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
|
||||
The 0.3.1 specification corrects the `Result` structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
|
||||
|
||||
# How to upgrade to CNI Specification v0.3.0
|
||||
|
||||
Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information
|
||||
Version 0.3.0 of the [CNI Specification](https://github.com/containernetworking/cni/blob/spec-v0.3.0/SPEC.md) provides rich information
|
||||
about container network configuration, including details of network interfaces
|
||||
and support for multiple IP addresses.
|
||||
|
||||
|
@ -31,12 +64,12 @@ ensure that the configuration files specify a `cniVersion` field and that the
|
|||
version there is supported by your container runtime and CNI plugins.
|
||||
Configuration files without a version field should be given version 0.2.0.
|
||||
The CNI spec includes example configuration files for
|
||||
[single plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations)
|
||||
and for [lists of chained plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations).
|
||||
[single plugins](SPEC.md#example-configurations)
|
||||
and for [lists of chained plugins](SPEC.md#example-configurations).
|
||||
|
||||
Consult the documentation for your runtime and plugins to determine what
|
||||
CNI spec versions they support. Test any plugin upgrades before deploying to
|
||||
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool)
|
||||
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/main/cnitool)
|
||||
useful. Specifically, your configuration version should be the lowest common
|
||||
version supported by your plugins.
|
||||
|
||||
|
@ -46,7 +79,7 @@ This section provides guidance for upgrading plugins to CNI Spec Version 0.3.0.
|
|||
### General guidance for all plugins (language agnostic)
|
||||
To provide the smoothest upgrade path, **existing plugins should support
|
||||
multiple versions of the CNI spec**. In particular, plugins with existing
|
||||
installed bases should add support for CNI spec version 0.3.0 while maintaining
|
||||
installed bases should add support for CNI spec version 1.0.0 while maintaining
|
||||
compatibility with older versions.
|
||||
|
||||
To do this, two changes are required. First, a plugin should advertise which
|
||||
|
@ -55,13 +88,13 @@ command with the following JSON data:
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.0",
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0" ]
|
||||
"cniVersion": "1.0.0",
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
|
||||
}
|
||||
```
|
||||
|
||||
Second, for the `ADD` command, a plugin must respect the `cniVersion` field
|
||||
provided in the [network configuration JSON](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration).
|
||||
provided in the [network configuration JSON](SPEC.md#network-configuration).
|
||||
That field is a request for the plugin to return results of a particular format:
|
||||
|
||||
- If the `cniVersion` field is not present, then spec v0.2.0 should be assumed
|
||||
|
@ -69,11 +102,11 @@ That field is a request for the plugin to return results of a particular format:
|
|||
|
||||
- If the plugin doesn't support the version, the plugin must error.
|
||||
|
||||
- Otherwise, the plugin must return a [CNI Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
|
||||
- Otherwise, the plugin must return a [CNI Result](SPEC.md#result)
|
||||
in the format requested.
|
||||
|
||||
Result formats for older CNI spec versions are available in the
|
||||
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/master/SPEC.md).
|
||||
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/main/SPEC.md).
|
||||
|
||||
For example, suppose a plugin, via its `VERSION` response, advertises CNI specification
|
||||
support for v0.2.0 and v0.3.0. When it receives `cniVersion` key of `0.2.0`,
|
||||
|
@ -88,15 +121,15 @@ require some changes now, but should more-easily handle spec changes and
|
|||
new features going forward.
|
||||
|
||||
For plugin authors, the biggest change is that `types.Result` is now an
|
||||
interface implemented by concrete struct types in the `types/current` and
|
||||
`types/020` subpackages.
|
||||
interface implemented by concrete struct types in the `types/100`,
|
||||
`types/040`, and `types/020` subpackages.
|
||||
|
||||
Internally, plugins should use the `types/current` structs, and convert
|
||||
to or from specific versions when required. A typical plugin will only need
|
||||
to do a single conversion. That is when it is about to complete and needs to
|
||||
print the result JSON in the correct format to stdout. The library
|
||||
function `types.PrintResult()` simplifies this by converting and printing in
|
||||
a single call.
|
||||
Internally, plugins should use the latest spec version (eg `types/100`) structs,
|
||||
and convert to or from specific versions when required. A typical plugin will
|
||||
only need to do a single conversion when it is about to complete and
|
||||
needs to print the result JSON in the requested `cniVersion` format to stdout.
|
||||
The library function `types.PrintResult()` simplifies this by converting and
|
||||
printing in a single call.
|
||||
|
||||
Additionally, the plugin should advertise which CNI Spec versions it supports
|
||||
via the 3rd argument to `skel.PluginMain()`.
|
||||
|
@ -107,7 +140,7 @@ Here is some example code
|
|||
import (
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
|
@ -136,7 +169,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"))
|
||||
skel.PluginMain(cmdAdd, cmdDel, version.All)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -149,13 +182,13 @@ result, err := current.NewResultFromResult(ipamResult)
|
|||
```
|
||||
|
||||
Other examples of spec v0.3.0-compatible plugins are the
|
||||
[main plugins in this repo](https://github.com/containernetworking/plugins/tree/master/plugins)
|
||||
[main plugins in this repo](https://github.com/containernetworking/plugins/)
|
||||
|
||||
|
||||
## For Runtime Authors
|
||||
|
||||
This section provides guidance for upgrading container runtimes to support
|
||||
CNI Spec Version 0.3.0.
|
||||
CNI Spec Version 0.3.0 and later.
|
||||
|
||||
### General guidance for all runtimes (language agnostic)
|
||||
|
||||
|
@ -163,22 +196,22 @@ CNI Spec Version 0.3.0.
|
|||
To provide the smoothest upgrade path and support the broadest range of CNI
|
||||
plugins, **container runtimes should support multiple versions of the CNI spec**.
|
||||
In particular, runtimes with existing installed bases should add support for CNI
|
||||
spec version 0.3.0 while maintaining compatibility with older versions.
|
||||
spec version 0.3.0 and later while maintaining compatibility with older versions.
|
||||
|
||||
To support multiple versions of the CNI spec, runtimes should be able to
|
||||
call both new and legacy plugins, and handle the results from either.
|
||||
|
||||
When calling a plugin, the runtime must request that the plugin respond in a
|
||||
particular format by specifying the `cniVersion` field in the
|
||||
[Network Configuration](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration)
|
||||
[Network Configuration](SPEC.md#network-configuration)
|
||||
JSON block. The plugin will then respond with
|
||||
a [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
|
||||
a [Result](SPEC.md#result)
|
||||
in the format defined by that CNI spec version, and the runtime must parse
|
||||
and handle this result.
|
||||
|
||||
#### Handle errors due to version incompatibility
|
||||
Plugins may respond with error indicating that they don't support the requested
|
||||
CNI version (see [Well-known Error Codes](https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes)),
|
||||
CNI version (see [Well-known Error Codes](SPEC.md#well-known-error-codes)),
|
||||
e.g.
|
||||
```json
|
||||
{
|
||||
|
@ -197,16 +230,17 @@ added in CNI spec v0.2.0, so older plugins may not respect it. In the absence
|
|||
of a successful response to `VERSION`, assume that the plugin only supports
|
||||
CNI spec v0.1.0.
|
||||
|
||||
#### Handle missing data in v0.3.0 results
|
||||
The Result for the `ADD` command in CNI spec version 0.3.0 includes a new field
|
||||
`interfaces`. An IP address in the `ip` field may describe which interface
|
||||
it is assigned to, by placing a numeric index in the `interface` subfield.
|
||||
#### Handle missing data in v0.3.0 and later results
|
||||
The Result for the `ADD` command in CNI spec version 0.3.0 and later includes
|
||||
a new field `interfaces`. An IP address in the `ip` field may describe which
|
||||
interface it is assigned to, by placing a numeric index in the `interface`
|
||||
subfield.
|
||||
|
||||
However, some plugins which are v0.3.0 compatible may nonetheless omit the
|
||||
`interfaces` field and/or set the `interface` index value to `-1`. Runtimes
|
||||
should gracefully handle this situation, unless they have good reason to rely
|
||||
on the existence of the interface data. In that case, provide the user an
|
||||
error message that helps diagnose the issue.
|
||||
However, some plugins which are v0.3.0 and later compatible may nonetheless
|
||||
omit the `interfaces` field and/or set the `interface` index value to `-1`.
|
||||
Runtimes should gracefully handle this situation, unless they have good reason
|
||||
to rely on the existence of the interface data. In that case, provide the user
|
||||
an error message that helps diagnose the issue.
|
||||
|
||||
### Specific guidance for container runtimes written in Go
|
||||
Container runtimes written in Go may leverage the Go language packages in this
|
||||
|
@ -223,17 +257,18 @@ other packages, such as the high-level `libcni` package, have been updated to us
|
|||
this interface. Concrete types are now per-version subpackages. The `types/current`
|
||||
subpackage contains the latest (spec v0.3.0) types.
|
||||
|
||||
When up-converting older result types to spec v0.3.0, fields new in
|
||||
spec v0.3.0 (like `interfaces`) may be empty. Conversely, when
|
||||
down-converting v0.3.0 results to an older version, any data in those fields
|
||||
will be lost.
|
||||
|
||||
| From | 0.1 | 0.2 | 0.3 |
|
||||
|--------|-----|-----|-----|
|
||||
| To 0.1 | ✔ | ✔ | x |
|
||||
| To 0.2 | ✔ | ✔ | x |
|
||||
| To 0.3 | ✴ | ✴ | ✔ |
|
||||
When up-converting older result types to spec v0.3.0 and later, fields new in
|
||||
spec v0.3.0 and later (like `interfaces`) may be empty. Conversely, when
|
||||
down-converting v0.3.0 and later results to an older version, any data in
|
||||
those fields will be lost.
|
||||
|
||||
| From | 0.1 | 0.2 | 0.3 | 0.4 | 1.0 |
|
||||
|--------|-----|-----|-----|-----|-----|
|
||||
| To 0.1 | ✔ | ✔ | x | x | x |
|
||||
| To 0.2 | ✔ | ✔ | x | x | x |
|
||||
| To 0.3 | ✴ | ✴ | ✔ | ✔ | ✔ |
|
||||
| To 0.4 | ✴ | ✴ | ✔ | ✔ | ✔ |
|
||||
| To 1.0 | ✴ | ✴ | ✔ | ✔ | ✔ |
|
||||
|
||||
Key:
|
||||
> ✔ : lossless conversion <br>
|
||||
|
|
18
MAINTAINERS
18
MAINTAINERS
|
@ -1,8 +1,14 @@
|
|||
Bruce Ma <brucema19901024@gmail.com> (@mars1024)
|
||||
Bryan Boreham <bryan@weave.works> (@bboreham)
|
||||
Casey Callendrello <cdc@redhat.com> (@squeed)
|
||||
Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
|
||||
Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
Casey Callendrello <cdc@isovalent.com> (@squeed)
|
||||
Michael Cambria <mcambria@redhat.com> (@mccv1r0)
|
||||
Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
|
||||
Michael Zappa <Michael.Zappa@gmail.com> (@MikeZappa87)
|
||||
Tomofumi Hayashi <s1061123@gmail.com> (@s1061123)
|
||||
Lionel Jouin <lionel.jouin@est.tech> (@LionelJouin)
|
||||
Ben Leggett <benjamin@edera.dev> (@bleggett)
|
||||
Marcelo Guerrero <guerrero.viveros@gmail.com> (@mlguerrero12)
|
||||
Doug Smith <douglas.kipp.smith@gmail.com> (@dougbtv)
|
||||
|
||||
Emeritus:
|
||||
Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek)
|
44
README.md
44
README.md
|
@ -1,13 +1,12 @@
|
|||
[](https://travis-ci.org/containernetworking/cni)
|
||||
[](https://ci.appveyor.com/project/cni-bot/cni/branch/master)
|
||||
[](https://coveralls.io/github/containernetworking/cni?branch=master)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# CNI - the Container Network Interface
|
||||
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/2446)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/containernetworking/cni)
|
||||
|
||||
## What is CNI?
|
||||
|
||||
CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
|
||||
|
@ -24,6 +23,14 @@ Here are the recordings of two sessions that the CNI maintainers hosted at KubeC
|
|||
- [Introduction to CNI](https://youtu.be/YjjrQiJOyME)
|
||||
- [CNI deep dive](https://youtu.be/zChkx-AB5Xc)
|
||||
|
||||
|
||||
## Contributing to CNI
|
||||
|
||||
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
|
||||
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
|
||||
|
||||
The CNI project has a [weekly meeting](https://meet.jit.si/CNIMaintainersMeeting). It takes place Mondays at 11:00 US/Eastern. All are welcome to join.
|
||||
|
||||
## Why develop CNI?
|
||||
|
||||
Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific.
|
||||
|
@ -33,8 +40,7 @@ To avoid duplication, we think it is prudent to define a common interface betwee
|
|||
|
||||
## Who is using CNI?
|
||||
### Container runtimes
|
||||
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
|
||||
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/admin/network-plugins/)
|
||||
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/)
|
||||
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
|
||||
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
|
||||
- [Apache Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
|
||||
|
@ -43,17 +49,14 @@ To avoid duplication, we think it is prudent to define a common interface betwee
|
|||
- [OpenSVC - orchestrator for legacy and containerized application stacks](https://docs.opensvc.com/latest/fr/agent.configure.cni.html)
|
||||
|
||||
### 3rd party plugins
|
||||
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
|
||||
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
|
||||
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico)
|
||||
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
|
||||
- [SR-IOV](https://github.com/hustcat/sriov-cni)
|
||||
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium)
|
||||
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox)
|
||||
- [Multus - a Multi plugin](https://github.com/Intel-Corp/multus-cni)
|
||||
- [Cilium - eBPF & XDP for containers](https://github.com/cilium/cilium)
|
||||
- [Multus - a Multi plugin](https://github.com/k8snetworkplumbingwg/multus-cni)
|
||||
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
|
||||
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
|
||||
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
|
||||
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
|
||||
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
|
||||
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
|
||||
- [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins)
|
||||
|
@ -62,22 +65,19 @@ To avoid duplication, we think it is prudent to define a common interface betwee
|
|||
- [Juniper Contrail](https://www.juniper.net/cloud) / [TungstenFabric](https://tungstenfabric.io) - Provides overlay SDN solution, delivering multicloud networking, hybrid cloud networking, simultaneous overlay-underlay support, network policy enforcement, network isolation, service chaining and flexible load balancing
|
||||
- [Knitter - a CNI plugin supporting multiple networking for Kubernetes](https://github.com/ZTE/Knitter)
|
||||
- [DANM - a CNI-compliant networking solution for TelCo workloads running on Kubernetes](https://github.com/nokia/danm)
|
||||
- [VMware NSX – a CNI plugin that enables automated NSX L2/L3 networking and L4/L7 Load Balancing; network isolation at the pod, node, and cluster level; and zero-trust security policy for your Kubernetes cluster.](https://docs.vmware.com/en/VMware-NSX-T/2.2/com.vmware.nsxt.ncp_kubernetes.doc/GUID-6AFA724E-BB62-4693-B95C-321E8DDEA7E1.html)
|
||||
- [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override)
|
||||
- [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway)
|
||||
- [Cisco ACI CNI - for on-prem and cloud container networking with consistent policy and security model.](https://github.com/noironetworks/aci-containers)
|
||||
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/alauda/kube-ovn)
|
||||
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/kubeovn/kube-ovn)
|
||||
- [Project Antrea - an Open vSwitch k8s CNI](https://github.com/vmware-tanzu/antrea)
|
||||
- [OVN4NFV-K8S-Plugin - a OVN based CNI controller plugin to provide cloud native based Service function chaining (SFC), Multiple OVN overlay networking](https://github.com/opnfv/ovn4nfv-k8s-plugin)
|
||||
- [Azure CNI - a CNI plugin that natively extends Azure Virtual Networks to containers](https://github.com/Azure/azure-container-networking)
|
||||
- [Hybridnet - a CNI plugin designed for hybrid clouds which provides both overlay and underlay networking for containers in one or more clusters. Overlay and underlay containers can run on the same node and have cluster-wide bidirectional network connectivity.](https://github.com/alibaba/hybridnet)
|
||||
- [Spiderpool - An IP Address Management (IPAM) CNI plugin of Kubernetes for managing static ip for underlay network](https://github.com/spidernet-io/spiderpool)
|
||||
- [AWS VPC CNI - Networking plugin for pod networking in Kubernetes using Elastic Network Interfaces on AWS](https://github.com/aws/amazon-vpc-cni-k8s)
|
||||
|
||||
The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
|
||||
|
||||
|
||||
## Contributing to CNI
|
||||
|
||||
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
|
||||
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
|
||||
|
||||
## How do I use CNI?
|
||||
|
||||
### Requirements
|
||||
|
@ -214,3 +214,7 @@ For any questions about CNI, please reach out via:
|
|||
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
|
||||
- IRC: #[containernetworking](irc://irc.freenode.net:6667/#containernetworking) channel on [freenode.net](https://freenode.net/)
|
||||
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/). NOTE: the previous CNI Slack (containernetworking.slack.com) has been sunsetted.
|
||||
|
||||
## Security
|
||||
|
||||
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [MAINTAINERS](MAINTAINERS) file.
|
||||
|
|
37
RELEASING.md
37
RELEASING.md
|
@ -1,40 +1,19 @@
|
|||
# Release process
|
||||
|
||||
## Resulting artifacts
|
||||
|
||||
Creating a new release produces the following artifacts:
|
||||
|
||||
- Binaries (stored in the `release-<TAG>` directory) :
|
||||
- `cni-<PLATFORM>-<VERSION>.tgz` binaries
|
||||
- `cni-<VERSION>.tgz` binary (copy of amd64 platform binary)
|
||||
- `sha1`, `sha256` and `sha512` files for the above files.
|
||||
|
||||
## Preparing for a release
|
||||
|
||||
1. Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
|
||||
Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
|
||||
|
||||
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
|
||||
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
|
||||
- Create a draft of the release note
|
||||
- Discuss the level of testing that's needed and create a test plan if sensible
|
||||
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
|
||||
|
||||
## Creating the release artifacts
|
||||
|
||||
1. Make sure you are on the master branch and don't have any local uncommitted changes.
|
||||
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
|
||||
1. Run the release script from the root of the repository
|
||||
|
||||
- `scripts/release.sh`
|
||||
- The script requires Docker and ensures that a consistent environment is used.
|
||||
- The artifacts will now be present in the `release-<TAG>` directory.
|
||||
|
||||
1. Test these binaries according to the test plan.
|
||||
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
|
||||
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
|
||||
- Create a draft of the release note
|
||||
- Discuss the level of testing that's needed and create a test plan if sensible
|
||||
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
|
||||
|
||||
## Publishing the release
|
||||
|
||||
1. Make sure you are on the master branch and don't have any local uncommitted changes.
|
||||
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
|
||||
1. Push the tag to git `git push origin <TAG>`
|
||||
1. Create a release on Github, using the tag which was just pushed.
|
||||
1. Attach all the artifacts from the release directory.
|
||||
1. Add the release note to the release.
|
||||
1. Announce the release on at least the CNI mailing, IRC and Slack.
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "bento/ubuntu-18.04"
|
||||
|
||||
config.vm.synced_folder ".", "/go/src/github.com/containernetworking/cni"
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
set -e -x -u
|
||||
|
||||
apt-get update -y || (sleep 40 && apt-get update -y)
|
||||
apt-get install -y git
|
||||
|
||||
wget -qO- https://storage.googleapis.com/golang/go1.12.5.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
|
||||
echo 'export GOPATH=/go; export PATH=/usr/local/go/bin:$GOPATH/bin:$PATH' >> /root/.bashrc
|
||||
eval `tail -n1 /root/.bashrc`
|
||||
|
||||
go get github.com/onsi/ginkgo/ginkgo
|
||||
go get github.com/onsi/gomega/...
|
||||
|
||||
cd /go/src/github.com/containernetworking/cni
|
||||
SHELL
|
||||
end
|
|
@ -7,8 +7,12 @@ add or remove an interface in an already-created network namespace.
|
|||
|
||||
* `NETCONFPATH`: This environment variable needs to be set to a
|
||||
directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches
|
||||
for CNI configuration files in this directory with the extension
|
||||
`*.conf` or `*.json`. It loads all the CNI configuration files in
|
||||
for CNI configuration files in this directory according to the following priorities:
|
||||
1. Search files with the extension `*.conflist`, representing a list of plugin configurations.
|
||||
2. If there are no `*.conflist` files in the directory, search files with the extension `*.conf` or `*.json`,
|
||||
representing a single plugin configuration.
|
||||
|
||||
It loads all the CNI configuration files in
|
||||
this directory and if it finds a CNI configuration with the `network
|
||||
name` given to the cnitool it returns the corresponding CNI
|
||||
configuration, else it returns `nil`.
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/containernetworking/cni/libcni"
|
||||
)
|
||||
|
||||
// Protocol parameters are passed to the plugins via OS environment variables.
|
||||
const (
|
||||
EnvCNIPath = "CNI_PATH"
|
||||
EnvNetDir = "NETCONFPATH"
|
||||
|
@ -38,6 +39,8 @@ const (
|
|||
CmdAdd = "add"
|
||||
CmdCheck = "check"
|
||||
CmdDel = "del"
|
||||
CmdGC = "gc"
|
||||
CmdStatus = "status"
|
||||
)
|
||||
|
||||
func parseArgs(args string) ([][2]string, error) {
|
||||
|
@ -59,14 +62,13 @@ func parseArgs(args string) ([][2]string, error) {
|
|||
func main() {
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
return
|
||||
}
|
||||
|
||||
netdir := os.Getenv(EnvNetDir)
|
||||
if netdir == "" {
|
||||
netdir = DefaultNetDir
|
||||
}
|
||||
netconf, err := libcni.LoadConfList(netdir, os.Args[2])
|
||||
netconf, err := libcni.LoadNetworkConf(netdir, os.Args[2])
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
@ -125,16 +127,23 @@ func main() {
|
|||
exit(err)
|
||||
case CmdDel:
|
||||
exit(cninet.DelNetworkList(context.TODO(), netconf, rt))
|
||||
case CmdGC:
|
||||
// Currently just invoke GC without args, hence all network interface should be GC'ed!
|
||||
exit(cninet.GCNetworkList(context.TODO(), netconf, nil))
|
||||
case CmdStatus:
|
||||
exit(cninet.GetStatusNetworkList(context.TODO(), netconf))
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
exe := filepath.Base(os.Args[0])
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s: Add, check, or remove network interfaces from a network namespace\n", exe)
|
||||
fmt.Fprintf(os.Stderr, "%s: Add, check, remove, gc or status network interfaces from a network namespace\n", exe)
|
||||
fmt.Fprintf(os.Stderr, " %s add <net> <netns>\n", exe)
|
||||
fmt.Fprintf(os.Stderr, " %s check <net> <netns>\n", exe)
|
||||
fmt.Fprintf(os.Stderr, " %s del <net> <netns>\n", exe)
|
||||
fmt.Fprintf(os.Stderr, " %s gc <net> <netns>\n", exe)
|
||||
fmt.Fprintf(os.Stderr, " %s status <net> <netns>\n", exe)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
module github.com/containernetworking/cni
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/onsi/ginkgo/v2 v2.20.1
|
||||
github.com/onsi/gomega v1.34.1
|
||||
github.com/vishvananda/netns v0.0.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
|
||||
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
357
libcni/api.go
357
libcni/api.go
|
@ -14,23 +14,33 @@
|
|||
|
||||
package libcni
|
||||
|
||||
// Note this is the actual implementation of the CNI specification, which
|
||||
// is reflected in the SPEC.md file.
|
||||
// it is typically bundled into runtime providers (i.e. containerd or cri-o would use this
|
||||
// before calling runc or hcsshim). It is also bundled into CNI providers as well, for example,
|
||||
// to add an IP to a container, to parse the configuration of the CNI and so on.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
"github.com/containernetworking/cni/pkg/utils"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
CacheDir = "/var/lib/cni"
|
||||
// slightly awkward wording to preserve anyone matching on error strings
|
||||
ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command")
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -57,8 +67,12 @@ type RuntimeConf struct {
|
|||
CacheDir string
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Network *types.NetConf
|
||||
// Use PluginConfig instead of NetworkConfig, the NetworkConfig
|
||||
// backwards-compat alias will be removed in a future release.
|
||||
type NetworkConfig = PluginConfig
|
||||
|
||||
type PluginConfig struct {
|
||||
Network *types.PluginConf
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
|
@ -66,10 +80,26 @@ type NetworkConfigList struct {
|
|||
Name string
|
||||
CNIVersion string
|
||||
DisableCheck bool
|
||||
Plugins []*NetworkConfig
|
||||
DisableGC bool
|
||||
LoadOnlyInlinedPlugins bool
|
||||
Plugins []*PluginConfig
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type NetworkAttachment struct {
|
||||
ContainerID string
|
||||
Network string
|
||||
IfName string
|
||||
Config []byte
|
||||
NetNS string
|
||||
CniArgs [][2]string
|
||||
CapabilityArgs map[string]interface{}
|
||||
}
|
||||
|
||||
type GCArgs struct {
|
||||
ValidAttachments []types.GCAttachment
|
||||
}
|
||||
|
||||
type CNI interface {
|
||||
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
|
||||
|
@ -77,14 +107,21 @@ type CNI interface {
|
|||
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, 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)
|
||||
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
|
||||
CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
|
||||
DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
|
||||
GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
|
||||
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
|
||||
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
|
||||
ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error)
|
||||
|
||||
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
|
||||
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
|
||||
|
||||
GetCachedAttachments(containerID string) ([]*NetworkAttachment, error)
|
||||
|
||||
GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error)
|
||||
}
|
||||
|
||||
type CNIConfig struct {
|
||||
|
@ -115,7 +152,7 @@ func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec)
|
|||
}
|
||||
}
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
|
||||
func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
|
@ -132,8 +169,11 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rt != nil {
|
||||
return injectRuntimeConfig(orig, rt)
|
||||
}
|
||||
|
||||
return orig, nil
|
||||
}
|
||||
|
||||
// This function takes a libcni RuntimeConf structure and injects values into
|
||||
|
@ -148,7 +188,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
|
|||
// 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) {
|
||||
func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) {
|
||||
var err error
|
||||
|
||||
rc := make(map[string]interface{})
|
||||
|
@ -188,6 +228,7 @@ type cachedInfo struct {
|
|||
Config []byte `json:"config"`
|
||||
IfName string `json:"ifName"`
|
||||
NetworkName string `json:"networkName"`
|
||||
NetNS string `json:"netns,omitempty"`
|
||||
CniArgs [][2]string `json:"cniArgs,omitempty"`
|
||||
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
|
||||
RawResult map[string]interface{} `json:"result,omitempty"`
|
||||
|
@ -222,6 +263,7 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
|
|||
Config: config,
|
||||
IfName: rt.IfName,
|
||||
NetworkName: netName,
|
||||
NetNS: rt.NetNS,
|
||||
CniArgs: rt.Args,
|
||||
CapabilityArgs: rt.CapabilityArgs,
|
||||
}
|
||||
|
@ -247,11 +289,11 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(fname, newBytes, 0600)
|
||||
return os.WriteFile(fname, newBytes, 0o600)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
|
||||
|
@ -270,7 +312,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bytes, err = ioutil.ReadFile(fname)
|
||||
bytes, err = os.ReadFile(fname)
|
||||
if err != nil {
|
||||
// Ignore read errors; the cached result may not exist on-disk
|
||||
return nil, nil, nil
|
||||
|
@ -278,7 +320,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R
|
|||
|
||||
unmarshaled := cachedInfo{}
|
||||
if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %w", netName, err)
|
||||
}
|
||||
if unmarshaled.Kind != CNICacheV1 {
|
||||
return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
|
||||
|
@ -298,21 +340,14 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(fname)
|
||||
data, err := os.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)
|
||||
// Load the cached result
|
||||
result, err := create.CreateFromBytes(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -322,10 +357,10 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
|
|||
// 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
|
||||
}
|
||||
return result, err
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
|
||||
|
@ -333,7 +368,7 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fdata, err := ioutil.ReadFile(fname)
|
||||
fdata, err := os.ReadFile(fname)
|
||||
if err != nil {
|
||||
// Ignore read errors; the cached result may not exist on-disk
|
||||
return nil, nil
|
||||
|
@ -346,18 +381,11 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
|
|||
|
||||
newBytes, err := json.Marshal(&cachedInfo.RawResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
|
||||
return nil, fmt.Errorf("failed to marshal cached network %q config: %w", netName, err)
|
||||
}
|
||||
|
||||
// Read the version of the cached result
|
||||
decoder := version.ConfigDecoder{}
|
||||
resultCniVersion, err := decoder.Decode(newBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure we can understand the result
|
||||
result, err := version.NewResult(resultCniVersion, newBytes)
|
||||
// Load the cached result
|
||||
result, err := create.CreateFromBytes(newBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -367,10 +395,10 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
|
|||
// 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
|
||||
}
|
||||
return result, err
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetNetworkListCachedResult returns the cached Result of the previous
|
||||
|
@ -381,7 +409,7 @@ func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *Runt
|
|||
|
||||
// GetNetworkCachedResult returns the cached Result of the previous
|
||||
// AddNetwork() operation for a network, or an error.
|
||||
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
}
|
||||
|
||||
|
@ -393,11 +421,73 @@ func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *Runt
|
|||
|
||||
// GetNetworkCachedConfig copies the input RuntimeConf to output
|
||||
// RuntimeConf with fields updated with info from the cached Config.
|
||||
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
return c.getCachedConfig(net.Network.Name, rt)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||
// GetCachedAttachments returns a list of network attachments from the cache.
|
||||
// The returned list will be filtered by the containerID if the value is not empty.
|
||||
func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) {
|
||||
dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results")
|
||||
entries, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileNames := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
fileNames = append(fileNames, e.Name())
|
||||
}
|
||||
sort.Strings(fileNames)
|
||||
|
||||
attachments := []*NetworkAttachment{}
|
||||
for _, fname := range fileNames {
|
||||
if len(containerID) > 0 {
|
||||
part := fmt.Sprintf("-%s-", containerID)
|
||||
pos := strings.Index(fname, part)
|
||||
if pos <= 0 || pos+len(part) >= len(fname) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cacheFile := filepath.Join(dirPath, fname)
|
||||
bytes, err := os.ReadFile(cacheFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cachedInfo := cachedInfo{}
|
||||
|
||||
if err := json.Unmarshal(bytes, &cachedInfo); err != nil {
|
||||
continue
|
||||
}
|
||||
if cachedInfo.Kind != CNICacheV1 {
|
||||
continue
|
||||
}
|
||||
if len(containerID) > 0 && cachedInfo.ContainerID != containerID {
|
||||
continue
|
||||
}
|
||||
if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
attachments = append(attachments, &NetworkAttachment{
|
||||
ContainerID: cachedInfo.ContainerID,
|
||||
Network: cachedInfo.NetworkName,
|
||||
IfName: cachedInfo.IfName,
|
||||
Config: cachedInfo.Config,
|
||||
NetNS: cachedInfo.NetNS,
|
||||
CniArgs: cachedInfo.CniArgs,
|
||||
CapabilityArgs: cachedInfo.CapabilityArgs,
|
||||
})
|
||||
}
|
||||
return attachments, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -428,18 +518,18 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
for _, net := range list.Plugins {
|
||||
result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("plugin %s failed (add): %w", pluginDescription(net.Network), err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %w", list.Name, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -460,7 +550,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
|
|||
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)
|
||||
return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp)
|
||||
}
|
||||
|
||||
if list.DisableCheck {
|
||||
|
@ -469,7 +559,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
|
|||
|
||||
cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||
return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err)
|
||||
}
|
||||
|
||||
for _, net := range list.Plugins {
|
||||
|
@ -481,7 +571,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
|
@ -504,55 +594,69 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
|
||||
return err
|
||||
} else if gtet {
|
||||
cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||
if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil {
|
||||
_ = c.cacheDel(list.Name, rt)
|
||||
cachedResult = nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err)
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.cacheDel(list.Name, rt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginDescription(net *types.PluginConf) string {
|
||||
if net == nil {
|
||||
return "<missing>"
|
||||
}
|
||||
pluginType := net.Type
|
||||
out := fmt.Sprintf("type=%q", pluginType)
|
||||
name := net.Name
|
||||
if name != "" {
|
||||
out += fmt.Sprintf(" name=%q", name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AddNetwork executes the plugin with the ADD command
|
||||
func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, 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 = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %w", 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 {
|
||||
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, 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)
|
||||
return fmt.Errorf("configuration version %q %w", net.Network.CNIVersion, ErrorCheckNotSupp)
|
||||
}
|
||||
|
||||
cachedResult, err := c.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 fmt.Errorf("failed to get network %q cached result: %w", 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 {
|
||||
func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
|
||||
var cachedResult types.Result
|
||||
|
||||
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
|
||||
|
@ -561,7 +665,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
|||
} else if gtet {
|
||||
cachedResult, err = c.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 fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,7 +716,7 @@ func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfig
|
|||
// 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) {
|
||||
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) {
|
||||
caps := []string{}
|
||||
for c, ok := range net.Network.Capabilities {
|
||||
if ok {
|
||||
|
@ -660,6 +764,129 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers
|
|||
return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
|
||||
}
|
||||
|
||||
// GCNetworkList will do two things
|
||||
// - dump the list of cached attachments, and issue deletes as necessary
|
||||
// - issue a GC to the underlying plugins (if the version is high enough)
|
||||
func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error {
|
||||
// If DisableGC is set, then don't bother GCing at all.
|
||||
if list.DisableGC {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First, get the list of cached attachments
|
||||
cachedAttachments, err := c.GetCachedAttachments("")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var validAttachments map[types.GCAttachment]interface{}
|
||||
if args != nil {
|
||||
validAttachments = make(map[types.GCAttachment]interface{}, len(args.ValidAttachments))
|
||||
for _, a := range args.ValidAttachments {
|
||||
validAttachments[a] = nil
|
||||
}
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, cachedAttachment := range cachedAttachments {
|
||||
if cachedAttachment.Network != list.Name {
|
||||
continue
|
||||
}
|
||||
// we found this attachment
|
||||
gca := types.GCAttachment{
|
||||
ContainerID: cachedAttachment.ContainerID,
|
||||
IfName: cachedAttachment.IfName,
|
||||
}
|
||||
if _, ok := validAttachments[gca]; ok {
|
||||
continue
|
||||
}
|
||||
// otherwise, this attachment wasn't valid and we should issue a CNI DEL
|
||||
rt := RuntimeConf{
|
||||
ContainerID: cachedAttachment.ContainerID,
|
||||
NetNS: cachedAttachment.NetNS,
|
||||
IfName: cachedAttachment.IfName,
|
||||
Args: cachedAttachment.CniArgs,
|
||||
CapabilityArgs: cachedAttachment.CapabilityArgs,
|
||||
}
|
||||
if err := c.DelNetworkList(ctx, list, &rt); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to delete stale attachment %s %s: %w", rt.ContainerID, rt.IfName, err))
|
||||
}
|
||||
}
|
||||
|
||||
// now, if the version supports it, issue a GC
|
||||
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt {
|
||||
inject := map[string]interface{}{
|
||||
"name": list.Name,
|
||||
"cniVersion": list.CNIVersion,
|
||||
}
|
||||
if args != nil {
|
||||
inject["cni.dev/valid-attachments"] = args.ValidAttachments
|
||||
// #1101: spec used incorrect variable name
|
||||
inject["cni.dev/attachments"] = args.ValidAttachments
|
||||
}
|
||||
|
||||
for _, plugin := range list.Plugins {
|
||||
// build config here
|
||||
pluginConfig, err := InjectConf(plugin, inject)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to generate configuration to GC plugin %s: %w", plugin.Network.Type, err))
|
||||
}
|
||||
if err := c.gcNetwork(ctx, pluginConfig); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to GC plugin %s: %w", plugin.Network.Type, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) gcNetwork(ctx context.Context, net *PluginConfig) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := c.args("GC", &RuntimeConf{})
|
||||
|
||||
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error {
|
||||
// If the version doesn't support status, abort.
|
||||
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt {
|
||||
return nil
|
||||
}
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": list.Name,
|
||||
"cniVersion": list.CNIVersion,
|
||||
}
|
||||
|
||||
for _, plugin := range list.Plugins {
|
||||
// build config here
|
||||
pluginConfig, err := InjectConf(plugin, inject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err)
|
||||
}
|
||||
if err := c.getStatusNetwork(ctx, pluginConfig); err != nil {
|
||||
return err // Don't collect errors here, so we return a clean error code.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *PluginConfig) error {
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := c.args("STATUS", &RuntimeConf{})
|
||||
|
||||
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
|
||||
}
|
||||
|
||||
// =====
|
||||
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
|
||||
return &invoke.Args{
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,18 +17,18 @@ package libcni_test
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/version/legacy_examples"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/version/legacy_examples"
|
||||
)
|
||||
|
||||
var _ = Describe("Backwards compatibility", func() {
|
||||
|
@ -36,7 +36,7 @@ var _ = Describe("Backwards compatibility", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
|
||||
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -66,6 +66,9 @@ var _ = Describe("Backwards compatibility", func() {
|
|||
|
||||
Expect(result).To(Equal(legacy_examples.ExpectedResult))
|
||||
|
||||
err = cniConfig.DelNetwork(context.TODO(), netConf, runtimeConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(os.RemoveAll(pluginPath)).To(Succeed())
|
||||
})
|
||||
|
||||
|
|
245
libcni/conf.go
245
libcni/conf.go
|
@ -16,11 +16,16 @@ package libcni
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type NotFoundError struct {
|
||||
|
@ -40,10 +45,17 @@ 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)
|
||||
// This will not validate that the plugins actually belong to the netconfig by ensuring
|
||||
// that they are loaded from a directory named after the networkName, relative to the network config.
|
||||
//
|
||||
// Since here we are just accepting raw bytes, the caller is responsible for ensuring that the plugin
|
||||
// config provided here actually "belongs" to the networkconfig in question.
|
||||
func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) {
|
||||
// TODO why are we creating a struct that holds both the byte representation and the deserialized
|
||||
// representation, and returning that, instead of just returning the deserialized representation?
|
||||
conf := &PluginConfig{Bytes: pluginConfBytes, Network: &types.PluginConf{}}
|
||||
if err := json.Unmarshal(pluginConfBytes, conf.Network); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %w", err)
|
||||
}
|
||||
if conf.Network.Type == "" {
|
||||
return nil, fmt.Errorf("error parsing configuration: missing 'type'")
|
||||
|
@ -51,18 +63,36 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
|||
return conf, nil
|
||||
}
|
||||
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
// Given a path to a directory containing a network configuration, and the name of a network,
|
||||
// loads all plugin definitions found at path `networkConfPath/networkName/*.conf`
|
||||
func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) {
|
||||
var pConfs []*PluginConfig
|
||||
|
||||
pluginConfPath := filepath.Join(networkConfPath, networkName)
|
||||
|
||||
pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
|
||||
for _, pluginConfFile := range pluginConfFiles {
|
||||
pluginConfBytes, err := os.ReadFile(pluginConfFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", pluginConfFile, err)
|
||||
}
|
||||
pluginConf, err := NetworkPluginConfFromBytes(pluginConfBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pConfs = append(pConfs, pluginConf)
|
||||
}
|
||||
return pConfs, nil
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
func NetworkConfFromBytes(confBytes []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)
|
||||
if err := json.Unmarshal(confBytes, &rawList); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: %w", err)
|
||||
}
|
||||
|
||||
rawName, ok := rawList["name"]
|
||||
|
@ -83,26 +113,115 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
|||
}
|
||||
}
|
||||
|
||||
disableCheck := false
|
||||
if rawDisableCheck, ok := rawList["disableCheck"]; ok {
|
||||
disableCheck, ok = rawDisableCheck.(bool)
|
||||
rawVersions, ok := rawList["cniVersions"]
|
||||
if ok {
|
||||
// Parse the current package CNI version
|
||||
rvs, ok := rawVersions.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck)
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs)
|
||||
}
|
||||
vs := make([]string, 0, len(rvs))
|
||||
for i, rv := range rvs {
|
||||
v, ok := rv.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv)
|
||||
}
|
||||
gt, err := version.GreaterThan(v, version.Current())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err)
|
||||
} else if !gt {
|
||||
// Skip versions "greater" than this implementation of the spec
|
||||
vs = append(vs, v)
|
||||
}
|
||||
}
|
||||
|
||||
// if cniVersion was already set, append it to the list for sorting.
|
||||
if cniVersion != "" {
|
||||
gt, err := version.GreaterThan(cniVersion, version.Current())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err)
|
||||
} else if !gt {
|
||||
// ignore any versions higher than the current implemented spec version
|
||||
vs = append(vs, cniVersion)
|
||||
}
|
||||
}
|
||||
slices.SortFunc[[]string](vs, func(v1, v2 string) int {
|
||||
if v1 == v2 {
|
||||
return 0
|
||||
}
|
||||
if gt, _ := version.GreaterThan(v1, v2); gt {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
})
|
||||
if len(vs) > 0 {
|
||||
cniVersion = vs[len(vs)-1]
|
||||
}
|
||||
}
|
||||
|
||||
readBool := func(key string) (bool, error) {
|
||||
rawVal, ok := rawList[key]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if b, ok := rawVal.(bool); ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
s, ok := rawVal.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("error parsing configuration list: invalid type %T for %s", rawVal, key)
|
||||
}
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "false":
|
||||
return false, nil
|
||||
case "true":
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("error parsing configuration list: invalid value %q for %s", s, key)
|
||||
}
|
||||
|
||||
disableCheck, err := readBool("disableCheck")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
disableGC, err := readBool("disableGC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loadOnlyInlinedPlugins, err := readBool("loadOnlyInlinedPlugins")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &NetworkConfigList{
|
||||
Name: name,
|
||||
DisableCheck: disableCheck,
|
||||
DisableGC: disableGC,
|
||||
LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: bytes,
|
||||
Bytes: confBytes,
|
||||
}
|
||||
|
||||
var plugins []interface{}
|
||||
plug, ok := rawList["plugins"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
|
||||
// We can have a `plugins` list key in the main conf,
|
||||
// We can also have `loadOnlyInlinedPlugins == true`
|
||||
//
|
||||
// If `plugins` is there, then `loadOnlyInlinedPlugins` can be true
|
||||
//
|
||||
// If plugins is NOT there, then `loadOnlyInlinedPlugins` cannot be true
|
||||
//
|
||||
// We have to have at least some plugins.
|
||||
if !ok && loadOnlyInlinedPlugins {
|
||||
return nil, fmt.Errorf("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key")
|
||||
} else if !ok && !loadOnlyInlinedPlugins {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
plugins, ok = plug.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
|
||||
|
@ -114,32 +233,76 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
|||
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)
|
||||
return nil, fmt.Errorf("failed to marshal plugin config %d: %w", i, err)
|
||||
}
|
||||
netConf, err := ConfFromBytes(newBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err)
|
||||
return nil, fmt.Errorf("failed to parse plugin config %d: %w", i, err)
|
||||
}
|
||||
list.Plugins = append(list.Plugins, netConf)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", filename, err)
|
||||
return nil, fmt.Errorf("error reading %s: %w", filename, err)
|
||||
}
|
||||
return ConfListFromBytes(bytes)
|
||||
|
||||
conf, err := NetworkConfFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !conf.LoadOnlyInlinedPlugins {
|
||||
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.Plugins = append(conf.Plugins, plugins...)
|
||||
}
|
||||
|
||||
if len(conf.Plugins) == 0 {
|
||||
// Having 0 plugins for a given network is not necessarily a problem,
|
||||
// but return as error for caller to decide, since they tried to load
|
||||
return nil, fmt.Errorf("no plugin configs found")
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
|
||||
return NetworkPluginConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfFromFile(filename string) (*NetworkConfig, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", filename, err)
|
||||
}
|
||||
return ConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
return NetworkConfFromBytes(bytes)
|
||||
}
|
||||
|
||||
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
|
||||
return NetworkConfFromFile(filename)
|
||||
}
|
||||
|
||||
// ConfFiles simply returns a slice of all files in the provided directory
|
||||
// with extensions matching the provided set.
|
||||
func ConfFiles(dir string, extensions []string) ([]string, error) {
|
||||
// In part, adapted from rkt/networking/podenv.go#listFiles
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
files, err := os.ReadDir(dir)
|
||||
switch {
|
||||
case err == nil: // break
|
||||
case os.IsNotExist(err):
|
||||
// If folder not there, return no error - only return an
|
||||
// error if we cannot read contents or there are no contents.
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
|
@ -160,6 +323,7 @@ func ConfFiles(dir string, extensions []string) ([]string, error) {
|
|||
return confFiles, nil
|
||||
}
|
||||
|
||||
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func LoadConf(dir, name string) (*NetworkConfig, error) {
|
||||
files, err := ConfFiles(dir, []string{".conf", ".json"})
|
||||
switch {
|
||||
|
@ -183,6 +347,15 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
|
|||
}
|
||||
|
||||
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
||||
return LoadNetworkConf(dir, name)
|
||||
}
|
||||
|
||||
// LoadNetworkConf looks at all the network configs in a given dir,
|
||||
// loads and parses them all, and returns the first one with an extension of `.conf`
|
||||
// that matches the provided network name predicate.
|
||||
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
|
||||
// TODO this .conflist/.conf extension thing is confusing and inexact
|
||||
// for implementors. We should pick one extension for everything and stick with it.
|
||||
files, err := ConfFiles(dir, []string{".conflist"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -190,7 +363,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
sort.Strings(files)
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := ConfListFromFile(confFile)
|
||||
conf, err := NetworkConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -199,12 +372,13 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Try and load a network configuration file (instead of list)
|
||||
// Deprecated: 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 {
|
||||
var ncfErr NoConfigsFoundError
|
||||
if len(files) != 0 && errors.As(err, &ncfErr) {
|
||||
// Config lists found but no config files found
|
||||
return nil, NotFoundError{dir, name}
|
||||
}
|
||||
|
@ -214,11 +388,12 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
|
|||
return ConfListFromConf(singleConf)
|
||||
}
|
||||
|
||||
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
|
||||
// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
|
||||
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, 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)
|
||||
return nil, fmt.Errorf("unmarshal existing network bytes: %w", err)
|
||||
}
|
||||
|
||||
for key, value := range newValues {
|
||||
|
@ -238,12 +413,14 @@ func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*Net
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return ConfFromBytes(newBytes)
|
||||
return NetworkPluginConfFromBytes(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) {
|
||||
//
|
||||
// Deprecated: Non-conflist file formats are unsupported, use NetworkConfXXX and NetworkPluginXXX functions
|
||||
func ConfListFromConf(original *PluginConfig) (*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
|
||||
|
|
|
@ -15,14 +15,16 @@
|
|||
package libcni_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Loading configuration from disk", func() {
|
||||
|
@ -34,11 +36,11 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
configDir, err = ioutil.TempDir("", "plugin-conf")
|
||||
configDir, err = os.MkdirTemp("", "plugin-conf")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
pluginConfig = []byte(`{ "name": "some-plugin", "type": "foobar", "some-key": "some-value" }`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -48,8 +50,8 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
It("finds the network config file for the plugin of the given type", func() {
|
||||
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Expect(netConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{
|
||||
Name: "some-plugin",
|
||||
Type: "foobar",
|
||||
},
|
||||
|
@ -72,13 +74,13 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
BeforeEach(func() {
|
||||
Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed())
|
||||
pluginConfig = []byte(`{ "name": "some-plugin", "some-key": "some-value", "type": "foobar" }`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0o600)).To(Succeed())
|
||||
})
|
||||
It("finds the network config file for the plugin of the given type", func() {
|
||||
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Expect(netConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{
|
||||
Name: "some-plugin",
|
||||
Type: "foobar",
|
||||
},
|
||||
|
@ -96,7 +98,7 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
|
||||
Context("when a config file is malformed", func() {
|
||||
BeforeEach(func() {
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
It("returns a useful error", func() {
|
||||
|
@ -108,10 +110,10 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
Context("when the config is in a nested subdir", func() {
|
||||
BeforeEach(func() {
|
||||
subdir := filepath.Join(configDir, "subdir1", "subdir2")
|
||||
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
|
||||
Expect(os.MkdirAll(subdir, 0o700)).To(Succeed())
|
||||
|
||||
pluginConfig = []byte(`{ "name": "deep", "some-key": "some-value" }`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
It("will not find the config", func() {
|
||||
|
@ -126,11 +128,11 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
configDir, err = ioutil.TempDir("", "plugin-conf")
|
||||
configDir, err = os.MkdirTemp("", "plugin-conf")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -160,12 +162,12 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
var fileName, configDir string
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
configDir, err = ioutil.TempDir("", "plugin-conf")
|
||||
configDir, err = os.MkdirTemp("", "plugin-conf")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
fileName = filepath.Join(configDir, "50-whatever.conf")
|
||||
pluginConfig := []byte(`{ "name": "some-plugin", "some-key": "some-value" }`)
|
||||
Expect(ioutil.WriteFile(fileName, pluginConfig, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(fileName, pluginConfig, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -179,7 +181,7 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("ConfFromBytes", func() {
|
||||
Describe("NetworkPluginConfFromBytes", func() {
|
||||
Context("when the config is missing 'type'", func() {
|
||||
It("returns a useful error", func() {
|
||||
_, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`))
|
||||
|
@ -188,7 +190,7 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("LoadConfList", func() {
|
||||
Describe("LoadNetworkConf", func() {
|
||||
var (
|
||||
configDir string
|
||||
configList []byte
|
||||
|
@ -196,11 +198,11 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
configDir, err = ioutil.TempDir("", "plugin-conf")
|
||||
configDir, err = os.MkdirTemp("", "plugin-conf")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
configList = []byte(`{
|
||||
"name": "some-list",
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.2.0",
|
||||
"disableCheck": true,
|
||||
"plugins": [
|
||||
|
@ -218,7 +220,7 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
}
|
||||
]
|
||||
}`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -226,23 +228,23 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
|
||||
It("finds the network config file for the plugin of the given type", func() {
|
||||
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{
|
||||
Name: "some-list",
|
||||
Name: "some-network",
|
||||
CNIVersion: "0.2.0",
|
||||
DisableCheck: true,
|
||||
Plugins: []*libcni.NetworkConfig{
|
||||
Plugins: []*libcni.PluginConfig{
|
||||
{
|
||||
Network: &types.NetConf{Type: "host-local"},
|
||||
Network: &types.PluginConf{Type: "host-local"},
|
||||
Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`),
|
||||
},
|
||||
{
|
||||
Network: &types.NetConf{Type: "bridge"},
|
||||
Network: &types.PluginConf{Type: "bridge"},
|
||||
Bytes: []byte(`{"mtu":1400,"type":"bridge"}`),
|
||||
},
|
||||
{
|
||||
Network: &types.NetConf{Type: "port-forwarding"},
|
||||
Network: &types.PluginConf{Type: "port-forwarding"},
|
||||
Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`),
|
||||
},
|
||||
},
|
||||
|
@ -253,25 +255,25 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
Context("when there is a config file with the same name as the list", func() {
|
||||
BeforeEach(func() {
|
||||
configFile := []byte(`{
|
||||
"name": "some-list",
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.2.0",
|
||||
"type": "bridge"
|
||||
}`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
It("Loads the config list first", func() {
|
||||
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(netConfigList.Plugins)).To(Equal(3))
|
||||
Expect(netConfigList.Plugins).To(HaveLen(3))
|
||||
})
|
||||
|
||||
It("falls back to the config file", func() {
|
||||
Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(netConfigList.Plugins)).To(Equal(1))
|
||||
Expect(netConfigList.Plugins).To(HaveLen(1))
|
||||
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge"))
|
||||
})
|
||||
})
|
||||
|
@ -282,25 +284,25 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
|
||||
It("returns a useful error", func() {
|
||||
_, err := libcni.LoadConfList(configDir, "some-plugin")
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no config for the desired plugin list", func() {
|
||||
Context("when there is no config for the desired network name", func() {
|
||||
It("returns a useful error", func() {
|
||||
_, err := libcni.LoadConfList(configDir, "some-other-plugin")
|
||||
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"}))
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-other-network")
|
||||
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-network"}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when a config file is malformed", func() {
|
||||
BeforeEach(func() {
|
||||
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
It("returns a useful error", func() {
|
||||
_, err := libcni.LoadConfList(configDir, "some-plugin")
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-plugin")
|
||||
Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`))
|
||||
})
|
||||
})
|
||||
|
@ -308,7 +310,7 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
Context("when the config is in a nested subdir", func() {
|
||||
BeforeEach(func() {
|
||||
subdir := filepath.Join(configDir, "subdir1", "subdir2")
|
||||
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
|
||||
Expect(os.MkdirAll(subdir, 0o700)).To(Succeed())
|
||||
|
||||
configList = []byte(`{
|
||||
"name": "deep",
|
||||
|
@ -320,37 +322,309 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
},
|
||||
]
|
||||
}`)
|
||||
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0600)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0o600)).To(Succeed())
|
||||
})
|
||||
|
||||
It("will not find the config", func() {
|
||||
_, err := libcni.LoadConfList(configDir, "deep")
|
||||
_, err := libcni.LoadNetworkConf(configDir, "deep")
|
||||
Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when disableCheck is a string not a boolean", func() {
|
||||
It("will read a 'true' value and convert to boolean", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"disableCheck": "true",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.DisableCheck).To(BeTrue())
|
||||
})
|
||||
|
||||
It("will read a 'false' value and convert to boolean", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"disableCheck": "false",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.DisableCheck).To(BeFalse())
|
||||
})
|
||||
|
||||
It("will return an error on an unrecognized value", func() {
|
||||
const badValue string = "adsfasdfasf"
|
||||
configList = []byte(fmt.Sprintf(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"disableCheck": "%s",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`, badValue))
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).To(MatchError(fmt.Sprintf("error parsing configuration list: invalid value \"%s\" for disableCheck", badValue)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("for loadOnlyInlinedPlugins", func() {
|
||||
It("the value will be parsed", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"loadOnlyInlinedPlugins": true,
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
dirPluginConf := []byte(`{
|
||||
"type": "bro-check-out-my-plugin",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}`)
|
||||
|
||||
subDir := filepath.Join(configDir, "some-network")
|
||||
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
|
||||
})
|
||||
|
||||
It("the value will be false if not in config", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
|
||||
})
|
||||
|
||||
It("will return an error on an unrecognized value", func() {
|
||||
const badValue string = "sphagnum"
|
||||
configList = []byte(fmt.Sprintf(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"loadOnlyInlinedPlugins": "%s",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`, badValue))
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).To(MatchError(fmt.Sprintf(`error parsing configuration list: invalid value "%s" for loadOnlyInlinedPlugins`, badValue)))
|
||||
})
|
||||
|
||||
It("will return an error if `plugins` is missing and `loadOnlyInlinedPlugins` is `true`", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"loadOnlyInlinedPlugins": true
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).To(MatchError("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key"))
|
||||
})
|
||||
|
||||
It("will return no error if `plugins` is missing and `loadOnlyInlinedPlugins` is false", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"loadOnlyInlinedPlugins": false
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
dirPluginConf := []byte(`{
|
||||
"type": "bro-check-out-my-plugin",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}`)
|
||||
|
||||
subDir := filepath.Join(configDir, "some-network")
|
||||
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
|
||||
Expect(netConfigList.Plugins).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("will return error if `loadOnlyInlinedPlugins` is implicitly false + no conf plugin is defined, but no plugins subfolder with network name exists", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0"
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).To(MatchError("no plugin configs found"))
|
||||
})
|
||||
|
||||
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined, but no plugins subfolder with network name exists", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined and network name subfolder exists, but is empty/unreadable", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
subDir := filepath.Join(configDir, "some-network")
|
||||
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
|
||||
|
||||
_, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("will merge loaded and inlined plugin lists if both `plugins` is set and `loadOnlyInlinedPlugins` is false", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
dirPluginConf := []byte(`{
|
||||
"type": "bro-check-out-my-plugin",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}`)
|
||||
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
|
||||
subDir := filepath.Join(configDir, "some-network")
|
||||
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
|
||||
Expect(netConfigList.Plugins).To(HaveLen(2))
|
||||
})
|
||||
|
||||
It("will ignore loaded plugins if `plugins` is set and `loadOnlyInlinedPlugins` is true", func() {
|
||||
configList = []byte(`{
|
||||
"name": "some-network",
|
||||
"cniVersion": "0.4.0",
|
||||
"loadOnlyInlinedPlugins": true,
|
||||
"plugins": [
|
||||
{
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
dirPluginConf := []byte(`{
|
||||
"type": "bro-check-out-my-plugin",
|
||||
"subnet": "10.0.0.1/24"
|
||||
}`)
|
||||
|
||||
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
|
||||
subDir := filepath.Join(configDir, "some-network")
|
||||
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
|
||||
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
|
||||
|
||||
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
|
||||
Expect(netConfigList.Plugins).To(HaveLen(1))
|
||||
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("host-local"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ConfListFromFile", func() {
|
||||
Describe("NetworkConfFromFile", func() {
|
||||
Context("when the file cannot be opened", func() {
|
||||
It("returns a useful error", func() {
|
||||
_, err := libcni.ConfListFromFile("/tmp/nope/not-here")
|
||||
_, err := libcni.NetworkConfFromFile("/tmp/nope/not-here")
|
||||
Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("InjectConf", func() {
|
||||
var testNetConfig *libcni.NetworkConfig
|
||||
var testNetConfig *libcni.PluginConfig
|
||||
|
||||
BeforeEach(func() {
|
||||
testNetConfig = &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin", Type: "foobar"},
|
||||
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`)}
|
||||
testNetConfig = &libcni.PluginConfig{
|
||||
Network: &types.PluginConf{Name: "some-plugin", Type: "foobar"},
|
||||
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`),
|
||||
}
|
||||
})
|
||||
|
||||
Context("when function parameters are incorrect", func() {
|
||||
It("returns unmarshal error", func() {
|
||||
conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"},
|
||||
Bytes: []byte(`{ cc cc cc}`)}
|
||||
conf := &libcni.PluginConfig{
|
||||
Network: &types.PluginConf{Name: "some-plugin"},
|
||||
Bytes: []byte(`{ cc cc cc}`),
|
||||
}
|
||||
|
||||
_, err := libcni.InjectConf(conf, map[string]interface{}{"": nil})
|
||||
Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`)))
|
||||
|
@ -373,8 +647,8 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
|
||||
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{
|
||||
Name: "some-plugin",
|
||||
Type: "foobar",
|
||||
},
|
||||
|
@ -391,8 +665,8 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{
|
||||
Name: "some-plugin",
|
||||
Type: "foobar",
|
||||
},
|
||||
|
@ -409,8 +683,8 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{
|
||||
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{
|
||||
Name: "some-plugin",
|
||||
Type: "foobar",
|
||||
},
|
||||
|
@ -419,7 +693,6 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
|
||||
It("adds sub-fields of NetworkConfig.Network to the config", func() {
|
||||
|
||||
expectedPluginConfig := []byte(`{"dns":{"domain":"local","nameservers":["server1","server2"]},"name":"some-plugin","type":"bridge"}`)
|
||||
servers := []string{"server1", "server2"}
|
||||
newDNS := &types.DNS{Nameservers: servers, Domain: "local"}
|
||||
|
@ -432,8 +705,8 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
|
||||
Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
|
||||
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
|
||||
Network: &types.PluginConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
|
||||
Bytes: expectedPluginConfig,
|
||||
}))
|
||||
})
|
||||
|
@ -441,8 +714,55 @@ var _ = Describe("Loading configuration from disk", func() {
|
|||
})
|
||||
})
|
||||
|
||||
var _ = Describe("NetworkConfFromBytes", func() {
|
||||
Describe("Version selection", func() {
|
||||
makeConfig := func(versions ...string) []byte {
|
||||
// ugly fake json encoding, but whatever
|
||||
vs := []string{}
|
||||
for _, v := range versions {
|
||||
vs = append(vs, fmt.Sprintf(`"%s"`, v))
|
||||
}
|
||||
return []byte(fmt.Sprintf(`{"name": "test", "cniVersions": [%s], "plugins": [{"type": "foo"}]}`, strings.Join(vs, ",")))
|
||||
}
|
||||
It("correctly selects the maximum version", func() {
|
||||
conf, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.0"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.CNIVersion).To(Equal("1.1.0"))
|
||||
})
|
||||
|
||||
It("selects the highest version supported by libcni", func() {
|
||||
conf, err := libcni.NetworkConfFromBytes(makeConfig("99.0.0", "1.1.0", "0.4.0", "1.0.0"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.CNIVersion).To(Equal("1.1.0"))
|
||||
})
|
||||
|
||||
It("fails when invalid versions are specified", func() {
|
||||
_, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.f"))
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("falls back to cniVersion", func() {
|
||||
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersion": "1.2.3", "plugins": [{"type": "foo"}]}`))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.CNIVersion).To(Equal("1.2.3"))
|
||||
})
|
||||
|
||||
It("merges cniVersions and cniVersion", func() {
|
||||
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersion": "1.0.0", "cniVersions": ["0.1.0", "0.4.0"], "plugins": [{"type": "foo"}]}`))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.CNIVersion).To(Equal("1.0.0"))
|
||||
})
|
||||
|
||||
It("handles an empty cniVersions array", func() {
|
||||
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersions": [], "plugins": [{"type": "foo"}]}`))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.CNIVersion).To(Equal(""))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ConfListFromConf", func() {
|
||||
var testNetConfig *libcni.NetworkConfig
|
||||
var testNetConfig *libcni.PluginConfig
|
||||
|
||||
BeforeEach(func() {
|
||||
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`)
|
||||
|
@ -464,16 +784,15 @@ var _ = Describe("ConfListFromConf", func() {
|
|||
Expect(ncl).To(Equal(&libcni.NetworkConfigList{
|
||||
Name: "some-plugin",
|
||||
CNIVersion: "0.3.1",
|
||||
Plugins: []*libcni.NetworkConfig{testNetConfig},
|
||||
Plugins: []*libcni.PluginConfig{testNetConfig},
|
||||
}))
|
||||
|
||||
//Test that the json unmarshals to the same data
|
||||
ncl2, err := libcni.ConfListFromBytes(bytes)
|
||||
// Test that the json unmarshals to the same data
|
||||
ncl2, err := libcni.NetworkConfFromBytes(bytes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
ncl2.Bytes = nil
|
||||
ncl2.Plugins[0].Bytes = nil
|
||||
|
||||
Expect(ncl2).To(Equal(ncl))
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -17,12 +17,11 @@ package libcni_test
|
|||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLibcni(t *testing.T) {
|
||||
|
@ -35,11 +34,12 @@ var pluginPackages = map[string]string{
|
|||
"sleep": "github.com/containernetworking/cni/plugins/test/sleep",
|
||||
}
|
||||
|
||||
var pluginPaths map[string]string
|
||||
var pluginDirs []string // array of plugin dirs
|
||||
var (
|
||||
pluginPaths map[string]string
|
||||
pluginDirs []string // array of plugin dirs
|
||||
)
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
|
||||
paths := map[string]string{}
|
||||
for name, packagePath := range pluginPackages {
|
||||
execPath, err := gexec.Build(packagePath)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
GOLANGCI_LINT_VERSION="v1.57.1"
|
||||
|
||||
go install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}"
|
|
@ -0,0 +1,14 @@
|
|||
.PHONY: lint
|
||||
lint: golangci/install golangci/lint
|
||||
|
||||
.PHONY: golangci/install
|
||||
golangci/install:
|
||||
./mk/dependencies/golangci.sh
|
||||
|
||||
.PHONY: golangci/lint
|
||||
golangci/lint:
|
||||
golangci-lint run --verbose
|
||||
|
||||
.PHONY: golangci/fix
|
||||
golangci/fix:
|
||||
golangci-lint run --verbose --fix
|
|
@ -17,10 +17,10 @@ package invoke_test
|
|||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
)
|
||||
|
||||
var _ = Describe("CNIArgs AsEnv", func() {
|
||||
|
@ -51,21 +51,21 @@ var _ = Describe("CNIArgs AsEnv", func() {
|
|||
numLatentEnvs := len(latentEnvs)
|
||||
|
||||
cniEnvs := args.AsEnv()
|
||||
Expect(len(cniEnvs)).To(Equal(numLatentEnvs))
|
||||
Expect(cniEnvs).To(HaveLen(numLatentEnvs))
|
||||
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_IFNAME=eth7", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_CONTAINERID=some-container-id", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_NETNS=/some/netns/path", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_PATH=/some/cni/path", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_IFNAME=eth7", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_CONTAINERID=some-container-id", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_NETNS=/some/netns/path", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_PATH=/some/cni/path", cniEnvs)).To(BeTrue())
|
||||
|
||||
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_IFNAME=eth0", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_CONTAINERID=id", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_NETNS=testns", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_ARGS=args", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_PATH=testpath", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(BeFalse())
|
||||
Expect(inStringSlice("CNI_IFNAME=eth0", cniEnvs)).To(BeFalse())
|
||||
Expect(inStringSlice("CNI_CONTAINERID=id", cniEnvs)).To(BeFalse())
|
||||
Expect(inStringSlice("CNI_NETNS=testns", cniEnvs)).To(BeFalse())
|
||||
Expect(inStringSlice("CNI_ARGS=args", cniEnvs)).To(BeFalse())
|
||||
Expect(inStringSlice("CNI_PATH=testpath", cniEnvs)).To(BeFalse())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -94,10 +94,10 @@ var _ = Describe("CNIArgs AsEnv", func() {
|
|||
numLatentEnvs := len(latentEnvs)
|
||||
|
||||
cniEnvs := delegateArgs.AsEnv()
|
||||
Expect(len(cniEnvs)).To(Equal(numLatentEnvs))
|
||||
Expect(cniEnvs).To(HaveLen(numLatentEnvs))
|
||||
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(Equal(false))
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(BeTrue())
|
||||
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("append CNI_COMMAND if it does not exist in environment variables", func() {
|
||||
|
@ -109,9 +109,9 @@ var _ = Describe("CNIArgs AsEnv", func() {
|
|||
numLatentEnvs := len(latentEnvs)
|
||||
|
||||
cniEnvs := delegateArgs.AsEnv()
|
||||
Expect(len(cniEnvs)).To(Equal(numLatentEnvs + 1))
|
||||
Expect(cniEnvs).To(HaveLen(numLatentEnvs + 1))
|
||||
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(Equal(true))
|
||||
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(BeTrue())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
|
|
@ -51,25 +51,34 @@ func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exe
|
|||
// 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 {
|
||||
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK")
|
||||
}
|
||||
|
||||
func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) 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)
|
||||
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), 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
|
||||
}
|
||||
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL")
|
||||
}
|
||||
|
||||
// DelegateDel will override the original CNI_COMMAND env from process with DEL
|
||||
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
|
||||
// DelegateStatus calls the given delegate plugin with the CNI STATUS action and
|
||||
// JSON configuration
|
||||
func DelegateStatus(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
|
||||
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "STATUS")
|
||||
}
|
||||
|
||||
// DelegateGC calls the given delegate plugin with the CNI GC action and
|
||||
// JSON configuration
|
||||
func DelegateGC(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
|
||||
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "GC")
|
||||
}
|
||||
|
||||
// return CNIArgs used by delegation
|
||||
|
|
|
@ -17,17 +17,17 @@ package invoke_test
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
)
|
||||
|
||||
var _ = Describe("Delegate", func() {
|
||||
|
@ -43,14 +43,13 @@ var _ = Describe("Delegate", func() {
|
|||
BeforeEach(func() {
|
||||
netConf, _ = json.Marshal(map[string]string{
|
||||
"name": "delegate-test",
|
||||
"cniVersion": "0.4.0",
|
||||
"cniVersion": version.Current(),
|
||||
})
|
||||
|
||||
expectedResult = ¤t.Result{
|
||||
CNIVersion: "0.4.0",
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
|
@ -60,7 +59,7 @@ var _ = Describe("Delegate", func() {
|
|||
}
|
||||
expectedResultBytes, _ := json.Marshal(expectedResult)
|
||||
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFileName = debugFile.Name()
|
||||
|
@ -224,4 +223,48 @@ var _ = Describe("Delegate", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DelegateStatus", func() {
|
||||
BeforeEach(func() {
|
||||
os.Setenv("CNI_COMMAND", "STATUS")
|
||||
})
|
||||
|
||||
It("finds and execs the named plugin", func() {
|
||||
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
pluginInvocation, err := debug.ReadDebug(debugFileName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginInvocation.Command).To(Equal("STATUS"))
|
||||
})
|
||||
|
||||
Context("if the STATUS delegation runs on an existing non-STATUS command", func() {
|
||||
BeforeEach(func() {
|
||||
os.Setenv("CNI_COMMAND", "NOPE")
|
||||
})
|
||||
|
||||
It("aborts and returns a useful error", func() {
|
||||
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
pluginInvocation, err := debug.ReadDebug(debugFileName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(pluginInvocation.Command).To(Equal("STATUS"))
|
||||
|
||||
// check the original env
|
||||
Expect(os.Getenv("CNI_COMMAND")).To(Equal("NOPE"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin cannot be found", func() {
|
||||
BeforeEach(func() {
|
||||
pluginName = "non-existent-plugin"
|
||||
})
|
||||
|
||||
It("returns a useful error", func() {
|
||||
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
|
||||
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,10 +16,12 @@ package invoke
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
|
@ -32,21 +34,64 @@ type Exec interface {
|
|||
Decode(jsonBytes []byte) (version.PluginInfo, error)
|
||||
}
|
||||
|
||||
// Plugin must return result in same version as specified in netconf; but
|
||||
// for backwards compatibility reasons if the result version is empty use
|
||||
// config version (rather than technically correct 0.1.0).
|
||||
// https://github.com/containernetworking/cni/issues/895
|
||||
func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
confVersion, err := versionDecoder.Decode(netconf)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
var rawResult map[string]interface{}
|
||||
if err := json.Unmarshal(result, &rawResult); err != nil {
|
||||
return "", nil, fmt.Errorf("failed to unmarshal raw result: %w", err)
|
||||
}
|
||||
|
||||
// plugin output of "null" is successfully unmarshalled, but results in a nil
|
||||
// map which causes a panic when the confVersion is assigned below.
|
||||
if rawResult == nil {
|
||||
rawResult = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Manually decode Result version; we need to know whether its cniVersion
|
||||
// is empty, while built-in decoders (correctly) substitute 0.1.0 for an
|
||||
// empty version per the CNI spec.
|
||||
if resultVerRaw, ok := rawResult["cniVersion"]; ok {
|
||||
resultVer, ok := resultVerRaw.(string)
|
||||
if ok && resultVer != "" {
|
||||
return resultVer, result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the cniVersion is not present or empty, assume the result is
|
||||
// the same CNI spec version as the config
|
||||
rawResult["cniVersion"] = confVersion
|
||||
newBytes, err := json.Marshal(rawResult)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to remarshal fixed result: %w", err)
|
||||
}
|
||||
|
||||
return confVersion, newBytes, nil
|
||||
}
|
||||
|
||||
// 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 (
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "path"
|
||||
// "strings"
|
||||
//)
|
||||
// )
|
||||
//
|
||||
//type fakeExec struct {
|
||||
// type fakeExec struct {
|
||||
// version.PluginDecoder
|
||||
//}
|
||||
// }
|
||||
//
|
||||
//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
// net := &types.NetConf{}
|
||||
// err := json.Unmarshal(stdinData, net)
|
||||
// if err != nil {
|
||||
|
@ -64,14 +109,14 @@ type Exec interface {
|
|||
// }
|
||||
// }
|
||||
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
|
||||
//}
|
||||
// }
|
||||
//
|
||||
//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
|
||||
// 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 {
|
||||
|
@ -83,14 +128,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Plugin must return result in same version as specified in netconf
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
confVersion, err := versionDecoder.Decode(netconf)
|
||||
resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return version.NewResult(confVersion, stdoutBytes)
|
||||
return create.Create(resultVersion, fixedBytes)
|
||||
}
|
||||
|
||||
func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {
|
||||
|
|
|
@ -19,13 +19,13 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/invoke/fakes"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Executing a plugin, unit tests", func() {
|
||||
|
@ -42,7 +42,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
rawExec = &fakes.RawExec{}
|
||||
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
|
||||
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "0.3.1", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
|
||||
|
||||
versionDecoder = &fakes.VersionDecoder{}
|
||||
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
|
||||
|
@ -68,12 +68,13 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
|
||||
})
|
||||
|
||||
It("passes its arguments through to the rawExec", func() {
|
||||
invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
_, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||
|
@ -96,11 +97,44 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||
_, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, nil)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("assumes config version if result version is missing", func() {
|
||||
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
|
||||
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Version()).To(Equal("0.3.1"))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
|
||||
})
|
||||
|
||||
It("assumes config version if result version is empty", func() {
|
||||
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
|
||||
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Version()).To(Equal("0.3.1"))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
|
||||
})
|
||||
|
||||
It("assumes 0.1.0 if config and result version are empty", func() {
|
||||
netconf = []byte(`{ "some": "stdin" }`)
|
||||
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
|
||||
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Version()).To(Equal("0.1.0"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("without returning a result", func() {
|
||||
It("passes its arguments through to the rawExec", func() {
|
||||
invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||
|
@ -131,7 +165,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||
})
|
||||
|
||||
It("execs the plugin with the command VERSION", func() {
|
||||
invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
|
||||
_, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
|
||||
expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()})
|
||||
|
@ -167,7 +202,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
|
|||
})
|
||||
|
||||
It("sets dummy values for env vars required by very old plugins", func() {
|
||||
invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
|
||||
_, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
env := rawExec.ExecPluginCall.Received.Environ
|
||||
Expect(env).To(ContainElement("CNI_NETNS=dummy"))
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FindInPath returns the full path of the plugin by searching in the provided path
|
||||
|
@ -26,6 +27,10 @@ func FindInPath(plugin string, paths []string) (string, error) {
|
|||
return "", fmt.Errorf("no plugin name provided")
|
||||
}
|
||||
|
||||
if strings.ContainsRune(plugin, os.PathSeparator) {
|
||||
return "", fmt.Errorf("invalid plugin name: %s", plugin)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return "", fmt.Errorf("no paths provided")
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ package invoke_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
)
|
||||
|
||||
var _ = Describe("FindInPath", func() {
|
||||
|
@ -37,17 +37,17 @@ var _ = Describe("FindInPath", func() {
|
|||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tempDir, err := ioutil.TempDir("", "cni-find")
|
||||
tempDir, err := os.MkdirTemp("", "cni-find")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin")
|
||||
plugin, err := os.CreateTemp(tempDir, "a-cni-plugin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0]
|
||||
plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
anotherTempDir, err = ioutil.TempDir("", "nothing-here")
|
||||
anotherTempDir, err = os.MkdirTemp("", "nothing-here")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
multiplePaths = []string{anotherTempDir, tempDir}
|
||||
|
@ -99,5 +99,13 @@ var _ = Describe("FindInPath", func() {
|
|||
Expect(err).To(MatchError(fmt.Sprintf("failed to find plugin %q in path %s", pluginName, pathsWithNothing)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When the plugin contains a directory separator", func() {
|
||||
It("returns an error", func() {
|
||||
bogusPlugin := ".." + string(os.PathSeparator) + "pluginname"
|
||||
_, err := invoke.FindInPath(bogusPlugin, []string{anotherTempDir})
|
||||
Expect(err).To(MatchError("invalid plugin name: " + bogusPlugin))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,18 +16,16 @@ package invoke_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GetVersion, integration tests", func() {
|
||||
|
@ -37,7 +35,7 @@ var _ = Describe("GetVersion, integration tests", func() {
|
|||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
pluginDir, err := ioutil.TempDir("", "plugins")
|
||||
pluginDir, err := os.MkdirTemp("", "plugins")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pluginPath = filepath.Join(pluginDir, "test-plugin")
|
||||
if runtime.GOOS == "windows" {
|
||||
|
@ -129,5 +127,7 @@ func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
|
|||
func main() { skel.PluginMain(c, c) }
|
||||
`
|
||||
|
||||
const git_ref_v010 = "2c482f4"
|
||||
const git_ref_v020_no_custom_versions = "349d66d"
|
||||
const (
|
||||
git_ref_v010 = "2c482f4"
|
||||
git_ref_v020_no_custom_versions = "349d66d"
|
||||
)
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
package invoke_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInvoke(t *testing.T) {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package invoke
|
||||
|
|
|
@ -17,15 +17,13 @@ package invoke_test
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
)
|
||||
|
||||
var _ = Describe("RawExec", func() {
|
||||
|
@ -41,7 +39,7 @@ var _ = Describe("RawExec", func() {
|
|||
const reportResult = `{ "some": "result" }`
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFileName = debugFile.Name()
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2022 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 ns
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
func CheckNetNS(nsPath string) (bool, *types.Error) {
|
||||
return false, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2022 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 ns
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
// Returns an object representing the current OS thread's network namespace
|
||||
func getCurrentNS() (netns.NsHandle, error) {
|
||||
// Lock the thread in case other goroutine executes in it and changes its
|
||||
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
|
||||
// return an unexpected network namespace.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
return netns.Get()
|
||||
}
|
||||
|
||||
func CheckNetNS(nsPath string) (bool, *types.Error) {
|
||||
ns, err := netns.GetFromPath(nsPath)
|
||||
// Let plugins check whether nsPath from args is valid. Also support CNI DEL for empty nsPath as already-deleted nsPath.
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
pluginNS, err := getCurrentNS()
|
||||
if err != nil {
|
||||
return false, types.NewError(types.ErrInvalidNetNS, "get plugin's netns failed", "")
|
||||
}
|
||||
defer pluginNS.Close()
|
||||
|
||||
return pluginNS.Equal(ns), nil
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2022 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 ns
|
||||
|
||||
import "github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
func CheckNetNS(nsPath string) (bool, *types.Error) {
|
||||
return false, nil
|
||||
}
|
191
pkg/skel/skel.go
191
pkg/skel/skel.go
|
@ -19,13 +19,14 @@ package skel
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/utils"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
@ -39,6 +40,7 @@ type CmdArgs struct {
|
|||
IfName string
|
||||
Args string
|
||||
Path string
|
||||
NetnsOverride string
|
||||
StdinData []byte
|
||||
}
|
||||
|
||||
|
@ -55,12 +57,13 @@ type dispatcher struct {
|
|||
type reqForCmdEntry map[string]bool
|
||||
|
||||
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
||||
var cmd, contID, netns, ifName, args, path string
|
||||
var cmd, contID, netns, ifName, args, path, netnsOverride string
|
||||
|
||||
vars := []struct {
|
||||
name string
|
||||
val *string
|
||||
reqForCmd reqForCmdEntry
|
||||
validateFn func(string) *types.Error
|
||||
}{
|
||||
{
|
||||
"CNI_COMMAND",
|
||||
|
@ -69,7 +72,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"ADD": true,
|
||||
"CHECK": true,
|
||||
"DEL": true,
|
||||
"GC": true,
|
||||
"STATUS": true,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CNI_CONTAINERID",
|
||||
|
@ -79,6 +85,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"CHECK": true,
|
||||
"DEL": true,
|
||||
},
|
||||
utils.ValidateContainerID,
|
||||
},
|
||||
{
|
||||
"CNI_NETNS",
|
||||
|
@ -88,6 +95,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"CHECK": true,
|
||||
"DEL": false,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CNI_IFNAME",
|
||||
|
@ -97,6 +105,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"CHECK": true,
|
||||
"DEL": true,
|
||||
},
|
||||
utils.ValidateInterfaceName,
|
||||
},
|
||||
{
|
||||
"CNI_ARGS",
|
||||
|
@ -106,6 +115,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"CHECK": false,
|
||||
"DEL": false,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CNI_PATH",
|
||||
|
@ -114,7 +124,20 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"ADD": true,
|
||||
"CHECK": true,
|
||||
"DEL": true,
|
||||
"GC": true,
|
||||
"STATUS": true,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CNI_NETNS_OVERRIDE",
|
||||
&netnsOverride,
|
||||
reqForCmdEntry{
|
||||
"ADD": false,
|
||||
"CHECK": false,
|
||||
"DEL": false,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -125,6 +148,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
|
||||
argsMissing = append(argsMissing, v.name)
|
||||
}
|
||||
} else if v.reqForCmd[cmd] && v.validateFn != nil {
|
||||
if err := v.validateFn(*v.val); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,11 +164,17 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
t.Stdin = bytes.NewReader(nil)
|
||||
}
|
||||
|
||||
stdinData, err := ioutil.ReadAll(t.Stdin)
|
||||
stdinData, err := io.ReadAll(t.Stdin)
|
||||
if err != nil {
|
||||
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
|
||||
}
|
||||
|
||||
if cmd != "VERSION" {
|
||||
if err := validateConfig(stdinData); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cmdArgs := &CmdArgs{
|
||||
ContainerID: contID,
|
||||
Netns: netns,
|
||||
|
@ -149,6 +182,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
Args: args,
|
||||
Path: path,
|
||||
StdinData: stdinData,
|
||||
NetnsOverride: netnsOverride,
|
||||
}
|
||||
return cmd, cmdArgs, nil
|
||||
}
|
||||
|
@ -163,8 +197,13 @@ func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo ver
|
|||
return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
|
||||
}
|
||||
|
||||
if toCall == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = toCall(cmdArgs); err != nil {
|
||||
if e, ok := err.(*types.Error); ok {
|
||||
var e *types.Error
|
||||
if errors.As(err, &e) {
|
||||
// don't wrap Error in Error
|
||||
return e
|
||||
}
|
||||
|
@ -190,32 +229,32 @@ func validateConfig(jsonBytes []byte) *types.Error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
|
||||
func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
|
||||
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
|
||||
if err != nil {
|
||||
// Print the about string to stderr when no command is set
|
||||
if err.Code == types.ErrInvalidEnvironmentVariables && t.Getenv("CNI_COMMAND") == "" && about != "" {
|
||||
_, _ = fmt.Fprintln(t.Stderr, about)
|
||||
_, _ = fmt.Fprintf(t.Stderr, "CNI protocol versions supported: %s\n", strings.Join(versionInfo.SupportedVersions(), ", "))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd != "VERSION" {
|
||||
if err = validateConfig(cmdArgs.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = utils.ValidateContainerID(cmdArgs.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = utils.ValidateInterfaceName(cmdArgs.IfName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "ADD":
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Add)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
|
||||
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
} else if isPluginNetNS {
|
||||
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
|
||||
}
|
||||
}
|
||||
case "CHECK":
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
|
@ -231,7 +270,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
|||
if err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if gtet {
|
||||
if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil {
|
||||
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Check); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -239,7 +278,62 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
|||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
|
||||
case "DEL":
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Del)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
|
||||
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
} else if isPluginNetNS {
|
||||
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
|
||||
}
|
||||
}
|
||||
case "GC":
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
}
|
||||
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if !gtet {
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow GC", "")
|
||||
}
|
||||
for _, pluginVersion := range versionInfo.SupportedVersions() {
|
||||
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
|
||||
if err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if gtet {
|
||||
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.GC); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow GC", "")
|
||||
case "STATUS":
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
}
|
||||
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if !gtet {
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow STATUS", "")
|
||||
}
|
||||
for _, pluginVersion := range versionInfo.SupportedVersions() {
|
||||
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
|
||||
if err != nil {
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if gtet {
|
||||
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow STATUS", "")
|
||||
case "VERSION":
|
||||
if err := versionInfo.Encode(t.Stdout); err != nil {
|
||||
return types.NewError(types.ErrIOFailure, err.Error(), "")
|
||||
|
@ -248,10 +342,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
|||
return types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("unknown CNI_COMMAND: %v", cmd), "")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginMainWithError is the core "main" for a plugin. It accepts
|
||||
|
@ -266,13 +357,63 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
|||
//
|
||||
// To let this package automatically handle errors and call os.Exit(1) for you,
|
||||
// use PluginMain() instead.
|
||||
//
|
||||
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncsWithError instead.
|
||||
func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
|
||||
return PluginMainFuncsWithError(CNIFuncs{Add: cmdAdd, Check: cmdCheck, Del: cmdDel}, versionInfo, about)
|
||||
}
|
||||
|
||||
// CNIFuncs contains a group of callback command funcs to be passed in as
|
||||
// parameters to the core "main" for a plugin.
|
||||
type CNIFuncs struct {
|
||||
Add func(_ *CmdArgs) error
|
||||
Del func(_ *CmdArgs) error
|
||||
Check func(_ *CmdArgs) error
|
||||
GC func(_ *CmdArgs) error
|
||||
Status func(_ *CmdArgs) error
|
||||
}
|
||||
|
||||
// PluginMainFuncsWithError is the core "main" for a plugin. It accepts
|
||||
// callback functions defined within CNIFuncs and returns an error.
|
||||
//
|
||||
// The caller must also specify what CNI spec versions the plugin supports.
|
||||
//
|
||||
// It is the responsibility of the caller to check for non-nil error return.
|
||||
//
|
||||
// For a plugin to comply with the CNI spec, it must print any error to stdout
|
||||
// as JSON and then exit with nonzero status code.
|
||||
//
|
||||
// To let this package automatically handle errors and call os.Exit(1) for you,
|
||||
// use PluginMainFuncs() instead.
|
||||
func PluginMainFuncsWithError(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
|
||||
return (&dispatcher{
|
||||
Getenv: os.Getenv,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}).pluginMain(cmdAdd, cmdCheck, cmdDel, versionInfo, about)
|
||||
}).pluginMain(funcs, versionInfo, about)
|
||||
}
|
||||
|
||||
// PluginMainFuncs is the core "main" for a plugin which includes automatic error handling.
|
||||
// This is a newer alternative func to PluginMain which abstracts CNI commands within a
|
||||
// CNIFuncs interface.
|
||||
//
|
||||
// The caller must also specify what CNI spec versions the plugin supports.
|
||||
//
|
||||
// The caller can specify an "about" string, which is printed on stderr
|
||||
// when no CNI_COMMAND is specified. The recommended output is "CNI plugin <foo> v<version>"
|
||||
//
|
||||
// When an error occurs in any func in CNIFuncs, PluginMainFuncs will print the error
|
||||
// as JSON to stdout and call os.Exit(1).
|
||||
//
|
||||
// To have more control over error handling, use PluginMainFuncsWithError() instead.
|
||||
func PluginMainFuncs(funcs CNIFuncs, versionInfo version.PluginInfo, about string) {
|
||||
if e := PluginMainFuncsWithError(funcs, versionInfo, about); e != nil {
|
||||
if err := e.Print(); err != nil {
|
||||
log.Print("Error writing error JSON to stdout: ", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// PluginMain is the core "main" for a plugin which includes automatic error handling.
|
||||
|
@ -286,6 +427,8 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio
|
|||
// as JSON to stdout and call os.Exit(1).
|
||||
//
|
||||
// To have more control over error handling, use PluginMainWithError() instead.
|
||||
//
|
||||
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncs instead.
|
||||
func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) {
|
||||
if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil {
|
||||
if err := e.Print(); err != nil {
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package skel
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestSkel(t *testing.T) {
|
||||
|
|
|
@ -20,13 +20,11 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type fakeCmd struct {
|
||||
|
@ -50,10 +48,11 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
environment map[string]string
|
||||
stdinData string
|
||||
stdout, stderr *bytes.Buffer
|
||||
cmdAdd, cmdCheck, cmdDel *fakeCmd
|
||||
cmdAdd, cmdCheck, cmdDel, cmdGC *fakeCmd
|
||||
dispatch *dispatcher
|
||||
expectedCmdArgs *CmdArgs
|
||||
versionInfo version.PluginInfo
|
||||
funcs CNIFuncs
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
|
@ -69,7 +68,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
stdinData = `{ "name":"skel-test", "some": "config", "cniVersion": "9.8.7" }`
|
||||
stdout = &bytes.Buffer{}
|
||||
stderr = &bytes.Buffer{}
|
||||
versionInfo = version.PluginSupports("9.8.7")
|
||||
versionInfo = version.PluginSupports("9.8.7", "10.0.0")
|
||||
dispatch = &dispatcher{
|
||||
Getenv: func(key string) string { return environment[key] },
|
||||
Stdin: strings.NewReader(stdinData),
|
||||
|
@ -79,6 +78,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
cmdAdd = &fakeCmd{}
|
||||
cmdCheck = &fakeCmd{}
|
||||
cmdDel = &fakeCmd{}
|
||||
cmdGC = &fakeCmd{}
|
||||
funcs = CNIFuncs{
|
||||
Add: cmdAdd.Func,
|
||||
Del: cmdDel.Func,
|
||||
Check: cmdCheck.Func,
|
||||
GC: cmdGC.Func,
|
||||
}
|
||||
|
||||
expectedCmdArgs = &CmdArgs{
|
||||
ContainerID: "some-container-id",
|
||||
Netns: "/some/netns/path",
|
||||
|
@ -89,10 +96,10 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
}
|
||||
})
|
||||
|
||||
var envVarChecker = func(envVar string, isRequired bool) {
|
||||
envVarChecker := func(envVar string, isRequired bool) {
|
||||
delete(environment, envVar)
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
if isRequired {
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -104,8 +111,17 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
}
|
||||
|
||||
Context("when the CNI_COMMAND is ADD", func() {
|
||||
expectedCmdArgs = &CmdArgs{
|
||||
ContainerID: "some-container-id",
|
||||
Netns: "/some/netns/path",
|
||||
IfName: "eth0",
|
||||
Args: "some;extra;args",
|
||||
Path: "/some/cni/path",
|
||||
StdinData: []byte(stdinData),
|
||||
}
|
||||
|
||||
It("extracts env vars and stdin data and calls cmdAdd", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(1))
|
||||
|
@ -116,7 +132,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
|
||||
It("returns an error when containerID has invalid characters", func() {
|
||||
environment["CNI_CONTAINERID"] = "some-%%container-id"
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -129,7 +145,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name is too long", func() {
|
||||
environment["CNI_IFNAME"] = "1234567890123456"
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -141,7 +157,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name is .", func() {
|
||||
environment["CNI_IFNAME"] = "."
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -153,7 +169,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name is ..", func() {
|
||||
environment["CNI_IFNAME"] = ".."
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -165,7 +181,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name contains invalid characters /", func() {
|
||||
environment["CNI_IFNAME"] = "test/test"
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -177,7 +193,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name contains invalid characters :", func() {
|
||||
environment["CNI_IFNAME"] = "test:test"
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -189,7 +205,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("interface name contains invalid characters whitespace", func() {
|
||||
environment["CNI_IFNAME"] = "test test"
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -200,7 +216,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("does not call cmdCheck or cmdDel", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
|
@ -224,7 +240,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("reports that all of them are missing, not just the first", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
|
@ -246,8 +262,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(1))
|
||||
|
@ -261,14 +276,15 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("immediately returns a useful error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("incompatible CNI versions"))
|
||||
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
|
||||
})
|
||||
|
||||
It("does not call either callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
|
@ -283,7 +299,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("extracts env vars and stdin data and calls cmdCheck", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
|
@ -293,7 +309,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("does not call cmdAdd or cmdDel", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
|
@ -317,7 +333,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("reports that all of them are missing, not just the first", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
|
@ -330,8 +346,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
Context("when cniVersion is less than 0.4.0", func() {
|
||||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("config version does not allow CHECK"))
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
|
@ -343,8 +359,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.4.0", "some": "config" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("plugin version does not allow CHECK"))
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
|
@ -356,8 +372,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
|
@ -368,8 +384,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("immediately returns invalid network config", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.4.0")
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(uint(types.ErrInvalidNetworkConfig)))
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
|
@ -380,8 +396,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
|
@ -389,13 +405,132 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("when the CNI_COMMAND is GC", func() {
|
||||
BeforeEach(func() {
|
||||
environment["CNI_COMMAND"] = "GC"
|
||||
delete(environment, "CNI_NETNS")
|
||||
delete(environment, "CNI_IFNAME")
|
||||
delete(environment, "CNI_CONTAINERID")
|
||||
delete(environment, "CNI_ARGS")
|
||||
|
||||
expectedCmdArgs = &CmdArgs{
|
||||
Path: "/some/cni/path",
|
||||
StdinData: []byte(stdinData),
|
||||
}
|
||||
|
||||
dispatch = &dispatcher{
|
||||
Getenv: func(key string) string {
|
||||
return environment[key]
|
||||
},
|
||||
Stdin: strings.NewReader(stdinData),
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
})
|
||||
|
||||
It("extracts env vars and stdin data and calls cmdGC", func() {
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdCheck.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
Expect(cmdGC.CallCount).To(Equal(1))
|
||||
Expect(cmdGC.Received.CmdArgs).To(Equal(expectedCmdArgs))
|
||||
})
|
||||
|
||||
It("does not call cmdAdd or cmdDel", func() {
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
DescribeTable("required / optional env vars", envVarChecker,
|
||||
Entry("command", "CNI_COMMAND", true),
|
||||
Entry("container id", "CNI_CONTAINERID", false),
|
||||
Entry("net ns", "CNI_NETNS", false),
|
||||
Entry("if name", "CNI_IFNAME", false),
|
||||
Entry("args", "CNI_ARGS", false),
|
||||
Entry("path", "CNI_PATH", true),
|
||||
)
|
||||
|
||||
Context("when multiple required env vars are missing", func() {
|
||||
BeforeEach(func() {
|
||||
delete(environment, "CNI_PATH")
|
||||
})
|
||||
|
||||
It("reports that all of them are missing, not just the first", func() {
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
Msg: "required env variables [CNI_PATH] missing",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when cniVersion is less than 1.1.0", func() {
|
||||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("config version does not allow GC"))
|
||||
Expect(cmdGC.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when plugin does not support 1.1.0", func() {
|
||||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "1.1.0", "some": "config" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
|
||||
Expect(err.Msg).To(Equal("plugin version does not allow GC"))
|
||||
Expect(cmdGC.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the config has a bad version", func() {
|
||||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
|
||||
versionInfo = version.PluginSupports("1.1.0")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
|
||||
Expect(cmdGC.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the config has a bad name", func() {
|
||||
It("immediately returns invalid network config", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
|
||||
versionInfo = version.PluginSupports("1.1.0")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
|
||||
Expect(cmdGC.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the plugin has a bad version", func() {
|
||||
It("immediately returns a useful error", func() {
|
||||
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "1.1.0", "some": "config", "name": "test" }`)
|
||||
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
|
||||
Expect(cmdGC.CallCount).To(Equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the CNI_COMMAND is DEL", func() {
|
||||
BeforeEach(func() {
|
||||
environment["CNI_COMMAND"] = "DEL"
|
||||
})
|
||||
|
||||
It("calls cmdDel with the env vars and stdin data", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdDel.CallCount).To(Equal(1))
|
||||
|
@ -403,7 +538,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("does not call cmdAdd", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
|
@ -425,17 +560,17 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("prints the version to stdout", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"supportedVersions": ["9.8.7"]
|
||||
}`, current.ImplementedSpecVersion)))
|
||||
"supportedVersions": ["9.8.7", "10.0.0"]
|
||||
}`, version.Current())))
|
||||
})
|
||||
|
||||
It("does not call cmdAdd or cmdDel", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
|
@ -455,14 +590,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
r := &BadReader{}
|
||||
dispatch.Stdin = r
|
||||
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.ReadCount).To(Equal(0))
|
||||
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"supportedVersions": ["9.8.7"]
|
||||
}`, current.ImplementedSpecVersion)))
|
||||
"supportedVersions": ["9.8.7", "10.0.0"]
|
||||
}`, version.Current())))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -472,14 +607,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("does not call any cmd callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
It("returns an error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInvalidEnvironmentVariables,
|
||||
|
@ -489,7 +624,8 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
|
||||
It("prints the about string when the command is blank", func() {
|
||||
environment["CNI_COMMAND"] = ""
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "test framework v42")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "test framework v42")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(stderr.String()).To(ContainSubstring("test framework v42"))
|
||||
})
|
||||
})
|
||||
|
@ -497,18 +633,18 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
Context("when the CNI_COMMAND is missing", func() {
|
||||
It("prints the about string to stderr", func() {
|
||||
environment = map[string]string{}
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "AWESOME PLUGIN")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "AWESOME PLUGIN")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
log := stderr.String()
|
||||
Expect(log).To(Equal("AWESOME PLUGIN\n"))
|
||||
Expect(log).To(Equal("AWESOME PLUGIN\nCNI protocol versions supported: 9.8.7, 10.0.0\n"))
|
||||
})
|
||||
|
||||
It("fails if there is no about string", func() {
|
||||
environment = map[string]string{}
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
|
@ -526,14 +662,14 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("does not call any cmd callback", func() {
|
||||
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(cmdAdd.CallCount).To(Equal(0))
|
||||
Expect(cmdDel.CallCount).To(Equal(0))
|
||||
})
|
||||
|
||||
It("wraps and returns the error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrIOFailure,
|
||||
|
@ -552,7 +688,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("returns the error as-is", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: 1234,
|
||||
|
@ -567,7 +703,7 @@ var _ = Describe("dispatching to the correct callback", func() {
|
|||
})
|
||||
|
||||
It("wraps and returns the error", func() {
|
||||
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
|
||||
err := dispatch.pluginMain(funcs, versionInfo, "")
|
||||
|
||||
Expect(err).To(Equal(&types.Error{
|
||||
Code: types.ErrInternal,
|
||||
|
|
|
@ -22,25 +22,47 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
convert "github.com/containernetworking/cni/pkg/types/internal"
|
||||
)
|
||||
|
||||
const ImplementedSpecVersion string = "0.2.0"
|
||||
|
||||
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
|
||||
var supportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
|
||||
|
||||
// Register converters for all versions less than the implemented spec version
|
||||
func init() {
|
||||
convert.RegisterConverter("0.1.0", []string{ImplementedSpecVersion}, convertFrom010)
|
||||
convert.RegisterConverter(ImplementedSpecVersion, []string{"0.1.0"}, convertTo010)
|
||||
|
||||
// Creator
|
||||
convert.RegisterCreator(supportedVersions, NewResult)
|
||||
}
|
||||
|
||||
// Compatibility types for CNI version 0.1.0 and 0.2.0
|
||||
|
||||
// NewResult creates a new Result object from JSON data. The JSON data
|
||||
// must be compatible with the CNI versions implemented by this type.
|
||||
func NewResult(data []byte) (types.Result, error) {
|
||||
result := &Result{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range supportedVersions {
|
||||
if result.CNIVersion == v {
|
||||
if result.CNIVersion == "" {
|
||||
result.CNIVersion = "0.1.0"
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
|
||||
supportedVersions, result.CNIVersion)
|
||||
}
|
||||
|
||||
// GetResult converts the given Result object to the ImplementedSpecVersion
|
||||
// and returns the concrete type or an error
|
||||
func GetResult(r types.Result) (*Result, error) {
|
||||
// We expect version 0.1.0/0.2.0 results
|
||||
result020, err := r.GetAsVersion(ImplementedSpecVersion)
|
||||
result020, err := convert.Convert(r, ImplementedSpecVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -51,6 +73,32 @@ func GetResult(r types.Result) (*Result, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func convertFrom010(from types.Result, toVersion string) (types.Result, error) {
|
||||
if toVersion != "0.2.0" {
|
||||
panic("only converts to version 0.2.0")
|
||||
}
|
||||
fromResult := from.(*Result)
|
||||
return &Result{
|
||||
CNIVersion: ImplementedSpecVersion,
|
||||
IP4: fromResult.IP4.Copy(),
|
||||
IP6: fromResult.IP6.Copy(),
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertTo010(from types.Result, toVersion string) (types.Result, error) {
|
||||
if toVersion != "0.1.0" {
|
||||
panic("only converts to version 0.1.0")
|
||||
}
|
||||
fromResult := from.(*Result)
|
||||
return &Result{
|
||||
CNIVersion: "0.1.0",
|
||||
IP4: fromResult.IP4.Copy(),
|
||||
IP6: fromResult.IP6.Copy(),
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Result is what gets returned from the plugin (via stdout) to the caller
|
||||
type Result struct {
|
||||
CNIVersion string `json:"cniVersion,omitempty"`
|
||||
|
@ -60,17 +108,16 @@ type Result struct {
|
|||
}
|
||||
|
||||
func (r *Result) Version() string {
|
||||
return ImplementedSpecVersion
|
||||
return r.CNIVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||
for _, supportedVersion := range SupportedVersions {
|
||||
if version == supportedVersion {
|
||||
r.CNIVersion = version
|
||||
return r, nil
|
||||
// If the creator of the result did not set the CNIVersion, assume it
|
||||
// should be the highest spec version implemented by this Result
|
||||
if r.CNIVersion == "" {
|
||||
r.CNIVersion = ImplementedSpecVersion
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
|
||||
return convert.Convert(r, version)
|
||||
}
|
||||
|
||||
func (r *Result) Print() error {
|
||||
|
@ -93,6 +140,22 @@ type IPConfig struct {
|
|||
Routes []types.Route
|
||||
}
|
||||
|
||||
func (i *IPConfig) Copy() *IPConfig {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var routes []types.Route
|
||||
for _, fromRoute := range i.Routes {
|
||||
routes = append(routes, *fromRoute.Copy())
|
||||
}
|
||||
return &IPConfig{
|
||||
IP: i.IP,
|
||||
Gateway: i.Gateway,
|
||||
Routes: routes,
|
||||
}
|
||||
}
|
||||
|
||||
// net.IPNet is not JSON (un)marshallable so this duality is needed
|
||||
// for our custom IPNet type
|
||||
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package types020_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTypes010(t *testing.T) {
|
||||
|
|
|
@ -15,19 +15,19 @@
|
|||
package types020_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
)
|
||||
|
||||
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
||||
It("correctly encodes a 0.1.0/0.2.0 Result", func() {
|
||||
func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
@ -47,8 +47,8 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
|||
Expect(routegwv6).NotTo(BeNil())
|
||||
|
||||
// Set every field of the struct to ensure source compatibility
|
||||
res := types020.Result{
|
||||
CNIVersion: types020.ImplementedSpecVersion,
|
||||
res := &types020.Result{
|
||||
CNIVersion: resultCNIVersion,
|
||||
IP4: &types020.IPConfig{
|
||||
IP: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
|
@ -71,23 +71,8 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
|||
},
|
||||
}
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(Equal(`{
|
||||
"cniVersion": "0.2.0",
|
||||
json := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"ip4": {
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
|
@ -123,6 +108,50 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
|||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
}`, jsonCNIVersion)
|
||||
|
||||
return res, json
|
||||
}
|
||||
|
||||
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
|
||||
It("correctly encodes a 0.2.0 Result", func() {
|
||||
res, expectedJSON := testResult(types020.ImplementedSpecVersion, types020.ImplementedSpecVersion)
|
||||
out, err := json.Marshal(res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(out).To(MatchJSON(expectedJSON))
|
||||
})
|
||||
|
||||
It("correctly encodes a 0.1.0 Result", func() {
|
||||
res, expectedJSON := testResult("0.1.0", "0.1.0")
|
||||
out, err := json.Marshal(res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(out).To(MatchJSON(expectedJSON))
|
||||
})
|
||||
|
||||
It("converts a 0.2.0 result to 0.1.0", func() {
|
||||
res, expectedJSON := testResult(types020.ImplementedSpecVersion, "0.1.0")
|
||||
res010, err := res.GetAsVersion("0.1.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
out, err := json.Marshal(res010)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(out).To(MatchJSON(expectedJSON))
|
||||
})
|
||||
|
||||
It("converts a 0.1.0 result to 0.2.0", func() {
|
||||
res, expectedJSON := testResult("0.1.0", types020.ImplementedSpecVersion)
|
||||
res020, err := res.GetAsVersion(types020.ImplementedSpecVersion)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
out, err := json.Marshal(res020)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(out).To(MatchJSON(expectedJSON))
|
||||
})
|
||||
|
||||
It("creates a 0.1.0 result passing CNIVersion ''", func() {
|
||||
_, expectedJSON := testResult("", "")
|
||||
resT, err := create.Create("", []byte(expectedJSON))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
res010, ok := resT.(*types020.Result)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(res010.CNIVersion).To(Equal("0.1.0"))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
// 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 types040
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
convert "github.com/containernetworking/cni/pkg/types/internal"
|
||||
)
|
||||
|
||||
const ImplementedSpecVersion string = "0.4.0"
|
||||
|
||||
var supportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
|
||||
|
||||
// Register converters for all versions less than the implemented spec version
|
||||
func init() {
|
||||
// Up-converters
|
||||
convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x)
|
||||
convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x)
|
||||
convert.RegisterConverter("0.3.0", supportedVersions, convertInternal)
|
||||
convert.RegisterConverter("0.3.1", supportedVersions, convertInternal)
|
||||
|
||||
// Down-converters
|
||||
convert.RegisterConverter("0.4.0", []string{"0.3.0", "0.3.1"}, convertInternal)
|
||||
convert.RegisterConverter("0.4.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
|
||||
convert.RegisterConverter("0.3.1", []string{"0.1.0", "0.2.0"}, convertTo02x)
|
||||
convert.RegisterConverter("0.3.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
|
||||
|
||||
// Creator
|
||||
convert.RegisterCreator(supportedVersions, NewResult)
|
||||
}
|
||||
|
||||
func NewResult(data []byte) (types.Result, error) {
|
||||
result := &Result{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range supportedVersions {
|
||||
if result.CNIVersion == v {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
|
||||
supportedVersions, result.CNIVersion)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewResultFromResult(result types.Result) (*Result, error) {
|
||||
newResult, err := convert.Convert(result, ImplementedSpecVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newResult.(*Result), nil
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
func convert020IPConfig(from *types020.IPConfig, ipVersion string) *IPConfig {
|
||||
return &IPConfig{
|
||||
Version: ipVersion,
|
||||
Address: from.IP,
|
||||
Gateway: from.Gateway,
|
||||
}
|
||||
}
|
||||
|
||||
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*types020.Result)
|
||||
toResult := &Result{
|
||||
CNIVersion: toVersion,
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
Routes: []*types.Route{},
|
||||
}
|
||||
if fromResult.IP4 != nil {
|
||||
toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP4, "4"))
|
||||
for _, fromRoute := range fromResult.IP4.Routes {
|
||||
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
|
||||
}
|
||||
}
|
||||
|
||||
if fromResult.IP6 != nil {
|
||||
toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP6, "6"))
|
||||
for _, fromRoute := range fromResult.IP6.Routes {
|
||||
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
|
||||
}
|
||||
}
|
||||
|
||||
return toResult, nil
|
||||
}
|
||||
|
||||
func convertInternal(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*Result)
|
||||
toResult := &Result{
|
||||
CNIVersion: toVersion,
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
Routes: []*types.Route{},
|
||||
}
|
||||
for _, fromIntf := range fromResult.Interfaces {
|
||||
toResult.Interfaces = append(toResult.Interfaces, fromIntf.Copy())
|
||||
}
|
||||
for _, fromIPC := range fromResult.IPs {
|
||||
toResult.IPs = append(toResult.IPs, fromIPC.Copy())
|
||||
}
|
||||
for _, fromRoute := range fromResult.Routes {
|
||||
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
|
||||
}
|
||||
return toResult, nil
|
||||
}
|
||||
|
||||
func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*Result)
|
||||
toResult := &types020.Result{
|
||||
CNIVersion: toVersion,
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
}
|
||||
|
||||
for _, fromIP := range fromResult.IPs {
|
||||
// Only convert the first IP address of each version as 0.2.0
|
||||
// and earlier cannot handle multiple IP addresses
|
||||
if fromIP.Version == "4" && toResult.IP4 == nil {
|
||||
toResult.IP4 = &types020.IPConfig{
|
||||
IP: fromIP.Address,
|
||||
Gateway: fromIP.Gateway,
|
||||
}
|
||||
} else if fromIP.Version == "6" && toResult.IP6 == nil {
|
||||
toResult.IP6 = &types020.IPConfig{
|
||||
IP: fromIP.Address,
|
||||
Gateway: fromIP.Gateway,
|
||||
}
|
||||
}
|
||||
if toResult.IP4 != nil && toResult.IP6 != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, fromRoute := range fromResult.Routes {
|
||||
is4 := fromRoute.Dst.IP.To4() != nil
|
||||
if is4 && toResult.IP4 != nil {
|
||||
toResult.IP4.Routes = append(toResult.IP4.Routes, types.Route{
|
||||
Dst: fromRoute.Dst,
|
||||
GW: fromRoute.GW,
|
||||
})
|
||||
} else if !is4 && toResult.IP6 != nil {
|
||||
toResult.IP6.Routes = append(toResult.IP6.Routes, types.Route{
|
||||
Dst: fromRoute.Dst,
|
||||
GW: fromRoute.GW,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 0.2.0 and earlier require at least one IP address in the Result
|
||||
if toResult.IP4 == nil && toResult.IP6 == nil {
|
||||
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
|
||||
}
|
||||
|
||||
return toResult, nil
|
||||
}
|
||||
|
||||
func (r *Result) Version() string {
|
||||
return r.CNIVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||
// If the creator of the result did not set the CNIVersion, assume it
|
||||
// should be the highest spec version implemented by this Result
|
||||
if r.CNIVersion == "" {
|
||||
r.CNIVersion = ImplementedSpecVersion
|
||||
}
|
||||
return convert.Convert(r, 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (i *Interface) Copy() *Interface {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
newIntf := *i
|
||||
return &newIntf
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (i *IPConfig) Copy() *IPConfig {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipc := &IPConfig{
|
||||
Version: i.Version,
|
||||
Address: i.Address,
|
||||
Gateway: i.Gateway,
|
||||
}
|
||||
if i.Interface != nil {
|
||||
intf := *i.Interface
|
||||
ipc.Interface = &intf
|
||||
}
|
||||
return ipc
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -12,13 +12,13 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package current_test
|
||||
package types040_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTypesCurrent(t *testing.T) {
|
|
@ -12,23 +12,23 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package current_test
|
||||
package types040_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
types040 "github.com/containernetworking/cni/pkg/types/040"
|
||||
)
|
||||
|
||||
func testResult() *current.Result {
|
||||
func testResult() *types040.Result {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
@ -48,25 +48,25 @@ func testResult() *current.Result {
|
|||
Expect(routegwv6).NotTo(BeNil())
|
||||
|
||||
// Set every field of the struct to ensure source compatibility
|
||||
return ¤t.Result{
|
||||
return &types040.Result{
|
||||
CNIVersion: "0.3.1",
|
||||
Interfaces: []*current.Interface{
|
||||
Interfaces: []*types040.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
IPs: []*types040.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Interface: types040.Int(0),
|
||||
Address: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
},
|
||||
{
|
||||
Version: "6",
|
||||
Interface: current.Int(0),
|
||||
Interface: types040.Int(0),
|
||||
Address: *ipv6,
|
||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ func testResult() *current.Result {
|
|||
}
|
||||
}
|
||||
|
||||
var _ = Describe("Current types operations", func() {
|
||||
var _ = Describe("040 types operations", func() {
|
||||
It("correctly encodes a 0.3.x Result", func() {
|
||||
res := testResult()
|
||||
|
||||
|
@ -99,7 +99,7 @@ var _ = Describe("Current types operations", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
out, err := io.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
|
@ -169,12 +169,12 @@ var _ = Describe("Current types operations", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := ioutil.ReadAll(r)
|
||||
out, err := io.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(MatchJSON(`{
|
||||
"cniVersion": "0.2.0",
|
||||
"cniVersion": "0.1.0",
|
||||
"ip4": {
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
|
@ -257,8 +257,8 @@ var _ = Describe("Current types operations", func() {
|
|||
},
|
||||
}
|
||||
|
||||
// Convert to current
|
||||
newRes, err := current.NewResultFromResult(res)
|
||||
// Convert to 040
|
||||
newRes, err := types040.NewResultFromResult(res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Convert back to 0.2.0
|
||||
oldRes, err := newRes.GetAsVersion("0.2.0")
|
||||
|
@ -314,8 +314,8 @@ var _ = Describe("Current types operations", func() {
|
|||
},
|
||||
}
|
||||
|
||||
// Convert to current
|
||||
newRes, err := current.NewResultFromResult(res)
|
||||
// Convert to 040
|
||||
newRes, err := types040.NewResultFromResult(res)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Convert back to 0.2.0
|
||||
oldRes, err := newRes.GetAsVersion("0.2.0")
|
||||
|
@ -330,9 +330,9 @@ var _ = Describe("Current types operations", func() {
|
|||
})
|
||||
|
||||
It("correctly marshals and unmarshals interface index 0", func() {
|
||||
ipc := ¤t.IPConfig{
|
||||
ipc := &types040.IPConfig{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Interface: types040.Int(0),
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
|
@ -347,21 +347,29 @@ var _ = Describe("Current types operations", func() {
|
|||
"address": "10.1.2.3/24"
|
||||
}`))
|
||||
|
||||
recovered := ¤t.IPConfig{}
|
||||
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
|
||||
recovered := &types040.IPConfig{}
|
||||
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
|
||||
Expect(recovered).To(Equal(ipc))
|
||||
})
|
||||
|
||||
It("fails when downconverting a config to 0.2.0 that has no IPs", func() {
|
||||
res := testResult()
|
||||
res.IPs = nil
|
||||
res.Routes = nil
|
||||
_, err := res.GetAsVersion("0.2.0")
|
||||
Expect(err).To(MatchError("cannot convert: no valid IP addresses"))
|
||||
})
|
||||
|
||||
Context("when unmarshalling json fails", func() {
|
||||
It("returns an error", func() {
|
||||
recovered := ¤t.IPConfig{}
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
|
||||
recovered := &types040.IPConfig{}
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
|
||||
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
|
||||
})
|
||||
})
|
||||
|
||||
It("correctly marshals a missing interface index", func() {
|
||||
ipc := ¤t.IPConfig{
|
||||
ipc := &types040.IPConfig{
|
||||
Version: "4",
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
|
@ -0,0 +1,352 @@
|
|||
// 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 types100
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types040 "github.com/containernetworking/cni/pkg/types/040"
|
||||
convert "github.com/containernetworking/cni/pkg/types/internal"
|
||||
)
|
||||
|
||||
// The types did not change between v1.0 and v1.1
|
||||
const ImplementedSpecVersion string = "1.1.0"
|
||||
|
||||
var supportedVersions = []string{"1.0.0", "1.1.0"}
|
||||
|
||||
// Register converters for all versions less than the implemented spec version
|
||||
func init() {
|
||||
// Up-converters
|
||||
convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x)
|
||||
convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x)
|
||||
convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x)
|
||||
convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x)
|
||||
convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x)
|
||||
convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100)
|
||||
|
||||
// Down-converters
|
||||
convert.RegisterConverter("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
|
||||
convert.RegisterConverter("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
|
||||
convert.RegisterConverter("1.1.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
|
||||
convert.RegisterConverter("1.1.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
|
||||
convert.RegisterConverter("1.1.0", []string{"1.0.0"}, convertFrom100)
|
||||
|
||||
// Creator
|
||||
convert.RegisterCreator(supportedVersions, NewResult)
|
||||
}
|
||||
|
||||
func NewResult(data []byte) (types.Result, error) {
|
||||
result := &Result{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range supportedVersions {
|
||||
if result.CNIVersion == v {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
|
||||
supportedVersions, result.CNIVersion)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewResultFromResult(result types.Result) (*Result, error) {
|
||||
newResult, err := convert.Convert(result, ImplementedSpecVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newResult.(*Result), nil
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Note: DNS should be omit if DNS is empty but default Marshal function
|
||||
// will output empty structure hence need to write a Marshal function
|
||||
func (r *Result) MarshalJSON() ([]byte, error) {
|
||||
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
|
||||
type fixObjType = Result
|
||||
|
||||
bytes, err := json.Marshal(fixObjType(*r)) //nolint:all
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fixupObj := make(map[string]interface{})
|
||||
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.DNS.IsEmpty() {
|
||||
delete(fixupObj, "dns")
|
||||
}
|
||||
|
||||
return json.Marshal(fixupObj)
|
||||
}
|
||||
|
||||
// convertFrom100 does nothing except set the version; the types are the same
|
||||
func convertFrom100(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*Result)
|
||||
|
||||
result := &Result{
|
||||
CNIVersion: toVersion,
|
||||
Interfaces: fromResult.Interfaces,
|
||||
IPs: fromResult.IPs,
|
||||
Routes: fromResult.Routes,
|
||||
DNS: fromResult.DNS,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
|
||||
result040, err := convert.Convert(from, "0.4.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result100, err := convertFrom04x(result040, toVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result100, nil
|
||||
}
|
||||
|
||||
func convertIPConfigFrom040(from *types040.IPConfig) *IPConfig {
|
||||
to := &IPConfig{
|
||||
Address: from.Address,
|
||||
Gateway: from.Gateway,
|
||||
}
|
||||
if from.Interface != nil {
|
||||
intf := *from.Interface
|
||||
to.Interface = &intf
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func convertInterfaceFrom040(from *types040.Interface) *Interface {
|
||||
return &Interface{
|
||||
Name: from.Name,
|
||||
Mac: from.Mac,
|
||||
Sandbox: from.Sandbox,
|
||||
}
|
||||
}
|
||||
|
||||
func convertFrom04x(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*types040.Result)
|
||||
toResult := &Result{
|
||||
CNIVersion: toVersion,
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
Routes: []*types.Route{},
|
||||
}
|
||||
for _, fromIntf := range fromResult.Interfaces {
|
||||
toResult.Interfaces = append(toResult.Interfaces, convertInterfaceFrom040(fromIntf))
|
||||
}
|
||||
for _, fromIPC := range fromResult.IPs {
|
||||
toResult.IPs = append(toResult.IPs, convertIPConfigFrom040(fromIPC))
|
||||
}
|
||||
for _, fromRoute := range fromResult.Routes {
|
||||
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
|
||||
}
|
||||
return toResult, nil
|
||||
}
|
||||
|
||||
func convertIPConfigTo040(from *IPConfig) *types040.IPConfig {
|
||||
version := "6"
|
||||
if from.Address.IP.To4() != nil {
|
||||
version = "4"
|
||||
}
|
||||
to := &types040.IPConfig{
|
||||
Version: version,
|
||||
Address: from.Address,
|
||||
Gateway: from.Gateway,
|
||||
}
|
||||
if from.Interface != nil {
|
||||
intf := *from.Interface
|
||||
to.Interface = &intf
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func convertInterfaceTo040(from *Interface) *types040.Interface {
|
||||
return &types040.Interface{
|
||||
Name: from.Name,
|
||||
Mac: from.Mac,
|
||||
Sandbox: from.Sandbox,
|
||||
}
|
||||
}
|
||||
|
||||
func convertTo04x(from types.Result, toVersion string) (types.Result, error) {
|
||||
fromResult := from.(*Result)
|
||||
toResult := &types040.Result{
|
||||
CNIVersion: toVersion,
|
||||
DNS: *fromResult.DNS.Copy(),
|
||||
Routes: []*types.Route{},
|
||||
}
|
||||
for _, fromIntf := range fromResult.Interfaces {
|
||||
toResult.Interfaces = append(toResult.Interfaces, convertInterfaceTo040(fromIntf))
|
||||
}
|
||||
for _, fromIPC := range fromResult.IPs {
|
||||
toResult.IPs = append(toResult.IPs, convertIPConfigTo040(fromIPC))
|
||||
}
|
||||
for _, fromRoute := range fromResult.Routes {
|
||||
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
|
||||
}
|
||||
return toResult, nil
|
||||
}
|
||||
|
||||
func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
|
||||
// First convert to 0.4.0
|
||||
result040, err := convertTo04x(from, "0.4.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result02x, err := convert.Convert(result040, toVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result02x, nil
|
||||
}
|
||||
|
||||
func (r *Result) Version() string {
|
||||
return r.CNIVersion
|
||||
}
|
||||
|
||||
func (r *Result) GetAsVersion(version string) (types.Result, error) {
|
||||
// If the creator of the result did not set the CNIVersion, assume it
|
||||
// should be the highest spec version implemented by this Result
|
||||
if r.CNIVersion == "" {
|
||||
r.CNIVersion = ImplementedSpecVersion
|
||||
}
|
||||
return convert.Convert(r, 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
|
||||
}
|
||||
|
||||
// Interface contains values about the created interfaces
|
||||
type Interface struct {
|
||||
Name string `json:"name"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
Sandbox string `json:"sandbox,omitempty"`
|
||||
SocketPath string `json:"socketPath,omitempty"`
|
||||
PciID string `json:"pciID,omitempty"`
|
||||
}
|
||||
|
||||
func (i *Interface) String() string {
|
||||
return fmt.Sprintf("%+v", *i)
|
||||
}
|
||||
|
||||
func (i *Interface) Copy() *Interface {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
newIntf := *i
|
||||
return &newIntf
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Index into Result structs Interfaces list
|
||||
Interface *int
|
||||
Address net.IPNet
|
||||
Gateway net.IP
|
||||
}
|
||||
|
||||
func (i *IPConfig) String() string {
|
||||
return fmt.Sprintf("%+v", *i)
|
||||
}
|
||||
|
||||
func (i *IPConfig) Copy() *IPConfig {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipc := &IPConfig{
|
||||
Address: i.Address,
|
||||
Gateway: i.Gateway,
|
||||
}
|
||||
if i.Interface != nil {
|
||||
intf := *i.Interface
|
||||
ipc.Interface = &intf
|
||||
}
|
||||
return ipc
|
||||
}
|
||||
|
||||
// JSON (un)marshallable types
|
||||
type ipConfig struct {
|
||||
Interface *int `json:"interface,omitempty"`
|
||||
Address types.IPNet `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
func (c *IPConfig) MarshalJSON() ([]byte, error) {
|
||||
ipc := ipConfig{
|
||||
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.Interface = ipc.Interface
|
||||
c.Address = net.IPNet(ipc.Address)
|
||||
c.Gateway = ipc.Gateway
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// 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 types100_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTypesCurrent(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Current Types Suite")
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
// 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 types100_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
func testResult() *current.Result {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4).NotTo(BeNil())
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
Expect(routegwv6).NotTo(BeNil())
|
||||
// Set every field of the struct to ensure source compatibility
|
||||
return ¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Mtu: 1500,
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
PciID: "8086:9a01",
|
||||
SocketPath: "/path/to/vhost/fd",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
},
|
||||
{
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv6,
|
||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
||||
},
|
||||
},
|
||||
Routes: []*types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"1.2.3.4", "1::cafe"},
|
||||
Domain: "acompany.com",
|
||||
Search: []string{"somedomain.com", "otherdomain.net"},
|
||||
Options: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("Current types operations", func() {
|
||||
It("correctly encodes a 1.1.0 Result", func() {
|
||||
res := testResult()
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := io.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(MatchJSON(`{
|
||||
"cniVersion": "` + current.ImplementedSpecVersion + `",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"mtu": 1500,
|
||||
"sandbox": "/proc/3553/ns/net",
|
||||
"pciID": "8086:9a01",
|
||||
"socketPath": "/path/to/vhost/fd"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"interface": 0,
|
||||
"address": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1"
|
||||
},
|
||||
{
|
||||
"interface": 0,
|
||||
"address": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
},
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
It("correctly converts a 1.0.0 Result to 1.1.0", func() {
|
||||
tr, err := testResult().GetAsVersion("1.0.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
trv1, ok := tr.(*current.Result)
|
||||
Expect(ok).To(BeTrue())
|
||||
|
||||
// 1.0.0 and 1.1.0 should be the same except for CNI version
|
||||
Expect(trv1.CNIVersion).To(Equal("1.0.0"))
|
||||
|
||||
// If we convert 1.0.0 back to 1.1.0 it should be identical
|
||||
trv11, err := trv1.GetAsVersion("1.1.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(trv11).To(Equal(testResult()))
|
||||
})
|
||||
|
||||
It("correctly encodes a 0.1.0 Result", func() {
|
||||
res, err := testResult().GetAsVersion("0.1.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := io.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(MatchJSON(`{
|
||||
"cniVersion": "0.1.0",
|
||||
"ip4": {
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip6": {
|
||||
"ip": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
It("correctly encodes a 0.4.0 Result", func() {
|
||||
res, err := testResult().GetAsVersion("0.4.0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
os.Stdout = w
|
||||
err = res.Print()
|
||||
w.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// parse the result
|
||||
out, err := io.ReadAll(r)
|
||||
os.Stdout = oldStdout
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(string(out)).To(MatchJSON(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"interface": 0,
|
||||
"version": "4",
|
||||
"address": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1"
|
||||
},
|
||||
{
|
||||
"interface": 0,
|
||||
"version": "6",
|
||||
"address": "abcd:1234:ffff::cdde/64",
|
||||
"gateway": "abcd:1234:ffff::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"dst": "15.5.6.0/24",
|
||||
"gw": "15.5.6.8"
|
||||
},
|
||||
{
|
||||
"dst": "1111:dddd::/80",
|
||||
"gw": "1111:dddd::aaaa"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"nameservers": [
|
||||
"1.2.3.4",
|
||||
"1::cafe"
|
||||
],
|
||||
"domain": "acompany.com",
|
||||
"search": [
|
||||
"somedomain.com",
|
||||
"otherdomain.net"
|
||||
],
|
||||
"options": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
})
|
||||
|
||||
It("correctly marshals and unmarshals interface index 0", func() {
|
||||
ipc := ¤t.IPConfig{
|
||||
Interface: current.Int(0),
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(ipc)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(jsonBytes).To(MatchJSON(`{
|
||||
"interface": 0,
|
||||
"address": "10.1.2.3/24"
|
||||
}`))
|
||||
|
||||
recovered := ¤t.IPConfig{}
|
||||
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
|
||||
Expect(recovered).To(Equal(ipc))
|
||||
})
|
||||
|
||||
Context("when unmarshalling json fails", func() {
|
||||
It("returns an error", func() {
|
||||
recovered := ¤t.IPConfig{}
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
|
||||
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
|
||||
})
|
||||
})
|
||||
|
||||
It("correctly marshals a missing interface index", func() {
|
||||
ipc := ¤t.IPConfig{
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
}
|
||||
|
||||
json, err := json.Marshal(ipc)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(json).To(MatchJSON(`{
|
||||
"address": "10.1.2.3/24"
|
||||
}`))
|
||||
})
|
||||
})
|
|
@ -26,8 +26,8 @@ import (
|
|||
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"
|
||||
// Returns boolean true if the string is "1" or "true" or "True"
|
||||
// Returns boolean false if the string is "0" or "false" or "False”
|
||||
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
|
||||
s := strings.ToLower(string(data))
|
||||
switch s {
|
||||
|
@ -91,16 +91,26 @@ func LoadArgs(args string, container interface{}) error {
|
|||
unknownArgs = append(unknownArgs, pair)
|
||||
continue
|
||||
}
|
||||
keyFieldIface := keyField.Addr().Interface()
|
||||
u, ok := keyFieldIface.(encoding.TextUnmarshaler)
|
||||
|
||||
var keyFieldInterface interface{}
|
||||
switch {
|
||||
case keyField.Kind() == reflect.Ptr:
|
||||
keyField.Set(reflect.New(keyField.Type().Elem()))
|
||||
keyFieldInterface = keyField.Interface()
|
||||
case keyField.CanAddr() && keyField.Addr().CanInterface():
|
||||
keyFieldInterface = keyField.Addr().Interface()
|
||||
default:
|
||||
return UnmarshalableArgsError{fmt.Errorf("field '%s' has no valid interface", keyString)}
|
||||
}
|
||||
u, ok := keyFieldInterface.(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))}
|
||||
keyString, reflect.TypeOf(keyFieldInterface))}
|
||||
}
|
||||
err := u.UnmarshalText([]byte(valueString))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
|
||||
return fmt.Errorf("ARGS: error parsing value of pair %q: %w", pair, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
package types_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
. "github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
. "github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("UnmarshallableBool UnmarshalText", func() {
|
||||
|
@ -126,7 +126,37 @@ var _ = Describe("LoadArgs", func() {
|
|||
}{}
|
||||
err := LoadArgs("IP=10.0.0.0/24", &conf)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When loading known arguments", func() {
|
||||
It("should succeed if argument is marshallable value type", func() {
|
||||
conf := struct {
|
||||
IP net.IP
|
||||
CommonArgs
|
||||
}{}
|
||||
err := LoadArgs("IP=10.0.0.0", &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IP.String()).To(Equal("10.0.0.0"))
|
||||
})
|
||||
|
||||
It("should succeed if argument is marshallable pointer type", func() {
|
||||
conf := struct {
|
||||
IP *net.IP
|
||||
CommonArgs
|
||||
}{}
|
||||
err := LoadArgs("IP=10.0.0.0", &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IP.String()).To(Equal("10.0.0.0"))
|
||||
})
|
||||
|
||||
It("should fail if argument is pointer of marshallable pointer type", func() {
|
||||
conf := struct {
|
||||
IP **net.IP
|
||||
CommonArgs
|
||||
}{}
|
||||
err := LoadArgs("IP=10.0.0.0", &conf)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// 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 create
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
_ "github.com/containernetworking/cni/pkg/types/020"
|
||||
_ "github.com/containernetworking/cni/pkg/types/040"
|
||||
_ "github.com/containernetworking/cni/pkg/types/100"
|
||||
convert "github.com/containernetworking/cni/pkg/types/internal"
|
||||
)
|
||||
|
||||
// DecodeVersion returns the CNI version from CNI configuration or result JSON,
|
||||
// or an error if the operation could not be performed.
|
||||
func DecodeVersion(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: %w", err)
|
||||
}
|
||||
if conf.CNIVersion == "" {
|
||||
return "0.1.0", nil
|
||||
}
|
||||
return conf.CNIVersion, nil
|
||||
}
|
||||
|
||||
// Create creates a CNI Result using the given JSON with the expected
|
||||
// version, or an error if the creation could not be performed
|
||||
func Create(version string, bytes []byte) (types.Result, error) {
|
||||
return convert.Create(version, bytes)
|
||||
}
|
||||
|
||||
// CreateFromBytes creates a CNI Result from the given JSON, automatically
|
||||
// detecting the CNI spec version of the result. An error is returned if the
|
||||
// operation could not be performed.
|
||||
func CreateFromBytes(bytes []byte) (types.Result, error) {
|
||||
version, err := DecodeVersion(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convert.Create(version, bytes)
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// 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 convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
// ConvertFn should convert from the given arbitrary Result type into a
|
||||
// Result implementing CNI specification version passed in toVersion.
|
||||
// The function is guaranteed to be passed a Result type matching the
|
||||
// fromVersion it was registered with, and is guaranteed to be
|
||||
// passed a toVersion matching one of the toVersions it was registered with.
|
||||
type ConvertFn func(from types.Result, toVersion string) (types.Result, error)
|
||||
|
||||
type converter struct {
|
||||
// fromVersion is the CNI Result spec version that convertFn accepts
|
||||
fromVersion string
|
||||
// toVersions is a list of versions that convertFn can convert to
|
||||
toVersions []string
|
||||
convertFn ConvertFn
|
||||
}
|
||||
|
||||
var converters []*converter
|
||||
|
||||
func findConverter(fromVersion, toVersion string) *converter {
|
||||
for _, c := range converters {
|
||||
if c.fromVersion == fromVersion {
|
||||
for _, v := range c.toVersions {
|
||||
if v == toVersion {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert converts a CNI Result to the requested CNI specification version,
|
||||
// or returns an error if the conversion could not be performed or failed
|
||||
func Convert(from types.Result, toVersion string) (types.Result, error) {
|
||||
if toVersion == "" {
|
||||
toVersion = "0.1.0"
|
||||
}
|
||||
|
||||
fromVersion := from.Version()
|
||||
|
||||
// Shortcut for same version
|
||||
if fromVersion == toVersion {
|
||||
return from, nil
|
||||
}
|
||||
|
||||
// Otherwise find the right converter
|
||||
c := findConverter(fromVersion, toVersion)
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("no converter for CNI result version %s to %s",
|
||||
fromVersion, toVersion)
|
||||
}
|
||||
return c.convertFn(from, toVersion)
|
||||
}
|
||||
|
||||
// RegisterConverter registers a CNI Result converter. SHOULD NOT BE CALLED
|
||||
// EXCEPT FROM CNI ITSELF.
|
||||
func RegisterConverter(fromVersion string, toVersions []string, convertFn ConvertFn) {
|
||||
// Make sure there is no converter already registered for these
|
||||
// from and to versions
|
||||
for _, v := range toVersions {
|
||||
if findConverter(fromVersion, v) != nil {
|
||||
panic(fmt.Sprintf("converter already registered for %s to %s",
|
||||
fromVersion, v))
|
||||
}
|
||||
}
|
||||
converters = append(converters, &converter{
|
||||
fromVersion: fromVersion,
|
||||
toVersions: toVersions,
|
||||
convertFn: convertFn,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// 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 convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
type ResultFactoryFunc func([]byte) (types.Result, error)
|
||||
|
||||
type creator struct {
|
||||
// CNI Result spec versions that createFn can create a Result for
|
||||
versions []string
|
||||
createFn ResultFactoryFunc
|
||||
}
|
||||
|
||||
var creators []*creator
|
||||
|
||||
func findCreator(version string) *creator {
|
||||
for _, c := range creators {
|
||||
for _, v := range c.versions {
|
||||
if v == version {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates a CNI Result using the given JSON, or an error if the creation
|
||||
// could not be performed
|
||||
func Create(version string, bytes []byte) (types.Result, error) {
|
||||
if c := findCreator(version); c != nil {
|
||||
return c.createFn(bytes)
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported CNI result version %q", version)
|
||||
}
|
||||
|
||||
// RegisterCreator registers a CNI Result creator. SHOULD NOT BE CALLED
|
||||
// EXCEPT FROM CNI ITSELF.
|
||||
func RegisterCreator(versions []string, createFn ResultFactoryFunc) {
|
||||
// Make sure there is no creator already registered for these versions
|
||||
for _, v := range versions {
|
||||
if findCreator(v) != nil {
|
||||
panic(fmt.Sprintf("creator already registered for %s", v))
|
||||
}
|
||||
}
|
||||
creators = append(creators, &creator{
|
||||
versions: versions,
|
||||
createFn: createFn,
|
||||
})
|
||||
}
|
|
@ -56,35 +56,74 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NetConf describes a network.
|
||||
type NetConf struct {
|
||||
// Use PluginConf instead of NetConf, the NetConf
|
||||
// backwards-compat alias will be removed in a future release.
|
||||
type NetConf = PluginConf
|
||||
|
||||
// PluginConf describes a plugin configuration for a specific network.
|
||||
type PluginConf 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"`
|
||||
DNS DNS `json:"dns,omitempty"`
|
||||
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
PrevResult Result `json:"-"`
|
||||
|
||||
// ValidAttachments is only supplied when executing a GC operation
|
||||
ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
|
||||
}
|
||||
|
||||
// GCAttachment is the parameters to a GC call -- namely,
|
||||
// the container ID and ifname pair that represents a
|
||||
// still-valid attachment.
|
||||
type GCAttachment struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
IfName string `json:"ifname"`
|
||||
}
|
||||
|
||||
// Note: DNS should be omit if DNS is empty but default Marshal function
|
||||
// will output empty structure hence need to write a Marshal function
|
||||
func (n *PluginConf) MarshalJSON() ([]byte, error) {
|
||||
bytes, err := json.Marshal(*n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fixupObj := make(map[string]interface{})
|
||||
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n.DNS.IsEmpty() {
|
||||
delete(fixupObj, "dns")
|
||||
}
|
||||
|
||||
return json.Marshal(fixupObj)
|
||||
}
|
||||
|
||||
type IPAM struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// IsEmpty returns true if IPAM structure has no value, otherwise return false
|
||||
func (i *IPAM) IsEmpty() bool {
|
||||
return i.Type == ""
|
||||
}
|
||||
|
||||
// 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"`
|
||||
DisableGC bool `json:"disableGC,omitempty"`
|
||||
Plugins []*PluginConf `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
|
||||
|
@ -118,17 +157,79 @@ type DNS struct {
|
|||
Options []string `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// IsEmpty returns true if DNS structure has no value, otherwise return false
|
||||
func (d *DNS) IsEmpty() bool {
|
||||
if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *DNS) Copy() *DNS {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
to := &DNS{Domain: d.Domain}
|
||||
to.Nameservers = append(to.Nameservers, d.Nameservers...)
|
||||
to.Search = append(to.Search, d.Search...)
|
||||
to.Options = append(to.Options, d.Options...)
|
||||
return to
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Dst net.IPNet
|
||||
GW net.IP
|
||||
MTU int
|
||||
AdvMSS int
|
||||
Priority int
|
||||
Table *int
|
||||
Scope *int
|
||||
}
|
||||
|
||||
func (r *Route) String() string {
|
||||
return fmt.Sprintf("%+v", *r)
|
||||
table := "<nil>"
|
||||
if r.Table != nil {
|
||||
table = fmt.Sprintf("%d", *r.Table)
|
||||
}
|
||||
|
||||
scope := "<nil>"
|
||||
if r.Scope != nil {
|
||||
scope = fmt.Sprintf("%d", *r.Scope)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope)
|
||||
}
|
||||
|
||||
func (r *Route) Copy() *Route {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
route := &Route{
|
||||
Dst: r.Dst,
|
||||
GW: r.GW,
|
||||
MTU: r.MTU,
|
||||
AdvMSS: r.AdvMSS,
|
||||
Priority: r.Priority,
|
||||
Scope: r.Scope,
|
||||
}
|
||||
|
||||
if r.Table != nil {
|
||||
table := *r.Table
|
||||
route.Table = &table
|
||||
}
|
||||
|
||||
if r.Scope != nil {
|
||||
scope := *r.Scope
|
||||
route.Scope = &scope
|
||||
}
|
||||
|
||||
return route
|
||||
}
|
||||
|
||||
// Well known error codes
|
||||
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
||||
// see https://github.com/containernetworking/cni/blob/main/SPEC.md#error
|
||||
const (
|
||||
ErrUnknown uint = iota // 0
|
||||
ErrIncompatibleCNIVersion // 1
|
||||
|
@ -138,7 +239,10 @@ const (
|
|||
ErrIOFailure // 5
|
||||
ErrDecodingFailure // 6
|
||||
ErrInvalidNetworkConfig // 7
|
||||
ErrInvalidNetNS // 8
|
||||
ErrTryAgainLater uint = 11
|
||||
ErrPluginNotAvailable uint = 50
|
||||
ErrLimitedConnectivity uint = 51
|
||||
ErrInternal uint = 999
|
||||
)
|
||||
|
||||
|
@ -175,6 +279,11 @@ func (e *Error) Print() error {
|
|||
type route struct {
|
||||
Dst IPNet `json:"dst"`
|
||||
GW net.IP `json:"gw,omitempty"`
|
||||
MTU int `json:"mtu,omitempty"`
|
||||
AdvMSS int `json:"advmss,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Table *int `json:"table,omitempty"`
|
||||
Scope *int `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Route) UnmarshalJSON(data []byte) error {
|
||||
|
@ -185,6 +294,12 @@ func (r *Route) UnmarshalJSON(data []byte) error {
|
|||
|
||||
r.Dst = net.IPNet(rt.Dst)
|
||||
r.GW = rt.GW
|
||||
r.MTU = rt.MTU
|
||||
r.AdvMSS = rt.AdvMSS
|
||||
r.Priority = rt.Priority
|
||||
r.Table = rt.Table
|
||||
r.Scope = rt.Scope
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -192,6 +307,11 @@ func (r Route) MarshalJSON() ([]byte, error) {
|
|||
rt := route{
|
||||
Dst: IPNet(r.Dst),
|
||||
GW: r.GW,
|
||||
MTU: r.MTU,
|
||||
AdvMSS: r.AdvMSS,
|
||||
Priority: r.Priority,
|
||||
Table: r.Table,
|
||||
Scope: r.Scope,
|
||||
}
|
||||
|
||||
return json.Marshal(rt)
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package types_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTypes(t *testing.T) {
|
||||
|
|
|
@ -18,14 +18,15 @@ import (
|
|||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types040 "github.com/containernetworking/cni/pkg/types/040"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
var _ = Describe("Types", func() {
|
||||
|
||||
Describe("ParseCIDR", func() {
|
||||
DescribeTable("Parse and stringify",
|
||||
func(input, expectedIP string, expectedMask int) {
|
||||
|
@ -88,13 +89,18 @@ var _ = Describe("Types", func() {
|
|||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
GW: net.ParseIP("1.2.3.1"),
|
||||
MTU: 1500,
|
||||
AdvMSS: 1340,
|
||||
Priority: 100,
|
||||
Table: types040.Int(50),
|
||||
Scope: types040.Int(253),
|
||||
}
|
||||
})
|
||||
|
||||
It("marshals and unmarshals to JSON", func() {
|
||||
jsonBytes, err := json.Marshal(example)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1" }`))
|
||||
Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1", "mtu": 1500, "advmss": 1340, "priority": 100, "table": 50, "scope": 253 }`))
|
||||
|
||||
var unmarshaled types.Route
|
||||
Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
|
||||
|
@ -110,7 +116,7 @@ var _ = Describe("Types", func() {
|
|||
})
|
||||
|
||||
It("formats as a string with a hex mask", func() {
|
||||
Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1}`))
|
||||
Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1 MTU:1500 AdvMSS:1340 Priority:100 Table:50 Scope:253}`))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -143,4 +149,53 @@ var _ = Describe("Types", func() {
|
|||
Expect(err).To(Equal(example))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Result conversion", func() {
|
||||
var result *current.Result
|
||||
BeforeEach(func() {
|
||||
ipv4, err := types.ParseCIDR("1.2.3.30/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
result = ¤t.Result{
|
||||
CNIVersion: "1.0.0",
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
PciID: "8086:9a01",
|
||||
SocketPath: "/path/to/vhost/fd",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv4,
|
||||
Gateway: net.ParseIP("1.2.3.1"),
|
||||
},
|
||||
{
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv6,
|
||||
Gateway: net.ParseIP("abcd:1234:ffff::1"),
|
||||
},
|
||||
},
|
||||
DNS: types.DNS{
|
||||
Nameservers: []string{"1.2.3.4", "1::cafe"},
|
||||
Domain: "acompany.com",
|
||||
Search: []string{"somedomain.com", "otherdomain.net"},
|
||||
Options: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("can create a CNIVersion '' (0.1.0) result", func() {
|
||||
newResult, err := result.GetAsVersion("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(newResult.Version()).To(Equal("0.1.0"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -36,7 +36,6 @@ var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`)
|
|||
|
||||
// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters
|
||||
func ValidateContainerID(containerID string) *types.Error {
|
||||
|
||||
if containerID == "" {
|
||||
return types.NewError(types.ErrUnknownContainer, "missing containerID", "")
|
||||
}
|
||||
|
@ -48,7 +47,6 @@ func ValidateContainerID(containerID string) *types.Error {
|
|||
|
||||
// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters
|
||||
func ValidateNetworkName(networkName string) *types.Error {
|
||||
|
||||
if networkName == "" {
|
||||
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "")
|
||||
}
|
||||
|
@ -58,11 +56,11 @@ func ValidateNetworkName(networkName string) *types.Error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValidateInterfaceName will validate the interface name based on the three rules below
|
||||
// ValidateInterfaceName will validate the interface name based on the four rules below
|
||||
// 1. The name must not be empty
|
||||
// 2. The name must be less than 16 characters
|
||||
// 3. The name must not be "." or ".."
|
||||
// 3. The name must not contain / or : or any whitespace characters
|
||||
// 4. The name must not contain / or : or any whitespace characters
|
||||
// ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024
|
||||
func ValidateInterfaceName(ifName string) *types.Error {
|
||||
if len(ifName) == 0 {
|
||||
|
|
|
@ -15,23 +15,12 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
)
|
||||
|
||||
// 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
|
||||
return create.DecodeVersion(jsonBytes)
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
var _ = Describe("Decoding the version of network config", func() {
|
||||
|
|
|
@ -16,7 +16,6 @@ package legacy_examples
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
@ -108,9 +107,9 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
|
|||
return nil, fmt.Errorf("unknown example net config template %q", name)
|
||||
}
|
||||
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create noop plugin debug file: %v", err)
|
||||
return nil, fmt.Errorf("failed to create noop plugin debug file: %w", err)
|
||||
}
|
||||
debugFilePath := debugFile.Name()
|
||||
|
||||
|
@ -119,7 +118,7 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
|
|||
}
|
||||
if err := debug.WriteDebug(debugFilePath); err != nil {
|
||||
os.Remove(debugFilePath)
|
||||
return nil, fmt.Errorf("failed to write noop plugin debug file %q: %v", debugFilePath, err)
|
||||
return nil, fmt.Errorf("failed to write noop plugin debug file %q: %w", debugFilePath, err)
|
||||
}
|
||||
conf := &ExampleNetConf{
|
||||
Config: fmt.Sprintf(template.conf, debugFilePath),
|
||||
|
@ -144,12 +143,12 @@ var V010_Runtime = ExampleRuntime{
|
|||
NetConfs: []string{"unversioned", "0.1.0"},
|
||||
Example: Example{
|
||||
Name: "example_invoker_v010",
|
||||
CNIRepoGitRef: "c0d34c69", //version with ns.Do
|
||||
CNIRepoGitRef: "c0d34c69", // version with ns.Do
|
||||
PluginSource: `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
|
@ -161,7 +160,7 @@ func main(){
|
|||
}
|
||||
|
||||
func exec() int {
|
||||
confBytes, err := ioutil.ReadAll(os.Stdin)
|
||||
confBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Printf("could not read netconfig from stdin: %+v", err)
|
||||
return 1
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
package legacy_examples
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
@ -39,8 +39,10 @@ type Example struct {
|
|||
PluginSource string
|
||||
}
|
||||
|
||||
var buildDir = ""
|
||||
var buildDirLock sync.Mutex
|
||||
var (
|
||||
buildDir = ""
|
||||
buildDirLock sync.Mutex
|
||||
)
|
||||
|
||||
func ensureBuildDirExists() error {
|
||||
buildDirLock.Lock()
|
||||
|
@ -51,7 +53,7 @@ func ensureBuildDirExists() error {
|
|||
}
|
||||
|
||||
var err error
|
||||
buildDir, err = ioutil.TempDir("", "cni-example-plugins")
|
||||
buildDir, err = os.MkdirTemp("", "cni-example-plugins")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -120,6 +122,7 @@ func main() { skel.PluginMain(c, c) }
|
|||
// As we change the CNI spec, the Result type and this value may change.
|
||||
// The text of the example plugins should not.
|
||||
var ExpectedResult = &types020.Result{
|
||||
CNIVersion: "0.1.0",
|
||||
IP4: &types020.IPConfig{
|
||||
IP: net.IPNet{
|
||||
IP: net.ParseIP("10.1.2.3"),
|
||||
|
@ -127,7 +130,7 @@ var ExpectedResult = &types020.Result{
|
|||
},
|
||||
Gateway: net.ParseIP("10.1.2.1"),
|
||||
Routes: []types.Route{
|
||||
types.Route{
|
||||
{
|
||||
Dst: net.IPNet{
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package legacy_examples_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestLegacyExamples(t *testing.T) {
|
||||
|
|
|
@ -19,9 +19,10 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version/legacy_examples"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version/legacy_examples"
|
||||
)
|
||||
|
||||
var _ = Describe("The v0.1.0 Example", func() {
|
||||
|
|
|
@ -68,7 +68,7 @@ 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)
|
||||
return nil, fmt.Errorf("decoding version info: %w", err)
|
||||
}
|
||||
if info.CNIVersion_ == "" {
|
||||
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
|
||||
|
@ -86,8 +86,8 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
|
|||
// 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)
|
||||
if version == "" { // special case: no version declared == v0.1.0
|
||||
return 0, 1, 0, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(version, ".")
|
||||
|
@ -97,20 +97,20 @@ func ParseVersion(version string) (int, int, int, error) {
|
|||
|
||||
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)
|
||||
return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %w", 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)
|
||||
return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %w", 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 -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %w", parts[2], err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
|
|||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GreaterThan returns true if the first version is greater than the second
|
||||
func GreaterThan(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
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
package version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
var _ = Describe("Decoding versions reported by a plugin", func() {
|
||||
|
@ -91,8 +92,16 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
|
|||
Expect(micro).To(Equal(3))
|
||||
})
|
||||
|
||||
It("parses an empty string as v0.1.0", func() {
|
||||
major, minor, micro, err := version.ParseVersion("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(major).To(Equal(0))
|
||||
Expect(minor).To(Equal(1))
|
||||
Expect(micro).To(Equal(0))
|
||||
})
|
||||
|
||||
It("returns an error for malformed versions", func() {
|
||||
badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5", ""}
|
||||
badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5"}
|
||||
for _, v := range badVersions {
|
||||
_, _, _, err := version.ParseVersion(v)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
@ -112,19 +121,19 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
|
|||
// Make sure the first is greater than the second
|
||||
gt, err := version.GreaterThanOrEqualTo(v[0], v[1])
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(gt).To(Equal(true))
|
||||
Expect(gt).To(BeTrue())
|
||||
|
||||
// And the opposite
|
||||
gt, err = version.GreaterThanOrEqualTo(v[1], v[0])
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(gt).To(Equal(false))
|
||||
Expect(gt).To(BeFalse())
|
||||
}
|
||||
})
|
||||
|
||||
It("returns true when versions are the same", func() {
|
||||
gt, err := version.GreaterThanOrEqualTo("1.2.3", "1.2.3")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(gt).To(Equal(true))
|
||||
Expect(gt).To(BeTrue())
|
||||
})
|
||||
|
||||
It("returns an error for malformed versions", func() {
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
package version_test
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
var _ = Describe("Reconcile versions of net config with versions supported by plugins", func() {
|
||||
|
|
|
@ -22,17 +22,12 @@ package testhelpers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const packageBaseName = "github.com/containernetworking/cni"
|
||||
|
||||
func run(cmd *exec.Cmd) error {
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
|
@ -42,115 +37,75 @@ func run(cmd *exec.Cmd) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func goBuildEnviron(gopath string) []string {
|
||||
environ := os.Environ()
|
||||
for i, kvp := range environ {
|
||||
if strings.HasPrefix(kvp, "GOPATH=") {
|
||||
environ[i] = "GOPATH=" + gopath
|
||||
return environ
|
||||
// unset GOPATH if it's set, so we use modules
|
||||
func goBuildEnviron() []string {
|
||||
out := []string{}
|
||||
for _, kvp := range os.Environ() {
|
||||
if !strings.HasPrefix(kvp, "GOPATH=") {
|
||||
out = append(out, kvp)
|
||||
}
|
||||
}
|
||||
environ = append(environ, "GOPATH="+gopath)
|
||||
return environ
|
||||
return out
|
||||
}
|
||||
|
||||
func buildGoProgram(gopath, packageName, outputFilePath string) error {
|
||||
cmd := exec.Command("go", "build", "-o", outputFilePath, packageName)
|
||||
cmd.Env = goBuildEnviron(gopath)
|
||||
func buildGoProgram(modPath, outputFilePath string) error {
|
||||
cmd := exec.Command("go", "build", "-o", outputFilePath, ".")
|
||||
cmd.Dir = modPath
|
||||
cmd.Env = goBuildEnviron()
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
func createSingleFilePackage(gopath, packageName string, fileContents []byte) error {
|
||||
dirName := filepath.Join(gopath, "src", packageName)
|
||||
err := os.MkdirAll(dirName, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filepath.Join(dirName, "main.go"), fileContents, 0600)
|
||||
func modInit(path, name string) error {
|
||||
cmd := exec.Command("go", "mod", "init", name)
|
||||
cmd.Dir = path
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
func removePackage(gopath, packageName string) error {
|
||||
dirName := filepath.Join(gopath, "src", packageName)
|
||||
return os.RemoveAll(dirName)
|
||||
// addLibcni will execute `go mod edit -replace` to fix libcni at a specified version
|
||||
func addLibcni(path, gitRef string) error {
|
||||
cmd := exec.Command("go", "mod", "edit", "-replace=github.com/containernetworking/cni=github.com/containernetworking/cni@"+gitRef)
|
||||
cmd.Dir = path
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
func isRepoRoot(path string) bool {
|
||||
_, err := ioutil.ReadDir(filepath.Join(path, ".git"))
|
||||
return (err == nil) && (filepath.Base(path) == "cni")
|
||||
}
|
||||
|
||||
func LocateCurrentGitRepo() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if isRepoRoot(dir) {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
dir, err = filepath.Abs(filepath.Dir(dir))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("abs(dir(%q)): %s", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to find cni repo root, landed at %q", dir)
|
||||
}
|
||||
|
||||
func gitCloneThisRepo(cloneDestination string) error {
|
||||
err := os.MkdirAll(cloneDestination, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentGitRepo, err := LocateCurrentGitRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return run(exec.Command("git", "clone", currentGitRepo, cloneDestination))
|
||||
}
|
||||
|
||||
func gitCheckout(localRepo string, gitRef string) error {
|
||||
return run(exec.Command("git", "-C", localRepo, "checkout", gitRef))
|
||||
// modTidy will execute `go mod tidy` to ensure all necessary dependencies
|
||||
func modTidy(path string) error {
|
||||
cmd := exec.Command("go", "mod", "tidy")
|
||||
cmd.Dir = path
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
// BuildAt builds the go programSource using the version of the CNI library
|
||||
// at gitRef, and saves the resulting binary file at outputFilePath
|
||||
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
|
||||
tempGoPath, err := ioutil.TempDir("", "cni-git-")
|
||||
tempDir, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "cni-test-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempGoPath)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
cloneDestination := filepath.Join(tempGoPath, "src", packageBaseName)
|
||||
err = gitCloneThisRepo(cloneDestination)
|
||||
if err != nil {
|
||||
modName := filepath.Base(tempDir)
|
||||
|
||||
if err := modInit(tempDir, modName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gitCheckout(cloneDestination, gitRef)
|
||||
if err != nil {
|
||||
if err := addLibcni(tempDir, gitRef); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
testPackageName := fmt.Sprintf("test-package-%x", rand.Int31())
|
||||
|
||||
err = createSingleFilePackage(tempGoPath, testPackageName, programSource)
|
||||
if err != nil {
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
defer removePackage(tempGoPath, testPackageName)
|
||||
|
||||
err = buildGoProgram(tempGoPath, testPackageName, outputFilePath)
|
||||
if err != nil {
|
||||
if err := modTidy(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = buildGoProgram(tempDir, outputFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package testhelpers_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTesthelpers(t *testing.T) {
|
||||
|
|
|
@ -11,17 +11,15 @@
|
|||
// 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 testhelpers_test
|
||||
package testhelpers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/version/testhelpers"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
|
@ -44,7 +42,7 @@ func main() { skel.PluginMain(c, c) }
|
|||
gitRef = "f4364185253"
|
||||
|
||||
var err error
|
||||
outputDir, err = ioutil.TempDir("", "bin")
|
||||
outputDir, err = os.MkdirTemp("", "bin")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
outputFilePath = filepath.Join(outputDir, "some-binary")
|
||||
if runtime.GOOS == "windows" {
|
||||
|
@ -59,7 +57,7 @@ func main() { skel.PluginMain(c, c) }
|
|||
It("builds the provided source code using the CNI library at the given git ref", func() {
|
||||
Expect(outputFilePath).NotTo(BeAnExistingFile())
|
||||
|
||||
err := testhelpers.BuildAt(programSource, gitRef, outputFilePath)
|
||||
err := BuildAt(programSource, gitRef, outputFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(outputFilePath).To(BeAnExistingFile())
|
||||
|
@ -71,40 +69,3 @@ func main() { skel.PluginMain(c, c) }
|
|||
Expect(output).To(ContainSubstring("unknown CNI_COMMAND: VERSION"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("LocateCurrentGitRepo", func() {
|
||||
It("returns the path to the root of the CNI git repo", func() {
|
||||
path, err := testhelpers.LocateCurrentGitRepo()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
AssertItIsTheCNIRepoRoot(path)
|
||||
})
|
||||
|
||||
Context("when run from a different directory", func() {
|
||||
BeforeEach(func() {
|
||||
os.Chdir("..")
|
||||
})
|
||||
|
||||
It("still finds the CNI repo root", func() {
|
||||
path, err := testhelpers.LocateCurrentGitRepo()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
AssertItIsTheCNIRepoRoot(path)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func AssertItIsTheCNIRepoRoot(path string) {
|
||||
Expect(path).To(BeADirectory())
|
||||
files, err := ioutil.ReadDir(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
names := []string{}
|
||||
for _, file := range files {
|
||||
names = append(names, file.Name())
|
||||
}
|
||||
|
||||
Expect(names).To(ContainElement("SPEC.md"))
|
||||
Expect(names).To(ContainElement("libcni"))
|
||||
Expect(names).To(ContainElement("cnitool"))
|
||||
}
|
||||
|
|
|
@ -19,13 +19,12 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
)
|
||||
|
||||
// Current reports the version of the CNI spec implemented by this library
|
||||
func Current() string {
|
||||
return "0.4.0"
|
||||
return "1.1.0"
|
||||
}
|
||||
|
||||
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||
|
@ -35,48 +34,56 @@ func Current() string {
|
|||
//
|
||||
// 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 (
|
||||
Legacy = PluginSupports("0.1.0", "0.2.0")
|
||||
All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0")
|
||||
)
|
||||
|
||||
var resultFactories = []struct {
|
||||
supportedVersions []string
|
||||
newResult types.ResultFactoryFunc
|
||||
}{
|
||||
{current.SupportedVersions, current.NewResult},
|
||||
{types020.SupportedVersions, types020.NewResult},
|
||||
// VersionsFrom returns a list of versions starting from min, inclusive
|
||||
func VersionsStartingFrom(min string) PluginInfo {
|
||||
out := []string{}
|
||||
// cheat, just assume ordered
|
||||
ok := false
|
||||
for _, v := range All.SupportedVersions() {
|
||||
if !ok && v == min {
|
||||
ok = true
|
||||
}
|
||||
if ok {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return PluginSupports(out...)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return create.Create(version, resultBytes)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func ParsePrevResult(conf *types.PluginConf) error {
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prior to 1.0.0, Result types may not marshal a CNIVersion. Since the
|
||||
// result version must match the config version, if the Result's version
|
||||
// is empty, inject the config version.
|
||||
if ver, ok := conf.RawPrevResult["CNIVersion"]; !ok || ver == "" {
|
||||
conf.RawPrevResult["CNIVersion"] = conf.CNIVersion
|
||||
}
|
||||
|
||||
resultBytes, err := json.Marshal(conf.RawPrevResult)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not serialize prevResult: %v", err)
|
||||
return fmt.Errorf("could not serialize prevResult: %w", err)
|
||||
}
|
||||
|
||||
conf.RawPrevResult = nil
|
||||
conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes)
|
||||
conf.PrevResult, err = create.Create(conf.CNIVersion, resultBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse prevResult: %v", err)
|
||||
return fmt.Errorf("could not parse prevResult: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package version_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
|
|
|
@ -19,24 +19,31 @@ import (
|
|||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
cniv1 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
var _ = Describe("Version operations", func() {
|
||||
It("computes a list of versions correctly", func() {
|
||||
actual := version.VersionsStartingFrom("0.3.1")
|
||||
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0", "1.1.0"}))
|
||||
})
|
||||
|
||||
Context("when a prevResult is available", func() {
|
||||
It("parses the prevResult", func() {
|
||||
rawBytes := []byte(`{
|
||||
"cniVersion": "0.3.0",
|
||||
"cniVersion": "1.0.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net"
|
||||
"sandbox": "/proc/3553/ns/net",
|
||||
"pciID": "8086:9a01",
|
||||
"socketPath": "/path/to/vhost/fd"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
|
@ -52,8 +59,8 @@ var _ = Describe("Version operations", func() {
|
|||
err := json.Unmarshal(rawBytes, &raw)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: "0.3.0",
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: "1.0.0",
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
RawPrevResult: raw,
|
||||
|
@ -61,20 +68,20 @@ var _ = Describe("Version operations", func() {
|
|||
|
||||
err = version.ParsePrevResult(conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedResult := ¤t.Result{
|
||||
CNIVersion: "0.3.0",
|
||||
Interfaces: []*current.Interface{
|
||||
expectedResult := &cniv1.Result{
|
||||
CNIVersion: "1.0.0",
|
||||
Interfaces: []*cniv1.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
PciID: "8086:9a01",
|
||||
SocketPath: "/path/to/vhost/fd",
|
||||
},
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
IPs: []*cniv1.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Interface: cniv1.Int(0),
|
||||
Address: net.IPNet{
|
||||
IP: net.ParseIP("1.2.3.30"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
|
@ -87,8 +94,8 @@ var _ = Describe("Version operations", func() {
|
|||
})
|
||||
|
||||
It("fails if the prevResult version is unknown", func() {
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: "0.3.0",
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
RawPrevResult: map[string]interface{}{
|
||||
|
@ -97,28 +104,32 @@ var _ = Describe("Version operations", func() {
|
|||
}
|
||||
|
||||
err := version.ParsePrevResult(conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(err).To(MatchError(`could not parse prevResult: result type supports [1.0.0 1.1.0] but unmarshalled CNIVersion is "5678.456"`))
|
||||
})
|
||||
|
||||
It("fails if the prevResult is invalid", func() {
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: "0.3.0",
|
||||
It("fails if the prevResult version does not match the prevResult version", func() {
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
RawPrevResult: map[string]interface{}{
|
||||
"adsfasdfasdfasdfasdfaf": nil,
|
||||
"cniVersion": "0.2.0",
|
||||
"ip4": map[string]interface{}{
|
||||
"ip": "1.2.3.30/24",
|
||||
"gateway": "1.2.3.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := version.ParsePrevResult(conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0 1.1.0] but unmarshalled CNIVersion is \"0.2.0\""))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when a prevResult is not available", func() {
|
||||
It("does not fail", func() {
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: "0.3.0",
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
}
|
||||
|
@ -128,4 +139,22 @@ var _ = Describe("Version operations", func() {
|
|||
Expect(conf.PrevResult).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Context("version parsing", func() {
|
||||
It("parses versions correctly", func() {
|
||||
v1 := "1.1.0"
|
||||
v2 := "1.1.1"
|
||||
|
||||
check := func(a, b string, want bool) {
|
||||
GinkgoHelper()
|
||||
gt, err := version.GreaterThan(a, b)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(gt).To(Equal(want))
|
||||
}
|
||||
|
||||
check(v1, v2, false)
|
||||
check(v2, v1, true)
|
||||
check(v2, v2, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# debug plugin
|
||||
|
||||
## Overview
|
||||
|
||||
This plugin aims to help debugging or troubleshooting in CNI plugin development.
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.16.30.0/24",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "debug",
|
||||
"cniOutput": "/tmp/cni_output.txt",
|
||||
"addHooks": [
|
||||
[ "sh", "-c", "ip link set $CNI_IFNAME promisc on" ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {"portMappings": true},
|
||||
"externalSetMarkChain": "KUBE-MARK-MASQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Config Reference
|
||||
|
||||
* `cniOutput` (string, optional): output CNI request into file.
|
||||
* `addHooks` (string array, optional): commands executed in container network namespace at interface add.
|
||||
(note: but just execute it and does not catch command failure)
|
||||
* `delHooks` (string array, optional): commands executed in container network namespace at interface delete.
|
||||
(note: but just execute it and does not catch command failure)
|
||||
* `checkHooks` (string array, optional): commands executed in container network namespace at interface check.
|
||||
(note: but just execute it and does not catch command failure)
|
||||
|
||||
### Sample CNI Ouput
|
||||
|
||||
```
|
||||
CmdAdd
|
||||
ContainerID: cnitool-20c433bb2b1d6ede56d6
|
||||
Netns: /var/run/netns/cnitest
|
||||
IfName: eth0
|
||||
Args:
|
||||
Path: /opt/cni/bin
|
||||
StdinData: {"cniOutput":"/tmp/cni_output.txt","cniVersion":"0.3.1","name":"test","prevResult":{"cniVersion":"0.3.1","interfaces":[{"name":"veth92e295cc","mac":"56:22:7f:b7:5b:75"},{"name":"eth0","mac":"46:b3:f3:77:bf:21","sandbox":"/var/run/netns/cnitest"}],"ips":[{"version":"4","interface":1,"address":"10.1.1.2/24","gateway":"10.1.1.1"}],"dns":{"nameservers":["10.64.255.25","8.8.8.8"]}},"type":"none"}
|
||||
----------------------
|
||||
CmdDel
|
||||
ContainerID: cnitool-20c433bb2b1d6ede56d6
|
||||
Netns: /var/run/netns/cnitest
|
||||
IfName: eth0
|
||||
Args:
|
||||
Path: /opt/cni/bin
|
||||
StdinData: {"cniOutput":"/tmp/cni_output.txt","cniVersion":"0.3.1","name":"test","type":"none"}
|
||||
----------------------
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
module github.com/containernetworking/cni/plugins/debug
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/containernetworking/cni v1.1.2
|
||||
github.com/containernetworking/plugins v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/containernetworking/cni => ../..
|
|
@ -0,0 +1,26 @@
|
|||
github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7wwSv9iTbXzzic=
|
||||
github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright 2021 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
type100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
CNIOutput string `json:"cniOutput,omitempty"`
|
||||
AddHooks [][]string `json:"addHooks,omitempty"`
|
||||
DelHooks [][]string `json:"delHooks,omitempty"`
|
||||
CheckHooks [][]string `json:"checkHooks,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("none"))
|
||||
}
|
||||
|
||||
func outputCmdArgs(fp io.Writer, args *skel.CmdArgs) {
|
||||
fmt.Fprintf(fp, `ContainerID: %s
|
||||
Netns: %s
|
||||
IfName: %s
|
||||
Args: %s
|
||||
Path: %s
|
||||
StdinData: %s
|
||||
----------------------
|
||||
`,
|
||||
args.ContainerID,
|
||||
args.Netns,
|
||||
args.IfName,
|
||||
args.Args,
|
||||
args.Path,
|
||||
string(args.StdinData))
|
||||
}
|
||||
|
||||
func parseConf(data []byte) (*NetConf, error) {
|
||||
conf := &NetConf{}
|
||||
if err := json.Unmarshal(data, &conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse")
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func getResult(netConf *NetConf) *type100.Result {
|
||||
if netConf.RawPrevResult == nil {
|
||||
return &type100.Result{}
|
||||
}
|
||||
|
||||
version.ParsePrevResult(&netConf.NetConf)
|
||||
result, _ := type100.NewResultFromResult(netConf.PrevResult)
|
||||
return result
|
||||
}
|
||||
|
||||
func executeHooks(netnsName string, hooks [][]string) {
|
||||
netns, err := ns.GetNS(netnsName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
netns.Do(func(_ ns.NetNS) error {
|
||||
for _, hookStrs := range hooks {
|
||||
hookCmd := hookStrs[0]
|
||||
hookArgs := hookStrs[1:]
|
||||
output, err := exec.Command(hookCmd, hookArgs...).Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "OUTPUT: %v", output)
|
||||
fmt.Fprintf(os.Stderr, "ERR: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
netConf, _ := parseConf(args.StdinData)
|
||||
// Output CNI
|
||||
if netConf.CNIOutput != "" {
|
||||
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
|
||||
defer fp.Close()
|
||||
fmt.Fprintf(fp, "CmdAdd\n")
|
||||
outputCmdArgs(fp, args)
|
||||
}
|
||||
// call hooks
|
||||
if netConf.AddHooks != nil {
|
||||
executeHooks(args.Netns, netConf.AddHooks)
|
||||
}
|
||||
return types.PrintResult(getResult(netConf), netConf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
netConf, _ := parseConf(args.StdinData)
|
||||
// Output CNI
|
||||
if netConf.CNIOutput != "" {
|
||||
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
|
||||
defer fp.Close()
|
||||
fmt.Fprintf(fp, "CmdDel\n")
|
||||
outputCmdArgs(fp, args)
|
||||
}
|
||||
// call hooks
|
||||
if netConf.DelHooks != nil {
|
||||
executeHooks(args.Netns, netConf.DelHooks)
|
||||
}
|
||||
return types.PrintResult(&type100.Result{}, netConf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
netConf, _ := parseConf(args.StdinData)
|
||||
// Output CNI
|
||||
if netConf.CNIOutput != "" {
|
||||
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
|
||||
defer fp.Close()
|
||||
fmt.Fprintf(fp, "CmdCheck\n")
|
||||
outputCmdArgs(fp, args)
|
||||
}
|
||||
// call hooks
|
||||
if netConf.CheckHooks != nil {
|
||||
executeHooks(args.Netns, netConf.CheckHooks)
|
||||
}
|
||||
return types.PrintResult(&type100.Result{}, netConf.CNIVersion)
|
||||
}
|
|
@ -17,7 +17,7 @@ package debug
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
)
|
||||
|
@ -29,6 +29,7 @@ type Debug struct {
|
|||
// Report* fields allow the test to control the behavior of the no-op plugin
|
||||
ReportResult string
|
||||
ReportError string
|
||||
ReportErrorCode uint
|
||||
ReportStderr string
|
||||
ReportVersionSupport []string
|
||||
ExitWithCode int
|
||||
|
@ -40,9 +41,18 @@ type Debug struct {
|
|||
CmdArgs skel.CmdArgs
|
||||
}
|
||||
|
||||
// CmdLogEntry records a single CNI command as well as its args
|
||||
type CmdLogEntry struct {
|
||||
Command string
|
||||
CmdArgs skel.CmdArgs
|
||||
}
|
||||
|
||||
// CmdLog records a list of CmdLogEntry received by the noop plugin
|
||||
type CmdLog []CmdLogEntry
|
||||
|
||||
// ReadDebug will return a debug file recorded by the noop plugin
|
||||
func ReadDebug(debugFilePath string) (*Debug, error) {
|
||||
debugBytes, err := ioutil.ReadFile(debugFilePath)
|
||||
debugBytes, err := os.ReadFile(debugFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -63,10 +73,41 @@ func (debug *Debug) WriteDebug(debugFilePath string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(debugFilePath, debugBytes, 0600)
|
||||
err = os.WriteFile(debugFilePath, debugBytes, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteCommandLog appends the executed cni command to the record file
|
||||
func WriteCommandLog(path string, entry CmdLogEntry) error {
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cmds CmdLog
|
||||
if len(buf) > 0 {
|
||||
if err = json.Unmarshal(buf, &cmds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cmds = append(cmds, entry)
|
||||
if buf, err = json.Marshal(&cmds); err != nil {
|
||||
return nil
|
||||
}
|
||||
return os.WriteFile(path, buf, 0o644)
|
||||
}
|
||||
|
||||
func ReadCommandLog(path string) (CmdLog, error) {
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cmds CmdLog
|
||||
if err = json.Unmarshal(buf, &cmds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmds, nil
|
||||
}
|
||||
|
|
|
@ -23,29 +23,31 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
types.PluginConf
|
||||
DebugFile string `json:"debugFile"`
|
||||
PrevResult *current.Result `json:"prevResult,omitempty"`
|
||||
CommandLog string `json:"commandLog"`
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, error) {
|
||||
n := &NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v %q", err, string(bytes))
|
||||
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes))
|
||||
}
|
||||
if err := version.ParsePrevResult(&n.PluginConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
@ -115,38 +117,57 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if netConf.CommandLog != "" {
|
||||
if err = noop_debug.WriteCommandLog(
|
||||
netConf.CommandLog,
|
||||
noop_debug.CmdLogEntry{
|
||||
Command: command,
|
||||
CmdArgs: *args,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if debug.ReportStderr != "" {
|
||||
if _, err = os.Stderr.WriteString(debug.ReportStderr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if debug.ReportError != "" {
|
||||
return errors.New(debug.ReportError)
|
||||
} else if debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS" {
|
||||
switch {
|
||||
case debug.ReportError != "":
|
||||
ec := debug.ReportErrorCode
|
||||
if ec == 0 {
|
||||
ec = types.ErrInternal
|
||||
}
|
||||
return &types.Error{
|
||||
Msg: debug.ReportError,
|
||||
Code: ec,
|
||||
}
|
||||
case debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS":
|
||||
prevResult := netConf.PrevResult
|
||||
if debug.ReportResult == "INJECT-DNS" {
|
||||
prevResult, err = current.NewResultFromResult(netConf.PrevResult)
|
||||
newResult, err := current.NewResultFromResult(netConf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prevResult.DNS.Nameservers = []string{"1.2.3.4"}
|
||||
newResult.DNS.Nameservers = []string{"1.2.3.4"}
|
||||
prevResult = newResult
|
||||
}
|
||||
|
||||
// Must print the prevResult as the CNIVersion of the config
|
||||
newResult, err := prevResult.GetAsVersion(netConf.CNIVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert result to config %q: %v", netConf.CNIVersion, err)
|
||||
return fmt.Errorf("failed to convert result to config %q: %w", netConf.CNIVersion, err)
|
||||
}
|
||||
resultBytes, err := json.Marshal(newResult)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal new result: %v", err)
|
||||
return fmt.Errorf("failed to marshal new result: %w", err)
|
||||
}
|
||||
_, err = os.Stdout.WriteString(string(resultBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if debug.ReportResult != "" {
|
||||
case debug.ReportResult != "":
|
||||
_, err = os.Stdout.WriteString(debug.ReportResult)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -160,7 +181,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
|
|||
}
|
||||
|
||||
func debugGetSupportedVersions(stdinData []byte) []string {
|
||||
vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"}
|
||||
vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"}
|
||||
cniArgs := os.Getenv("CNI_ARGS")
|
||||
if cniArgs == "" {
|
||||
return vers
|
||||
|
@ -193,9 +214,17 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||
return debugBehavior(args, "DEL")
|
||||
}
|
||||
|
||||
func cmdGC(args *skel.CmdArgs) error {
|
||||
return debugBehavior(args, "GC")
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
return debugBehavior(args, "STATUS")
|
||||
}
|
||||
|
||||
func saveStdin() ([]byte, error) {
|
||||
// Read original stdin
|
||||
stdinData, err := ioutil.ReadAll(os.Stdin)
|
||||
stdinData, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -224,5 +253,11 @@ func main() {
|
|||
}
|
||||
|
||||
supportedVersions := debugGetSupportedVersions(stdinData)
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0")
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
GC: cmdGC,
|
||||
Status: cmdStatus,
|
||||
}, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0")
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNoop(t *testing.T) {
|
||||
|
|
|
@ -16,18 +16,18 @@ package main_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("No-op plugin", func() {
|
||||
|
@ -46,7 +46,7 @@ var _ = Describe("No-op plugin", func() {
|
|||
ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"},
|
||||
}
|
||||
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFileName = debugFile.Name()
|
||||
|
@ -107,6 +107,7 @@ var _ = Describe("No-op plugin", func() {
|
|||
"some":"stdin-json",
|
||||
"cniVersion": "0.3.1",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.1",
|
||||
"ips": [{"version": "4", "address": "10.1.2.15/24"}]
|
||||
}
|
||||
}`)
|
||||
|
@ -125,6 +126,7 @@ var _ = Describe("No-op plugin", func() {
|
|||
"some":"stdin-json",
|
||||
"cniVersion": "0.4.0",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.4.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.15/24"}]
|
||||
}
|
||||
}`)
|
||||
|
@ -143,7 +145,7 @@ var _ = Describe("No-op plugin", func() {
|
|||
"some":"stdin-json",
|
||||
"cniVersion": "0.4.0",
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "0.4.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}
|
||||
|
@ -228,7 +230,7 @@ var _ = Describe("No-op plugin", func() {
|
|||
})
|
||||
|
||||
Context("when the ReportResult debug field is set", func() {
|
||||
var expectedResultString = fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
|
||||
expectedResultString := fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
|
||||
|
||||
BeforeEach(func() {
|
||||
debug.ReportResult = expectedResultString
|
||||
|
|
|
@ -2,7 +2,36 @@
|
|||
|
||||
if [[ ${DEBUG} -gt 0 ]]; then set -x; fi
|
||||
|
||||
NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
|
||||
NETCONFPATH="${NETCONFPATH-/etc/cni/net.d}"
|
||||
|
||||
function exec_list() {
|
||||
plist="$1"
|
||||
name="$2"
|
||||
cniVersion="$3"
|
||||
echo "$plist" | jq -c '.[]' | while read -r conf; do
|
||||
plugin_bin="$(echo "$conf" | jq -r '.type')"
|
||||
conf="$(echo "$conf" | jq -r ".name = \"$name\" | .cniVersion = \"$cniVersion\"")"
|
||||
if [ -n "$res" ]; then
|
||||
conf="$(echo "$conf" | jq -r ".prevResult=$res")"
|
||||
fi
|
||||
if ! res=$(echo "$conf" | $plugin_bin); then
|
||||
error "$name" "$res"
|
||||
elif [[ ${DEBUG} -gt 0 ]]; then
|
||||
echo "${res}" | jq -r .
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function error () {
|
||||
name="$1"
|
||||
res="$2"
|
||||
err_msg=$(echo "$res" | jq -r '.msg')
|
||||
if [ -z "$errmsg" ]; then
|
||||
err_msg=$res
|
||||
fi
|
||||
echo "${name} : error executing $CNI_COMMAND: $err_msg"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function exec_plugins() {
|
||||
i=0
|
||||
|
@ -13,25 +42,24 @@ function exec_plugins() {
|
|||
export CNI_CONTAINERID=$contid
|
||||
export CNI_NETNS=$netns
|
||||
|
||||
for netconf in $(echo $NETCONFPATH/*.conf | sort); do
|
||||
name=$(jq -r '.name' <$netconf)
|
||||
plugin=$(jq -r '.type' <$netconf)
|
||||
for netconf in $(echo "$NETCONFPATH"/*.conf | sort); do
|
||||
export CNI_IFNAME=$(printf eth%d $i)
|
||||
name=$(jq -r '.name' <"$netconf")
|
||||
cniVersion=$(jq -r '.cniVersion' <"$netconf")
|
||||
plist=$(jq '.plugins | select(.!=null)' <"$netconf")
|
||||
if [ -n "$plist" ]; then
|
||||
exec_list "$plist" "$name" "$cniVersion"
|
||||
else
|
||||
plugin=$(jq -r '.type' <"$netconf")
|
||||
|
||||
res=$($plugin <$netconf)
|
||||
if [ $? -ne 0 ]; then
|
||||
errmsg=$(echo $res | jq -r '.msg')
|
||||
if [ -z "$errmsg" ]; then
|
||||
errmsg=$res
|
||||
fi
|
||||
|
||||
echo "${name} : error executing $CNI_COMMAND: $errmsg"
|
||||
exit 1
|
||||
if ! res=$($plugin <"$netconf"); then
|
||||
error "$name" "$res"
|
||||
elif [[ ${DEBUG} -gt 0 ]]; then
|
||||
echo ${res} | jq -r .
|
||||
echo "${res}" | jq -r .
|
||||
fi
|
||||
fi
|
||||
|
||||
let "i=i+1"
|
||||
(( i++ )) || true
|
||||
done
|
||||
}
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
|
||||
SRC_DIR="${SRC_DIR:-$PWD}"
|
||||
BUILDFLAGS="-a --ldflags '-extldflags \"-static\"'"
|
||||
|
||||
TAG=$(git describe --tags --dirty)
|
||||
RELEASE_DIR=release-${TAG}
|
||||
|
||||
OUTPUT_DIR=bin
|
||||
|
||||
# Always clean first
|
||||
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
docker run -i -v ${SRC_DIR}:/opt/src --rm golang:1.12-alpine \
|
||||
/bin/sh -xe -c "\
|
||||
apk --no-cache add bash tar;
|
||||
cd /opt/src; umask 0022;
|
||||
for arch in amd64 arm arm64 ppc64le s390x; do \
|
||||
rm -f ${OUTPUT_DIR}/* ; \
|
||||
CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
FILENAME=cni-\$arch-${TAG}.\$format; \
|
||||
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
|
||||
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
|
||||
if [ \"\$arch\" == \"amd64\" ]; then \
|
||||
cp \$FILEPATH ${RELEASE_DIR}/cni-${TAG}.\$format; \
|
||||
fi; \
|
||||
done; \
|
||||
done;
|
||||
cd ${RELEASE_DIR};
|
||||
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
|
||||
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
|
||||
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
|
||||
cd ..
|
||||
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"
|
25
test.sh
25
test.sh
|
@ -7,38 +7,17 @@ cd "$(dirname $0)"
|
|||
PKGS=${PKGS:-$(go list ./... | xargs echo)}
|
||||
|
||||
echo -n "Running tests "
|
||||
function testrun {
|
||||
bash -c "umask 0; PATH=$PATH go test $@"
|
||||
}
|
||||
if [ ! -z "${COVERALLS:-""}" ]; then
|
||||
# coverage profile only works per-package
|
||||
echo "with coverage profile generation..."
|
||||
i=0
|
||||
for t in ${PKGS}; do
|
||||
testrun "-covermode set -coverprofile ${i}.coverprofile ${t}"
|
||||
go test -covermode set -coverprofile ${i}.coverprofile "${t}"
|
||||
i=$((i+1))
|
||||
done
|
||||
gover
|
||||
goveralls -service=travis-ci -coverprofile=gover.coverprofile
|
||||
else
|
||||
echo "without coverage profile generation..."
|
||||
for t in ${PKGS}; do
|
||||
testrun "${t}"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Checking gofmt..."
|
||||
fmtRes=$(go fmt ${PKGS})
|
||||
if [ -n "${fmtRes}" ]; then
|
||||
echo -e "go fmt checking failed:\n${fmtRes}"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
echo "Checking govet..."
|
||||
vetRes=$(go vet ${PKGS})
|
||||
if [ -n "${vetRes}" ]; then
|
||||
echo -e "go vet checking failed:\n${vetRes}"
|
||||
exit 255
|
||||
go test ${PKGS}
|
||||
fi
|
||||
|
||||
echo "Checking license header..."
|
||||
|
|
Loading…
Reference in New Issue