Compare commits

...

223 Commits

Author SHA1 Message Date
Casey Callendrello ee7c96f201
Merge pull request #1152 from architkulkarni/status-codes
Add error codes for STATUS verb to `types.go` and `SPEC.md`
2025-06-02 17:11:33 +02:00
Casey Callendrello d0bdb01ee9
Merge pull request #1153 from xmulligan/patch-1
docs: remove archived 3rd party plugins
2025-05-16 10:30:26 +02:00
Lionel Jouin 8338cebec2
Merge pull request #1161 from bleggett/bleggett/update-email
Update my email
2025-04-22 23:46:17 +02:00
Benjamin Leggett 44a77d77e9
Update my email
Signed-off-by: Benjamin Leggett <benjamin@edera.io>
2025-04-22 11:20:07 -04:00
Tomofumi Hayashi 277757c333
Merge pull request #1157 from squeed/update-maintainers
update MAINTAINERS
2025-04-15 00:40:04 +09:00
Casey Callendrello 0b7ae2f1b1
Merge pull request #1156 from bleggett/bleggett/clarify-del
SPEC: Clarify some language around `DEL` and `prevResult`
2025-04-14 17:31:05 +02:00
Casey Callendrello 40b0a08c95 update MAINTAINERS
Add some new ones, mark others as emeritus.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2025-04-14 17:26:51 +02:00
Benjamin Leggett 7f701df6ab
Clarify some language around `DEL` and `prevResult`
Signed-off-by: Benjamin Leggett <benjamin@edera.io>
2025-04-09 18:07:38 -04:00
Casey Callendrello a28faab926
Merge pull request #1155 from squeed/remove-deprecated
libcni: remove some deprecation warnings
2025-04-07 17:37:53 +02:00
Casey Callendrello 3f7369aeb9 libcni: remove some deprecation warnings
We were a bit over-eager with the deprecations; these types and aliases
are doing no harm :-).

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2025-04-07 17:34:18 +02:00
Mike Zappa 097592d34b
Merge pull request #1154 from LionelJouin/maintainers
Update MAINTAINERS
2025-03-31 08:15:00 -06:00
Lionel Jouin d2f3f46deb Update MAINTAINERS
Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2025-03-27 15:07:21 +01:00
Bill Mulligan 3578abe586 remove archived 3rd party plugins
Signed-off-by: Bill Mulligan <billmulligan516@gmail.com>
2025-03-16 03:48:34 +01:00
Archit Kulkarni 5f84615a5d
Add error codes for STATUS verb to `types.go` and `SPEC.md`
Signed-off-by: Archit Kulkarni <architkulkarni@google.com>
2025-01-16 00:33:03 +00:00
Casey Callendrello 3b4dfc5dff
Merge pull request #1119 from containernetworking/dependabot/github_actions/github/codeql-action-3.26.7
build(deps): bump github/codeql-action from 3.26.4 to 3.26.7
2024-09-17 16:14:21 +02:00
dependabot[bot] a845cc88d2
build(deps): bump github/codeql-action from 3.26.4 to 3.26.7
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.4 to 3.26.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f0f3afee80...8214744c54)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 00:12:22 +00:00
Casey Callendrello 4c9ae43c0e
Merge pull request #1109 from containernetworking/dependabot/go_modules/golang-53495c1738
build(deps): bump the golang group across 1 directory with 2 updates
2024-08-29 10:01:03 +02:00
Casey Callendrello be3f5a9b2d
Merge pull request #1108 from mmorel-35/scorecard
Setup scorecard workflow
2024-08-29 10:00:34 +02:00
Matthieu MOREL f4f2dc7430 Setup scorecard workflow
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2024-08-28 15:01:51 +00:00
Casey Callendrello 81ed2d06d6
Merge pull request #1110 from danwinship/cnitool-binary
Remove accidentally-committed cnitool binary
2024-08-28 15:30:50 +02:00
Dan Winship 00f4094a12 Remove accidentally-committed cnitool binary
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-08-28 09:04:46 -04:00
dependabot[bot] f0289faa47
build(deps): bump the golang group across 1 directory with 2 updates
Bumps the golang group with 1 update in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.19.0 to 2.20.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.19.0...v2.20.1)

Updates `github.com/onsi/gomega` from 1.33.1 to 1.34.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.1...v1.34.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:39:46 +00:00
Casey Callendrello 81d15e90c3
Merge pull request #1052 from bleggett/bleggett/multi-dir-pluginconfig
RFC - Support safe subdirectory-based plugin conf loading
2024-07-22 11:14:15 -04:00
Casey Callendrello 309b6bbc17
Merge pull request #1103 from squeed/fix-gc-key-name
SPEC, libcni: harmonize GC valid-attachment key
2024-07-22 11:10:34 -04:00
Casey Callendrello 692efbd2a7 libcni: set both GC valid attachment keys
I made an (embarassing) error where SPEC.md and libcni disagreed on the
key for valid attachments for a GC operation. The spec has been
updated, but libcni should set both keys as a transition mechanism.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-07-22 10:57:12 -04:00
Casey Callendrello 6a68851f26 SPEC: use correct GC field name.
The SPEC and libcni disagreed on the name to use for still-valid
attachments. Change the spec to be aligned with libcni.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-07-01 17:28:26 +02:00
Mike Zappa d5c71ad528
Merge pull request #1100 from squeed/parse-semver 2024-06-24 13:59:30 -06:00
Casey Callendrello 587837eeda libcni: remove use of Masterminds/semver
We didn't really need it, and it's yet another dep to update.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-24 18:32:57 +02:00
Benjamin Leggett 425425837e
libcni: Support subdirectory-based plugin loading (#928)
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 12:22:05 -04:00
Benjamin Leggett c0cc785dbc
Use type aliases to hint deprecation for old API types (#928)
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 11:45:13 -04:00
Benjamin Leggett 9f73d4da82
SPEC: #928 support non-inlined plugin loading
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 11:19:33 -04:00
Tomofumi Hayashi e82d9969cf
Merge pull request #1098 from squeed/gc-shared-warning
SPEC: add warning about preserving shared resources for GC
2024-06-18 00:15:44 +09:00
Casey Callendrello 2c926b5f17 SPEC: add warning about preserving shared resources for GC
For plugins that may share resources or resource pools across networks,
we should make it explicitly clear that GC must only clean up resources
known to be owned by the calling network.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-10 18:06:29 +02:00
Casey Callendrello ed014fe9dc
Merge pull request #1089 from Icarus9913/chore/darwin-ns
supplement ns building files for darwin OS
2024-06-10 17:23:41 +02:00
Casey Callendrello db70b6d343
Merge pull request #1094 from containernetworking/dependabot/go_modules/golang-a90ed34d00
build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.3 to 2.19.0 in the golang group
2024-06-10 17:23:26 +02:00
Casey Callendrello fac588ba52
Merge pull request #1093 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.20
build(deps): bump alpine from 3.19 to 3.20 in /.github/actions/retest-action
2024-06-10 17:23:04 +02:00
Casey Callendrello 6e57c2ee5d
Merge pull request #1097 from s1061123/fix/1096
Fix faulty json.Marshal behavior for embeds types.NetConf
2024-06-10 17:20:05 +02:00
Tomofumi Hayashi 499596af1b
Merge branch 'main' into fix/1096 2024-06-11 00:06:55 +09:00
Casey Callendrello 73debca5ce
Merge pull request #1090 from squeed/gc-improvements
Spec, libcni: add disableGC flag
2024-06-10 17:06:14 +02:00
Tomofumi Hayashi 9d422db72c Fix faulty json.Marshal behavior for embeds types.NetConf
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-06-04 03:31:55 +09:00
Casey Callendrello c28331408d libcni: implement disableGC
This allows administrators to disable GC behavior.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-03 16:58:20 +02:00
Casey Callendrello 1125b83816 Spec: add disableGC flag
This flag allows administrators to disable garbage collection for
exceptional circumstances.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-03 16:51:40 +02:00
Casey Callendrello 529bccb346 GetCachedAttachments should ignore missing directories
If there's no cache dir, there are no cached attachments to return. No
sense in propagating that error.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-03 16:51:40 +02:00
dependabot[bot] 267db7092b
build(deps): bump github.com/onsi/ginkgo/v2 in the golang group
Bumps the golang group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.3 to 2.19.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.3...v2.19.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 00:57:15 +00:00
Casey Callendrello cebd7dff3a
Merge pull request #1095 from maiqueb/always-attempt-to-cleanup-cache-on-list-delete
libcni: always delete the cache on conflist for CNI DEL
2024-05-28 10:26:17 +02:00
Miguel Duarte Barroso 6ef571535d libcni: always delete the cache on conflist for CNI DEL
This aligns the call with `DelNetwork`, and allows CRIO tests to bump -
[0].

[0] - https://github.com/cri-o/cri-o/pull/8207#issuecomment-2129937741

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2024-05-27 17:52:09 +02:00
dependabot[bot] 02ffd013fe
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.19 to 3.20.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 01:00:56 +00:00
Icarus Wu 1e7858f987
supplement ns building files for darwin os
Signed-off-by: Icarus Wu <icaruswu66@qq.com>
2024-05-13 22:43:34 +08:00
Casey Callendrello c04330ee9e
Merge pull request #1063 from austinvazquez/update-golangci-lint-action-package
Update golangci-lint to v1.57.1
2024-05-13 16:41:31 +02:00
Austin Vazquez 52babb0dd3 Update golangci-lint to v1.57.1
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-05-13 16:40:38 +02:00
Casey Callendrello 9f32fa36f7
Merge pull request #1087 from containernetworking/dependabot/github_actions/golangci/golangci-lint-action-6
build(deps): bump golangci/golangci-lint-action from 4 to 6
2024-05-13 16:37:27 +02:00
dependabot[bot] f5e23f52c0 build(deps): bump golangci/golangci-lint-action from 4 to 6
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 16:37:19 +02:00
Casey Callendrello 85241009c7
Merge pull request #1088 from containernetworking/dependabot/go_modules/golang-4bdc54f992
build(deps): bump the golang group with 2 updates
2024-05-13 16:36:35 +02:00
dependabot[bot] e34763a7ec
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.17.2 to 2.17.3
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.17.3)

Updates `github.com/onsi/gomega` from 1.33.0 to 1.33.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.0...v1.33.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 01:01:31 +00:00
Casey Callendrello f85b8fe31f
Merge pull request #1084 from containernetworking/dependabot/go_modules/golang-7f8ea5be5c
build(deps): bump the golang group across 1 directory with 2 updates
2024-05-06 16:48:11 +02:00
dependabot[bot] 3d7be1bae9
build(deps): bump the golang group across 1 directory with 2 updates
Bumps the golang group with 1 update in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.1 to 2.17.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.17.2)

Updates `github.com/onsi/gomega` from 1.32.0 to 1.33.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 00:39:11 +00:00
Casey Callendrello df52e9460d
Merge pull request #1082 from squeed/v1.2-release
spec: remove "-dev" tag
2024-04-15 17:47:08 +02:00
Casey Callendrello 047255eeed spec: remove "-dev" tag
This is ready for release :-)

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-04-15 17:39:18 +02:00
Casey Callendrello 0137b32398
Merge pull request #1054 from MikeZappa87/feature/addversiontocni
Add Version to CNI interface
2024-04-08 17:16:56 +02:00
Michael Zappa dd65e2d527 add version to cni interface
Signed-off-by: Michael Zappa <michael.zappa@gmail.com>
2024-04-08 17:09:10 +02:00
Casey Callendrello bbae2202a6
Merge pull request #1080 from s1061123/cnitool-1.1.0-support
cnitool CNI 1.1.0 support
2024-04-05 14:00:25 +02:00
Tomofumi Hayashi 0036b628e1 cnitool CNI 1.1.0 support
This change make cnitool CNI v1.1.0 support. This also addresses
to fix error crash in case of GC with empty attachments.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-04-03 23:49:46 +09:00
Casey Callendrello 83287db830
Merge pull request #1078 from henry118/multierror
use errors package in golang v20 to join multiple errors
2024-03-27 16:40:23 +01:00
Henry Wang a67dabb3d0 use errors package in golang v20 to join multiple errors
Signed-off-by: Henry Wang <henwang@amazon.com>
2024-03-26 16:57:40 +00:00
Casey Callendrello 951d8d0e05
Merge pull request #1077 from containernetworking/dependabot/go_modules/golang-74105ccef4
build(deps): bump the golang group with 2 updates
2024-03-25 15:35:32 +01:00
dependabot[bot] f846cb72b0
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.13.2 to 2.17.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.2...v2.17.1)

Updates `github.com/onsi/gomega` from 1.30.0 to 1.32.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.30.0...v1.32.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 14:28:03 +00:00
Casey Callendrello e4b261e1a2
Merge pull request #1073 from henry118/updatego
Update golang to active supported version
2024-03-25 15:27:11 +01:00
Henry Wang 829ae61c58 Update golang to active supported version
Signed-off-by: Henry Wang <henwang@amazon.com>
2024-03-19 20:24:14 +00:00
Tomofumi Hayashi 86c37cb0a2
Merge pull request #1071 from squeed/minor-api-fixes
Minor CNI v1.1 api fixes
2024-03-19 11:36:33 +01:00
Casey Callendrello 56f11c6c0c types: add ValidAttachments to "base" NetConf struct
Since every plugin needs to deserialize this, it should be in the
standard network configuration struct.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-03-18 12:22:01 +01:00
Casey Callendrello 8eb1091d78 pkg/invoke: add DelegateStatus, DelegateGC methods
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-03-18 12:22:01 +01:00
Casey Callendrello a448e71e98
Merge pull request #961 from squeed/cleanup-releasing
Docs: remove obsolete information about building release artifacts
2024-03-17 21:37:38 +01:00
Casey Callendrello 2e9688f3b4 Docs: remove obsolete information about building release artifacts
Now that plugins live in a different repository, we don't need these old
files. We don't actuall build any release binaries.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-03-17 21:32:20 +01:00
Casey Callendrello 1c8da137b2
Merge pull request #1060 from SchSeba/add_mtu
Add MTU to CNI result
2024-03-15 16:11:10 +01:00
Sebastian Sch c45adb66fe Add MTU to CNI result
This commit allow CNIs to expose the MTU of interface

Signed-off-by: Sebastian Sch <sebassch@gmail.com>
2024-03-14 14:19:38 +02:00
Casey Callendrello 563cf56c27
Merge pull request #1072 from henry118/invalidcache
tolerate invalid cni caches for deletion
2024-03-12 13:27:38 +01:00
Henry Wang 2d04079cfe tolerate invalid cni caches for deletion
Signed-off-by: Henry Wang <henwang@amazon.com>
2024-03-07 23:12:45 +00:00
Casey Callendrello 845a73789a
Merge pull request #1068 from LionelJouin/scope
Add Scope property for routes
2024-03-05 10:53:32 +01:00
Lionel Jouin 00576a2e55 Scope property as int pointer
Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-03-04 18:34:55 +01:00
Lionel Jouin a4d4a0d1ff Add Scope property for routes
Fixes: #598
Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-03-04 18:32:23 +01:00
Casey Callendrello 06d9dec402
Merge pull request #1062 from LionelJouin/tableid
Add table ID property for routes
2024-03-04 17:53:01 +01:00
Tomofumi Hayashi d9f3a7bb2b
Merge branch 'main' into tableid 2024-03-05 01:45:57 +09:00
Casey Callendrello c822f1331b
Merge pull request #1069 from MikeZappa87/spec/socketpci
Add SocketPath/PciID to Interface struct
2024-03-04 17:41:10 +01:00
Michael Zappa 163db33480 Add socket path and pciid to CNI interface result
Signed-off-by: Michael Zappa <michaelzappa@microsoft.com>
2024-03-04 09:23:25 -07:00
Lionel Jouin 94fc2b1545 Table ID property as int pointer
Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-02-19 18:58:35 +01:00
Lionel Jouin 3c4079b3db Add table ID property for routes
Fixes #1061

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-02-02 15:49:20 +01:00
Casey Callendrello b62753aa2b
Merge pull request #1039 from s1061123/json-remove-dns
Add Marshal function in Result/NetConf to omit empty value
2024-01-08 17:47:25 +01:00
Casey Callendrello 1557aeb57c
Merge pull request #1047 from containernetworking/dependabot/github_actions/actions/setup-go-5
build(deps): bump actions/setup-go from 4 to 5
2024-01-08 17:00:11 +01:00
Casey Callendrello dc24f70105
Merge pull request #1048 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.19
build(deps): bump alpine from 3.18 to 3.19 in /.github/actions/retest-action
2024-01-08 16:59:21 +01:00
Casey Callendrello 8ddb290852
Merge pull request #1049 from containernetworking/dependabot/go_modules/plugins/debug/github.com/containernetworking/plugins-1.4.0
build(deps): bump github.com/containernetworking/plugins from 1.2.0 to 1.4.0 in /plugins/debug
2024-01-08 16:58:55 +01:00
Tomofumi Hayashi d1276f1ed4 Add Marshal function in Result/NetConf to omit empty value
This change fix to avoid empty DNS field in NetConf/Result
if DNS is empty.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-12-19 01:02:21 +09:00
Casey Callendrello 66c292a7c5
Merge pull request #1041 from alopintsev/add-route-attributes
Add route attributes - MTU, AdvMSS, Priority
2023-12-18 16:58:10 +01:00
Casey Callendrello 2317778d4d
Merge pull request #1010 from squeed/cni-versions
spec, libcni: add cniVersions field in CNI configurations
2023-12-11 18:00:26 +01:00
Casey Callendrello a6a989119a libcni: implement version negotiation
This implements the `cniVersions` field in the network configuration
list. It allows for CNI plugins to specify that they support multiple
versions, and the runtime may select the highest version it supports.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-12-11 17:48:56 +01:00
Casey Callendrello 1362169a1f go get github.com/Masterminds/semver/v3
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-12-11 17:39:41 +01:00
Casey Callendrello f51f8eafe2 SPEC: add version negotiation
This adds a new top-level configuration field, `cniVersions`, where
network configurations can indicate support for more than one version.

