Compare commits
255 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 |
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.10
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
|
|
|
@ -8,4 +8,4 @@ runs:
|
|||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
|
|
|
@ -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"
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
|
|
|
@ -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
|
|
@ -4,19 +4,45 @@ name: test
|
|||
on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.15"
|
||||
GO_VERSION: "1.22"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
lint:
|
||||
name: Lint
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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: |
|
||||
|
@ -28,27 +54,27 @@ jobs:
|
|||
|
||||
test-linux:
|
||||
name: Run tests on Linux amd64
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Install test binaries
|
||||
env:
|
||||
GO111MODULE: off
|
||||
run: |
|
||||
go get github.com/mattn/goveralls
|
||||
go get github.com/modocache/gover
|
||||
go install github.com/mattn/goveralls@v0.0.12
|
||||
go install github.com/modocache/gover@latest
|
||||
|
||||
- name: test
|
||||
run: COVERALLS=1 ./test.sh
|
||||
|
||||
- name: Send coverage to coveralls
|
||||
env:
|
||||
- env:
|
||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Send coverage to coveralls
|
||||
run: |
|
||||
PATH=$PATH:$(go env GOPATH)/bin
|
||||
gover
|
||||
|
@ -56,12 +82,15 @@ jobs:
|
|||
|
||||
test-win:
|
||||
name: Build and run tests on Windows
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
@ -66,9 +66,10 @@ But the runtime would fill in the mappings so the plugin itself would receive so
|
|||
| 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-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 reserved as a field in the `0.2.0` release of the CNI spec.
|
||||
`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.
|
||||
|
|
|
@ -64,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.
|
||||
|
||||
|
@ -94,7 +94,7 @@ command with the following JSON data:
|
|||
```
|
||||
|
||||
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
|
||||
|
@ -102,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`,
|
||||
|
@ -182,7 +182,7 @@ 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
|
||||
|
@ -203,15 +203,15 @@ 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
|
||||
{
|
||||
|
|
17
MAINTAINERS
17
MAINTAINERS
|
@ -1,7 +1,14 @@
|
|||
Bruce Ma <brucema19901024@gmail.com> (@mars1024)
|
||||
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 Skamruk <piotr.skamruk@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)
|
35
README.md
35
README.md
|
@ -4,6 +4,9 @@
|
|||
|
||||
# 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.
|
||||
|
@ -20,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.
|
||||
|
@ -29,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)
|
||||
|
@ -39,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)
|
||||
|
@ -58,23 +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/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
|
||||
|
@ -212,4 +215,6 @@ For any questions about CNI, please reach out via:
|
|||
- 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.
|
||||
|
|
220
SPEC.md
220
SPEC.md
|
@ -8,6 +8,7 @@
|
|||
- [Configuration format](#configuration-format)
|
||||
- [Plugin configuration objects:](#plugin-configuration-objects)
|
||||
- [Example configuration](#example-configuration)
|
||||
- [Version considerations](#version-considerations)
|
||||
- [Section 2: Execution Protocol](#section-2-execution-protocol)
|
||||
- [Overview](#overview-1)
|
||||
- [Parameters](#parameters)
|
||||
|
@ -16,21 +17,25 @@
|
|||
- [`ADD`: Add container to network, or apply modifications](#add-add-container-to-network-or-apply-modifications)
|
||||
- [`DEL`: Remove container from network, or un-apply modifications](#del-remove-container-from-network-or-un-apply-modifications)
|
||||
- [`CHECK`: Check container's networking is as expected](#check-check-containers-networking-is-as-expected)
|
||||
- [`STATUS`: Check plugin status](#status-check-plugin-status)
|
||||
- [`VERSION`: probe plugin version support](#version-probe-plugin-version-support)
|
||||
- [`GC`: Clean up any stale resources](#gc-clean-up-any-stale-resources)
|
||||
- [Section 3: Execution of Network Configurations](#section-3-execution-of-network-configurations)
|
||||
- [Lifecycle & Ordering](#lifecycle--ordering)
|
||||
- [Lifecycle \& Ordering](#lifecycle--ordering)
|
||||
- [Attachment Parameters](#attachment-parameters)
|
||||
- [Adding an attachment](#adding-an-attachment)
|
||||
- [Deleting an attachment](#deleting-an-attachment)
|
||||
- [Checking an attachment](#checking-an-attachment)
|
||||
- [Deriving execution configuration from plugin configuration](#deriving-execution-configuration-from-plugin-configuration)
|
||||
- [Garbage-collecting a network](#garbage-collecting-a-network)
|
||||
- [Deriving request configuration from plugin configuration](#deriving-request-configuration-from-plugin-configuration)
|
||||
- [Deriving `runtimeConfig`](#deriving-runtimeconfig)
|
||||
- [Section 4: Plugin Delegation](#section-4-plugin-delegation)
|
||||
- [Delegated Plugin protocol](#delegated-plugin-protocol)
|
||||
- [Delegated plugin execution procedure](#delegated-plugin-execution-procedure)
|
||||
- [Section 5: Result Types](#section-5-result-types)
|
||||
- [Success](#success)
|
||||
- [ADD Success](#add-success)
|
||||
- [Delegated plugins (IPAM)](#delegated-plugins-ipam)
|
||||
- [VERSION Success](#version-success)
|
||||
- [Error](#error)
|
||||
- [Version](#version-1)
|
||||
- [Appendix: Examples](#appendix-examples)
|
||||
|
@ -40,7 +45,7 @@
|
|||
|
||||
## Version
|
||||
|
||||
This is CNI **spec** version **1.0.0**.
|
||||
This is CNI **spec** version **1.1.0**.
|
||||
|
||||
Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)).
|
||||
|
||||
|
@ -102,14 +107,18 @@ require this.
|
|||
|
||||
A network configuration consists of a JSON object with the following keys:
|
||||
|
||||
- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. Currently "1.0.0"
|
||||
- `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-).
|
||||
- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. Currently "1.1.0"
|
||||
- `cniVersions` (string list): List of all CNI versions which this configuration supports. See [version selection](#version-selection) below.
|
||||
- `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-). Must not contain characters disallowed in file paths.
|
||||
- `disableCheck` (boolean): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors.
|
||||
- `plugins` (list): A list of CNI plugins and their configuration, which is a list of plugin configuration objects.
|
||||
- `disableGC` (boolean): Either `true` or `false`. If `disableGC` is `true`, runtimes must not call `GC` for this network configuration list. This allows an administrator to prevent `GC`ing when it is known that garbage collection may have undesired effects (e.g. shared configuration between multiple runtimes).
|
||||
- `loadOnlyInlinedPlugins` (boolean): Either `true` or `false`. If `false` (default), indicates [plugin configuration objects](#plugin-configuration-objects) can be aggregated from multiple sources. Any valid plugin configuration objects aggregated from other sources must be appended to the final list of `plugins` for that network name. If set to `true`, indicates that valid plugin configuration objects aggregated from sources other than the main network configuration will be ignored. If `plugins` is not present in the network configuration, `loadOnlyInlinedPlugins` cannot be set to `true`.
|
||||
- `plugins` (list): A list of inlined [plugin configuration objects](#plugin-configuration-objects). If this key is populated with inlined plugin objects, and `loadOnlyInlinedPlugins` is true, the final set of plugins for a network must consist of all the plugin objects in this list, merged with all the plugins loaded from the sibling folder with the same name as the network.
|
||||
|
||||
#### Plugin configuration objects:
|
||||
Plugin configuration objects may contain additional fields than the ones defined here.
|
||||
The runtime MUST pass through these fields, unchanged, to the plugin, as defined in section 3.
|
||||
Runtimes may aggregate plugin configuration objects from multiple sources, and must unambiguously associate each loaded plugin configuration object with a single, valid network configuration. All aggregated plugin configuration objects must be validated, and each plugin with a valid configuration object must be invoked.
|
||||
|
||||
Plugin configuration objects may contain additional fields beyond the ones defined here. The runtime MUST pass through these fields, unchanged, to the invoked plugin, as defined in section 3.
|
||||
|
||||
**Required keys:**
|
||||
- `type` (string): Matches the name of the CNI plugin binary on disk. Must not contain characters disallowed in file paths for the system (e.g. / or \\).
|
||||
|
@ -140,9 +149,11 @@ Plugins that consume any of these configuration keys should respect their intend
|
|||
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
|
||||
|
||||
#### Example configuration
|
||||
The following is an example JSON representation of a network configuration `dbnet` with three plugin configurations (`bridge`, `tuning`, and `portmap`).
|
||||
```jsonc
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
|
||||
"name": "dbnet",
|
||||
"plugins": [
|
||||
{
|
||||
|
@ -150,7 +161,7 @@ Plugins may define additional fields that they accept and may generate an error
|
|||
// plugin specific parameters
|
||||
"bridge": "cni0",
|
||||
"keyA": ["some more", "plugin specific", "configuration"],
|
||||
|
||||
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
// ipam specific
|
||||
|
@ -181,6 +192,15 @@ Plugins may define additional fields that they accept and may generate an error
|
|||
}
|
||||
```
|
||||
|
||||
### Version considerations
|
||||
|
||||
CNI runtimes, plugins, and network configurations may support multiple CNI specification versions independently. Plugins indicate their set of supported versions through the VERSION command, while network configurations indicate their set of supported versions through the `cniVersion` and `cniVersions` fields.
|
||||
|
||||
CNI runtimes MUST select the highest supported version from the set of network configuration versions given by the `cniVersion` and `cniVersions` fields. Runtimes MAY consider the set of supported plugin versions as reported by the VERSION command when determining available versions.
|
||||
|
||||
|
||||
The CNI protocol follows Semantic Versioning principles, so the configuration format MUST remain backwards and forwards compatible within major versions.
|
||||
|
||||
## Section 2: Execution Protocol
|
||||
|
||||
### Overview
|
||||
|
@ -202,7 +222,7 @@ The runtime must execute the plugin in the runtime's networking domain. (For mos
|
|||
|
||||
Protocol parameters are passed to the plugins via OS environment variables.
|
||||
|
||||
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `CHECK`, or `VERSION`.
|
||||
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `CHECK`, `GC`, or `VERSION`.
|
||||
- `CNI_CONTAINERID`: Container ID. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty. Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-).
|
||||
- `CNI_NETNS`: A reference to the container's "isolation domain". If using network namespaces, then a path to the network namespace (e.g. `/run/netns/[nsname]`)
|
||||
- `CNI_IFNAME`: Name of the interface to create inside the container; if the plugin is unable to use this interface name it must return an error.
|
||||
|
@ -214,7 +234,7 @@ A plugin must exit with a return code of 0 on success, and non-zero on failure.
|
|||
|
||||
### CNI operations
|
||||
|
||||
CNI defines 4 operations: `ADD`, `DEL`, `CHECK`, and `VERSION`. These are passed to the plugin via the `CNI_COMMAND` environment variable.
|
||||
CNI defines 5 operations: `ADD`, `DEL`, `CHECK`, `GC`, and `VERSION`. These are passed to the plugin via the `CNI_COMMAND` environment variable.
|
||||
|
||||
#### `ADD`: Add container to network, or apply modifications
|
||||
|
||||
|
@ -248,7 +268,10 @@ A CNI plugin, upon receiving a `DEL` command, should either
|
|||
- delete the interface defined by `CNI_IFNAME` inside the container at `CNI_NETNS`, or
|
||||
- undo any modifications applied in the plugin's `ADD` functionality
|
||||
|
||||
Plugins should generally complete a `DEL` action without error even if some resources are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist.
|
||||
A `prevResult` must be supplied to CNI plugins as part of a `DEL` command. For the first plugin in the `DEL` command plugin chain, this `prevResult` will be the final result of the previous `ADD` command.
|
||||
Plugins should still return without error if `prevResult` is empty for a `DEL` command, however.
|
||||
|
||||
`DEL` command invocations are always considered best-effort - plugins should always complete a `DEL` action without error to the fullest extent possible, even if some resources or state are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist.
|
||||
|
||||
Plugins MUST accept multiple `DEL` calls for the same (`CNI_CONTAINERID`, `CNI_IFNAME`) pair, and return success if the interface in question, or any modifications added, are missing.
|
||||
|
||||
|
@ -310,6 +333,32 @@ Optional environment parameters:
|
|||
|
||||
All parameters, with the exception of `CNI_PATH`, must be the same as the corresponding `ADD` for this container.
|
||||
|
||||
|
||||
#### `STATUS`: Check plugin status
|
||||
`STATUS` is a way for a runtime to determine the readiness of a network plugin.
|
||||
|
||||
A plugin must exit with a zero (success) return code if the plugin is ready to service ADD requests. If the plugin knows that it is not able to service ADD requests, it must exit with a non-zero return code and output an error on standard out (see below).
|
||||
|
||||
For example, if a plugin relies on an external service or daemon, it should return an error to `STATUS` if that service is unavailable. Likewise, if a plugin has a limited number of resources (e.g. IP addresses, hardware queues), it should return an error if those resources are exhausted and no new `ADD` requests can be serviced.
|
||||
|
||||
The following error codes are defined in the context of `STATUS`:
|
||||
|
||||
- 50: The plugin is not available (i.e. cannot service `ADD` requests)
|
||||
- 51: The plugin is not available, and existing containers in the network may have limited connectivity.
|
||||
|
||||
Plugin considerations:
|
||||
- Status is purely informational. A plugin MUST NOT rely on `STATUS` being called.
|
||||
- Plugins should always expect other CNI operations (like `ADD`, `DEL`, etc) even if `STATUS` returns an error. `STATUS` does not prevent other runtime requests.
|
||||
- If a plugin relies on a delegated plugin (e.g. IPAM) to service `ADD` requests, it must also execute a `STATUS` request to that plugin when it receives a `STATUS` request for itself. If the delegated plugin return an error result, the executing plugin should return an error result.
|
||||
|
||||
**Input:**
|
||||
|
||||
The runtime will provide a json-serialized plugin configuration object (defined below) on standard in.
|
||||
|
||||
Optional environment parameters:
|
||||
- `CNI_PATH`
|
||||
|
||||
|
||||
#### `VERSION`: probe plugin version support
|
||||
The plugin should output via standard-out a json-serialized version result object (see below).
|
||||
|
||||
|
@ -321,6 +370,38 @@ A json-serialized object, with the following key:
|
|||
Required environment parameters:
|
||||
- `CNI_COMMAND`
|
||||
|
||||
#### `GC`: Clean up any stale resources
|
||||
|
||||
The GC command provides a way for runtimes to specify the expected set of attachments to a network.
|
||||
The network plugin may then remove any resources related to attachments that do not exist in this set.
|
||||
|
||||
Resources may, for example, include:
|
||||
- IPAM reservations
|
||||
- Firewall rules
|
||||
|
||||
A plugin SHOULD remove as many stale resources as possible. For example, a plugin should remove any IPAM reservations associated with attachments not in the provided list. The plugin MAY assume that the isolation domain (e.g. network namespace) has been deleted, and thus any resources (e.g. network interfaces) therein have been removed.
|
||||
|
||||
Plugins should generally complete a `GC` action without error. If an error is encountered, a plugin should continue; removing as many resources as possible, and report the errors back to the runtime.
|
||||
|
||||
Plugins MUST, additionally, forward any GC calls to delegated plugins they are configured to use (see section 4).
|
||||
|
||||
The runtime MUST NOT use GC as a substitute for DEL. Plugins may be unable to clean up some resources from GC that they would have been able to clean up from DEL.
|
||||
|
||||
**Input:**
|
||||
|
||||
The runtime must provide a JSON-serialized plugin configuration object (defined below) on standard in. It contains an additional key;
|
||||
|
||||
- `cni.dev/valid-attachments` (array of objects): The list of **still valid** attachments to this network:
|
||||
- `containerID` (string): the value of CNI_CONTAINERID as provided during the CNI ADD operation
|
||||
- `ifname` (string): the value of CNI_IFNAME as provided during the CNI ADD operation
|
||||
|
||||
Required environment parameters:
|
||||
- `CNI_COMMAND`
|
||||
- `CNI_PATH`
|
||||
|
||||
**Output:**
|
||||
No output on success, ["error" result structure](#Error) on error.
|
||||
|
||||
|
||||
## Section 3: Execution of Network Configurations
|
||||
|
||||
|
@ -332,11 +413,13 @@ The operation of a network configuration on a container is called an _attachment
|
|||
|
||||
- The container runtime must create a new network namespace for the container before invoking any plugins.
|
||||
- The container runtime must not invoke parallel operations for the same container, but is allowed to invoke parallel operations for different containers. This includes across multiple attachments.
|
||||
- **Exception**: The runtime must exclusively execute either _gc_ or _add_ and _delete_. The runtime must ensure that no _add_ or _delete_ operations are in progress before executing _gc_, and must wait for _gc_ to complete before issuing new _add_ or _delete_ commands.
|
||||
- Plugins must handle being executed concurrently across different containers. If necessary, they must implement locking on shared resources (e.g. IPAM databases).
|
||||
- The container runtime must ensure that _add_ is eventually followed by a corresponding _delete_. The only exception is in the event of catastrophic failure, such as node loss. A _delete_ must still be executed even if the _add_ fails.
|
||||
- _delete_ may be followed by additional _deletes_.
|
||||
- The network configuration should not change between _add_ and _delete_.
|
||||
- The network configuration should not change between _attachments_.
|
||||
- The container runtime is responsible for cleanup of the container's network namespace.
|
||||
|
||||
### Attachment Parameters
|
||||
While a network configuration should not change between _attachments_, there are certain parameters supplied by the container runtime that are per-attachment. They are:
|
||||
|
@ -388,21 +471,38 @@ For every plugin defined in the `plugins` key of the network configuration,
|
|||
|
||||
If all plugins return success, return success to the caller.
|
||||
|
||||
### Deriving execution configuration from plugin configuration
|
||||
### Garbage-collecting a network
|
||||
The runtime may also ask every plugin in a network configuration to clean up any stale resources via the _GC_ command.
|
||||
|
||||
When garbage-collecting a configuration, there are no [Attachment Parameters](#attachment-parameters).
|
||||
|
||||
For every plugin defined in the `plugins` key of the network configuration,
|
||||
1. Look up the executable specified in the `type` field. If this does not exist, then this is an error.
|
||||
2. Derive request configuration from the plugin configuration.
|
||||
3. Execute the plugin binary, with `CNI_COMMAND=GC`. Supply the derived configuration via standard in.
|
||||
4. If the plugin returns an error, **continue** with execution, returning all errors to the caller.
|
||||
|
||||
If all plugins return success, return success to the caller.
|
||||
|
||||
### Deriving request configuration from plugin configuration
|
||||
The network configuration format (which is a list of plugin configurations to execute) must be transformed to a format understood by the plugin (which is a single plugin configuration). This section describes that transformation.
|
||||
|
||||
The execution configuration for a single plugin invocation is also JSON. It consists of the plugin configuration, primarily unchanged except for the specified additions and removals.
|
||||
The request configuration for a single plugin invocation is also JSON. It consists of the plugin configuration, primarily unchanged except for the specified additions and removals.
|
||||
|
||||
The following fields must be inserted into the execution configuration by the runtime:
|
||||
- `cniVersion`: taken from the `cniVersion` field of the network configuration
|
||||
The following fields are always to be inserted into the request configuration by the runtime:
|
||||
- `cniVersion`: the protocol version selected by the runtime - the string "1.1.0"
|
||||
- `name`: taken from the `name` field of the network configuration
|
||||
- `runtimeConfig`: A JSON object, consisting of the union of capabilities provided by the plugin and requested by the runtime (more details below)
|
||||
- `prevResult`: A JSON object, consisting of the result type returned by the "previous" plugin. The meaning of "previous" is defined by the specific operation (_add_, _delete_, or _check_).
|
||||
|
||||
The following fields must be **removed** by the runtime:
|
||||
- `capabilities`
|
||||
|
||||
All other fields should be passed through unaltered.
|
||||
For attachment-specific operations (ADD, DEL, CHECK), additional field requirements apply:
|
||||
- `runtimeConfig`: the runtime must insert an object consisting of the union of capabilities provided by the plugin and requested by the runtime (more details below).
|
||||
- `prevResult`: the runtime must insert consisting of the result type returned by the "previous" plugin. The meaning of "previous" is defined by the specific operation (_add_, _delete_, or _check_). This field must not be set for the first _add_ in a chain.
|
||||
- `capabilities`: must not be set
|
||||
|
||||
For GC operations:
|
||||
- `cni.dev/valid-attachments`: as specified in section 2.
|
||||
|
||||
All other fields not prefixed with `cni.dev/` should be passed through unaltered.
|
||||
|
||||
#### Deriving `runtimeConfig`
|
||||
|
||||
|
@ -458,29 +558,26 @@ When a plugin executes a delegated plugin, it should:
|
|||
- Execute that plugin with the same environment and configuration that it received.
|
||||
- Ensure that the delegated plugin's stderr is output to the calling plugin's stderr.
|
||||
|
||||
If a plugin is executed with `CNI_COMMAND=CHECK` or `DEL`, it must also execute any delegated plugins. If any of the delegated plugins return error, error should be returned by the upper plugin.
|
||||
If a plugin is executed with `CNI_COMMAND=CHECK`, `DEL`, or `GC`, it must also execute any delegated plugins. If any of the delegated plugins return error, error should be returned by the upper plugin.
|
||||
|
||||
If, on `ADD`, a delegated plugin fails, the "upper" plugin should execute again with `DEL` before returning failure.
|
||||
|
||||
## Section 5: Result Types
|
||||
|
||||
Plugins can return one of three result types:
|
||||
For certain operations, plugins must output result information. The output should be serialized as JSON on standard out.
|
||||
|
||||
- _Success_ (or _Abbreviated Success_)
|
||||
- _Error_
|
||||
- _Version
|
||||
|
||||
### Success
|
||||
|
||||
Plugins provided a `prevResult` key as part of their request configuration must output it as their result, with any possible modifications made by that plugin included. If a plugin makes no changes that would be reflected in the _Success result_ type, then it must output a result equivalent to the provided `prevResult`.
|
||||
### ADD Success
|
||||
|
||||
Plugins must output a JSON object with the following keys upon a successful `ADD` operation:
|
||||
|
||||
- `cniVersion`: The same version supplied on input - the string "1.0.0"
|
||||
- `cniVersion`: The same version supplied on input - the string "1.1.0"
|
||||
- `interfaces`: An array of all interfaces created by the attachment, including any host-level interfaces:
|
||||
- `name`: The name of the interface.
|
||||
- `mac`: The hardware address of the interface (if applicable).
|
||||
- `sandbox`: The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
|
||||
- `name` (string): The name of the interface.
|
||||
- `mac` (string): The hardware address of the interface (if applicable).
|
||||
- `mtu`: (uint) The MTU of the interface (if applicable).
|
||||
- `sandbox` (string): The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
|
||||
- `socketPath` (string, optional): An absolute path to a socket file corresponding to this interface, if applicable.
|
||||
- `pciID` (string, optional): The platform-specific identifier of the PCI device corresponding to this interface, if applicable.
|
||||
- `ips`: IPs assigned by this attachment. Plugins may include IPs assigned external to the container.
|
||||
- `address` (string): an IP address in CIDR notation (eg "192.168.1.3/24").
|
||||
- `gateway` (string): the default gateway for this subnet, if one exists.
|
||||
|
@ -488,23 +585,45 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
|
|||
- `routes`: Routes created by this attachment:
|
||||
- `dst`: The destination of the route, in CIDR notation
|
||||
- `gw`: The next hop address. If unset, a value in `gateway` in the `ips` array may be used.
|
||||
- `mtu` (uint): The MTU (Maximum transmission unit) along the path to the destination.
|
||||
- `advmss` (uint): The MSS (Maximal Segment Size) to advertise to these destinations when establishing TCP connections.
|
||||
- `priority` (uint): The priority of route, lower is higher.
|
||||
- `table` (uint): The table to add the route to.
|
||||
- `scope` (uint): The scope of the destinations covered by the route prefix (global (0), link (253), host (254)).
|
||||
- `dns`: a dictionary consisting of DNS configuration information
|
||||
- `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
|
||||
- `domain` (string): the local domain used for short hostname lookups.
|
||||
- `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers.
|
||||
- `options` (list of strings): list of options that can be passed to the resolver.
|
||||
|
||||
Plugins provided a `prevResult` key as part of their request configuration must output it as their result, with any possible modifications made by that plugin included. If a plugin makes no changes that would be reflected in the _Success result_ type, then it must output a result equivalent to the provided `prevResult`.
|
||||
|
||||
#### Delegated plugins (IPAM)
|
||||
Delegated plugins may omit irrelevant sections.
|
||||
|
||||
Delegated IPAM plugins must return an abbreviated _Success_ object. Specifically, it is missing the `interfaces` array, as well as the `interface` entry in `ips`.
|
||||
|
||||
|
||||
### VERSION Success
|
||||
|
||||
Plugins must output a JSON object with the following keys upon a `VERSION` operation:
|
||||
|
||||
- `cniVersion`: The value of `cniVersion` specified on input
|
||||
- `supportedVersions`: A list of supported specification versions
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
|
||||
}
|
||||
```
|
||||
|
||||
### Error
|
||||
|
||||
Plugins should output a JSON object with the following keys if they encounter an error:
|
||||
|
||||
- `cniVersion`: The same value as provided by the configuration
|
||||
- `cniVersion`: The protocol version in use - "1.1.0"
|
||||
- `code`: A numeric error code, see below for reserved codes.
|
||||
- `msg`: A short message characterizing the error.
|
||||
- `details`: A longer message describing the error.
|
||||
|
@ -513,7 +632,7 @@ Example:
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"code": 7,
|
||||
"msg": "Invalid Configuration",
|
||||
"details": "Network 192.168.0.0/31 too small to allocate from."
|
||||
|
@ -522,7 +641,6 @@ Example:
|
|||
|
||||
Error codes 0-99 are reserved for well-known errors. Values of 100+ can be freely used for plugin specific errors.
|
||||
|
||||
|
||||
Error Code|Error Description
|
||||
---|---
|
||||
`1`|Incompatible CNI version
|
||||
|
@ -533,6 +651,8 @@ Error Code|Error Description
|
|||
`6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
|
||||
`7`|Invalid network config. If some validations on network configs do not pass, this error will be raised.
|
||||
`11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
|
||||
`50`|The plugin is not available (i.e. cannot service `ADD` requests)
|
||||
`51`|The plugin is not available, and existing containers in the network may have limited connectivity.
|
||||
|
||||
In addition, stderr can be used for unstructured output such as logs.
|
||||
|
||||
|
@ -546,8 +666,8 @@ Plugins must output a JSON object with the following keys upon a `VERSION` opera
|
|||
Example:
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
|
||||
"cniVersion": "1.1.0",
|
||||
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0" ]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -565,7 +685,7 @@ The container runtime would perform the following steps for the `add` operation.
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
|
@ -645,7 +765,7 @@ The bridge plugin returns the following result, configuring the interface accord
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
|
@ -730,7 +850,7 @@ The plugin returns the following result. Note that the **mac** has changed.
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "portmap",
|
||||
"runtimeConfig": {
|
||||
|
@ -784,7 +904,7 @@ Given the previous _Add_, the container runtime would perform the following step
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
|
@ -840,7 +960,7 @@ Assuming the `bridge` plugin is satisfied, it produces no output on standard out
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
|
@ -890,7 +1010,7 @@ Likewise, the `tuning` plugin exits indicating success.
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "portmap",
|
||||
"runtimeConfig": {
|
||||
|
@ -943,7 +1063,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "portmap",
|
||||
"runtimeConfig": {
|
||||
|
@ -991,7 +1111,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
|
@ -1039,7 +1159,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
|
|||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "1.1.0",
|
||||
"name": "dbnet",
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
|
|
|
@ -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"
|
||||
|
@ -35,9 +36,11 @@ const (
|
|||
|
||||
DefaultNetDir = "/etc/cni/net.d"
|
||||
|
||||
CmdAdd = "add"
|
||||
CmdCheck = "check"
|
||||
CmdDel = "del"
|
||||
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 <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: 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)
|
||||
}
|
||||
|
||||
|
|
20
go.mod
20
go.mod
|
@ -1,8 +1,22 @@
|
|||
module github.com/containernetworking/cni
|
||||
|
||||
go 1.14
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/onsi/ginkgo v1.13.0
|
||||
github.com/onsi/gomega v1.10.1
|
||||
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
|
||||
)
|
||||
|
|
92
go.sum
92
go.sum
|
@ -1,60 +1,36 @@
|
|||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
|
||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
297
libcni/api.go
297
libcni/api.go
|
@ -15,7 +15,7 @@
|
|||
package libcni
|
||||
|
||||
// Note this is the actual implementation of the CNI specification, which
|
||||
// is reflected in the https://github.com/containernetworking/cni/blob/master/SPEC.md file
|
||||
// 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.
|
||||
|
@ -23,10 +23,11 @@ package libcni
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
|
@ -38,6 +39,8 @@ import (
|
|||
|
||||
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 (
|
||||
|
@ -64,17 +67,37 @@ 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
|
||||
}
|
||||
|
||||
type NetworkConfigList struct {
|
||||
Name string
|
||||
CNIVersion string
|
||||
DisableCheck bool
|
||||
Plugins []*NetworkConfig
|
||||
Bytes []byte
|
||||
Name string
|
||||
CNIVersion string
|
||||
DisableCheck bool
|
||||
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 {
|
||||
|
@ -84,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 {
|
||||
|
@ -122,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{}{
|
||||
|
@ -139,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 injectRuntimeConfig(orig, rt)
|
||||
return orig, nil
|
||||
}
|
||||
|
||||
// This function takes a libcni RuntimeConf structure and injects values into
|
||||
|
@ -155,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{})
|
||||
|
@ -195,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"`
|
||||
|
@ -229,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,
|
||||
}
|
||||
|
@ -254,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 {
|
||||
|
@ -277,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
|
||||
|
@ -305,7 +340,7 @@ 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
|
||||
|
@ -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
|
||||
|
@ -374,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)
|
||||
}
|
||||
|
||||
|
@ -386,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 {
|
||||
|
@ -432,7 +529,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
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 {
|
||||
|
@ -453,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 {
|
||||
|
@ -474,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 {
|
||||
|
@ -497,9 +594,9 @@ 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: %w", list.Name, err)
|
||||
if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil {
|
||||
_ = c.cacheDel(list.Name, rt)
|
||||
cachedResult = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,12 +606,13 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
|
|||
return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err)
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.cacheDel(list.Name, rt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginDescription(net *types.NetConf) string {
|
||||
func pluginDescription(net *types.PluginConf) string {
|
||||
if net == nil {
|
||||
return "<missing>"
|
||||
}
|
||||
|
@ -528,7 +626,7 @@ func pluginDescription(net *types.NetConf) string {
|
|||
}
|
||||
|
||||
// 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
|
||||
|
@ -542,12 +640,12 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
@ -558,7 +656,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
|
|||
}
|
||||
|
||||
// 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
|
||||
|
@ -618,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 {
|
||||
|
@ -666,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{
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -28,21 +27,23 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type pluginInfo struct {
|
||||
debugFilePath string
|
||||
debug *noop_debug.Debug
|
||||
config string
|
||||
stdinData []byte
|
||||
debugFilePath string
|
||||
commandFilePath string
|
||||
debug *noop_debug.Debug
|
||||
config string
|
||||
stdinData []byte
|
||||
}
|
||||
|
||||
type portMapping struct {
|
||||
|
@ -61,11 +62,16 @@ func stringInList(s string, list []string) bool {
|
|||
}
|
||||
|
||||
func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath := debugFile.Name()
|
||||
|
||||
commandLog, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(commandLog.Close()).To(Succeed())
|
||||
commandFilePath := commandLog.Name()
|
||||
|
||||
debug := &noop_debug.Debug{
|
||||
ReportResult: result,
|
||||
}
|
||||
|
@ -79,6 +85,7 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
|
|||
}
|
||||
if injectDebugFilePath {
|
||||
config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath)
|
||||
config += fmt.Sprintf(`, "commandLog": %q`, commandFilePath)
|
||||
}
|
||||
if len(capabilities) > 0 {
|
||||
config += `, "capabilities": {`
|
||||
|
@ -115,10 +122,11 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return pluginInfo{
|
||||
debugFilePath: debugFilePath,
|
||||
debug: debug,
|
||||
config: config,
|
||||
stdinData: stdinData,
|
||||
debugFilePath: debugFilePath,
|
||||
commandFilePath: commandFilePath,
|
||||
debug: debug,
|
||||
config: config,
|
||||
stdinData: stdinData,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +161,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
|
||||
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -168,19 +176,19 @@ var _ = Describe("Invoking plugins", func() {
|
|||
pluginConfig []byte
|
||||
cniConfig *libcni.CNIConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
netConfig *libcni.NetworkConfig
|
||||
netConfig *libcni.PluginConfig
|
||||
ctx context.Context
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
||||
debug = &noop_debug.Debug{
|
||||
ReportResult: `{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "` + version.Current() + `",
|
||||
"ips": [{"address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`,
|
||||
|
@ -196,7 +204,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"somethingElse": true,
|
||||
"noCapability": false
|
||||
}
|
||||
}`, current.ImplementedSpecVersion))
|
||||
}`, version.Current()))
|
||||
netConfig, err = libcni.ConfFromBytes(pluginConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
|
@ -238,12 +246,12 @@ var _ = Describe("Invoking plugins", func() {
|
|||
// We expect runtimeConfig keys only for portMappings and somethingElse
|
||||
rawRc := conf["runtimeConfig"]
|
||||
rc, ok := rawRc.(map[string]interface{})
|
||||
Expect(ok).To(Equal(true))
|
||||
Expect(ok).To(BeTrue())
|
||||
expectedKeys := []string{"portMappings", "somethingElse"}
|
||||
Expect(len(rc)).To(Equal(len(expectedKeys)))
|
||||
Expect(rc).To(HaveLen(len(expectedKeys)))
|
||||
for _, key := range expectedKeys {
|
||||
_, ok := rc[key]
|
||||
Expect(ok).To(Equal(true))
|
||||
Expect(ok).To(BeTrue())
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -279,7 +287,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(caps).To(ConsistOf("portMappings", "somethingElse"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Invoking a single plugin", func() {
|
||||
|
@ -288,7 +295,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
debug *noop_debug.Debug
|
||||
pluginConfig string
|
||||
cniConfig *libcni.CNIConfig
|
||||
netConfig *libcni.NetworkConfig
|
||||
netConfig *libcni.PluginConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
ctx context.Context
|
||||
|
||||
|
@ -296,14 +303,14 @@ var _ = Describe("Invoking plugins", func() {
|
|||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
||||
debug = &noop_debug.Debug{
|
||||
ReportResult: `{
|
||||
"cniVersion": "1.0.0",
|
||||
"cniVersion": "` + version.Current() + `",
|
||||
"ips": [{"address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`,
|
||||
|
@ -321,7 +328,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"some-key": "some-value",
|
||||
"cniVersion": "%s",
|
||||
"capabilities": { "portMappings": true }
|
||||
}`, current.ImplementedSpecVersion)
|
||||
}`, version.Current())
|
||||
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
|
||||
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -411,6 +418,27 @@ var _ = Describe("Invoking plugins", func() {
|
|||
returnedJson, err := json.Marshal(result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cachedJson).To(MatchJSON(returnedJson))
|
||||
|
||||
// Ensure the cached attachments matches requested one
|
||||
for _, containerID := range []string{"", runtimeConfig.ContainerID} {
|
||||
expected, err := json.Marshal(libcni.NetworkAttachment{
|
||||
ContainerID: runtimeConfig.ContainerID,
|
||||
Network: netConfig.Network.Name,
|
||||
NetNS: runtimeConfig.NetNS,
|
||||
IfName: runtimeConfig.IfName,
|
||||
Config: netConfig.Bytes,
|
||||
CniArgs: runtimeConfig.Args,
|
||||
CapabilityArgs: runtimeConfig.CapabilityArgs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
attachments, err := cniConfig.GetCachedAttachments(containerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if Expect(len(attachments)).To(Equal(1)) {
|
||||
json, err := json.Marshal(attachments[0])
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(json).To(MatchJSON(expected))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
|
@ -441,7 +469,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
// Make the results directory inaccessible by making it a
|
||||
// file instead of a directory
|
||||
tmpPath := filepath.Join(cacheDirPath, "results")
|
||||
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
|
||||
err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -454,14 +482,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Describe("CheckNetwork", func() {
|
||||
It("executes the plugin with command CHECK", func() {
|
||||
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cachedJson := `{
|
||||
"cniVersion": "1.0.0",
|
||||
"ips": [{"address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
"cniVersion": "` + version.Current() + `",
|
||||
"ips": [{"address": "10.1.2.3/24"}]
|
||||
}`
|
||||
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -516,7 +543,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -541,11 +568,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
Context("containing only a cached result", func() {
|
||||
It("only passes a prevResult to the plugin", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "1.0.0",
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "`+version.Current()+`",
|
||||
"ips": [{"address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -579,7 +606,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
}`))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug.ReportResult = ipResult
|
||||
|
@ -599,13 +626,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("is invalid JSON", func() {
|
||||
It("returns an error", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
|
||||
err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -615,11 +642,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
Context("version doesn't match the config version", func() {
|
||||
It("succeeds when the cached result can be converted", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -627,11 +654,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
})
|
||||
|
||||
It("returns an error when the cached result cannot be converted", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.4567.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -644,14 +671,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Describe("DelNetwork", func() {
|
||||
It("executes the plugin with command DEL", func() {
|
||||
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cachedJson := `{
|
||||
"cniVersion": "1.0.0",
|
||||
"ips": [{"address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
"cniVersion": "` + version.Current() + `",
|
||||
"ips": [{"address": "10.1.2.3/24"}]
|
||||
}`
|
||||
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -688,6 +714,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cachedConfig).To(BeNil())
|
||||
Expect(newRt).To(BeNil())
|
||||
|
||||
// Ensure the cached attachments no longer exist
|
||||
for _, containerID := range []string{"", runtimeConfig.ContainerID} {
|
||||
attachments, err := cniConfig.GetCachedAttachments(containerID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(attachments).To(BeEmpty())
|
||||
}
|
||||
})
|
||||
|
||||
Context("when finding the plugin fails", func() {
|
||||
|
@ -717,21 +750,21 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
resultCacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("deletes the cached result and config after the first DEL", func() {
|
||||
err := ioutil.WriteFile(resultCacheFile, []byte(`{
|
||||
err := os.WriteFile(resultCacheFile, []byte(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = ioutil.ReadFile(resultCacheFile)
|
||||
_, err = os.ReadFile(resultCacheFile)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -744,17 +777,17 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("less than 0.4.0", func() {
|
||||
It("does not pass a prevResult to the plugin", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Generate plugin config with older version
|
||||
|
@ -776,11 +809,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
Context("equal to 0.4.0", func() {
|
||||
It("passes a prevResult to the plugin", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -799,13 +832,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("result is invalid JSON", func() {
|
||||
It("returns an error", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
|
||||
err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -815,11 +848,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
Context("result version doesn't match the config version", func() {
|
||||
It("succeeds when the cached result can be converted", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -827,11 +860,11 @@ var _ = Describe("Invoking plugins", func() {
|
|||
})
|
||||
|
||||
It("returns an error when the cached result cannot be converted", func() {
|
||||
err := ioutil.WriteFile(cacheFile, []byte(`{
|
||||
err := os.WriteFile(cacheFile, []byte(`{
|
||||
"cniVersion": "0.4567.0",
|
||||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`), 0600)
|
||||
}`), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
|
||||
|
@ -848,7 +881,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
Expect(versionInfo).NotTo(BeNil())
|
||||
Expect(versionInfo.SupportedVersions()).To(Equal([]string{
|
||||
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0",
|
||||
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0",
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -936,8 +969,8 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"otherCapability": capabilityArgs["otherCapability"],
|
||||
}
|
||||
|
||||
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
|
||||
netConfigList, plugins = makePluginList(current.ImplementedSpecVersion, ipResult, rcMap)
|
||||
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "ips":[{"address": "10.1.2.3/24"}]}`, version.Current())
|
||||
netConfigList, plugins = makePluginList(version.Current(), ipResult, rcMap)
|
||||
|
||||
ctx = context.TODO()
|
||||
})
|
||||
|
@ -1163,7 +1196,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
})
|
||||
It("should not have written cache files", func() {
|
||||
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
_, err := ioutil.ReadFile(resultCacheFile)
|
||||
_, err := os.ReadFile(resultCacheFile)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -1173,7 +1206,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
// Make the results directory inaccessible by making it a
|
||||
// file instead of a directory
|
||||
tmpPath := filepath.Join(cacheDirPath, "results")
|
||||
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
|
||||
err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
|
@ -1186,9 +1219,9 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Describe("CheckNetworkList", func() {
|
||||
It("executes all plugins with command CHECK", func() {
|
||||
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
|
@ -1230,7 +1263,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -1241,7 +1274,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`
|
||||
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
|
||||
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
|
||||
|
@ -1280,6 +1313,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command"))
|
||||
Expect(errors.Is(err, libcni.ErrorCheckNotSupp)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1309,9 +1343,9 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Context("when the cached result is invalid", func() {
|
||||
It("returns an error", func() {
|
||||
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
|
@ -1353,7 +1387,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -1364,7 +1398,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`
|
||||
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
|
||||
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
|
||||
|
@ -1396,7 +1430,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
|
||||
"dns": {}
|
||||
}`
|
||||
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
|
||||
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
netConfigList, plugins = makePluginList("0.3.1", ipResult, rcMap)
|
||||
|
@ -1440,15 +1474,15 @@ var _ = Describe("Invoking plugins", func() {
|
|||
})
|
||||
|
||||
Context("when the cached result is invalid", func() {
|
||||
It("returns an error", func() {
|
||||
It("tolerates the error", func() {
|
||||
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
|
||||
err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1471,6 +1505,128 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(err).To(MatchError("[plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\"]"))
|
||||
})
|
||||
})
|
||||
Describe("GCNetworkList", func() {
|
||||
It("issues a DEL and GC as necessary", func() {
|
||||
By("doing a CNI ADD")
|
||||
_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Issuing a GC with disableGC=true")
|
||||
netConfigList.DisableGC = true
|
||||
gcargs := &libcni.GCArgs{}
|
||||
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
commands, err := noop_debug.ReadCommandLog(plugins[0].commandFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(commands).To(HaveLen(1)) // ADD
|
||||
|
||||
By("Issuing a GC with valid networks")
|
||||
netConfigList.DisableGC = false
|
||||
gcargs = &libcni.GCArgs{
|
||||
ValidAttachments: []types.GCAttachment{{
|
||||
ContainerID: runtimeConfig.ContainerID,
|
||||
IfName: runtimeConfig.IfName,
|
||||
}},
|
||||
}
|
||||
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Issuing a GC with no valid networks")
|
||||
gcargs.ValidAttachments = nil
|
||||
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
commands, err = noop_debug.ReadCommandLog(plugins[0].commandFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(commands).To(HaveLen(4))
|
||||
|
||||
validations := []struct {
|
||||
name string
|
||||
fn func(entry noop_debug.CmdLogEntry)
|
||||
}{
|
||||
{
|
||||
name: "ADD",
|
||||
fn: func(entry noop_debug.CmdLogEntry) {
|
||||
Expect(entry.CmdArgs.ContainerID).To(Equal(runtimeConfig.ContainerID))
|
||||
Expect(entry.CmdArgs.IfName).To(Equal(runtimeConfig.IfName))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GC",
|
||||
fn: func(entry noop_debug.CmdLogEntry) {
|
||||
var conf struct {
|
||||
Attachments []map[string]string `json:"cni.dev/valid-attachments"`
|
||||
}
|
||||
err = json.Unmarshal(entry.CmdArgs.StdinData, &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.Attachments).To(HaveLen(1))
|
||||
Expect(conf.Attachments[0]).To(Equal(map[string]string{"containerID": runtimeConfig.ContainerID, "ifname": runtimeConfig.IfName}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DEL",
|
||||
fn: func(entry noop_debug.CmdLogEntry) {
|
||||
Expect(entry.CmdArgs.ContainerID).To(Equal(runtimeConfig.ContainerID))
|
||||
Expect(entry.CmdArgs.IfName).To(Equal(runtimeConfig.IfName))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GC",
|
||||
fn: func(entry noop_debug.CmdLogEntry) {
|
||||
var conf struct {
|
||||
Attachments []map[string]string `json:"cni.dev/valid-attachments"`
|
||||
}
|
||||
err = json.Unmarshal(entry.CmdArgs.StdinData, &conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.Attachments).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, c := range validations {
|
||||
Expect(commands[i].Command).To(Equal(c.name))
|
||||
c.fn(commands[i])
|
||||
}
|
||||
})
|
||||
})
|
||||
Describe("GetStatusNetworkList", func() {
|
||||
It("issues a STATUS request", func() {
|
||||
netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap)
|
||||
|
||||
err := cniConfig.GetStatusNetworkList(ctx, netConfigList)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("STATUS"))
|
||||
})
|
||||
|
||||
It("correctly reports an error", func() {
|
||||
netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap)
|
||||
|
||||
plugins[1].debug.ReportError = "plugin error: banana"
|
||||
plugins[1].debug.ReportErrorCode = 50
|
||||
Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
|
||||
|
||||
err := cniConfig.GetStatusNetworkList(ctx, netConfigList)
|
||||
Expect(err).To(HaveOccurred())
|
||||
var eerr *types.Error
|
||||
Expect(errors.As(err, &eerr)).To(BeTrue())
|
||||
Expect(eerr.Code).To(Equal(uint(50)))
|
||||
|
||||
debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("STATUS"))
|
||||
|
||||
debug, err = noop_debug.ReadDebug(plugins[1].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal("STATUS"))
|
||||
|
||||
debug, err = noop_debug.ReadDebug(plugins[2].debugFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debug.Command).To(Equal(""))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Invoking a sleep plugin", func() {
|
||||
|
@ -1480,13 +1636,13 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cniBinPath string
|
||||
pluginConfig string
|
||||
cniConfig *libcni.CNIConfig
|
||||
netConfig *libcni.NetworkConfig
|
||||
netConfig *libcni.PluginConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
netConfigList *libcni.NetworkConfigList
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
@ -1506,7 +1662,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"some-key": "some-value",
|
||||
"cniVersion": "%s",
|
||||
"capabilities": { "portMappings": true }
|
||||
}`, current.ImplementedSpecVersion)
|
||||
}`, version.Current())
|
||||
|
||||
cniBinPath = filepath.Dir(pluginPaths["sleep"])
|
||||
cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
|
||||
|
@ -1533,11 +1689,10 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"plugins": [
|
||||
%s
|
||||
]
|
||||
}`, current.ImplementedSpecVersion, pluginConfig))
|
||||
}`, version.Current(), pluginConfig))
|
||||
|
||||
netConfigList, err = libcni.ConfListFromBytes(configList)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -1553,7 +1708,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(result).To(BeNil())
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1565,7 +1719,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1577,7 +1730,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1590,7 +1742,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(result).To(BeNil())
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1602,7 +1753,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1615,7 +1765,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Expect(result).To(BeNil())
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1627,7 +1776,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1639,7 +1787,6 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1651,10 +1798,8 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cancel()
|
||||
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Cache operations", func() {
|
||||
|
@ -1664,7 +1809,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
cniBinPath string
|
||||
pluginConfig string
|
||||
cniConfig *libcni.CNIConfig
|
||||
netConfig *libcni.NetworkConfig
|
||||
netConfig *libcni.PluginConfig
|
||||
runtimeConfig *libcni.RuntimeConf
|
||||
|
||||
ctx context.Context
|
||||
|
@ -1678,7 +1823,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
netNS := "/some/netns/path"
|
||||
|
||||
BeforeEach(func() {
|
||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
||||
debugFile, err := os.CreateTemp("", "cni_debug")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(debugFile.Close()).To(Succeed())
|
||||
debugFilePath = debugFile.Name()
|
||||
|
@ -1687,7 +1832,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
ReportResult: fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"ips": [{"version": "4", "address": "%s"}]
|
||||
}`, current.ImplementedSpecVersion, firstIP),
|
||||
}`, version.Current(), firstIP),
|
||||
}
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
|
||||
|
@ -1696,7 +1841,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
"type": "noop",
|
||||
"name": "%s",
|
||||
"cniVersion": "%s"
|
||||
}`, netName, current.ImplementedSpecVersion)
|
||||
}`, netName, version.Current())
|
||||
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
|
||||
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -1720,16 +1865,16 @@ var _ = Describe("Invoking plugins", func() {
|
|||
debug.ReportResult = fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"ips": [{"version": "4", "address": "%s"}]
|
||||
}`, current.ImplementedSpecVersion, secondIP)
|
||||
}`, version.Current(), secondIP)
|
||||
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
|
||||
runtimeConfig.IfName = secondIfname
|
||||
_, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
resultsDir := filepath.Join(cacheDirPath, "results")
|
||||
files, err := ioutil.ReadDir(resultsDir)
|
||||
files, err := os.ReadDir(resultsDir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(files)).To(Equal(2))
|
||||
Expect(files).To(HaveLen(2))
|
||||
var foundFirst, foundSecond bool
|
||||
for _, f := range files {
|
||||
type cachedConfig struct {
|
||||
|
@ -1739,7 +1884,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
NetworkName string `json:"networkName"`
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filepath.Join(resultsDir, f.Name()))
|
||||
data, err := os.ReadFile(filepath.Join(resultsDir, f.Name()))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cc := &cachedConfig{}
|
||||
err = json.Unmarshal(data, cc)
|
||||
|
@ -1786,14 +1931,14 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Context("when the RuntimeConf is incomplete", func() {
|
||||
var (
|
||||
testRt *libcni.RuntimeConf
|
||||
testNetConf *libcni.NetworkConfig
|
||||
testNetConf *libcni.PluginConfig
|
||||
testNetConfList *libcni.NetworkConfigList
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
testRt = &libcni.RuntimeConf{}
|
||||
testNetConf = &libcni.NetworkConfig{
|
||||
Network: &types.NetConf{},
|
||||
testNetConf = &libcni.PluginConfig{
|
||||
Network: &types.PluginConf{},
|
||||
}
|
||||
testNetConfList = &libcni.NetworkConfigList{}
|
||||
})
|
||||
|
@ -1849,10 +1994,10 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Context("is invalid JSON", func() {
|
||||
It("returns an error", func() {
|
||||
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = ioutil.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
|
||||
err = os.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
|
||||
|
@ -1864,7 +2009,7 @@ var _ = Describe("Invoking plugins", func() {
|
|||
Context("is missing", func() {
|
||||
It("returns no error", func() {
|
||||
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
|
||||
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
|
||||
|
@ -1874,6 +2019,5 @@ var _ = Describe("Invoking plugins", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
|
||||
|
|
239
libcni/conf.go
239
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,9 +45,16 @@ 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 {
|
||||
// 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 == "" {
|
||||
|
@ -51,17 +63,35 @@ 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: %w", 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 {
|
||||
if err := json.Unmarshal(confBytes, &rawList); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration list: %w", err)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
CNIVersion: cniVersion,
|
||||
Bytes: bytes,
|
||||
Name: name,
|
||||
DisableCheck: disableCheck,
|
||||
DisableGC: disableGC,
|
||||
LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
|
||||
CNIVersion: cniVersion,
|
||||
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)
|
||||
|
@ -122,24 +241,68 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
|||
}
|
||||
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: %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,7 +388,8 @@ 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 {
|
||||
|
@ -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/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"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Delegate", func() {
|
||||
|
@ -43,7 +43,7 @@ var _ = Describe("Delegate", func() {
|
|||
BeforeEach(func() {
|
||||
netConf, _ = json.Marshal(map[string]string{
|
||||
"name": "delegate-test",
|
||||
"cniVersion": current.ImplementedSpecVersion,
|
||||
"cniVersion": version.Current(),
|
||||
})
|
||||
|
||||
expectedResult = ¤t.Result{
|
||||
|
@ -59,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()
|
||||
|
@ -223,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,6 +16,7 @@ package invoke
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
|
@ -33,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 {
|
||||
|
@ -65,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 {
|
||||
|
@ -84,7 +128,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return create.CreateFromBytes(stdoutBytes)
|
||||
resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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"
|
||||
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() {
|
||||
|
@ -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"))
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
}
|
235
pkg/skel/skel.go
235
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"
|
||||
|
@ -34,12 +35,13 @@ import (
|
|||
// CmdArgs captures all the arguments passed in to the plugin
|
||||
// via both env vars and stdin
|
||||
type CmdArgs struct {
|
||||
ContainerID string
|
||||
Netns string
|
||||
IfName string
|
||||
Args string
|
||||
Path string
|
||||
StdinData []byte
|
||||
ContainerID string
|
||||
Netns string
|
||||
IfName string
|
||||
Args string
|
||||
Path string
|
||||
NetnsOverride string
|
||||
StdinData []byte
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
|
@ -55,21 +57,25 @@ 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
|
||||
name string
|
||||
val *string
|
||||
reqForCmd reqForCmdEntry
|
||||
validateFn func(string) *types.Error
|
||||
}{
|
||||
{
|
||||
"CNI_COMMAND",
|
||||
&cmd,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"CHECK": true,
|
||||
"DEL": true,
|
||||
"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,15 +115,29 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
|||
"CHECK": false,
|
||||
"DEL": false,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CNI_PATH",
|
||||
&path,
|
||||
reqForCmdEntry{
|
||||
"ADD": true,
|
||||
"CHECK": true,
|
||||
"DEL": true,
|
||||
"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,18 +164,25 @@ 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,
|
||||
IfName: ifName,
|
||||
Args: args,
|
||||
Path: path,
|
||||
StdinData: stdinData,
|
||||
ContainerID: contID,
|
||||
Netns: netns,
|
||||
IfName: ifName,
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
// 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"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"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 {
|
||||
|
@ -47,13 +45,14 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
|
|||
|
||||
var _ = Describe("dispatching to the correct callback", func() {
|
||||
var (
|
||||
environment map[string]string
|
||||
stdinData string
|
||||
stdout, stderr *bytes.Buffer
|
||||
cmdAdd, cmdCheck, cmdDel *fakeCmd
|
||||
dispatch *dispatcher
|
||||
expectedCmdArgs *CmdArgs
|
||||
versionInfo version.PluginInfo
|
||||
environment map[string]string
|
||||
stdinData string
|
||||
stdout, stderr *bytes.Buffer
|
||||
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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -19,12 +19,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
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) {
|
||||
|
|
|
@ -16,16 +16,16 @@ package types040_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
. "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"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func testResult() *types040.Result {
|
||||
|
@ -99,7 +99,7 @@ var _ = Describe("040 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,7 +169,7 @@ var _ = Describe("040 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())
|
||||
|
||||
|
@ -348,7 +348,7 @@ var _ = Describe("040 types operations", func() {
|
|||
}`))
|
||||
|
||||
recovered := &types040.IPConfig{}
|
||||
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
|
||||
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
|
||||
Expect(recovered).To(Equal(ipc))
|
||||
})
|
||||
|
||||
|
@ -363,7 +363,7 @@ var _ = Describe("040 types operations", func() {
|
|||
Context("when unmarshalling json fails", func() {
|
||||
It("returns an error", func() {
|
||||
recovered := &types040.IPConfig{}
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
|
||||
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -26,9 +26,10 @@ import (
|
|||
convert "github.com/containernetworking/cni/pkg/types/internal"
|
||||
)
|
||||
|
||||
const ImplementedSpecVersion string = "1.0.0"
|
||||
// The types did not change between v1.0 and v1.1
|
||||
const ImplementedSpecVersion string = "1.1.0"
|
||||
|
||||
var supportedVersions = []string{ImplementedSpecVersion}
|
||||
var supportedVersions = []string{"1.0.0", "1.1.0"}
|
||||
|
||||
// Register converters for all versions less than the implemented spec version
|
||||
func init() {
|
||||
|
@ -38,10 +39,14 @@ func init() {
|
|||
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)
|
||||
|
@ -90,12 +95,49 @@ type Result struct {
|
|||
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, ImplementedSpecVersion)
|
||||
result100, err := convertFrom04x(result040, toVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -226,9 +268,12 @@ func (r *Result) PrintTo(writer io.Writer) error {
|
|||
|
||||
// Interface contains values about the created interfaces
|
||||
type Interface struct {
|
||||
Name string `json:"name"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Sandbox string `json:"sandbox,omitempty"`
|
||||
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 {
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
package types100_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) {
|
||||
|
|
|
@ -16,15 +16,15 @@ package types100_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func testResult() *current.Result {
|
||||
|
@ -45,15 +45,17 @@ func testResult() *current.Result {
|
|||
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: "1.0.0",
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
Name: "eth0",
|
||||
Mac: "00:11:22:33:44:55",
|
||||
Sandbox: "/proc/3553/ns/net",
|
||||
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{
|
||||
|
@ -82,7 +84,7 @@ func testResult() *current.Result {
|
|||
}
|
||||
|
||||
var _ = Describe("Current types operations", func() {
|
||||
It("correctly encodes a 1.0.0 Result", func() {
|
||||
It("correctly encodes a 1.1.0 Result", func() {
|
||||
res := testResult()
|
||||
|
||||
// Redirect stdout to capture JSON result
|
||||
|
@ -96,17 +98,20 @@ 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": "1.0.0",
|
||||
"cniVersion": "` + current.ImplementedSpecVersion + `",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net"
|
||||
"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": [
|
||||
|
@ -149,6 +154,22 @@ var _ = Describe("Current types operations", func() {
|
|||
}`))
|
||||
})
|
||||
|
||||
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())
|
||||
|
@ -164,7 +185,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())
|
||||
|
||||
|
@ -223,7 +244,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())
|
||||
|
||||
|
@ -295,14 +316,14 @@ var _ = Describe("Current types operations", func() {
|
|||
}`))
|
||||
|
||||
recovered := ¤t.IPConfig{}
|
||||
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
|
||||
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)
|
||||
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
|
||||
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -18,11 +18,10 @@ 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() {
|
||||
|
@ -127,7 +126,6 @@ var _ = Describe("LoadArgs", func() {
|
|||
}{}
|
||||
err := LoadArgs("IP=10.0.0.0/24", &conf)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
|
|
|
@ -56,31 +56,72 @@ 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"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DisableCheck bool `json:"disableCheck,omitempty"`
|
||||
DisableGC bool `json:"disableGC,omitempty"`
|
||||
Plugins []*PluginConf `json:"plugins,omitempty"`
|
||||
}
|
||||
|
||||
// Result is an interface that provides the result of plugin execution
|
||||
|
@ -116,31 +157,48 @@ 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}
|
||||
for _, ns := range d.Nameservers {
|
||||
to.Nameservers = append(to.Nameservers, ns)
|
||||
}
|
||||
for _, s := range d.Search {
|
||||
to.Search = append(to.Search, s)
|
||||
}
|
||||
for _, o := range d.Options {
|
||||
to.Options = append(to.Options, o)
|
||||
}
|
||||
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
|
||||
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 {
|
||||
|
@ -148,14 +206,30 @@ func (r *Route) Copy() *Route {
|
|||
return nil
|
||||
}
|
||||
|
||||
return &Route{
|
||||
Dst: r.Dst,
|
||||
GW: r.GW,
|
||||
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
|
||||
|
@ -165,7 +239,10 @@ const (
|
|||
ErrIOFailure // 5
|
||||
ErrDecodingFailure // 6
|
||||
ErrInvalidNetworkConfig // 7
|
||||
ErrInvalidNetNS // 8
|
||||
ErrTryAgainLater uint = 11
|
||||
ErrPluginNotAvailable uint = 50
|
||||
ErrLimitedConnectivity uint = 51
|
||||
ErrInternal uint = 999
|
||||
)
|
||||
|
||||
|
@ -200,8 +277,13 @@ func (e *Error) Print() error {
|
|||
|
||||
// JSON (un)marshallable types
|
||||
type route struct {
|
||||
Dst IPNet `json:"dst"`
|
||||
GW net.IP `json:"gw,omitempty"`
|
||||
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 {
|
||||
|
@ -212,13 +294,24 @@ 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
|
||||
}
|
||||
|
||||
func (r Route) MarshalJSON() ([]byte, error) {
|
||||
rt := route{
|
||||
Dst: IPNet(r.Dst),
|
||||
GW: r.GW,
|
||||
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,15 +18,15 @@ import (
|
|||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
. "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,14 +88,19 @@ var _ = Describe("Types", func() {
|
|||
IP: net.ParseIP("1.2.3.0"),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
GW: net.ParseIP("1.2.3.1"),
|
||||
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())
|
||||
|
@ -111,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}`))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -155,14 +160,15 @@ var _ = Describe("Types", func() {
|
|||
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",
|
||||
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{
|
||||
|
|
|
@ -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,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,7 +107,7 @@ 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: %w", err)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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, ".")
|
||||
|
@ -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,15 +22,12 @@ package testhelpers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const packageBaseName = "github.com/containernetworking/cni"
|
||||
|
||||
func run(cmd *exec.Cmd) error {
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
|
@ -64,8 +61,16 @@ func modInit(path, name string) error {
|
|||
return run(cmd)
|
||||
}
|
||||
|
||||
// addLibcni will execute `go mod edit -replace` to fix libcni at a specified version
|
||||
func addLibcni(path, gitRef string) error {
|
||||
cmd := exec.Command("go", "get", "github.com/containernetworking/cni@"+gitRef)
|
||||
cmd := exec.Command("go", "mod", "edit", "-replace=github.com/containernetworking/cni=github.com/containernetworking/cni@"+gitRef)
|
||||
cmd.Dir = path
|
||||
return run(cmd)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
@ -73,7 +78,7 @@ func addLibcni(path, gitRef string) error {
|
|||
// 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 {
|
||||
tempDir, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "cni-test-")
|
||||
tempDir, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "cni-test-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -85,12 +90,15 @@ func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// go get
|
||||
if err := addLibcni(tempDir, gitRef); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0600); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := modTidy(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// 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
|
||||
// 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,
|
||||
|
@ -14,13 +14,12 @@
|
|||
package testhelpers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
|
@ -43,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" {
|
||||
|
|
|
@ -19,13 +19,12 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/types/create"
|
||||
)
|
||||
|
||||
// Current reports the version of the CNI spec implemented by this library
|
||||
func Current() string {
|
||||
return types100.ImplementedSpecVersion
|
||||
return "1.1.0"
|
||||
}
|
||||
|
||||
// Legacy PluginInfo describes a plugin that is backwards compatible with the
|
||||
|
@ -35,8 +34,26 @@ 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", "1.0.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")
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
@ -46,7 +63,7 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
|
|||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"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": "1.0.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net"
|
||||
"name": "eth0",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"sandbox": "/proc/3553/ns/net",
|
||||
"pciID": "8086:9a01",
|
||||
"socketPath": "/path/to/vhost/fd"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
|
@ -52,7 +59,7 @@ var _ = Describe("Version operations", func() {
|
|||
err := json.Unmarshal(rawBytes, &raw)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := &types.NetConf{
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: "1.0.0",
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
|
@ -61,19 +68,20 @@ var _ = Describe("Version operations", func() {
|
|||
|
||||
err = version.ParsePrevResult(conf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedResult := ¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
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",
|
||||
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{
|
||||
{
|
||||
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),
|
||||
|
@ -86,8 +94,8 @@ var _ = Describe("Version operations", func() {
|
|||
})
|
||||
|
||||
It("fails if the prevResult version is unknown", func() {
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
RawPrevResult: map[string]interface{}{
|
||||
|
@ -96,12 +104,12 @@ var _ = Describe("Version operations", func() {
|
|||
}
|
||||
|
||||
err := version.ParsePrevResult(conf)
|
||||
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0] but unmarshalled CNIVersion is \"5678.456\""))
|
||||
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 version does not match the prevResult version", func() {
|
||||
conf := &types.NetConf{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
RawPrevResult: map[string]interface{}{
|
||||
|
@ -114,14 +122,14 @@ var _ = Describe("Version operations", func() {
|
|||
}
|
||||
|
||||
err := version.ParsePrevResult(conf)
|
||||
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0] but unmarshalled CNIVersion is \"0.2.0\""))
|
||||
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: current.ImplementedSpecVersion,
|
||||
conf := &types.PluginConf{
|
||||
CNIVersion: version.Current(),
|
||||
Name: "foobar",
|
||||
Type: "baz",
|
||||
}
|
||||
|
@ -131,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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
module github.com/containernetworking/cni/plugins/debug
|
||||
|
||||
go 1.14
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/containernetworking/cni v0.8.1
|
||||
github.com/containernetworking/plugins v0.9.1
|
||||
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 => ../..
|
||||
|
|
|
@ -1,75 +1,26 @@
|
|||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI=
|
||||
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8=
|
||||
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
|
||||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
|
|
|
@ -17,15 +17,17 @@ 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"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type NetConf struct {
|
||||
|
@ -101,7 +103,7 @@ 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, 0644)
|
||||
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)
|
||||
|
@ -117,7 +119,7 @@ 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, 0644)
|
||||
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)
|
||||
|
@ -133,7 +135,7 @@ 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, 0644)
|
||||
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)
|
||||
|
|
|
@ -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,9 +23,8 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
|
@ -37,8 +36,9 @@ import (
|
|||
)
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
DebugFile string `json:"debugFile"`
|
||||
types.PluginConf
|
||||
DebugFile string `json:"debugFile"`
|
||||
CommandLog string `json:"commandLog"`
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, error) {
|
||||
|
@ -46,7 +46,7 @@ func loadConf(bytes []byte) (*NetConf, error) {
|
|||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes))
|
||||
}
|
||||
if err := version.ParsePrevResult(&n.NetConf); err != nil {
|
||||
if err := version.ParsePrevResult(&n.PluginConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n, nil
|
||||
|
@ -117,15 +117,33 @@ 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" {
|
||||
newResult, err := current.NewResultFromResult(netConf.PrevResult)
|
||||
|
@ -149,7 +167,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if debug.ReportResult != "" {
|
||||
case debug.ReportResult != "":
|
||||
_, err = os.Stdout.WriteString(debug.ReportResult)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -163,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", "1.0.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
|
||||
|
@ -196,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
|
||||
}
|
||||
|
@ -227,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()
|
||||
|
@ -230,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)
|
||||
export CNI_IFNAME=$(printf eth%d $i)
|
||||
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
|
||||
if ! res=$($plugin <"$netconf"); then
|
||||
error "$name" "$res"
|
||||
elif [[ ${DEBUG} -gt 0 ]]; then
|
||||
echo "${res}" | jq -r .
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${name} : error executing $CNI_COMMAND: $errmsg"
|
||||
exit 1
|
||||
elif [[ ${DEBUG} -gt 0 ]]; then
|
||||
echo ${res} | jq -r .
|
||||
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.14-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}"
|
16
test.sh
16
test.sh
|
@ -20,22 +20,6 @@ else
|
|||
go test ${PKGS}
|
||||
fi
|
||||
|
||||
GO_FILES=$(find . -name '*.go' -type f -print)
|
||||
|
||||
echo "Checking gofmt..."
|
||||
fmtRes=$(gofmt -d -e -s ${GO_FILES})
|
||||
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
|
||||
fi
|
||||
|
||||
echo "Checking license header..."
|
||||
licRes=$(
|
||||
for file in $(find . -type f -iname '*.go'); do
|
||||
|
|
Loading…
Reference in New Issue