This is to enable easy upgrading of CNI versions in plugins without
lock-step updates to the runtime.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-12-11 17:39:41 +01:00
dependabot[bot] f8d7b81f43
build(deps): bump github.com/containernetworking/plugins
Bumps [github.com/containernetworking/plugins](https://github.com/containernetworking/plugins) from 1.2.0 to 1.4.0.
- [Release notes](https://github.com/containernetworking/plugins/releases)
- [Commits](https://github.com/containernetworking/plugins/compare/v1.2.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/containernetworking/plugins
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 01:10:55 +00:00
dependabot[bot] 6bac4ee028
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.18 to 3.19.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 00:47:34 +00:00
dependabot[bot] 91db252b9d
build(deps): bump actions/setup-go from 4 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 00:45:33 +00:00
Casey Callendrello d2bbac8e63
Merge pull request #1045 from squeed/ref-cleanup
cleanup: update references, make links relative
2023-12-05 17:34:52 +01:00
Casey Callendrello 0e4c0e225b cleanup: update references, make links relative
Go through and update some incorrect / obsolete links and references.

No code changes.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-12-05 12:50:31 +01:00
Casey Callendrello 2cec989c82
Merge pull request #1030 from squeed/status-impl
libcni, skel: implement STATUS
2023-12-04 16:58:37 +01:00
Casey Callendrello 4caff13753 libcni, skel: implement STATUS
This adds an implementation of STATUS to libcni and skel, along with
tests.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-12-04 16:49:48 +01:00
Casey Callendrello 9c19f13738
Merge pull request #1003 from squeed/spec-status
spec: STATUS wording
2023-12-04 16:48:40 +01:00
Casey Callendrello d654dde532
Merge pull request #1044 from containernetworking/dependabot/go_modules/golang-15a4b9dd4e
build(deps): bump the golang group with 1 update
2023-12-04 12:02:07 +01:00
dependabot[bot] b9a9324609
build(deps): bump the golang group with 1 update
Bumps the golang group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).

- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 00:04:37 +00:00
Alexander Lopintsev 4a0bccb249 describe mtu, advmss, priority attributes of route object in specification
Signed-off-by: Alexander Lopintsev <alex@fort.plus>
2023-11-28 18:25:07 +03:00
Alexander Lopintsev efffbeeef4 Add route attributes - MTU, AdvMSS, Priority
Signed-off-by: Alexander Lopintsev <alex@fort.plus>
2023-11-21 23:45:49 +03:00
Dan Williams 93878e6530
Merge pull request #1038 from s1061123/fix-netnsoverride
Fix if condition for netns override
2023-11-20 10:08:23 -06:00
Tomofumi Hayashi 26cfdbe95b
Merge branch 'main' into fix-netnsoverride 2023-11-20 22:58:42 +09:00
Casey Callendrello cf9ca9e003
Merge pull request #1040 from containernetworking/dependabot/go_modules/golang-027c04c118
build(deps): bump the golang group with 2 updates
2023-11-15 10:08:39 +01:00
Tomofumi Hayashi baaee10d93
Merge branch 'main' into fix-netnsoverride 2023-11-14 01:04:26 +09:00
dependabot[bot] cd1b855eae
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.13.0 to 2.13.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.0...v2.13.1)

Updates `github.com/onsi/gomega` from 1.29.0 to 1.30.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 00:29:26 +00:00
Casey Callendrello c18965d032
Merge pull request #1019 from Yejining/fix/spec-typo
SPEC: fix misspelled word
2023-11-06 17:34:19 +01:00
Casey Callendrello 911855c357
Merge pull request #1031 from mmorel-35/replace
use replace directive for github.com/containernetworking/cni
2023-11-06 17:33:20 +01:00
Casey Callendrello 8640c3ff16
Merge pull request #990 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.18
build(deps): bump alpine from 3.17 to 3.18 in /.github/actions/retest-action
2023-11-06 17:33:10 +01:00
Casey Callendrello 58eb836866
Merge pull request #1015 from jayanthvn/main
Add AWS VPC CNI for 3P list
2023-11-06 17:32:52 +01:00
Tomofumi Hayashi 6ed65ac99d Fix if condition for netns override
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-11-06 17:32:01 +01:00
Matthieu MOREL f67e207467 use replace directive for github.com/containernetworking/cni
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-11-06 17:27:47 +01:00
Casey Callendrello 17331add54
Merge pull request #1036 from containernetworking/dependabot/go_modules/golang-a9a3abe015
build(deps): bump the golang group with 1 update
2023-11-06 17:09:18 +01:00
dependabot[bot] cfdc39b16b
build(deps): bump the golang group with 1 update
Bumps the golang group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).

- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.28.0...v1.29.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 00:48:29 +00:00
Casey Callendrello 509c4908cf spec: STATUS wording
This adds a new verb, STATUS, which indicates whether or not a plugin is
ready to accept ADD requests. Runtimes may choose to use the STATUS
mechanism to determine if a network is ready.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-23 11:40:38 +02:00
Casey Callendrello 5968d61a1e
Merge pull request #1020 from containernetworking/dependabot/github_actions/actions/checkout-4
build(deps): bump actions/checkout from 3 to 4
2023-10-23 10:56:01 +02:00
Casey Callendrello 94fd332ecd
Merge pull request #1029 from containernetworking/dependabot/go_modules/golang-91599c5cf7
build(deps): bump the golang group with 2 updates
2023-10-23 10:55:03 +02:00
dependabot[bot] 0c68fe12dc
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.11.0 to 2.13.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.11.0...v2.13.0)

Updates `github.com/onsi/gomega` from 1.27.10 to 1.28.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.10...v1.28.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 00:58:18 +00:00
Casey Callendrello a6eaa01146
Merge pull request #987 from mmorel-35/patch-1
Define Makefile with golangci-lint
2023-10-18 16:45:32 +02:00
Casey Callendrello 58553d6cdb
Merge pull request #994 from mmorel-35/debug
Upgrade debug module go version to 1.18
2023-10-18 16:37:57 +02:00
Casey Callendrello c9fdf812d2
Merge branch 'main' into patch-1 2023-10-18 16:37:14 +02:00
Casey Callendrello 82b44c5282
Merge pull request #988 from mmorel-35/linter-gocritic
enable gocritic linter
2023-10-18 16:35:34 +02:00
Matthieu MOREL 29ba516d90 Upgrade debug module go version to 1.18
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-10-18 07:44:33 +02:00
Matthieu MOREL b7d75ff4da Define Makefile with golangci-lint
As govet and gofmt are now handled by golangci-lint, there is no need to apply them here.

Install golangci-lint

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-10-18 07:44:03 +02:00
Matthieu MOREL 4683716a59 enable gocritic linter
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-10-18 07:43:38 +02:00
Casey Callendrello 2cfe79728d
Merge pull request #1028 from squeed/dependabot-batch
dependabot: batch updates
2023-10-17 13:02:29 +02:00
Casey Callendrello 91629d5751 dependabot: batch updates
This combines go pkg updates in to a weekly branch, saving bother and
rebase time.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-17 12:38:53 +02:00
Casey Callendrello b15c73737e
Merge pull request #1022 from henry118/gc
implement GC keyword for CNI spec 1.1
2023-10-16 17:27:07 +02:00
Henry Wang e86c9ca58f resume and complete gc impl
Signed-off-by: Henry Wang <henwang@amazon.com>
2023-10-13 20:46:19 +00:00
Casey Callendrello 02155e0c8c skel: add gc support
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-13 20:46:19 +00:00
Casey Callendrello d9f1e675b7 wip GC implementation -- spec
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-13 20:46:19 +00:00
Casey Callendrello 8fdefe9412 noop: support command log file
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-13 20:46:19 +00:00
Casey Callendrello 743a44d404
Merge pull request #1024 from s1061123/add-maintainer
Add Tomofumi Hayashi as a CNI maintainer
2023-10-02 17:28:04 +02:00
Tomofumi Hayashi 004b09b6a6 Add Tomofumi Hayashi as a CNI maintainer
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-09-26 00:14:20 +09:00
Jayanth Varavani 902534de61
Merge branch 'main' into main 2023-09-14 22:28:17 -07:00
dependabot[bot] 11077bcc03
build(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 00:42:05 +00:00
Yejin Kim 0021b93858 SPEC: fix misspelled word
Signed-off-by: Yejin Kim <kimyejin.kr@gmail.com>
2023-09-08 16:22:10 +00:00
Dan Williams f6506e215f
Merge pull request #981 from squeed/spec-gc
SPEC: add wording for GC
2023-08-07 10:56:19 -05:00
Casey Callendrello 3072cfe946 SPEC: add wording for GC verb
GC, or garbage collection, is intended as a way for runtimes to tell
plugins about valid attachments, enabling them to delete any leaked or
stale resources.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-08-07 17:49:53 +02:00
Dan Williams 3bbe370e68
Merge pull request #1013 from haiyux/fix/k8sUrl
fix kubernetes document url in readme
2023-08-07 10:37:19 -05:00
Dan Williams 44a6eef2b2
Merge pull request #1011 from squeed/fix-version-registration
pkg/types: explicitly import all versions
2023-08-07 10:35:28 -05:00
Jayanth Varavani 837171b012 Add AWS VPC CNI for 3P list
Signed-off-by: Jayanth Varavani <1111446+jayanthvn@users.noreply.github.com>
2023-08-03 17:35:00 +00:00
haiyux 215259b17d fix kubernetes document url in readme
Signed-off-by: haiyux <haiyux@foxmail.com>
2023-07-28 15:55:12 +08:00
Casey Callendrello 4a096f6be2 pkg/types: explicitly import all versions
There was a bug in which some versions didn't register their creation
function, thus leaving some types un-decodeable. Fix that by explicitly
importing all known versions.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-07-20 10:50:22 +02:00
Casey Callendrello ca96f4ca96
Merge pull request #967 from squeed/setup-1.1
Prepare for spec version v1.1
2023-07-14 00:28:22 +02:00
Casey Callendrello a899051e44 libcni: add version v1.1.0
This adds support for CNI spec v1.1.0. Since there are no result type
changes expected, this means that we can use the existing v1.0.0 types.
That takes a bit of plumbing to decouple the type from the version.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-07-13 22:57:00 +02:00
Casey Callendrello c768dcb129 SPEC: bump version to v1.1.0-dev
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-07-13 22:57:00 +02:00
Dan Williams 622537633d
Merge pull request #1001 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.11.0
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.11.0
2023-07-10 10:23:54 -05:00
dependabot[bot] e34f9d2bff
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.11.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.7 to 2.11.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.7...v2.11.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 01:26:35 +00:00
Dan Williams e255525e4d
Merge pull request #995 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.9.7
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.4 to 2.9.7
2023-06-05 10:30:24 -05:00
dependabot[bot] 2161bf8c24
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.4 to 2.9.7
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.4 to 2.9.7.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.4...v2.9.7)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 01:23:59 +00:00
dependabot[bot] a8fdbf9c40
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.17 to 3.18.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 01:22:35 +00:00
Dan Williams dc0779e8ce
Merge pull request #989 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.9.4
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.4
2023-05-08 10:55:03 -05:00
dependabot[bot] b6608f8c87
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.4
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.2 to 2.9.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.2...v2.9.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 01:08:09 +00:00
dependabot[bot] 15612fccaa build(deps): bump github.com/onsi/ginkgo/v2 from 2.8.0 to 2.9.2
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.8.0 to 2.9.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.8.0...v2.9.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-21 13:22:18 +02:00
dependabot[bot] 10ec024835 build(deps): bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-20 14:22:01 +02:00
Casey Callendrello cfb0b54407
Merge pull request #954 from containernetworking/dependabot/go_modules/github.com/vishvananda/netns-0.0.4
build(deps): bump github.com/vishvananda/netns from 0.0.2 to 0.0.4
2023-04-20 14:21:23 +02:00
Casey Callendrello c9cfcaa2c7
Merge pull request #950 from containernetworking/dependabot/go_modules/plugins/debug/github.com/containernetworking/plugins-1.2.0
build(deps): bump github.com/containernetworking/plugins from 1.1.1 to 1.2.0 in /plugins/debug
2023-04-20 14:20:51 +02:00
dependabot[bot] db718fc6d5
build(deps): bump github.com/containernetworking/plugins
Bumps [github.com/containernetworking/plugins](https://github.com/containernetworking/plugins) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/containernetworking/plugins/releases)
- [Commits](https://github.com/containernetworking/plugins/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/containernetworking/plugins
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-20 11:30:55 +00:00
Casey Callendrello 4093160909
Merge pull request #976 from mmorel-35/golangci-lint
enable more linters
2023-04-20 13:25:32 +02:00
Matthieu MOREL 9302e5ff67 enable more linters
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-04-20 13:08:34 +02:00
Casey Callendrello 9a71f523e2
Merge pull request #984 from weizhoublue/pr/welan/spiderpool
3rd party plugins - spiderpool
2023-04-20 13:07:29 +02:00
weizhou.lan@daocloud.io 05f80e4fd9 3rd party plugins - spiderpool
Signed-off-by: weizhou.lan@daocloud.io <weizhou.lan@daocloud.io>
2023-03-29 21:35:21 +08:00
Mike Zappa f8aa587bba
Merge pull request #963 from henry118/cachelist
Porting over getCachedNetworkInfo routine from cri-o
2023-03-14 13:23:13 -06:00
Henry Wang aef15f60b3 Porting over getCachedNetworkInfo routine from cri-o
Signed-off-by: Henry Wang <henwang@amazon.com>
2023-03-13 16:05:01 +00:00
Casey Callendrello 6626820a52
Merge pull request #973 from squeed/add-meeting
add meeting link to README
2023-03-06 16:33:41 +01:00
Casey Callendrello 435fcb1414 add meeting link to README
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-03-06 16:32:57 +01:00
Casey Callendrello 52b1cb11a5
Merge pull request #958 from mmorel-35/errorlint
ci(lint): enable errorlint linter
2023-02-20 18:05:01 +01:00
Matthieu MOREL d4c7848c2a ci(lint): enable errorlint linter
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-02-14 20:22:23 +01:00
Dan Williams d42556829e
Merge pull request #962 from yanggangtony/clean-notes
fix wrong notes for ValidateInterfaceName()
2023-02-13 10:14:48 -06:00
yanggang 1ffb655895
fix wrong notes for ValidateInterfaceName()
Signed-off-by: yanggang <gang.yang@daocloud.io>
2023-02-10 09:30:07 +08:00
Casey Callendrello cb0d26535b
Merge pull request #936 from aditighag/pr/aditighag/add-cgroup-path-to-cni-args
Extend capabilities with cgroup path
2023-02-09 11:44:20 +01:00
Aditi Ghag 420e5949b0 Extend capabilities with cgroup path
Container runtimes can pass pod cgroup path
when requested by CNI plugins.

Details around the use cases for this field are
documented - https://github.com/kubernetes/kubernetes/issues/113342.

Signed-off-by: Aditi Ghag <aditi@cilium.io>
2023-02-08 17:54:04 +01:00
dependabot[bot] 024c57f43f
build(deps): bump github.com/vishvananda/netns from 0.0.2 to 0.0.4
Bumps [github.com/vishvananda/netns](https://github.com/vishvananda/netns) from 0.0.2 to 0.0.4.
- [Release notes](https://github.com/vishvananda/netns/releases)
- [Commits](https://github.com/vishvananda/netns/compare/v0.0.2...v0.0.4)

---
updated-dependencies:
- dependency-name: github.com/vishvananda/netns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 16:11:09 +00:00
Dan Williams 0821ff55fc
Merge pull request #959 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.8.0
build(deps): bump github.com/onsi/ginkgo/v2 from 2.7.0 to 2.8.0
2023-02-06 10:08:01 -06:00
dependabot[bot] 121798a08d
build(deps): bump github.com/onsi/ginkgo/v2 from 2.7.0 to 2.8.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 01:14:50 +00:00
Casey Callendrello f89a8c6a3e
Merge pull request #953 from mmorel-35/lint
ci(lint): setup lint job
2023-01-30 17:41:22 +01:00
Matthieu MOREL da8672c859 ci(lint): setup lint job
Creates a lint job with a yamllint and a golangci-lint
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-01-27 11:03:37 +01:00
Casey Callendrello c5603949c1
Merge pull request #944 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.7.0
build(deps): bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0
2023-01-16 10:31:55 +01:00
dependabot[bot] 8ac5c8a7f1
build(deps): bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.6.1 to 2.7.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.6.1...v2.7.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 09:21:27 +00:00
Casey Callendrello ed461a9b0e
Merge pull request #945 from containernetworking/dependabot/go_modules/github.com/vishvananda/netns-0.0.2
build(deps): bump github.com/vishvananda/netns from 0.0.1 to 0.0.2
2023-01-16 10:13:26 +01:00
dependabot[bot] 5fa3464d59
build(deps): bump github.com/vishvananda/netns from 0.0.1 to 0.0.2
Bumps [github.com/vishvananda/netns](https://github.com/vishvananda/netns) from 0.0.1 to 0.0.2.
- [Release notes](https://github.com/vishvananda/netns/releases)
- [Commits](https://github.com/vishvananda/netns/compare/v0.0.1...v0.0.2)

---
updated-dependencies:
- dependency-name: github.com/vishvananda/netns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 01:27:27 +00:00
Casey Callendrello 6a43fb5511
Merge pull request #938 from mmorel-35/main
ci(deps): setup dependabot
2023-01-09 18:04:09 +01:00
Matthieu MOREL 45761d9522 update github.com/vishvananda/netns to v0.0.1
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
dependabot[bot] aeb1d8ebf8 build(deps): bump github.com/containernetworking/plugins
Bumps [github.com/containernetworking/plugins](https://github.com/containernetworking/plugins) from 0.9.1 to 1.1.1.
- [Release notes](https://github.com/containernetworking/plugins/releases)
- [Commits](https://github.com/containernetworking/plugins/compare/v0.9.1...v1.1.1)

---
updated-dependencies:
- dependency-name: github.com/containernetworking/plugins
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
dependabot[bot] dd2d40c4f6 build(deps): bump github.com/containernetworking/cni in /plugins/debug
Bumps [github.com/containernetworking/cni](https://github.com/containernetworking/cni) from 1.0.1 to 1.1.2.
- [Release notes](https://github.com/containernetworking/cni/releases)
- [Commits](https://github.com/containernetworking/cni/compare/v1.0.1...v1.1.2)

---
updated-dependencies:
- dependency-name: github.com/containernetworking/cni
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
Matthieu MOREL 97954935c5 Update dependabot.yml
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
dependabot[bot] 0a26996875 build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.10 to 3.17.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
dependabot[bot] cc036171cc build(deps): bump github.com/onsi/gomega from 1.17.0 to 1.24.2
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.17.0 to 1.24.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.17.0...v1.24.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
Matthieu MOREL 55be4ccd0d Create dependabot.yml
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2022-12-26 21:32:38 +00:00
Eng Zer Jun f024754da8 refactor: move from io/ioutil to io and os packages
The io/ioutil package has been deprecated as of Go 1.16 [1]. This commit
replaces the existing io/ioutil functions with their new definitions in
io and os packages.

[1]: https://golang.org/doc/go1.16#ioutil
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2022-12-12 17:44:39 +01:00
Michael Zappa e18f6322ff Update email to gmail
Signed-off-by: Michael Zappa <michael.zappa@gmail.com>
2022-12-12 17:44:01 +01:00
Dan Williams 6e5ac36b42
Merge pull request #935 from austinvazquez/update-github-actions-packages
Upgrade GitHub actions packages to resolve NodeJS 12 warnings
2022-12-05 10:12:12 -06:00
Austin Vazquez 58488a6214 Upgrade GitHub actions packages to resolve NodeJS 12 warnings
Upgrades actions/checkout and actions/setup-go packages to v3 to resolve
CI workflow NodeJS 12 warnings.

Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2022-12-01 21:43:45 +00:00
Dan Williams e629d106b4
Merge pull request #920 from loxi-admin/main
Removed loxilight as it is not supported anymore
2022-11-14 10:06:37 -06:00
loxi-admin 62709e0a59 Removed loxilight as it is not supported anymore
Signed-off-by: loxi-admin <admin@netlox.io>
2022-10-31 16:33:09 +01:00
Dan Williams 76aaefb2cb libcni: handle string-type disableCheck values
CNI spec 0.4x (and possibly earlier) mistakenly specified disableCheck
as a string type, while 1.0 cleaned that up to be a real boolean.

We should be nice and interpret the string value.

Fixes: https://github.com/containernetworking/cni/issues/877

Signed-off-by: Dan Williams <dcbw@redhat.com>
2022-10-31 16:32:33 +01:00
Bruce Ma 04dce8c7d3 testhelpers: use `go mod tidy` to ensure all necessary dependencies before building
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2022-10-31 16:28:20 +01:00
Dan Williams 307ed01652
Merge pull request #914 from squeed/iso
cdc: update email
2022-10-10 10:52:34 -05:00
Casey Callendrello be9139d588 cdc: update email
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-10-10 17:51:55 +02:00
Dan Williams 30fd41546b
Merge pull request #913 from squeed/bump-go
Bump go to v1.19
2022-10-10 10:41:14 -05:00
Casey Callendrello dc22d0462d go.mod: bump to go1.18
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-10-10 17:27:50 +02:00
Casey Callendrello 69967699d7 github: bump go version to v1.19
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-10-10 17:26:56 +02:00
Casey Callendrello 08fb4605fc go fmt
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-10-10 17:26:29 +02:00
s00613818 dbf33e2c59 fix 714-plugin add netns validation reinforcement
Signed-off-by: Poor12 <shentiecheng@huawei.com>
2022-10-10 17:19:31 +02:00
Casey Callendrello 8dba3824fb libcni: add specific type for CHECK not supported
Previously, we returned an arbitrary string error if the given CNI
version didn't support CHECK. This is not useful for downstream
consumption and matching.

So, declare an error variable so that users can use `errors.Is()`
instead of matching on a string.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2022-09-19 17:13:58 +02:00
Dan Williams 40fa4795fb
Merge pull request #908 from ShubhamTatvamasi/patch-1
Updated Calico project link
2022-08-08 10:06:39 -05:00
Shubham Tatvamasi a710a7b4c1 Updated Calico project link
Signed-off-by: Shubham Tatvamasi <shubhamtatvamasi@gmail.com>
2022-08-03 11:14:34 +05:30
Dan Williams 3363d14368
Merge pull request #904 from brandond/fix_null_output
Fix successfully unmarshalled nil raw result
2022-07-27 10:46:15 -05:00
Brad Davidson 1c7c696779 Fix successfully unmarshalled nil raw result
Properly handle "null" as plugin output. This successfully unmarshals to a nil map, which crashes later when assigning confVersion to the map.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2022-07-15 15:24:07 -07:00
Dan Williams 812a8cfa8a
Merge pull request #902 from drivebyer/drivebyer-patch-1
spec: fix format
2022-07-13 10:41:02 -05:00
Yang Wu 58b77bdf71 spec: fix format
Signed-off-by: drivebyer <wuyangmuc@gmail.com>
2022-07-01 21:45:33 +08:00
Michael C. Cambria 3ec19197ef
Merge pull request #896 from dcbw/empty-result-version-regression
invoke: if Result CNIVersion is empty use netconf CNIVersion
2022-05-25 13:12:46 -04:00
Dan Williams 55fe94e885 invoke: if Result CNIVersion is empty use netconf CNIVersion
The CNI Spec requires plugins to return a CNIVersion in the Result
that is the same as the CNIVersion of the incoming CNI config.

Empty CNIVersions are specified to map to 0.1.0 (since the first
CNI spec didn't have versioning) and libcni handles that automatically.

Unfortunately some versions of Weave don't do that and depend on
a libcni <= 0.8 quirk that used the netconf version if the result
version was empty. This is technically a libcni regression, though
the plugin itself is violating the specification.

Thus for backwards compatibility assume that if the Result
CNIVersion is empty, the netconf CNIVersion should be used as
the Result version.

Fixes: https://github.com/containernetworking/cni/issues/895

Signed-off-by: Dan Williams <dcbw@redhat.com>
2022-05-25 10:57:53 -05:00
Dan Williams 940e662699
Merge pull request #894 from fujitatomoya/bugfix-20220503-golint-error-cnitool
cnitool: address golint error
2022-05-11 10:43:46 -05:00
Tomoya Fujita 99eac24911 cnitool: address golint error
exported const EnvCNIPath should have comment (or a comment on this block) or be unexported

Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
2022-05-03 15:23:55 -07:00
82 changed files with 2776 additions and 1007 deletions

View File

@ -1,4 +1,4 @@
FROM alpine:3.10 FROM alpine:3.20
RUN apk add --no-cache curl jq RUN apk add --no-cache curl jq

View File

@ -8,4 +8,4 @@ runs:
using: 'docker' using: 'docker'
image: 'Dockerfile' image: 'Dockerfile'
env: env:
GITHUB_TOKEN: ${{ inputs.token }} GITHUB_TOKEN: ${{ inputs.token }}

27
.github/dependabot.yml vendored Normal file
View File

@ -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"

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v2 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Re-Test Action - name: Re-Test Action
uses: ./.github/actions/retest-action uses: ./.github/actions/retest-action

40
.github/workflows/scorecard.yml vendored Normal file
View File

@ -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

View File

@ -4,19 +4,45 @@ name: test
on: ["push", "pull_request"] on: ["push", "pull_request"]
env: env:
GO_VERSION: "1.16" GO_VERSION: "1.22"
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le" LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
jobs: jobs:
build: lint:
name: Build all linux architectures name: Lint
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v1 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} 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 - name: Build on all supported architectures
run: | run: |
@ -28,27 +54,27 @@ jobs:
test-linux: test-linux:
name: Run tests on Linux amd64 name: Run tests on Linux amd64
needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v1 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install test binaries - name: Install test binaries
env:
GO111MODULE: off
run: | run: |
go get github.com/mattn/goveralls go install github.com/mattn/goveralls@v0.0.12
go get github.com/modocache/gover go install github.com/modocache/gover@latest
- name: test - name: test
run: COVERALLS=1 ./test.sh run: COVERALLS=1 ./test.sh
- name: Send coverage to coveralls - env:
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Send coverage to coveralls
run: | run: |
PATH=$PATH:$(go env GOPATH)/bin PATH=$PATH:$(go env GOPATH)/bin
gover gover
@ -56,12 +82,15 @@ jobs:
test-win: test-win:
name: Build and run tests on Windows name: Build and run tests on Windows
needs: build
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v1 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: test - name: test
run: bash ./test.sh run: bash ./test.sh

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
.idea/ .idea/
bin/
gopath/
*.sw[ponm] *.sw[ponm]
.vagrant .vagrant
release-* release-*
cnitool/cnitool

30
.golangci.yml Normal file
View File

@ -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

10
.yamllint.yaml Normal file
View File

@ -0,0 +1,10 @@
---
extends: default
rules:
document-start: disable
line-length: disable
truthy:
ignore: |
.github/workflows/*.yml
.github/workflows/*.yaml

View File

@ -1,3 +1,3 @@
# Community Code of Conduct # 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).

View File

@ -34,7 +34,7 @@ are very busy and read the mailing lists.
This is a rough outline of how to prepare a contribution: 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 commits of logical units.
- Make sure your commit messages are in the proper format (see below). - 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. - Push your changes to a topic branch in your fork of the repository.

View File

@ -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. Additional conventions can be created by creating PRs which modify this document.
## Dynamic Plugin specific fields (Capabilities / Runtime Configuration) ## 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. > 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. 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 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. 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 ```json
{ {
"name" : "ExamplePlugin", "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 | | 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 | | 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 | | 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
`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 (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. `args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support.

View File

@ -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. version there is supported by your container runtime and CNI plugins.
Configuration files without a version field should be given version 0.2.0. Configuration files without a version field should be given version 0.2.0.
The CNI spec includes example configuration files for The CNI spec includes example configuration files for
[single plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations) [single plugins](SPEC.md#example-configurations)
and for [lists of chained plugins](https://github.com/containernetworking/cni/blob/master/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 Consult the documentation for your runtime and plugins to determine what
CNI spec versions they support. Test any plugin upgrades before deploying to 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 useful. Specifically, your configuration version should be the lowest common
version supported by your plugins. 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 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: 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 - 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. - 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. in the format requested.
Result formats for older CNI spec versions are available in the 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 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`, 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 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 ## 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 When calling a plugin, the runtime must request that the plugin respond in a
particular format by specifying the `cniVersion` field in the 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 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 in the format defined by that CNI spec version, and the runtime must parse
and handle this result. and handle this result.
#### Handle errors due to version incompatibility #### Handle errors due to version incompatibility
Plugins may respond with error indicating that they don't support the requested 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. e.g.
```json ```json
{ {

View File

@ -1,7 +1,14 @@
Bruce Ma <brucema19901024@gmail.com> (@mars1024) Bruce Ma <brucema19901024@gmail.com> (@mars1024)
Casey Callendrello <cdc@redhat.com> (@squeed) Casey Callendrello <cdc@isovalent.com> (@squeed)
Michael Cambria <mcambria@redhat.com> (@mccv1r0)
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) Dan Williams <dcbw@redhat.com> (@dcbw)
Matt Dupre <matt@tigera.io> (@matthewdupre) Matt Dupre <matt@tigera.io> (@matthewdupre)
Michael Cambria <mcambria@redhat.com> (@mccv1r0)
Michael Zappa <Michael.Zappa@Stateless.net> (@MikeZappa87)
Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek) Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek)

1
Makefile Normal file
View File

@ -0,0 +1 @@
include mk/lint.mk

View File

@ -4,6 +4,9 @@
# CNI - the Container Network Interface # CNI - the Container Network Interface
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2446/badge)](https://bestpractices.coreinfrastructure.org/projects/2446)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/containernetworking/cni/badge)](https://securityscorecards.dev/viewer/?uri=github.com/containernetworking/cni)
## What is 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. 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) - [Introduction to CNI](https://youtu.be/YjjrQiJOyME)
- [CNI deep dive](https://youtu.be/zChkx-AB5Xc) - [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? ## 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. 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? ## Who is using CNI?
### Container runtimes ### Container runtimes
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html) - [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/)
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/admin/network-plugins/)
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md) - [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) - [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) - [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) - [OpenSVC - orchestrator for legacy and containerized application stacks](https://docs.opensvc.com/latest/fr/agent.configure.cni.html)
### 3rd party plugins ### 3rd party plugins
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni) - [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin) - [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
- [SR-IOV](https://github.com/hustcat/sriov-cni) - [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium) - [Cilium - eBPF & 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/k8snetworkplumbingwg/multus-cni) - [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) - [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) - [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) - [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) - [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) - [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) - [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,25 +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 - [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) - [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) - [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) - [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) - [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) - [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) - [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) - [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) - [Azure CNI - a CNI plugin that natively extends Azure Virtual Networks to containers](https://github.com/Azure/azure-container-networking)
- [NetLOX Loxilight - Loxilight CNI is based on TC eBPF. It works either as a pure eBPF mode or in a hybrid-mode with multi-vendor DPU support when DPU units are available](https://www.netlox.io/product)
- [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) - [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). 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? ## How do I use CNI?
### Requirements ### Requirements

View File

@ -1,40 +1,19 @@
# Release process # 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 ## 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` - 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. - 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 - Create a draft of the release note
- Discuss the level of testing that's needed and create a test plan if sensible - 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. - 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.
## Publishing the 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. Push the tag to git `git push origin <TAG>`
1. Create a release on Github, using the tag which was just pushed. 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. Add the release note to the release.
1. Announce the release on at least the CNI mailing, IRC and Slack. 1. Announce the release on at least the CNI mailing, IRC and Slack.

219
SPEC.md
View File

@ -8,6 +8,7 @@
- [Configuration format](#configuration-format) - [Configuration format](#configuration-format)
- [Plugin configuration objects:](#plugin-configuration-objects) - [Plugin configuration objects:](#plugin-configuration-objects)
- [Example configuration](#example-configuration) - [Example configuration](#example-configuration)
- [Version considerations](#version-considerations)
- [Section 2: Execution Protocol](#section-2-execution-protocol) - [Section 2: Execution Protocol](#section-2-execution-protocol)
- [Overview](#overview-1) - [Overview](#overview-1)
- [Parameters](#parameters) - [Parameters](#parameters)
@ -16,21 +17,25 @@
- [`ADD`: Add container to network, or apply modifications](#add-add-container-to-network-or-apply-modifications) - [`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) - [`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) - [`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) - [`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) - [Section 3: Execution of Network Configurations](#section-3-execution-of-network-configurations)
- [Lifecycle & Ordering](#lifecycle--ordering) - [Lifecycle \& Ordering](#lifecycle--ordering)
- [Attachment Parameters](#attachment-parameters) - [Attachment Parameters](#attachment-parameters)
- [Adding an attachment](#adding-an-attachment) - [Adding an attachment](#adding-an-attachment)
- [Deleting an attachment](#deleting-an-attachment) - [Deleting an attachment](#deleting-an-attachment)
- [Checking an attachment](#checking-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) - [Deriving `runtimeConfig`](#deriving-runtimeconfig)
- [Section 4: Plugin Delegation](#section-4-plugin-delegation) - [Section 4: Plugin Delegation](#section-4-plugin-delegation)
- [Delegated Plugin protocol](#delegated-plugin-protocol) - [Delegated Plugin protocol](#delegated-plugin-protocol)
- [Delegated plugin execution procedure](#delegated-plugin-execution-procedure) - [Delegated plugin execution procedure](#delegated-plugin-execution-procedure)
- [Section 5: Result Types](#section-5-result-types) - [Section 5: Result Types](#section-5-result-types)
- [Success](#success) - [ADD Success](#add-success)
- [Delegated plugins (IPAM)](#delegated-plugins-ipam) - [Delegated plugins (IPAM)](#delegated-plugins-ipam)
- [VERSION Success](#version-success)
- [Error](#error) - [Error](#error)
- [Version](#version-1) - [Version](#version-1)
- [Appendix: Examples](#appendix-examples) - [Appendix: Examples](#appendix-examples)
@ -40,7 +45,7 @@
## Version ## 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)). 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: 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" - `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"
- `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 (-). - `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. - `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:
Plugin configuration objects may contain additional fields than the ones defined here. 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.
The runtime MUST pass through these fields, unchanged, to the plugin, as defined in section 3.
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:** **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 \\). - `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. 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 #### Example configuration
The following is an example JSON representation of a network configuration `dbnet` with three plugin configurations (`bridge`, `tuning`, and `portmap`).
```jsonc ```jsonc
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
"name": "dbnet", "name": "dbnet",
"plugins": [ "plugins": [
{ {
@ -150,7 +161,7 @@ Plugins may define additional fields that they accept and may generate an error
// plugin specific parameters // plugin specific parameters
"bridge": "cni0", "bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"], "keyA": ["some more", "plugin specific", "configuration"],
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
// ipam specific // 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 ## Section 2: Execution Protocol
### Overview ### 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. 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_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_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. - `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 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 #### `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 - delete the interface defined by `CNI_IFNAME` inside the container at `CNI_NETNS`, or
- undo any modifications applied in the plugin's `ADD` functionality - 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. 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. 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 #### `VERSION`: probe plugin version support
The plugin should output via standard-out a json-serialized version result object (see below). 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: Required environment parameters:
- `CNI_COMMAND` - `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 ## Section 3: Execution of Network Configurations
@ -332,6 +413,7 @@ 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 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. - 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). - 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. - 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_. - _delete_ may be followed by additional _deletes_.
@ -389,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. 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 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: The following fields are always to be inserted into the request configuration by the runtime:
- `cniVersion`: taken from the `cniVersion` field of the network configuration - `cniVersion`: the protocol version selected by the runtime - the string "1.1.0"
- `name`: taken from the `name` field of the network configuration - `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` #### Deriving `runtimeConfig`
@ -459,29 +558,26 @@ When a plugin executes a delegated plugin, it should:
- Execute that plugin with the same environment and configuration that it received. - 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. - 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. If, on `ADD`, a delegated plugin fails, the "upper" plugin should execute again with `DEL` before returning failure.
## Section 5: Result Types ## 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_) ### ADD 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`.
Plugins must output a JSON object with the following keys upon a successful `ADD` operation: 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: - `interfaces`: An array of all interfaces created by the attachment, including any host-level interfaces:
- `name`: The name of the interface. - `name` (string): The name of the interface.
- `mac`: The hardware address of the interface (if applicable). - `mac` (string): 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`. - `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. - `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"). - `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. - `gateway` (string): the default gateway for this subnet, if one exists.
@ -489,23 +585,45 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
- `routes`: Routes created by this attachment: - `routes`: Routes created by this attachment:
- `dst`: The destination of the route, in CIDR notation - `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. - `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 - `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. - `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. - `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. - `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. - `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 (IPAM)
Delegated plugins may omit irrelevant sections. 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`. 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 ### Error
Plugins should output a JSON object with the following keys if they encounter an 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. - `code`: A numeric error code, see below for reserved codes.
- `msg`: A short message characterizing the error. - `msg`: A short message characterizing the error.
- `details`: A longer message describing the error. - `details`: A longer message describing the error.
@ -514,7 +632,7 @@ Example:
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"code": 7, "code": 7,
"msg": "Invalid Configuration", "msg": "Invalid Configuration",
"details": "Network 192.168.0.0/31 too small to allocate from." "details": "Network 192.168.0.0/31 too small to allocate from."
@ -523,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 codes 0-99 are reserved for well-known errors. Values of 100+ can be freely used for plugin specific errors.
Error Code|Error Description Error Code|Error Description
---|--- ---|---
`1`|Incompatible CNI version `1`|Incompatible CNI version
@ -534,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. `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. `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. `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. In addition, stderr can be used for unstructured output such as logs.
@ -547,8 +666,8 @@ Plugins must output a JSON object with the following keys upon a `VERSION` opera
Example: Example:
```json ```json
{ {
"cniVersion": "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" ] "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0" ]
} }
``` ```
@ -566,7 +685,7 @@ The container runtime would perform the following steps for the `add` operation.
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",
@ -646,7 +765,7 @@ The bridge plugin returns the following result, configuring the interface accord
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "tuning", "type": "tuning",
"sysctl": { "sysctl": {
@ -731,7 +850,7 @@ The plugin returns the following result. Note that the **mac** has changed.
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "portmap", "type": "portmap",
"runtimeConfig": { "runtimeConfig": {
@ -785,7 +904,7 @@ Given the previous _Add_, the container runtime would perform the following step
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",
@ -841,7 +960,7 @@ Assuming the `bridge` plugin is satisfied, it produces no output on standard out
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "tuning", "type": "tuning",
"sysctl": { "sysctl": {
@ -891,7 +1010,7 @@ Likewise, the `tuning` plugin exits indicating success.
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "portmap", "type": "portmap",
"runtimeConfig": { "runtimeConfig": {
@ -944,7 +1063,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "portmap", "type": "portmap",
"runtimeConfig": { "runtimeConfig": {
@ -992,7 +1111,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "tuning", "type": "tuning",
"sysctl": { "sysctl": {
@ -1040,7 +1159,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json ```json
{ {
"cniVersion": "1.0.0", "cniVersion": "1.1.0",
"name": "dbnet", "name": "dbnet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",

View File

@ -26,6 +26,7 @@ import (
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
) )
// Protocol parameters are passed to the plugins via OS environment variables.
const ( const (
EnvCNIPath = "CNI_PATH" EnvCNIPath = "CNI_PATH"
EnvNetDir = "NETCONFPATH" EnvNetDir = "NETCONFPATH"
@ -35,9 +36,11 @@ const (
DefaultNetDir = "/etc/cni/net.d" DefaultNetDir = "/etc/cni/net.d"
CmdAdd = "add" CmdAdd = "add"
CmdCheck = "check" CmdCheck = "check"
CmdDel = "del" CmdDel = "del"
CmdGC = "gc"
CmdStatus = "status"
) )
func parseArgs(args string) ([][2]string, error) { func parseArgs(args string) ([][2]string, error) {
@ -59,14 +62,13 @@ func parseArgs(args string) ([][2]string, error) {
func main() { func main() {
if len(os.Args) < 4 { if len(os.Args) < 4 {
usage() usage()
return
} }
netdir := os.Getenv(EnvNetDir) netdir := os.Getenv(EnvNetDir)
if netdir == "" { if netdir == "" {
netdir = DefaultNetDir netdir = DefaultNetDir
} }
netconf, err := libcni.LoadConfList(netdir, os.Args[2]) netconf, err := libcni.LoadNetworkConf(netdir, os.Args[2])
if err != nil { if err != nil {
exit(err) exit(err)
} }
@ -125,16 +127,23 @@ func main() {
exit(err) exit(err)
case CmdDel: case CmdDel:
exit(cninet.DelNetworkList(context.TODO(), netconf, rt)) 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() { func usage() {
exe := filepath.Base(os.Args[0]) exe := filepath.Base(os.Args[0])
fmt.Fprintf(os.Stderr, "%s: Add, check, or remove network interfaces from a network namespace\n", exe) fmt.Fprintf(os.Stderr, "%s: Add, check, remove, gc or status network interfaces from a network namespace\n", exe)
fmt.Fprintf(os.Stderr, " %s add <net> <netns>\n", exe) fmt.Fprintf(os.Stderr, " %s add <net> <netns>\n", exe)
fmt.Fprintf(os.Stderr, " %s check <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 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) os.Exit(1)
} }

20
go.mod
View File

@ -1,8 +1,22 @@
module github.com/containernetworking/cni module github.com/containernetworking/cni
go 1.14 go 1.21
require ( require (
github.com/onsi/ginkgo/v2 v2.1.3 github.com/onsi/ginkgo/v2 v2.20.1
github.com/onsi/gomega v1.17.0 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
) )

131
go.sum
View File

@ -1,103 +1,36 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/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=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -15,7 +15,7 @@
package libcni package libcni
// Note this is the actual implementation of the CNI specification, which // 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 // 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, // 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. // to add an IP to a container, to parse the configuration of the CNI and so on.
@ -23,10 +23,11 @@ package libcni
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
@ -38,6 +39,8 @@ import (
var ( var (
CacheDir = "/var/lib/cni" CacheDir = "/var/lib/cni"
// slightly awkward wording to preserve anyone matching on error strings
ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command")
) )
const ( const (
@ -64,17 +67,37 @@ type RuntimeConf struct {
CacheDir string CacheDir string
} }
type NetworkConfig struct { // Use PluginConfig instead of NetworkConfig, the NetworkConfig
Network *types.NetConf // backwards-compat alias will be removed in a future release.
type NetworkConfig = PluginConfig
type PluginConfig struct {
Network *types.PluginConf
Bytes []byte Bytes []byte
} }
type NetworkConfigList struct { type NetworkConfigList struct {
Name string Name string
CNIVersion string CNIVersion string
DisableCheck bool DisableCheck bool
Plugins []*NetworkConfig DisableGC bool
Bytes []byte 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 { type CNI interface {
@ -84,14 +107,21 @@ type CNI interface {
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, 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 { 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 var err error
inject := map[string]interface{}{ inject := map[string]interface{}{
@ -139,8 +169,11 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
if err != nil { if err != nil {
return nil, err 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 // 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 // capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig" // "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin. // 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 var err error
rc := make(map[string]interface{}) rc := make(map[string]interface{})
@ -195,6 +228,7 @@ type cachedInfo struct {
Config []byte `json:"config"` Config []byte `json:"config"`
IfName string `json:"ifName"` IfName string `json:"ifName"`
NetworkName string `json:"networkName"` NetworkName string `json:"networkName"`
NetNS string `json:"netns,omitempty"`
CniArgs [][2]string `json:"cniArgs,omitempty"` CniArgs [][2]string `json:"cniArgs,omitempty"`
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"` CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
RawResult map[string]interface{} `json:"result,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, Config: config,
IfName: rt.IfName, IfName: rt.IfName,
NetworkName: netName, NetworkName: netName,
NetNS: rt.NetNS,
CniArgs: rt.Args, CniArgs: rt.Args,
CapabilityArgs: rt.CapabilityArgs, CapabilityArgs: rt.CapabilityArgs,
} }
@ -254,11 +289,11 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
if err != nil { if err != nil {
return err 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 err
} }
return ioutil.WriteFile(fname, newBytes, 0600) return os.WriteFile(fname, newBytes, 0o600)
} }
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error { 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
bytes, err = ioutil.ReadFile(fname) bytes, err = os.ReadFile(fname)
if err != nil { if err != nil {
// Ignore read errors; the cached result may not exist on-disk // Ignore read errors; the cached result may not exist on-disk
return nil, nil, nil return nil, nil, nil
@ -305,7 +340,7 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := ioutil.ReadFile(fname) data, err := os.ReadFile(fname)
if err != nil { if err != nil {
// Ignore read errors; the cached result may not exist on-disk // Ignore read errors; the cached result may not exist on-disk
return nil, nil return nil, nil
@ -333,7 +368,7 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fdata, err := ioutil.ReadFile(fname) fdata, err := os.ReadFile(fname)
if err != nil { if err != nil {
// Ignore read errors; the cached result may not exist on-disk // Ignore read errors; the cached result may not exist on-disk
return nil, nil return nil, nil
@ -374,7 +409,7 @@ func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *Runt
// GetNetworkCachedResult returns the cached Result of the previous // GetNetworkCachedResult returns the cached Result of the previous
// AddNetwork() operation for a network, or an error. // 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) 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 // GetNetworkCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config. // 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) 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() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
@ -432,7 +529,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
return result, nil 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() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { 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 { if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err return err
} else if !gtet { } 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 { if list.DisableCheck {
@ -474,7 +571,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
return nil 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() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { 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 { if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err return err
} else if gtet { } else if gtet {
cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt) if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil {
if err != nil { _ = c.cacheDel(list.Name, rt)
return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err) 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) return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err)
} }
} }
_ = c.cacheDel(list.Name, rt) _ = c.cacheDel(list.Name, rt)
return nil return nil
} }
func pluginDescription(net *types.NetConf) string { func pluginDescription(net *types.PluginConf) string {
if net == nil { if net == nil {
return "<missing>" return "<missing>"
} }
@ -528,7 +626,7 @@ func pluginDescription(net *types.NetConf) string {
} }
// AddNetwork executes the plugin with the ADD command // 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) result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
if err != nil { if err != nil {
return nil, err 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 // 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 // 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 { if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err return err
} else if !gtet { } 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) 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 // 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 var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher // 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. // ValidateNetwork checks that a configuration is reasonably valid.
// It uses the same logic as ValidateNetworkList) // It uses the same logic as ValidateNetworkList)
// Returns a list of capabilities // 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{} caps := []string{}
for c, ok := range net.Network.Capabilities { for c, ok := range net.Network.Capabilities {
if ok { if ok {
@ -666,6 +764,129 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers
return invoke.GetVersionInfo(ctx, pluginPath, c.exec) 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 { func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{ return &invoke.Args{

View File

@ -20,7 +20,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -28,21 +27,23 @@ import (
"strings" "strings"
"time" "time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
type pluginInfo struct { type pluginInfo struct {
debugFilePath string debugFilePath string
debug *noop_debug.Debug commandFilePath string
config string debug *noop_debug.Debug
stdinData []byte config string
stdinData []byte
} }
type portMapping struct { 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 { 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(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFilePath := debugFile.Name() 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{ debug := &noop_debug.Debug{
ReportResult: result, ReportResult: result,
} }
@ -79,6 +85,7 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
} }
if injectDebugFilePath { if injectDebugFilePath {
config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath) config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath)
config += fmt.Sprintf(`, "commandLog": %q`, commandFilePath)
} }
if len(capabilities) > 0 { if len(capabilities) > 0 {
config += `, "capabilities": {` config += `, "capabilities": {`
@ -115,10 +122,11 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
return pluginInfo{ return pluginInfo{
debugFilePath: debugFilePath, debugFilePath: debugFilePath,
debug: debug, commandFilePath: commandFilePath,
config: config, debug: debug,
stdinData: stdinData, config: config,
stdinData: stdinData,
} }
} }
@ -153,7 +161,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir") cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
@ -168,19 +176,19 @@ var _ = Describe("Invoking plugins", func() {
pluginConfig []byte pluginConfig []byte
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
ctx context.Context ctx context.Context
) )
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name() debugFilePath = debugFile.Name()
debug = &noop_debug.Debug{ debug = &noop_debug.Debug{
ReportResult: `{ ReportResult: `{
"cniVersion": "1.0.0", "cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}], "ips": [{"address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`, }`,
@ -196,7 +204,7 @@ var _ = Describe("Invoking plugins", func() {
"somethingElse": true, "somethingElse": true,
"noCapability": false "noCapability": false
} }
}`, current.ImplementedSpecVersion)) }`, version.Current()))
netConfig, err = libcni.ConfFromBytes(pluginConfig) netConfig, err = libcni.ConfFromBytes(pluginConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -238,12 +246,12 @@ var _ = Describe("Invoking plugins", func() {
// We expect runtimeConfig keys only for portMappings and somethingElse // We expect runtimeConfig keys only for portMappings and somethingElse
rawRc := conf["runtimeConfig"] rawRc := conf["runtimeConfig"]
rc, ok := rawRc.(map[string]interface{}) rc, ok := rawRc.(map[string]interface{})
Expect(ok).To(Equal(true)) Expect(ok).To(BeTrue())
expectedKeys := []string{"portMappings", "somethingElse"} expectedKeys := []string{"portMappings", "somethingElse"}
Expect(len(rc)).To(Equal(len(expectedKeys))) Expect(rc).To(HaveLen(len(expectedKeys)))
for _, key := range expectedKeys { for _, key := range expectedKeys {
_, ok := rc[key] _, 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(err).NotTo(HaveOccurred())
Expect(caps).To(ConsistOf("portMappings", "somethingElse")) Expect(caps).To(ConsistOf("portMappings", "somethingElse"))
}) })
}) })
Describe("Invoking a single plugin", func() { Describe("Invoking a single plugin", func() {
@ -288,7 +295,7 @@ var _ = Describe("Invoking plugins", func() {
debug *noop_debug.Debug debug *noop_debug.Debug
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
ctx context.Context ctx context.Context
@ -296,14 +303,14 @@ var _ = Describe("Invoking plugins", func() {
) )
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name() debugFilePath = debugFile.Name()
debug = &noop_debug.Debug{ debug = &noop_debug.Debug{
ReportResult: `{ ReportResult: `{
"cniVersion": "1.0.0", "cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}], "ips": [{"address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`, }`,
@ -321,7 +328,7 @@ var _ = Describe("Invoking plugins", func() {
"some-key": "some-value", "some-key": "some-value",
"cniVersion": "%s", "cniVersion": "%s",
"capabilities": { "portMappings": true } "capabilities": { "portMappings": true }
}`, current.ImplementedSpecVersion) }`, version.Current())
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil) cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -411,6 +418,27 @@ var _ = Describe("Invoking plugins", func() {
returnedJson, err := json.Marshal(result) returnedJson, err := json.Marshal(result)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cachedJson).To(MatchJSON(returnedJson)) 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() { 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 // Make the results directory inaccessible by making it a
// file instead of a directory // file instead of a directory
tmpPath := filepath.Join(cacheDirPath, "results") tmpPath := filepath.Join(cacheDirPath, "results")
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600) err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
@ -454,14 +482,13 @@ var _ = Describe("Invoking plugins", func() {
Describe("CheckNetwork", func() { Describe("CheckNetwork", func() {
It("executes the plugin with command CHECK", func() { It("executes the plugin with command CHECK", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
cachedJson := `{ cachedJson := `{
"cniVersion": "1.0.0", "cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}], "ips": [{"address": "10.1.2.3/24"}]
"dns": {}
}` }`
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -516,7 +543,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
}) })
@ -541,11 +568,11 @@ var _ = Describe("Invoking plugins", func() {
Context("containing only a cached result", func() { Context("containing only a cached result", func() {
It("only passes a prevResult to the plugin", func() { It("only passes a prevResult to the plugin", func() {
err := ioutil.WriteFile(cacheFile, []byte(`{ err := os.WriteFile(cacheFile, []byte(`{
"cniVersion": "1.0.0", "cniVersion": "`+version.Current()+`",
"ips": [{"address": "10.1.2.3/24"}], "ips": [{"address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -579,7 +606,7 @@ var _ = Describe("Invoking plugins", func() {
}`)) }`))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
debug.ReportResult = ipResult debug.ReportResult = ipResult
@ -599,13 +626,13 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
}) })
Context("is invalid JSON", func() { Context("is invalid JSON", func() {
It("returns an error", func() { It("returns an error", func() {
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -615,11 +642,11 @@ var _ = Describe("Invoking plugins", func() {
Context("version doesn't match the config version", func() { Context("version doesn't match the config version", func() {
It("succeeds when the cached result can be converted", 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", "cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) 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() { 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", "cniVersion": "0.4567.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -644,14 +671,13 @@ var _ = Describe("Invoking plugins", func() {
Describe("DelNetwork", func() { Describe("DelNetwork", func() {
It("executes the plugin with command DEL", func() { It("executes the plugin with command DEL", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
cachedJson := `{ cachedJson := `{
"cniVersion": "1.0.0", "cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}], "ips": [{"address": "10.1.2.3/24"}]
"dns": {}
}` }`
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -688,6 +714,13 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cachedConfig).To(BeNil()) Expect(cachedConfig).To(BeNil())
Expect(newRt).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() { Context("when finding the plugin fails", func() {
@ -717,21 +750,21 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
resultCacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
}) })
It("deletes the cached result and config after the first DEL", func() { 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", "cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
_, err = ioutil.ReadFile(resultCacheFile) _, err = os.ReadFile(resultCacheFile)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -744,17 +777,17 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
}) })
Context("less than 0.4.0", func() { Context("less than 0.4.0", func() {
It("does not pass a prevResult to the plugin", 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", "cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Generate plugin config with older version // Generate plugin config with older version
@ -776,11 +809,11 @@ var _ = Describe("Invoking plugins", func() {
Context("equal to 0.4.0", func() { Context("equal to 0.4.0", func() {
It("passes a prevResult to the plugin", func() { It("passes a prevResult to the plugin", func() {
err := ioutil.WriteFile(cacheFile, []byte(`{ err := os.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.4.0", "cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -799,13 +832,13 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
}) })
Context("result is invalid JSON", func() { Context("result is invalid JSON", func() {
It("returns an error", func() { It("returns an error", func() {
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) 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() { Context("result version doesn't match the config version", func() {
It("succeeds when the cached result can be converted", 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", "cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) 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() { 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", "cniVersion": "0.4567.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}`), 0600) }`), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -848,7 +881,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(versionInfo).NotTo(BeNil()) Expect(versionInfo).NotTo(BeNil())
Expect(versionInfo.SupportedVersions()).To(Equal([]string{ 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"], "otherCapability": capabilityArgs["otherCapability"],
} }
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion) ipResult = fmt.Sprintf(`{"cniVersion": "%s", "ips":[{"address": "10.1.2.3/24"}]}`, version.Current())
netConfigList, plugins = makePluginList(current.ImplementedSpecVersion, ipResult, rcMap) netConfigList, plugins = makePluginList(version.Current(), ipResult, rcMap)
ctx = context.TODO() ctx = context.TODO()
}) })
@ -1163,7 +1196,7 @@ var _ = Describe("Invoking plugins", func() {
}) })
It("should not have written cache files", func() { It("should not have written cache files", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) resultCacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
_, err := ioutil.ReadFile(resultCacheFile) _, err := os.ReadFile(resultCacheFile)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
}) })
@ -1173,7 +1206,7 @@ var _ = Describe("Invoking plugins", func() {
// Make the results directory inaccessible by making it a // Make the results directory inaccessible by making it a
// file instead of a directory // file instead of a directory
tmpPath := filepath.Join(cacheDirPath, "results") tmpPath := filepath.Join(cacheDirPath, "results")
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600) err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
@ -1186,9 +1219,9 @@ var _ = Describe("Invoking plugins", func() {
Describe("CheckNetworkList", func() { Describe("CheckNetworkList", func() {
It("executes all plugins with command CHECK", func() { It("executes all plugins with command CHECK", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
@ -1230,7 +1263,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
@ -1241,7 +1274,7 @@ var _ = Describe("Invoking plugins", func() {
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}` }`
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap) netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
@ -1280,6 +1313,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command")) 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() { Context("when the cached result is invalid", func() {
It("returns an error", func() { It("returns an error", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig) err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
@ -1353,7 +1387,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() { BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
@ -1364,7 +1398,7 @@ var _ = Describe("Invoking plugins", func() {
"ips": [{"version": "4", "address": "10.1.2.3/24"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}` }`
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap) 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"}], "ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {} "dns": {}
}` }`
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600) err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.3.1", ipResult, rcMap) 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() { Context("when the cached result is invalid", func() {
It("returns an error", func() { It("tolerates the error", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig) cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig) 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\"]")) 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() { Describe("Invoking a sleep plugin", func() {
@ -1480,13 +1636,13 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string cniBinPath string
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
netConfigList *libcni.NetworkConfigList netConfigList *libcni.NetworkConfigList
) )
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name() debugFilePath = debugFile.Name()
@ -1506,7 +1662,7 @@ var _ = Describe("Invoking plugins", func() {
"some-key": "some-value", "some-key": "some-value",
"cniVersion": "%s", "cniVersion": "%s",
"capabilities": { "portMappings": true } "capabilities": { "portMappings": true }
}`, current.ImplementedSpecVersion) }`, version.Current())
cniBinPath = filepath.Dir(pluginPaths["sleep"]) cniBinPath = filepath.Dir(pluginPaths["sleep"])
cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil) cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
@ -1533,11 +1689,10 @@ var _ = Describe("Invoking plugins", func() {
"plugins": [ "plugins": [
%s %s
] ]
}`, current.ImplementedSpecVersion, pluginConfig)) }`, version.Current(), pluginConfig))
netConfigList, err = libcni.ConfListFromBytes(configList) netConfigList, err = libcni.ConfListFromBytes(configList)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
AfterEach(func() { AfterEach(func() {
@ -1553,7 +1708,6 @@ var _ = Describe("Invoking plugins", func() {
Expect(result).To(BeNil()) Expect(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1565,7 +1719,6 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1577,7 +1730,6 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) 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(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1602,7 +1753,6 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) 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(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1627,7 +1776,6 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1639,7 +1787,6 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
@ -1651,10 +1798,8 @@ var _ = Describe("Invoking plugins", func() {
cancel() cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message"))) Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
}) })
}) })
}) })
}) })
Describe("Cache operations", func() { Describe("Cache operations", func() {
@ -1664,7 +1809,7 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string cniBinPath string
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
ctx context.Context ctx context.Context
@ -1678,7 +1823,7 @@ var _ = Describe("Invoking plugins", func() {
netNS := "/some/netns/path" netNS := "/some/netns/path"
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name() debugFilePath = debugFile.Name()
@ -1687,7 +1832,7 @@ var _ = Describe("Invoking plugins", func() {
ReportResult: fmt.Sprintf(`{ ReportResult: fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"ips": [{"version": "4", "address": "%s"}] "ips": [{"version": "4", "address": "%s"}]
}`, current.ImplementedSpecVersion, firstIP), }`, version.Current(), firstIP),
} }
Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
@ -1696,7 +1841,7 @@ var _ = Describe("Invoking plugins", func() {
"type": "noop", "type": "noop",
"name": "%s", "name": "%s",
"cniVersion": "%s" "cniVersion": "%s"
}`, netName, current.ImplementedSpecVersion) }`, netName, version.Current())
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil) cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig)) netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -1720,16 +1865,16 @@ var _ = Describe("Invoking plugins", func() {
debug.ReportResult = fmt.Sprintf(`{ debug.ReportResult = fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"ips": [{"version": "4", "address": "%s"}] "ips": [{"version": "4", "address": "%s"}]
}`, current.ImplementedSpecVersion, secondIP) }`, version.Current(), secondIP)
Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
runtimeConfig.IfName = secondIfname runtimeConfig.IfName = secondIfname
_, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) _, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
resultsDir := filepath.Join(cacheDirPath, "results") resultsDir := filepath.Join(cacheDirPath, "results")
files, err := ioutil.ReadDir(resultsDir) files, err := os.ReadDir(resultsDir)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(files)).To(Equal(2)) Expect(files).To(HaveLen(2))
var foundFirst, foundSecond bool var foundFirst, foundSecond bool
for _, f := range files { for _, f := range files {
type cachedConfig struct { type cachedConfig struct {
@ -1739,7 +1884,7 @@ var _ = Describe("Invoking plugins", func() {
NetworkName string `json:"networkName"` 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()) Expect(err).NotTo(HaveOccurred())
cc := &cachedConfig{} cc := &cachedConfig{}
err = json.Unmarshal(data, cc) err = json.Unmarshal(data, cc)
@ -1786,14 +1931,14 @@ var _ = Describe("Invoking plugins", func() {
Context("when the RuntimeConf is incomplete", func() { Context("when the RuntimeConf is incomplete", func() {
var ( var (
testRt *libcni.RuntimeConf testRt *libcni.RuntimeConf
testNetConf *libcni.NetworkConfig testNetConf *libcni.PluginConfig
testNetConfList *libcni.NetworkConfigList testNetConfList *libcni.NetworkConfigList
) )
BeforeEach(func() { BeforeEach(func() {
testRt = &libcni.RuntimeConf{} testRt = &libcni.RuntimeConf{}
testNetConf = &libcni.NetworkConfig{ testNetConf = &libcni.PluginConfig{
Network: &types.NetConf{}, Network: &types.PluginConf{},
} }
testNetConfList = &libcni.NetworkConfigList{} testNetConfList = &libcni.NetworkConfigList{}
}) })
@ -1849,10 +1994,10 @@ var _ = Describe("Invoking plugins", func() {
Context("is invalid JSON", func() { Context("is invalid JSON", func() {
It("returns an error", func() { It("returns an error", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) err = os.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig) cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
@ -1864,7 +2009,7 @@ var _ = Describe("Invoking plugins", func() {
Context("is missing", func() { Context("is missing", func() {
It("returns no error", func() { It("returns no error", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) 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()) Expect(err).NotTo(HaveOccurred())
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig) cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
@ -1874,6 +2019,5 @@ var _ = Describe("Invoking plugins", func() {
}) })
}) })
}) })
}) })
}) })

View File

@ -17,18 +17,18 @@ package libcni_test
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
) )
var _ = Describe("Backwards compatibility", func() { var _ = Describe("Backwards compatibility", func() {
@ -36,7 +36,7 @@ var _ = Describe("Backwards compatibility", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir") cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })

View File

@ -16,13 +16,16 @@ package libcni
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"sort" "sort"
"strings"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
) )
type NotFoundError struct { type NotFoundError struct {
@ -42,9 +45,16 @@ func (e NoConfigsFoundError) Error() string {
return fmt.Sprintf(`no net configurations found in %s`, e.Dir) return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
} }
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { // This will not validate that the plugins actually belong to the netconfig by ensuring
conf := &NetworkConfig{Bytes: bytes, Network: &types.NetConf{}} // that they are loaded from a directory named after the networkName, relative to the network config.
if err := json.Unmarshal(bytes, conf.Network); err != nil { //
// 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) return nil, fmt.Errorf("error parsing configuration: %w", err)
} }
if conf.Network.Type == "" { if conf.Network.Type == "" {
@ -53,17 +63,35 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return conf, nil return conf, nil
} }
func ConfFromFile(filename string) (*NetworkConfig, error) { // Given a path to a directory containing a network configuration, and the name of a network,
bytes, err := ioutil.ReadFile(filename) // 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 { 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{}) 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) return nil, fmt.Errorf("error parsing configuration list: %w", err)
} }
@ -85,26 +113,115 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
} }
} }
disableCheck := false rawVersions, ok := rawList["cniVersions"]
if rawDisableCheck, ok := rawList["disableCheck"]; ok { if ok {
disableCheck, ok = rawDisableCheck.(bool) // Parse the current package CNI version
rvs, ok := rawVersions.([]interface{})
if !ok { 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{ list := &NetworkConfigList{
Name: name, Name: name,
DisableCheck: disableCheck, DisableCheck: disableCheck,
CNIVersion: cniVersion, DisableGC: disableGC,
Bytes: bytes, LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
CNIVersion: cniVersion,
Bytes: confBytes,
} }
var plugins []interface{} var plugins []interface{}
plug, ok := rawList["plugins"] plug, ok := rawList["plugins"]
if !ok { // We can have a `plugins` list key in the main conf,
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key") // 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{}) plugins, ok = plug.([]interface{})
if !ok { if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug) return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
@ -124,24 +241,68 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
} }
list.Plugins = append(list.Plugins, netConf) list.Plugins = append(list.Plugins, netConf)
} }
return list, nil return list, nil
} }
func ConfListFromFile(filename string) (*NetworkConfigList, error) { func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := ioutil.ReadFile(filename) bytes, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err) return nil, fmt.Errorf("error reading %s: %w", filename, err)
} }
return ConfListFromBytes(bytes)
conf, err := NetworkConfFromBytes(bytes)
if err != nil {
return nil, err
}
if !conf.LoadOnlyInlinedPlugins {
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
if err != nil {
return nil, err
}
conf.Plugins = append(conf.Plugins, plugins...)
}
if len(conf.Plugins) == 0 {
// Having 0 plugins for a given network is not necessarily a problem,
// but return as error for caller to decide, since they tried to load
return nil, fmt.Errorf("no plugin configs found")
}
return conf, nil
} }
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return NetworkPluginConfFromBytes(bytes)
}
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
return ConfFromBytes(bytes)
}
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
return NetworkConfFromBytes(bytes)
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
return NetworkConfFromFile(filename)
}
// ConfFiles simply returns a slice of all files in the provided directory
// with extensions matching the provided set.
func ConfFiles(dir string, extensions []string) ([]string, error) { func ConfFiles(dir string, extensions []string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles // In part, adapted from rkt/networking/podenv.go#listFiles
files, err := ioutil.ReadDir(dir) files, err := os.ReadDir(dir)
switch { switch {
case err == nil: // break case err == nil: // break
case os.IsNotExist(err): 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 return nil, nil
default: default:
return nil, err return nil, err
@ -162,6 +323,7 @@ func ConfFiles(dir string, extensions []string) ([]string, error) {
return confFiles, nil return confFiles, nil
} }
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func LoadConf(dir, name string) (*NetworkConfig, error) { func LoadConf(dir, name string) (*NetworkConfig, error) {
files, err := ConfFiles(dir, []string{".conf", ".json"}) files, err := ConfFiles(dir, []string{".conf", ".json"})
switch { switch {
@ -185,6 +347,15 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
} }
func LoadConfList(dir, name string) (*NetworkConfigList, 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"}) files, err := ConfFiles(dir, []string{".conflist"})
if err != nil { if err != nil {
return nil, err return nil, err
@ -192,7 +363,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
sort.Strings(files) sort.Strings(files)
for _, confFile := range files { for _, confFile := range files {
conf, err := ConfListFromFile(confFile) conf, err := NetworkConfFromFile(confFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,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. // from the same name, then upconvert.
singleConf, err := LoadConf(dir, name) singleConf, err := LoadConf(dir, name)
if err != nil { if err != nil {
// A little extra logic so the error makes sense // 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 // Config lists found but no config files found
return nil, NotFoundError{dir, name} return nil, NotFoundError{dir, name}
} }
@ -216,7 +388,8 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return ConfListFromConf(singleConf) 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{}) config := make(map[string]interface{})
err := json.Unmarshal(original.Bytes, &config) err := json.Unmarshal(original.Bytes, &config)
if err != nil { if err != nil {
@ -240,12 +413,14 @@ func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*Net
return nil, err return nil, err
} }
return ConfFromBytes(newBytes) return NetworkPluginConfFromBytes(newBytes)
} }
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList, // ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
// with the single network as the only entry in the list. // 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. // 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 // This may seem a bit strange, but it's to make the Bytes fields
// actually make sense. Otherwise, the generated json is littered with // actually make sense. Otherwise, the generated json is littered with

View File

@ -15,14 +15,16 @@
package libcni_test package libcni_test
import ( import (
"io/ioutil" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
var _ = Describe("Loading configuration from disk", func() { var _ = Describe("Loading configuration from disk", func() {
@ -34,11 +36,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
configDir, err = ioutil.TempDir("", "plugin-conf") configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginConfig = []byte(`{ "name": "some-plugin", "type": "foobar", "some-key": "some-value" }`) 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() { 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() { It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin") netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{ Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -72,13 +74,13 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() { BeforeEach(func() {
Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed()) Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed())
pluginConfig = []byte(`{ "name": "some-plugin", "some-key": "some-value", "type": "foobar" }`) 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() { It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin") netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{ Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -96,7 +98,7 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when a config file is malformed", func() { Context("when a config file is malformed", func() {
BeforeEach(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() { 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() { Context("when the config is in a nested subdir", func() {
BeforeEach(func() { BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2") 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" }`) 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() { It("will not find the config", func() {
@ -126,11 +128,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
configDir, err = ioutil.TempDir("", "plugin-conf") configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`) 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() { AfterEach(func() {
@ -160,12 +162,12 @@ var _ = Describe("Loading configuration from disk", func() {
var fileName, configDir string var fileName, configDir string
BeforeEach(func() { BeforeEach(func() {
var err error var err error
configDir, err = ioutil.TempDir("", "plugin-conf") configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
fileName = filepath.Join(configDir, "50-whatever.conf") fileName = filepath.Join(configDir, "50-whatever.conf")
pluginConfig := []byte(`{ "name": "some-plugin", "some-key": "some-value" }`) 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() { 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() { Context("when the config is missing 'type'", func() {
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`)) _, 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 ( var (
configDir string configDir string
configList []byte configList []byte
@ -196,11 +198,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
configDir, err = ioutil.TempDir("", "plugin-conf") configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
configList = []byte(`{ configList = []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.2.0", "cniVersion": "0.2.0",
"disableCheck": true, "disableCheck": true,
"plugins": [ "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() { 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() { 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(err).NotTo(HaveOccurred())
Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{ Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{
Name: "some-list", Name: "some-network",
CNIVersion: "0.2.0", CNIVersion: "0.2.0",
DisableCheck: true, 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"}`), 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"}`), 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"}`), 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() { Context("when there is a config file with the same name as the list", func() {
BeforeEach(func() { BeforeEach(func() {
configFile := []byte(`{ configFile := []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.2.0", "cniVersion": "0.2.0",
"type": "bridge" "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() { 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(err).NotTo(HaveOccurred())
Expect(len(netConfigList.Plugins)).To(Equal(3)) Expect(netConfigList.Plugins).To(HaveLen(3))
}) })
It("falls back to the config file", func() { It("falls back to the config file", func() {
Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed()) 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(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")) 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() { 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})) 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() { It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-other-plugin") _, err := libcni.LoadNetworkConf(configDir, "some-other-network")
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"})) Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-network"}))
}) })
}) })
Context("when a config file is malformed", func() { Context("when a config file is malformed", func() {
BeforeEach(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() { 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`)) 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() { Context("when the config is in a nested subdir", func() {
BeforeEach(func() { BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2") subdir := filepath.Join(configDir, "subdir1", "subdir2")
Expect(os.MkdirAll(subdir, 0700)).To(Succeed()) Expect(os.MkdirAll(subdir, 0o700)).To(Succeed())
configList = []byte(`{ configList = []byte(`{
"name": "deep", "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() { 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"))) 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() { Context("when the file cannot be opened", func() {
It("returns a useful error", 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`))) Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
}) })
}) })
}) })
Describe("InjectConf", func() { Describe("InjectConf", func() {
var testNetConfig *libcni.NetworkConfig var testNetConfig *libcni.PluginConfig
BeforeEach(func() { BeforeEach(func() {
testNetConfig = &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin", Type: "foobar"}, testNetConfig = &libcni.PluginConfig{
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`)} Network: &types.PluginConf{Name: "some-plugin", Type: "foobar"},
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`),
}
}) })
Context("when function parameters are incorrect", func() { Context("when function parameters are incorrect", func() {
It("returns unmarshal error", func() { It("returns unmarshal error", func() {
conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"}, conf := &libcni.PluginConfig{
Bytes: []byte(`{ cc cc cc}`)} Network: &types.PluginConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`),
}
_, err := libcni.InjectConf(conf, map[string]interface{}{"": nil}) _, err := libcni.InjectConf(conf, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`))) 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"}) resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -391,8 +665,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -409,8 +683,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -419,7 +693,6 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("adds sub-fields of NetworkConfig.Network to the config", 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"}`) expectedPluginConfig := []byte(`{"dns":{"domain":"local","nameservers":["server1","server2"]},"name":"some-plugin","type":"bridge"}`)
servers := []string{"server1", "server2"} servers := []string{"server1", "server2"}
newDNS := &types.DNS{Nameservers: servers, Domain: "local"} 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"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}}, Network: &types.PluginConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
Bytes: expectedPluginConfig, 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 _ = Describe("ConfListFromConf", func() {
var testNetConfig *libcni.NetworkConfig var testNetConfig *libcni.PluginConfig
BeforeEach(func() { BeforeEach(func() {
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`) 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{ Expect(ncl).To(Equal(&libcni.NetworkConfigList{
Name: "some-plugin", Name: "some-plugin",
CNIVersion: "0.3.1", CNIVersion: "0.3.1",
Plugins: []*libcni.NetworkConfig{testNetConfig}, Plugins: []*libcni.PluginConfig{testNetConfig},
})) }))
//Test that the json unmarshals to the same data // Test that the json unmarshals to the same data
ncl2, err := libcni.ConfListFromBytes(bytes) ncl2, err := libcni.NetworkConfFromBytes(bytes)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
ncl2.Bytes = nil ncl2.Bytes = nil
ncl2.Plugins[0].Bytes = nil ncl2.Plugins[0].Bytes = nil
Expect(ncl2).To(Equal(ncl)) Expect(ncl2).To(Equal(ncl))
}) })
}) })

View File

@ -17,12 +17,11 @@ package libcni_test
import ( import (
"encoding/json" "encoding/json"
"path/filepath" "path/filepath"
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
"testing"
) )
func TestLibcni(t *testing.T) { func TestLibcni(t *testing.T) {
@ -35,11 +34,12 @@ var pluginPackages = map[string]string{
"sleep": "github.com/containernetworking/cni/plugins/test/sleep", "sleep": "github.com/containernetworking/cni/plugins/test/sleep",
} }
var pluginPaths map[string]string var (
var pluginDirs []string // array of plugin dirs pluginPaths map[string]string
pluginDirs []string // array of plugin dirs
)
var _ = SynchronizedBeforeSuite(func() []byte { var _ = SynchronizedBeforeSuite(func() []byte {
paths := map[string]string{} paths := map[string]string{}
for name, packagePath := range pluginPackages { for name, packagePath := range pluginPackages {
execPath, err := gexec.Build(packagePath) execPath, err := gexec.Build(packagePath)

6
mk/dependencies/golangci.sh Executable file
View File

@ -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}"

14
mk/lint.mk Normal file
View File

@ -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

View File

@ -17,10 +17,10 @@ package invoke_test
import ( import (
"os" "os"
"github.com/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
) )
var _ = Describe("CNIArgs AsEnv", func() { var _ = Describe("CNIArgs AsEnv", func() {
@ -51,21 +51,21 @@ var _ = Describe("CNIArgs AsEnv", func() {
numLatentEnvs := len(latentEnvs) numLatentEnvs := len(latentEnvs)
cniEnvs := args.AsEnv() 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_COMMAND=ADD", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_IFNAME=eth7", cniEnvs)).To(Equal(true)) Expect(inStringSlice("CNI_IFNAME=eth7", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_CONTAINERID=some-container-id", cniEnvs)).To(Equal(true)) Expect(inStringSlice("CNI_CONTAINERID=some-container-id", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_NETNS=/some/netns/path", cniEnvs)).To(Equal(true)) Expect(inStringSlice("CNI_NETNS=/some/netns/path", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2", cniEnvs)).To(Equal(true)) Expect(inStringSlice("CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_PATH=/some/cni/path", cniEnvs)).To(Equal(true)) Expect(inStringSlice("CNI_PATH=/some/cni/path", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(BeFalse())
Expect(inStringSlice("CNI_IFNAME=eth0", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_IFNAME=eth0", cniEnvs)).To(BeFalse())
Expect(inStringSlice("CNI_CONTAINERID=id", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_CONTAINERID=id", cniEnvs)).To(BeFalse())
Expect(inStringSlice("CNI_NETNS=testns", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_NETNS=testns", cniEnvs)).To(BeFalse())
Expect(inStringSlice("CNI_ARGS=args", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_ARGS=args", cniEnvs)).To(BeFalse())
Expect(inStringSlice("CNI_PATH=testpath", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_PATH=testpath", cniEnvs)).To(BeFalse())
}) })
AfterEach(func() { AfterEach(func() {
@ -94,10 +94,10 @@ var _ = Describe("CNIArgs AsEnv", func() {
numLatentEnvs := len(latentEnvs) numLatentEnvs := len(latentEnvs)
cniEnvs := delegateArgs.AsEnv() 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=ADD", cniEnvs)).To(BeTrue())
Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(Equal(false)) Expect(inStringSlice("CNI_COMMAND=DEL", cniEnvs)).To(BeFalse())
}) })
It("append CNI_COMMAND if it does not exist in environment variables", func() { 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) numLatentEnvs := len(latentEnvs)
cniEnvs := delegateArgs.AsEnv() 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() { AfterEach(func() {

View File

@ -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 // DelegateCheck calls the given delegate plugin with the CNI CHECK action and
// JSON configuration // JSON configuration
func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { 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) pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil { if err != nil {
return err return err
} }
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), realExec)
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
} }
// DelegateDel calls the given delegate plugin with the CNI DEL action and // DelegateDel calls the given delegate plugin with the CNI DEL action and
// JSON configuration // JSON configuration
func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL")
if err != nil { }
return err
}
// DelegateDel will override the original CNI_COMMAND env from process with DEL // DelegateStatus calls the given delegate plugin with the CNI STATUS action and
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec) // 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 // return CNIArgs used by delegation

View File

@ -17,17 +17,17 @@ package invoke_test
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"github.com/containernetworking/cni/pkg/invoke"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/plugins/test/noop/debug"
) )
var _ = Describe("Delegate", func() { var _ = Describe("Delegate", func() {
@ -43,7 +43,7 @@ var _ = Describe("Delegate", func() {
BeforeEach(func() { BeforeEach(func() {
netConf, _ = json.Marshal(map[string]string{ netConf, _ = json.Marshal(map[string]string{
"name": "delegate-test", "name": "delegate-test",
"cniVersion": current.ImplementedSpecVersion, "cniVersion": version.Current(),
}) })
expectedResult = &current.Result{ expectedResult = &current.Result{
@ -59,7 +59,7 @@ var _ = Describe("Delegate", func() {
} }
expectedResultBytes, _ := json.Marshal(expectedResult) expectedResultBytes, _ := json.Marshal(expectedResult)
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name() 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")))
})
})
})
}) })

View File

@ -16,6 +16,7 @@ package invoke
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
@ -33,21 +34,64 @@ type Exec interface {
Decode(jsonBytes []byte) (version.PluginInfo, error) 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 // For example, a testcase could pass an instance of the following fakeExec
// object to ExecPluginWithResult() to verify the incoming stdin and environment // object to ExecPluginWithResult() to verify the incoming stdin and environment
// and provide a tailored response: // and provide a tailored response:
// //
//import ( // import (
// "encoding/json" // "encoding/json"
// "path" // "path"
// "strings" // "strings"
//) // )
// //
//type fakeExec struct { // type fakeExec struct {
// version.PluginDecoder // 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{} // net := &types.NetConf{}
// err := json.Unmarshal(stdinData, net) // err := json.Unmarshal(stdinData, net)
// if err != nil { // if err != nil {
@ -65,14 +109,14 @@ type Exec interface {
// } // }
// } // }
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil // 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 { // if len(paths) > 0 {
// return path.Join(paths[0], plugin), nil // return path.Join(paths[0], plugin), nil
// } // }
// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths) // 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) { func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil { if exec == nil {
@ -84,7 +128,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte
return nil, err 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 { func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {

View File

@ -19,13 +19,13 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/invoke/fakes" "github.com/containernetworking/cni/pkg/invoke/fakes"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
var _ = Describe("Executing a plugin, unit tests", func() { 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) result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred()) 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")) Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
}) })
It("passes its arguments through to the rawExec", func() { 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.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) 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) _, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, nil)
Expect(err).To(HaveOccurred()) 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() { Describe("without returning a result", func() {
It("passes its arguments through to the rawExec", 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.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) 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() { 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.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION")) Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()}) 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() { 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 env := rawExec.ExecPluginCall.Received.Environ
Expect(env).To(ContainElement("CNI_NETNS=dummy")) Expect(env).To(ContainElement("CNI_NETNS=dummy"))

View File

@ -16,14 +16,14 @@ package invoke_test
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
) )
var _ = Describe("FindInPath", func() { var _ = Describe("FindInPath", func() {
@ -37,17 +37,17 @@ var _ = Describe("FindInPath", func() {
) )
BeforeEach(func() { BeforeEach(func() {
tempDir, err := ioutil.TempDir("", "cni-find") tempDir, err := os.MkdirTemp("", "cni-find")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin") plugin, err := os.CreateTemp(tempDir, "a-cni-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0] plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0]
plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name)) plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
anotherTempDir, err = ioutil.TempDir("", "nothing-here") anotherTempDir, err = os.MkdirTemp("", "nothing-here")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
multiplePaths = []string{anotherTempDir, tempDir} multiplePaths = []string{anotherTempDir, tempDir}

View File

@ -16,17 +16,16 @@ package invoke_test
import ( import (
"context" "context"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/pkg/version/testhelpers" "github.com/containernetworking/cni/pkg/version/testhelpers"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
var _ = Describe("GetVersion, integration tests", func() { var _ = Describe("GetVersion, integration tests", func() {
@ -36,7 +35,7 @@ var _ = Describe("GetVersion, integration tests", func() {
) )
BeforeEach(func() { BeforeEach(func() {
pluginDir, err := ioutil.TempDir("", "plugins") pluginDir, err := os.MkdirTemp("", "plugins")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginPath = filepath.Join(pluginDir, "test-plugin") pluginPath = filepath.Join(pluginDir, "test-plugin")
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@ -128,5 +127,7 @@ func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
func main() { skel.PluginMain(c, c) } func main() { skel.PluginMain(c, c) }
` `
const git_ref_v010 = "2c482f4" const (
const git_ref_v020_no_custom_versions = "349d66d" git_ref_v010 = "2c482f4"
git_ref_v020_no_custom_versions = "349d66d"
)

View File

@ -15,11 +15,11 @@
package invoke_test package invoke_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
"testing"
) )
func TestInvoke(t *testing.T) { func TestInvoke(t *testing.T) {

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris
package invoke package invoke

View File

@ -17,15 +17,13 @@ package invoke_test
import ( import (
"bytes" "bytes"
"context" "context"
"io/ioutil"
"os" "os"
"github.com/containernetworking/cni/pkg/invoke"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
) )
var _ = Describe("RawExec", func() { var _ = Describe("RawExec", func() {
@ -41,7 +39,7 @@ var _ = Describe("RawExec", func() {
const reportResult = `{ "some": "result" }` const reportResult = `{ "some": "result" }`
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name() debugFileName = debugFile.Name()

21
pkg/ns/ns_darwin.go Normal file
View File

@ -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
}

50
pkg/ns/ns_linux.go Normal file
View File

@ -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
}

21
pkg/ns/ns_windows.go Normal file
View File

@ -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
}

View File

@ -19,13 +19,14 @@ package skel
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils" "github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
@ -34,12 +35,13 @@ import (
// CmdArgs captures all the arguments passed in to the plugin // CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin // via both env vars and stdin
type CmdArgs struct { type CmdArgs struct {
ContainerID string ContainerID string
Netns string Netns string
IfName string IfName string
Args string Args string
Path string Path string
StdinData []byte NetnsOverride string
StdinData []byte
} }
type dispatcher struct { type dispatcher struct {
@ -55,21 +57,25 @@ type dispatcher struct {
type reqForCmdEntry map[string]bool type reqForCmdEntry map[string]bool
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { 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 { vars := []struct {
name string name string
val *string val *string
reqForCmd reqForCmdEntry reqForCmd reqForCmdEntry
validateFn func(string) *types.Error
}{ }{
{ {
"CNI_COMMAND", "CNI_COMMAND",
&cmd, &cmd,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"CHECK": true, "CHECK": true,
"DEL": true, "DEL": true,
"GC": true,
"STATUS": true,
}, },
nil,
}, },
{ {
"CNI_CONTAINERID", "CNI_CONTAINERID",
@ -79,6 +85,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true, "CHECK": true,
"DEL": true, "DEL": true,
}, },
utils.ValidateContainerID,
}, },
{ {
"CNI_NETNS", "CNI_NETNS",
@ -88,6 +95,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true, "CHECK": true,
"DEL": false, "DEL": false,
}, },
nil,
}, },
{ {
"CNI_IFNAME", "CNI_IFNAME",
@ -97,6 +105,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true, "CHECK": true,
"DEL": true, "DEL": true,
}, },
utils.ValidateInterfaceName,
}, },
{ {
"CNI_ARGS", "CNI_ARGS",
@ -106,15 +115,29 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": false, "CHECK": false,
"DEL": false, "DEL": false,
}, },
nil,
}, },
{ {
"CNI_PATH", "CNI_PATH",
&path, &path,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"CHECK": true, "CHECK": true,
"DEL": 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" { if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
argsMissing = append(argsMissing, v.name) 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) t.Stdin = bytes.NewReader(nil)
} }
stdinData, err := ioutil.ReadAll(t.Stdin) stdinData, err := io.ReadAll(t.Stdin)
if err != nil { if err != nil {
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "") 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{ cmdArgs := &CmdArgs{
ContainerID: contID, ContainerID: contID,
Netns: netns, Netns: netns,
IfName: ifName, IfName: ifName,
Args: args, Args: args,
Path: path, Path: path,
StdinData: stdinData, StdinData: stdinData,
NetnsOverride: netnsOverride,
} }
return cmd, cmdArgs, nil 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()) return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
} }
if toCall == nil {
return nil
}
if err = toCall(cmdArgs); err != 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 // don't wrap Error in Error
return e return e
} }
@ -190,7 +229,7 @@ func validateConfig(jsonBytes []byte) *types.Error {
return nil 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() cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil { if err != nil {
// Print the about string to stderr when no command is set // Print the about string to stderr when no command is set
@ -202,21 +241,20 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
return err 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 { switch cmd {
case "ADD": 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": case "CHECK":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil { if err != nil {
@ -232,7 +270,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
if err != nil { if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "") return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet { } 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 err
} }
return nil return nil
@ -240,7 +278,62 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
} }
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "") return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
case "DEL": 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": case "VERSION":
if err := versionInfo.Encode(t.Stdout); err != nil { if err := versionInfo.Encode(t.Stdout); err != nil {
return types.NewError(types.ErrIOFailure, err.Error(), "") return types.NewError(types.ErrIOFailure, err.Error(), "")
@ -264,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, // To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMain() instead. // 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 { 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{ return (&dispatcher{
Getenv: os.Getenv, Getenv: os.Getenv,
Stdin: os.Stdin, Stdin: os.Stdin,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, 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. // PluginMain is the core "main" for a plugin which includes automatic error handling.
@ -284,6 +427,8 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio
// as JSON to stdout and call os.Exit(1). // as JSON to stdout and call os.Exit(1).
// //
// To have more control over error handling, use PluginMainWithError() instead. // 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) { func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) {
if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil { if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil {
if err := e.Print(); err != nil { if err := e.Print(); err != nil {

View File

@ -15,10 +15,10 @@
package skel package skel
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestSkel(t *testing.T) { func TestSkel(t *testing.T) {

View File

@ -20,12 +20,11 @@ import (
"fmt" "fmt"
"strings" "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/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
) )
type fakeCmd struct { type fakeCmd struct {
@ -46,13 +45,14 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
var _ = Describe("dispatching to the correct callback", func() { var _ = Describe("dispatching to the correct callback", func() {
var ( var (
environment map[string]string environment map[string]string
stdinData string stdinData string
stdout, stderr *bytes.Buffer stdout, stderr *bytes.Buffer
cmdAdd, cmdCheck, cmdDel *fakeCmd cmdAdd, cmdCheck, cmdDel, cmdGC *fakeCmd
dispatch *dispatcher dispatch *dispatcher
expectedCmdArgs *CmdArgs expectedCmdArgs *CmdArgs
versionInfo version.PluginInfo versionInfo version.PluginInfo
funcs CNIFuncs
) )
BeforeEach(func() { BeforeEach(func() {
@ -78,6 +78,14 @@ var _ = Describe("dispatching to the correct callback", func() {
cmdAdd = &fakeCmd{} cmdAdd = &fakeCmd{}
cmdCheck = &fakeCmd{} cmdCheck = &fakeCmd{}
cmdDel = &fakeCmd{} cmdDel = &fakeCmd{}
cmdGC = &fakeCmd{}
funcs = CNIFuncs{
Add: cmdAdd.Func,
Del: cmdDel.Func,
Check: cmdCheck.Func,
GC: cmdGC.Func,
}
expectedCmdArgs = &CmdArgs{ expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id", ContainerID: "some-container-id",
Netns: "/some/netns/path", Netns: "/some/netns/path",
@ -88,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) delete(environment, envVar)
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
if isRequired { if isRequired {
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -103,8 +111,17 @@ var _ = Describe("dispatching to the correct callback", func() {
} }
Context("when the CNI_COMMAND is ADD", 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() { 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1)) Expect(cmdAdd.CallCount).To(Equal(1))
@ -115,7 +132,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("returns an error when containerID has invalid characters", func() { It("returns an error when containerID has invalid characters", func() {
environment["CNI_CONTAINERID"] = "some-%%container-id" 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -128,7 +145,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is too long", func() { It("interface name is too long", func() {
environment["CNI_IFNAME"] = "1234567890123456" 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -140,7 +157,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is .", func() { It("interface name is .", func() {
environment["CNI_IFNAME"] = "." 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -152,7 +169,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is ..", func() { It("interface name is ..", func() {
environment["CNI_IFNAME"] = ".." 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -164,7 +181,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters /", func() { It("interface name contains invalid characters /", func() {
environment["CNI_IFNAME"] = "test/test" 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -176,7 +193,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters :", func() { It("interface name contains invalid characters :", func() {
environment["CNI_IFNAME"] = "test:test" 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -188,7 +205,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters whitespace", func() { It("interface name contains invalid characters whitespace", func() {
environment["CNI_IFNAME"] = "test test" 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -199,7 +216,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call cmdCheck or cmdDel", 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(err).NotTo(HaveOccurred())
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
@ -223,7 +240,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("reports that all of them are missing, not just the first", 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
@ -245,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() { It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1)) Expect(cmdAdd.CallCount).To(Equal(1))
@ -260,14 +276,15 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes 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.Msg).To(Equal("incompatible CNI versions"))
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`)) Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
}) })
It("does not call either callback", func() { 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(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
@ -282,7 +299,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("extracts env vars and stdin data and calls cmdCheck", 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -292,7 +309,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call cmdAdd or cmdDel", 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -316,7 +333,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("reports that all of them are missing, not just the first", 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(HaveOccurred())
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
@ -329,8 +346,8 @@ var _ = Describe("dispatching to the correct callback", func() {
Context("when cniVersion is less than 0.4.0", func() { Context("when cniVersion is less than 0.4.0", func() {
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`) dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes 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(err.Msg).To(Equal("config version does not allow CHECK"))
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
@ -342,8 +359,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.4.0", "some": "config" }`) 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") versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes 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(err.Msg).To(Equal("plugin version does not allow CHECK"))
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
@ -355,8 +372,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`) dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0") versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure))) Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
@ -367,8 +384,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns invalid network config", func() { It("immediately returns invalid network config", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`) 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") 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, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrInvalidNetworkConfig))) Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
@ -379,8 +396,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`) dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf") versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure))) Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0)) Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
@ -388,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() { Context("when the CNI_COMMAND is DEL", func() {
BeforeEach(func() { BeforeEach(func() {
environment["CNI_COMMAND"] = "DEL" environment["CNI_COMMAND"] = "DEL"
}) })
It("calls cmdDel with the env vars and stdin data", func() { 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(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(1)) Expect(cmdDel.CallCount).To(Equal(1))
@ -402,7 +538,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call cmdAdd", 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -424,17 +560,17 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("prints the version to stdout", 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(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{ Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"supportedVersions": ["9.8.7", "10.0.0"] "supportedVersions": ["9.8.7", "10.0.0"]
}`, current.ImplementedSpecVersion))) }`, version.Current())))
}) })
It("does not call cmdAdd or cmdDel", 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -454,14 +590,14 @@ var _ = Describe("dispatching to the correct callback", func() {
r := &BadReader{} r := &BadReader{}
dispatch.Stdin = r dispatch.Stdin = r
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(r.ReadCount).To(Equal(0)) Expect(r.ReadCount).To(Equal(0))
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{ Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"cniVersion": "%s", "cniVersion": "%s",
"supportedVersions": ["9.8.7", "10.0.0"] "supportedVersions": ["9.8.7", "10.0.0"]
}`, current.ImplementedSpecVersion))) }`, version.Current())))
}) })
}) })
@ -471,14 +607,14 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call any cmd 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(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
It("returns an error", func() { 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{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables, Code: types.ErrInvalidEnvironmentVariables,
@ -488,7 +624,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("prints the about string when the command is blank", func() { It("prints the about string when the command is blank", func() {
environment["CNI_COMMAND"] = "" 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")) Expect(stderr.String()).To(ContainSubstring("test framework v42"))
}) })
}) })
@ -496,7 +633,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Context("when the CNI_COMMAND is missing", func() { Context("when the CNI_COMMAND is missing", func() {
It("prints the about string to stderr", func() { It("prints the about string to stderr", func() {
environment = map[string]string{} 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(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -507,7 +644,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("fails if there is no about string", func() { It("fails if there is no about string", func() {
environment = map[string]string{} environment = map[string]string{}
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "") err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -525,14 +662,14 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call any cmd 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(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
It("wraps and returns the error", 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{ Expect(err).To(Equal(&types.Error{
Code: types.ErrIOFailure, Code: types.ErrIOFailure,
@ -551,7 +688,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("returns the error as-is", 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{ Expect(err).To(Equal(&types.Error{
Code: 1234, Code: 1234,
@ -566,7 +703,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("wraps and returns the error", 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{ Expect(err).To(Equal(&types.Error{
Code: types.ErrInternal, Code: types.ErrInternal,

View File

@ -15,10 +15,10 @@
package types020_test package types020_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestTypes010(t *testing.T) { func TestTypes010(t *testing.T) {

View File

@ -19,12 +19,12 @@ import (
"fmt" "fmt"
"net" "net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/create" "github.com/containernetworking/cni/pkg/types/create"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) { func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {

View File

@ -15,10 +15,10 @@
package types040_test package types040_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestTypesCurrent(t *testing.T) { func TestTypesCurrent(t *testing.T) {

View File

@ -16,16 +16,16 @@ package types040_test
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io"
"net" "net"
"os" "os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020" types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040" types040 "github.com/containernetworking/cni/pkg/types/040"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
func testResult() *types040.Result { func testResult() *types040.Result {
@ -99,7 +99,7 @@ var _ = Describe("040 types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -169,7 +169,7 @@ var _ = Describe("040 types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())

View File

@ -26,9 +26,10 @@ import (
convert "github.com/containernetworking/cni/pkg/types/internal" 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 // Register converters for all versions less than the implemented spec version
func init() { func init() {
@ -38,10 +39,14 @@ func init() {
convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x) convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x) convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x) convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100)
// Down-converters // 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.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.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 // Creator
convert.RegisterCreator(supportedVersions, NewResult) convert.RegisterCreator(supportedVersions, NewResult)
@ -90,12 +95,49 @@ type Result struct {
DNS types.DNS `json:"dns,omitempty"` DNS types.DNS `json:"dns,omitempty"`
} }
// Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function
func (r *Result) MarshalJSON() ([]byte, error) {
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
type fixObjType = Result
bytes, err := json.Marshal(fixObjType(*r)) //nolint:all
if err != nil {
return nil, err
}
fixupObj := make(map[string]interface{})
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
return nil, err
}
if r.DNS.IsEmpty() {
delete(fixupObj, "dns")
}
return json.Marshal(fixupObj)
}
// convertFrom100 does nothing except set the version; the types are the same
func convertFrom100(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
result := &Result{
CNIVersion: toVersion,
Interfaces: fromResult.Interfaces,
IPs: fromResult.IPs,
Routes: fromResult.Routes,
DNS: fromResult.DNS,
}
return result, nil
}
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) { func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
result040, err := convert.Convert(from, "0.4.0") result040, err := convert.Convert(from, "0.4.0")
if err != nil { if err != nil {
return nil, err return nil, err
} }
result100, err := convertFrom04x(result040, ImplementedSpecVersion) result100, err := convertFrom04x(result040, toVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -226,9 +268,12 @@ func (r *Result) PrintTo(writer io.Writer) error {
// Interface contains values about the created interfaces // Interface contains values about the created interfaces
type Interface struct { type Interface struct {
Name string `json:"name"` Name string `json:"name"`
Mac string `json:"mac,omitempty"` Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,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 { func (i *Interface) String() string {

View File

@ -15,10 +15,10 @@
package types100_test package types100_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestTypesCurrent(t *testing.T) { func TestTypesCurrent(t *testing.T) {

View File

@ -16,15 +16,15 @@ package types100_test
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io"
"net" "net"
"os" "os"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
) )
func testResult() *current.Result { func testResult() *current.Result {
@ -45,15 +45,17 @@ func testResult() *current.Result {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil()) Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil()) Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility // Set every field of the struct to ensure source compatibility
return &current.Result{ return &current.Result{
CNIVersion: "1.0.0", CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{ Interfaces: []*current.Interface{
{ {
Name: "eth0", Name: "eth0",
Mac: "00:11:22:33:44:55", Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net", Mtu: 1500,
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
}, },
}, },
IPs: []*current.IPConfig{ IPs: []*current.IPConfig{
@ -82,7 +84,7 @@ func testResult() *current.Result {
} }
var _ = Describe("Current types operations", func() { 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() res := testResult()
// Redirect stdout to capture JSON result // Redirect stdout to capture JSON result
@ -96,17 +98,20 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{ Expect(string(out)).To(MatchJSON(`{
"cniVersion": "1.0.0", "cniVersion": "` + current.ImplementedSpecVersion + `",
"interfaces": [ "interfaces": [
{ {
"name": "eth0", "name": "eth0",
"mac": "00:11:22:33:44:55", "mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net" "mtu": 1500,
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
} }
], ],
"ips": [ "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() { It("correctly encodes a 0.1.0 Result", func() {
res, err := testResult().GetAsVersion("0.1.0") res, err := testResult().GetAsVersion("0.1.0")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -164,7 +185,7 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -223,7 +244,7 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())

View File

@ -26,8 +26,8 @@ import (
type UnmarshallableBool bool type UnmarshallableBool bool
// UnmarshalText implements the encoding.TextUnmarshaler interface. // UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns boolean true if the string is "1" or "[Tt]rue" // Returns boolean true if the string is "1" or "true" or "True"
// Returns boolean false if the string is "0" or "[Ff]alse" // Returns boolean false if the string is "0" or "false" or "False”
func (b *UnmarshallableBool) UnmarshalText(data []byte) error { func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data)) s := strings.ToLower(string(data))
switch s { switch s {

View File

@ -18,10 +18,10 @@ import (
"net" "net"
"reflect" "reflect"
. "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
. "github.com/containernetworking/cni/pkg/types"
) )
var _ = Describe("UnmarshallableBool UnmarshalText", func() { var _ = Describe("UnmarshallableBool UnmarshalText", func() {
@ -126,7 +126,6 @@ var _ = Describe("LoadArgs", func() {
}{} }{}
err := LoadArgs("IP=10.0.0.0/24", &conf) err := LoadArgs("IP=10.0.0.0/24", &conf)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
}) })

View File

@ -19,6 +19,9 @@ import (
"fmt" "fmt"
"github.com/containernetworking/cni/pkg/types" "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" convert "github.com/containernetworking/cni/pkg/types/internal"
) )

View File

@ -56,31 +56,72 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// NetConf describes a network. // Use PluginConf instead of NetConf, the NetConf
type NetConf struct { // 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"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"` Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM IPAM `json:"ipam,omitempty"` IPAM IPAM `json:"ipam,omitempty"`
DNS DNS `json:"dns"` DNS DNS `json:"dns,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult Result `json:"-"` 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 IPAM struct {
Type string `json:"type,omitempty"` 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. // NetConfList describes an ordered list of networks.
type NetConfList struct { type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"` DisableCheck bool `json:"disableCheck,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"` DisableGC bool `json:"disableGC,omitempty"`
Plugins []*PluginConf `json:"plugins,omitempty"`
} }
// Result is an interface that provides the result of plugin execution // Result is an interface that provides the result of plugin execution
@ -116,31 +157,48 @@ type DNS struct {
Options []string `json:"options,omitempty"` 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 { func (d *DNS) Copy() *DNS {
if d == nil { if d == nil {
return nil return nil
} }
to := &DNS{Domain: d.Domain} to := &DNS{Domain: d.Domain}
for _, ns := range d.Nameservers { to.Nameservers = append(to.Nameservers, d.Nameservers...)
to.Nameservers = append(to.Nameservers, ns) to.Search = append(to.Search, d.Search...)
} to.Options = append(to.Options, d.Options...)
for _, s := range d.Search {
to.Search = append(to.Search, s)
}
for _, o := range d.Options {
to.Options = append(to.Options, o)
}
return to return to
} }
type Route struct { type Route struct {
Dst net.IPNet Dst net.IPNet
GW net.IP GW net.IP
MTU int
AdvMSS int
Priority int
Table *int
Scope *int
} }
func (r *Route) String() string { 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 { func (r *Route) Copy() *Route {
@ -148,14 +206,30 @@ func (r *Route) Copy() *Route {
return nil return nil
} }
return &Route{ route := &Route{
Dst: r.Dst, Dst: r.Dst,
GW: r.GW, 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 // 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 ( const (
ErrUnknown uint = iota // 0 ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1 ErrIncompatibleCNIVersion // 1
@ -165,7 +239,10 @@ const (
ErrIOFailure // 5 ErrIOFailure // 5
ErrDecodingFailure // 6 ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7 ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11 ErrTryAgainLater uint = 11
ErrPluginNotAvailable uint = 50
ErrLimitedConnectivity uint = 51
ErrInternal uint = 999 ErrInternal uint = 999
) )
@ -200,8 +277,13 @@ func (e *Error) Print() error {
// JSON (un)marshallable types // JSON (un)marshallable types
type route struct { type route struct {
Dst IPNet `json:"dst"` Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"` 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 { 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.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW 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 return nil
} }
func (r Route) MarshalJSON() ([]byte, error) { func (r Route) MarshalJSON() ([]byte, error) {
rt := route{ rt := route{
Dst: IPNet(r.Dst), Dst: IPNet(r.Dst),
GW: r.GW, GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Table: r.Table,
Scope: r.Scope,
} }
return json.Marshal(rt) return json.Marshal(rt)

View File

@ -15,10 +15,10 @@
package types_test package types_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestTypes(t *testing.T) { func TestTypes(t *testing.T) {

View File

@ -18,14 +18,15 @@ import (
"encoding/json" "encoding/json"
"net" "net"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "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() { var _ = Describe("Types", func() {
Describe("ParseCIDR", func() { Describe("ParseCIDR", func() {
DescribeTable("Parse and stringify", DescribeTable("Parse and stringify",
func(input, expectedIP string, expectedMask int) { func(input, expectedIP string, expectedMask int) {
@ -87,14 +88,19 @@ var _ = Describe("Types", func() {
IP: net.ParseIP("1.2.3.0"), IP: net.ParseIP("1.2.3.0"),
Mask: net.CIDRMask(24, 32), 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() { It("marshals and unmarshals to JSON", func() {
jsonBytes, err := json.Marshal(example) jsonBytes, err := json.Marshal(example)
Expect(err).NotTo(HaveOccurred()) 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 var unmarshaled types.Route
Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed()) Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
@ -110,7 +116,7 @@ var _ = Describe("Types", func() {
}) })
It("formats as a string with a hex mask", func() { 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}`))
}) })
}) })
@ -154,14 +160,15 @@ var _ = Describe("Types", func() {
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64") ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil()) Expect(ipv6).NotTo(BeNil())
result = &current.Result{ result = &current.Result{
CNIVersion: "1.0.0", CNIVersion: "1.0.0",
Interfaces: []*current.Interface{ Interfaces: []*current.Interface{
{ {
Name: "eth0", Name: "eth0",
Mac: "00:11:22:33:44:55", Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net", Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
}, },
}, },
IPs: []*current.IPConfig{ IPs: []*current.IPConfig{

View File

@ -36,7 +36,6 @@ var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`)
// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters // ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters
func ValidateContainerID(containerID string) *types.Error { func ValidateContainerID(containerID string) *types.Error {
if containerID == "" { if containerID == "" {
return types.NewError(types.ErrUnknownContainer, "missing 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 // ValidateNetworkName will validate that the supplied networkName does not contain invalid characters
func ValidateNetworkName(networkName string) *types.Error { func ValidateNetworkName(networkName string) *types.Error {
if networkName == "" { if networkName == "" {
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "") return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "")
} }
@ -58,11 +56,11 @@ func ValidateNetworkName(networkName string) *types.Error {
return nil 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 // 1. The name must not be empty
// 2. The name must be less than 16 characters // 2. The name must be less than 16 characters
// 3. The name must not be "." or ".." // 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 // ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024
func ValidateInterfaceName(ifName string) *types.Error { func ValidateInterfaceName(ifName string) *types.Error {
if len(ifName) == 0 { if len(ifName) == 0 {

View File

@ -15,10 +15,10 @@
package version_test package version_test
import ( import (
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
) )
var _ = Describe("Decoding the version of network config", func() { var _ = Describe("Decoding the version of network config", func() {

View File

@ -16,7 +16,6 @@ package legacy_examples
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" 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) 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 { if err != nil {
return nil, fmt.Errorf("failed to create noop plugin debug file: %w", err) 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"}, NetConfs: []string{"unversioned", "0.1.0"},
Example: Example{ Example: Example{
Name: "example_invoker_v010", Name: "example_invoker_v010",
CNIRepoGitRef: "c0d34c69", //version with ns.Do CNIRepoGitRef: "c0d34c69", // version with ns.Do
PluginSource: `package main PluginSource: `package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
@ -161,7 +160,7 @@ func main(){
} }
func exec() int { func exec() int {
confBytes, err := ioutil.ReadAll(os.Stdin) confBytes, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
fmt.Printf("could not read netconfig from stdin: %+v", err) fmt.Printf("could not read netconfig from stdin: %+v", err)
return 1 return 1

View File

@ -17,8 +17,8 @@
package legacy_examples package legacy_examples
import ( import (
"io/ioutil"
"net" "net"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync" "sync"
@ -39,8 +39,10 @@ type Example struct {
PluginSource string PluginSource string
} }
var buildDir = "" var (
var buildDirLock sync.Mutex buildDir = ""
buildDirLock sync.Mutex
)
func ensureBuildDirExists() error { func ensureBuildDirExists() error {
buildDirLock.Lock() buildDirLock.Lock()
@ -51,7 +53,7 @@ func ensureBuildDirExists() error {
} }
var err error var err error
buildDir, err = ioutil.TempDir("", "cni-example-plugins") buildDir, err = os.MkdirTemp("", "cni-example-plugins")
return err return err
} }

View File

@ -15,10 +15,10 @@
package legacy_examples_test package legacy_examples_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestLegacyExamples(t *testing.T) { func TestLegacyExamples(t *testing.T) {

View File

@ -19,9 +19,10 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
) )
var _ = Describe("The v0.1.0 Example", func() { var _ = Describe("The v0.1.0 Example", func() {

View File

@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
} }
return false, nil 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
}

View File

@ -15,9 +15,10 @@
package version_test package version_test
import ( import (
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
) )
var _ = Describe("Decoding versions reported by a plugin", func() { var _ = Describe("Decoding versions reported by a plugin", func() {
@ -120,19 +121,19 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
// Make sure the first is greater than the second // Make sure the first is greater than the second
gt, err := version.GreaterThanOrEqualTo(v[0], v[1]) gt, err := version.GreaterThanOrEqualTo(v[0], v[1])
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(true)) Expect(gt).To(BeTrue())
// And the opposite // And the opposite
gt, err = version.GreaterThanOrEqualTo(v[1], v[0]) gt, err = version.GreaterThanOrEqualTo(v[1], v[0])
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(false)) Expect(gt).To(BeFalse())
} }
}) })
It("returns true when versions are the same", func() { It("returns true when versions are the same", func() {
gt, err := version.GreaterThanOrEqualTo("1.2.3", "1.2.3") gt, err := version.GreaterThanOrEqualTo("1.2.3", "1.2.3")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(true)) Expect(gt).To(BeTrue())
}) })
It("returns an error for malformed versions", func() { It("returns an error for malformed versions", func() {

View File

@ -15,9 +15,10 @@
package version_test package version_test
import ( import (
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
) )
var _ = Describe("Reconcile versions of net config with versions supported by plugins", func() { var _ = Describe("Reconcile versions of net config with versions supported by plugins", func() {

View File

@ -22,15 +22,12 @@ package testhelpers
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
) )
const packageBaseName = "github.com/containernetworking/cni"
func run(cmd *exec.Cmd) error { func run(cmd *exec.Cmd) error {
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@ -64,8 +61,16 @@ func modInit(path, name string) error {
return run(cmd) return run(cmd)
} }
// addLibcni will execute `go mod edit -replace` to fix libcni at a specified version
func addLibcni(path, gitRef string) error { 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 cmd.Dir = path
return run(cmd) 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 // BuildAt builds the go programSource using the version of the CNI library
// at gitRef, and saves the resulting binary file at outputFilePath // at gitRef, and saves the resulting binary file at outputFilePath
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error { 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 { if err != nil {
return err return err
} }
@ -85,12 +90,15 @@ func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
return err return err
} }
// go get
if err := addLibcni(tempDir, gitRef); err != nil { if err := addLibcni(tempDir, gitRef); err != nil {
return err 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 return err
} }

View File

@ -15,10 +15,10 @@
package testhelpers_test package testhelpers_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestTesthelpers(t *testing.T) { func TestTesthelpers(t *testing.T) {

View File

@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // 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 // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,6 @@
package testhelpers package testhelpers
import ( import (
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -43,7 +42,7 @@ func main() { skel.PluginMain(c, c) }
gitRef = "f4364185253" gitRef = "f4364185253"
var err error var err error
outputDir, err = ioutil.TempDir("", "bin") outputDir, err = os.MkdirTemp("", "bin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
outputFilePath = filepath.Join(outputDir, "some-binary") outputFilePath = filepath.Join(outputDir, "some-binary")
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {

View File

@ -19,13 +19,12 @@ import (
"fmt" "fmt"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/types/create" "github.com/containernetworking/cni/pkg/types/create"
) )
// Current reports the version of the CNI spec implemented by this library // Current reports the version of the CNI spec implemented by this library
func Current() string { func Current() string {
return types100.ImplementedSpecVersion return "1.1.0"
} }
// Legacy PluginInfo describes a plugin that is backwards compatible with the // Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -35,8 +34,10 @@ func Current() string {
// //
// Any future CNI spec versions which meet this definition should be added to // Any future CNI spec versions which meet this definition should be added to
// this list. // this list.
var Legacy = PluginSupports("0.1.0", "0.2.0") var (
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0") 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 // VersionsFrom returns a list of versions starting from min, inclusive
func VersionsStartingFrom(min string) PluginInfo { func VersionsStartingFrom(min string) PluginInfo {
@ -62,7 +63,7 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
// ParsePrevResult parses a prevResult in a NetConf structure and sets // ParsePrevResult parses a prevResult in a NetConf structure and sets
// the NetConf's PrevResult member to the parsed Result object. // 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 { if conf.RawPrevResult == nil {
return nil return nil
} }

View File

@ -15,10 +15,10 @@
package version_test package version_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {

View File

@ -19,18 +19,18 @@ import (
"net" "net"
"reflect" "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/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "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() { var _ = Describe("Version operations", func() {
It("computes a list of versions correctly", func() { It("computes a list of versions correctly", func() {
actual := version.VersionsStartingFrom("0.3.1") actual := version.VersionsStartingFrom("0.3.1")
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0"})) 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() { Context("when a prevResult is available", func() {
@ -39,9 +39,11 @@ var _ = Describe("Version operations", func() {
"cniVersion": "1.0.0", "cniVersion": "1.0.0",
"interfaces": [ "interfaces": [
{ {
"name": "eth0", "name": "eth0",
"mac": "00:11:22:33:44:55", "mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net" "sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
} }
], ],
"ips": [ "ips": [
@ -57,7 +59,7 @@ var _ = Describe("Version operations", func() {
err := json.Unmarshal(rawBytes, &raw) err := json.Unmarshal(rawBytes, &raw)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: "1.0.0", CNIVersion: "1.0.0",
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
@ -66,19 +68,20 @@ var _ = Describe("Version operations", func() {
err = version.ParsePrevResult(conf) err = version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
expectedResult := &cniv1.Result{
expectedResult := &current.Result{ CNIVersion: "1.0.0",
CNIVersion: current.ImplementedSpecVersion, Interfaces: []*cniv1.Interface{
Interfaces: []*current.Interface{
{ {
Name: "eth0", Name: "eth0",
Mac: "00:11:22:33:44:55", Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net", Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
}, },
}, },
IPs: []*current.IPConfig{ IPs: []*cniv1.IPConfig{
{ {
Interface: current.Int(0), Interface: cniv1.Int(0),
Address: net.IPNet{ Address: net.IPNet{
IP: net.ParseIP("1.2.3.30"), IP: net.ParseIP("1.2.3.30"),
Mask: net.IPv4Mask(255, 255, 255, 0), Mask: net.IPv4Mask(255, 255, 255, 0),
@ -91,8 +94,8 @@ var _ = Describe("Version operations", func() {
}) })
It("fails if the prevResult version is unknown", func() { It("fails if the prevResult version is unknown", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: current.ImplementedSpecVersion, CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
RawPrevResult: map[string]interface{}{ RawPrevResult: map[string]interface{}{
@ -101,12 +104,12 @@ var _ = Describe("Version operations", func() {
} }
err := version.ParsePrevResult(conf) 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() { It("fails if the prevResult version does not match the prevResult version", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: current.ImplementedSpecVersion, CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
RawPrevResult: map[string]interface{}{ RawPrevResult: map[string]interface{}{
@ -119,14 +122,14 @@ var _ = Describe("Version operations", func() {
} }
err := version.ParsePrevResult(conf) 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() { Context("when a prevResult is not available", func() {
It("does not fail", func() { It("does not fail", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: current.ImplementedSpecVersion, CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
} }
@ -136,4 +139,22 @@ var _ = Describe("Version operations", func() {
Expect(conf.PrevResult).To(BeNil()) 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)
})
})
}) })

View File

@ -1,8 +1,15 @@
module github.com/containernetworking/cni/plugins/debug module github.com/containernetworking/cni/plugins/debug
go 1.14 go 1.21
require ( require (
github.com/containernetworking/cni v1.0.1 github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v0.9.1 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 => ../..

View File

@ -1,98 +1,26 @@
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7wwSv9iTbXzzic=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/containernetworking/cni v1.0.1 h1:9OIL/sZmMYDBe+G8svzILAlulUpaDTUjeAbtH/JNLBo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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/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 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/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
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-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-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
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 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=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
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/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
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=

View File

@ -17,15 +17,17 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "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/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
type100 "github.com/containernetworking/cni/pkg/types/100" type100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "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 { type NetConf struct {
@ -101,7 +103,7 @@ func cmdAdd(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData) netConf, _ := parseConf(args.StdinData)
// Output CNI // Output CNI
if netConf.CNIOutput != "" { 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() defer fp.Close()
fmt.Fprintf(fp, "CmdAdd\n") fmt.Fprintf(fp, "CmdAdd\n")
outputCmdArgs(fp, args) outputCmdArgs(fp, args)
@ -117,7 +119,7 @@ func cmdDel(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData) netConf, _ := parseConf(args.StdinData)
// Output CNI // Output CNI
if netConf.CNIOutput != "" { 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() defer fp.Close()
fmt.Fprintf(fp, "CmdDel\n") fmt.Fprintf(fp, "CmdDel\n")
outputCmdArgs(fp, args) outputCmdArgs(fp, args)
@ -133,7 +135,7 @@ func cmdCheck(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData) netConf, _ := parseConf(args.StdinData)
// Output CNI // Output CNI
if netConf.CNIOutput != "" { 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() defer fp.Close()
fmt.Fprintf(fp, "CmdCheck\n") fmt.Fprintf(fp, "CmdCheck\n")
outputCmdArgs(fp, args) outputCmdArgs(fp, args)

View File

@ -17,7 +17,7 @@ package debug
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "os"
"github.com/containernetworking/cni/pkg/skel" "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 // Report* fields allow the test to control the behavior of the no-op plugin
ReportResult string ReportResult string
ReportError string ReportError string
ReportErrorCode uint
ReportStderr string ReportStderr string
ReportVersionSupport []string ReportVersionSupport []string
ExitWithCode int ExitWithCode int
@ -40,9 +41,18 @@ type Debug struct {
CmdArgs skel.CmdArgs 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 // ReadDebug will return a debug file recorded by the noop plugin
func ReadDebug(debugFilePath string) (*Debug, error) { func ReadDebug(debugFilePath string) (*Debug, error) {
debugBytes, err := ioutil.ReadFile(debugFilePath) debugBytes, err := os.ReadFile(debugFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -63,10 +73,41 @@ func (debug *Debug) WriteDebug(debugFilePath string) error {
return err return err
} }
err = ioutil.WriteFile(debugFilePath, debugBytes, 0600) err = os.WriteFile(debugFilePath, debugBytes, 0o600)
if err != nil { if err != nil {
return err return err
} }
return nil 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
}

View File

@ -23,9 +23,8 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"strings" "strings"
@ -37,8 +36,9 @@ import (
) )
type NetConf struct { type NetConf struct {
types.NetConf types.PluginConf
DebugFile string `json:"debugFile"` DebugFile string `json:"debugFile"`
CommandLog string `json:"commandLog"`
} }
func loadConf(bytes []byte) (*NetConf, error) { func loadConf(bytes []byte) (*NetConf, error) {
@ -46,7 +46,7 @@ func loadConf(bytes []byte) (*NetConf, error) {
if err := json.Unmarshal(bytes, n); err != nil { if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes)) 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 nil, err
} }
return n, nil return n, nil
@ -117,15 +117,33 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
return err 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 debug.ReportStderr != "" {
if _, err = os.Stderr.WriteString(debug.ReportStderr); err != nil { if _, err = os.Stderr.WriteString(debug.ReportStderr); err != nil {
return err return err
} }
} }
switch {
if debug.ReportError != "" { case debug.ReportError != "":
return errors.New(debug.ReportError) ec := debug.ReportErrorCode
} else if debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS" { 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 prevResult := netConf.PrevResult
if debug.ReportResult == "INJECT-DNS" { if debug.ReportResult == "INJECT-DNS" {
newResult, err := current.NewResultFromResult(netConf.PrevResult) newResult, err := current.NewResultFromResult(netConf.PrevResult)
@ -149,7 +167,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
if err != nil { if err != nil {
return err return err
} }
} else if debug.ReportResult != "" { case debug.ReportResult != "":
_, err = os.Stdout.WriteString(debug.ReportResult) _, err = os.Stdout.WriteString(debug.ReportResult)
if err != nil { if err != nil {
return err return err
@ -163,7 +181,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
} }
func debugGetSupportedVersions(stdinData []byte) []string { 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") cniArgs := os.Getenv("CNI_ARGS")
if cniArgs == "" { if cniArgs == "" {
return vers return vers
@ -196,9 +214,17 @@ func cmdDel(args *skel.CmdArgs) error {
return debugBehavior(args, "DEL") 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) { func saveStdin() ([]byte, error) {
// Read original stdin // Read original stdin
stdinData, err := ioutil.ReadAll(os.Stdin) stdinData, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -227,5 +253,11 @@ func main() {
} }
supportedVersions := debugGetSupportedVersions(stdinData) 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")
} }

View File

@ -15,11 +15,11 @@
package main_test package main_test
import ( import (
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
"testing"
) )
func TestNoop(t *testing.T) { func TestNoop(t *testing.T) {

View File

@ -16,18 +16,18 @@ package main_test
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strings" "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/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
) )
var _ = Describe("No-op plugin", func() { 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"}, 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(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name() debugFileName = debugFile.Name()
@ -230,7 +230,7 @@ var _ = Describe("No-op plugin", func() {
}) })
Context("when the ReportResult debug field is set", 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() { BeforeEach(func() {
debug.ReportResult = expectedResultString debug.ReportResult = expectedResultString

View File

@ -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
View File

@ -20,22 +20,6 @@ else
go test ${PKGS} go test ${PKGS}
fi 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..." echo "Checking license header..."
licRes=$( licRes=$(
for file in $(find . -type f -iname '*.go'); do for file in $(find . -type f -iname '*.go'); do