Compare commits

...

323 Commits
v0.8.1 ... main

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
Dan Williams 08f8596ff3
Merge pull request #893 from squeed/handle-empty-version
libcni: handle empty version when parsing version
2022-04-27 14:52:20 -05:00
Casey Callendrello 1054f8ead7 libcni: handle empty version when parsing version
Without this Delete failed for empty-version configs.

Update the tests to catch this case.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-04-27 21:47:32 +02:00
Dan Williams f32e3df6fb
Merge pull request #884 from jeefy/add-security-header
Add security heading to README
2022-04-13 10:43:50 -05:00
Casey Callendrello c9114c284a
Merge branch 'main' into add-security-header 2022-03-30 18:09:04 +02:00
Sascha Grunert 54f1587d67 Switch to ginkgo/v2
Update ginkgo to the next major version which has been released a while
ago.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
2022-03-30 18:05:12 +02:00
Casey Callendrello bb46e27465
Merge branch 'main' into add-security-header 2022-03-23 16:52:18 +01:00
Dan Williams e6dea17746
Merge pull request #885 from squeed/mz2maint
Maintainers: add Mike Zappa
2022-03-23 10:51:13 -05:00
Jeffrey Sica aba8f8b1fb add security heading to README
Signed-off-by: Jeffrey Sica <me@jeefy.dev>
2022-03-16 22:44:09 -05:00
Casey Callendrello 4b46fe6d73 Maintainers: add Mike Zappa
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2022-03-16 16:38:32 +01:00
Bruce Ma 2f6d8b1930 introduce hybridnet to thrid-party plugins
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2022-03-09 17:56:18 +01:00
Dan Williams 35efaabf93
Merge pull request #875 from netlox-dev/master
Updated README to reflect loxilight CNI plugin provided by netlox.io
2022-03-02 10:39:29 -06:00
NetLOX e4a9f965d0
Merge branch 'containernetworking:master' into master 2022-03-01 20:28:28 +09:00
Dan Williams 96a1883818
Merge pull request #880 from howardjohn/json/unmarshal-pointer
Fix incorrect pointer inputs to `json.Unmarshal`
2022-02-23 10:42:06 -06:00
John Howard 3e49ce16b5 Fix incorrect pointer inputs to `json.Unmarshal`
See https://github.com/howardjohn/go-unmarshal-double-pointer for more
info on why this is not safe and how this is detected.

Signed-off-by: John Howard <howardjohn@google.com>
2022-02-22 12:59:59 -08:00
NetLOX 96425dafeb
Merge branch 'containernetworking:master' into master 2022-02-10 09:57:13 +09:00
yanghesong b92c836f3a fix version of cni
v0.8.1 does not have a directory of github.com/containernetworking/cni/pkg/types/100
refer to https://github.com/containernetworking/cni/tree/v0.8.1/pkg/types

Signed-off-by: yanghesong <hesong.yang@foxmail.com>
2022-01-19 10:46:50 -06:00
Andreas Karis 269bf6103d Spec: Container runtime shall tear down namespaces
Explicitly state that the container runtime, not the plugin, shall tear
down network namespaces.

Signed-off-by: Andreas Karis <ak.karis@gmail.com>
2022-01-12 18:23:42 +01:00
NetLOX 48fac6a491 Update README.md
Signed-off-by: netlox-io <admin@netlox.io>
2022-01-05 12:19:52 +09:00
NetLOX 798e63d83f Updated README.md to include Netlox loxilight CNI
Signed-off-by: netlox-io <admin@netlox.io>
2022-01-05 12:19:52 +09:00
Dan Williams 4756a3616c
Merge pull request #865 from pkit/pkit/exec_plugins
[exec-plugins]: support plugin lists
2021-11-17 10:38:18 -06:00
Dan Williams cff680034b
Merge branch 'master' into pkit/exec_plugins 2021-11-17 10:34:15 -06:00
Dan Williams 10f777cb7d
Merge pull request #868 from kpaschen/multus-readme
documentation: update Multus link in README.md
2021-11-17 10:33:48 -06:00
Developer 9070cb3eec documentation: update Multus link in README.md to point to the k8snetworkplumbingwg repository
Signed-off-by: kpaschen <kathrin.paschen@gmail.com>
2021-11-16 14:07:48 +01:00
Bruce Ma 1d9f32dc0e
Merge pull request #864 from tklauser/skel-superfluous-err-nil-check
skel: remove superfluous err nil check in (*dispatcher).pluginMain
2021-11-08 15:37:44 +08:00
Constantine Peresypkin 21cd5f0c1f [exec-plugins]: support plugin lists
add ability to run configurations with plugin lists/chaning
see SPEC >= 0.3.1

Signed-off-by: Constantine Peresypkin <constantine@caspiandb.com>
2021-10-31 17:51:23 +02:00
Tobias Klauser c3625975b3 skel: remove superfluous err nil check in (*dispatcher).pluginMain
There is no need to check err against nil, just return it.

Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
2021-10-25 15:23:12 +02:00
Dan Williams 6a92df875a
Merge pull request #863 from rosenhouse/remove-gabe
Remove Gabe Rosenhouse as maintainer
2021-10-06 10:31:34 -05:00
Gabriel Rosenhouse 42f24747b7 Remove Gabe Rosenhouse as maintainer
Signed-off-by: Gabriel Rosenhouse <grosenhouse@vmware.com>
2021-09-28 16:37:03 -07:00
Bruce Ma 5725f27869
Merge pull request #860 from squeed/help-print-version
skel: print out CNI versions supported in help text.
2021-09-09 09:39:28 +08:00
Casey Callendrello 2e4887bf8a skel: print out CNI versions supported in help text.
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-09-08 15:31:29 +02:00
Dan Williams 1694fd7b57
Merge pull request #858 from squeed/version-from
pkg/version: add VersionsFrom function
2021-09-07 08:04:59 -05:00
Casey Callendrello c7f5f70554 pkg/version: add VersionsFrom function
So plugins can easily declare support for "from version 0.3 and
beyond".

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-09-07 11:44:54 +02:00
Dan Williams 5608690f77 spec: bump to 1.0.0
Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-08-04 18:01:03 +02:00
Dan Williams 699ad28878
Merge pull request #853 from squeed/bump-spec-upgrades
Docs: bump spec version information
2021-08-04 10:59:48 -05:00
Casey Callendrello 30e06a8b76 Docs: bump spec version information
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-08-04 17:58:20 +02:00
Dan Williams 0dfe02421e
Merge pull request #851 from DrmagicE/master
docs: correct the extension name of the configuration file
2021-08-04 10:22:43 -05:00
DrmagicE a9562466f1 docs: revise cnitool docs
Signed-off-by: DrmagicE <379342542@qq.com>
2021-07-24 16:33:41 +08:00
Paul Holzinger 63a3bca188 wrap returned errors
fmt.Errorf errors should wrap the original error with %w.
This allows the caller to check the errors with `errors.Is()`.

see: https://blog.golang.org/go1.13-errors

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2021-07-21 17:56:49 +02:00
DrmagicE b277ec1bc2 docs: correct the extension name of the configuration file
Signed-off-by: DrmagicE <379342542@qq.com>
2021-07-18 20:51:40 +08:00
Bruce Ma 34a8a464e1 chore: standardize documentation on IP assignment
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2021-07-07 17:12:04 +02:00
Dan Williams 76bf3de7f8 types: ensure empty CNIVersion always creates/converts to 0.1.0
Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-06-30 18:01:14 +02:00
Dan Williams b4e078af7a
Merge pull request #845 from jayunit100/patch-1
Add breadcrumbs to the api.go
2021-06-23 10:04:41 -05:00
jay vyas 4feedb9dde Add breadcrumbs for CNI.go
Signed-off-by: jay vyas <jvyas@vmware.com>
2021-06-16 11:36:18 -04:00
Dan Williams 57cf1cee7d types/create: add CreateFromBytes()
Turns out a number of users want to read in JSON and
create a Result object from it, and CNI didn't have
a function to do that in one call. Instead you had to
create a ConfigDecoder first, then create a Result
from the given version.

That's silly, let's just make a function to do that
and let everyone do less work.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-06-16 17:28:23 +02:00
Bruce Ma 4fdc5f62c9 chore: support both value type and pointer type in LoadArgs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2021-06-02 17:45:22 +02:00
Dan Williams 3a8fc073b5
Merge pull request #842 from containernetworking/remove-bryan
Remove Bryan Boreham as maintainer
2021-05-26 10:25:32 -05:00
Bryan Boreham f30a8249fd Remove Bryan Boreham as maintainer
Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2021-05-21 14:46:40 +00:00
Tomofumi Hayashi c63d850d60
Add debug plugin to help debugging/troubleshooting (#818)
This change introduces debug plugin to help to develop CNI plugins.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2021-05-19 11:22:44 -04:00
Dan Williams 37ce8d6fbe
Merge pull request #839 from Nordix/spec-updates
spec: Some minor corrections
2021-05-05 10:34:04 -05:00
Björn Svensson 9546b704c8 spec: Some minor corrections
Corrected typos, links, formatting and added some details.

Signed-off-by: Björn Svensson <bjorn.a.svensson@est.tech>
2021-04-30 23:50:24 +02:00
Michael C. Cambria 7ba00c594c
Merge pull request #838 from squeed/no-args-env
Spec: Bring 1.0's treatment of "args" in line with 0.4.0
2021-04-28 11:26:23 -04:00
Casey Callendrello 3a13f6854d Spec: Bring 1.0's treatment of "args" in line with 0.4.0
The spec deviated from the existing behavior, and it was never actually
implemented in libcni. So this is a no-op.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-04-28 11:23:56 -04:00
Dan Williams 05ea7269e1
Merge pull request #835 from hs0210/work
Fix typo in SPEC.md
2021-04-28 10:20:51 -05:00
Bruce Ma 16f664ce57
Merge pull request #836 from cuirunxing-hub/master
upgrade kube-ovn new github site
2021-04-26 10:25:43 +08:00
cuirunxing-hub c92642bda0 upgrade kube-ovn new github site
Signed-off-by: cuirunxing-hub <cuirunxing@inspur.com>
2021-04-25 19:07:50 +08:00
Hu Shuai 67ec90448e Fix typo in SPEC.md
Signed-off-by: Hu Shuai <hus.fnst@cn.fujitsu.com>
2021-04-25 13:24:29 +08:00
Bryan Boreham e33f449ec9
Merge pull request #833 from cuirunxing-hub/master
typos correct
2021-04-19 13:59:50 +01:00
cuirunxing-hub 0555966685 typos correct
Signed-off-by: cuirunxing-hub <cuirunxing@inspur.com>
2021-04-15 17:55:08 +08:00
Casey Callendrello 8b25a7bf9e
Merge pull request #805 from squeed/spec-rewrite
Rewrite spec.md for 1.0.0
2021-03-31 17:15:48 +02:00
Casey Callendrello 8ad568b543 Rewrite spec.md for 1.0.0
The protocol is unchanged, but the specification was badly in need of a
rewrite.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-03-31 17:14:35 +02:00
Bruce Ma ea59a90103
Merge pull request #819 from chandanAggarwal/update_azure_cni_reference
Adding reference to Azure CNI as 3rd party plugin in README
2021-03-26 14:51:12 +08:00
Chandan Aggarwal 00169178ed Adding reference to Azure CNI as 3rd party plugin in readme
Signed-off-by: Chandan Aggarwal <caggar@microsoft.com>
2021-03-22 12:59:28 -07:00
Dan Williams c2f6afa41f
Merge pull request #802 from fasaxc/log-plugin-name
Log out the plugin name on ADD/DEL failure.
2021-03-03 10:16:29 -06:00
Bryan Boreham 9c075a3e40
Merge branch 'master' into log-plugin-name 2021-03-03 16:10:03 +00:00
Dan Williams 23b841a269
Merge pull request #813 from dcbw/020-no-ip-testcase
types/040: add testcase for <= 0.2.0 Result requirement of one or more IPs
2021-02-17 11:33:32 -06:00
Dan Williams e00ed00197
Merge branch 'master' into 020-no-ip-testcase 2021-02-17 10:21:57 -06:00
Dan Williams ad59be0959 types/040: add testcase for <= 0.2.0 Result requirement of one or more IPs
The 0.2.0 spec strongly suggests via lack of the "(optional)" tag that
a Result must contain one or two IP addresses. We already throw an
error for that case when down-converting a 0.3.0+ Result to <= 0.2.0,
but we didn't have a testcase for it.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-02-10 10:49:48 -06:00
Dan Williams b5ab16f010
Merge pull request #810 from squeed/github-action
Switch from Travis to GH Actions
2021-02-03 10:52:38 -06:00
Casey Callendrello e32b586636 remove build badges from homepage
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-02-03 17:48:45 +01:00
Casey Callendrello 296290a913 Switch from Travis to GH Actions
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-02-03 17:48:44 +01:00
Casey Callendrello a199e6aa75 go fmt
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-02-01 13:26:11 +01:00
Dan Williams a5c35b1d17
Merge pull request #808 from squeed/plugin-name
tighten up plugin-finding logic
2021-01-20 11:08:45 -06:00
Casey Callendrello ada67263b1 tighten up plugin-finding logic
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-01-20 18:04:44 +01:00
Dan Williams 15391a8c4a
Merge pull request #801 from mccv1r0/alias
Add "alias" to conventions
2021-01-20 11:01:10 -06:00
Michael Cambria eec3755640 Add "alias" to conventions
Signed-off-by: Michael Cambria <mcambria@redhat.com>
2021-01-20 11:59:17 -05:00
Dan Williams 216a7099cb
Merge pull request #807 from hs0210/work
Fix typo in pkg/types/internal/convert.go
2021-01-19 11:06:47 -06:00
Hu Shuai 867451c7a0 Fix typo in pkg/types/internal/convert.go
Signed-off-by: Hu Shuai <hus.fnst@cn.fujitsu.com>
2021-01-15 09:47:40 +08:00
Dan Williams 77cd8fe3d6
Merge pull request #803 from champtar/maintainers-typo
maintainers: fix typo
2021-01-06 10:27:10 -06:00
Dan Williams 75e7788841
Merge pull request #804 from Ashon/issues/fix-typo-in-convention
Fix typo in CONVENTIONS.md
2021-01-06 10:10:32 -06:00
ashon 679ed9d310 Fix typo in CONVENTIONS.md
Signed-off-by: ashon <ashon8813@gmail.com>
2020-12-30 23:17:53 +09:00
Etienne Champetier 6d8228e1d3 maintainers: fix typo
Signed-off-by: Etienne Champetier <champetier.etienne@gmail.com>
2020-12-29 17:25:04 -05:00
Shaun Crampton c7357803b1 Markups.
Signed-off-by: Shaun Crampton <shaun@tigera.io>
2020-12-23 15:15:17 +00:00
Shaun Crampton b678c260c4 Log out the plugin name on ADD/DEL failure.
Makes it easier to diagnose conf lists when one plugin fails.

Signed-off-by: Shaun Crampton <shaun@tigera.io>
2020-12-21 11:01:14 +00:00
Casey Callendrello 62e54113f4
Merge pull request #800 from dcbw/100-remove-SupportedVersions
types: unexport SupportedVersions
2020-12-16 17:46:44 +01:00
Dan Williams e781c94eea types: remove SupportedVersions
SupportedVersions isn't really needed outside the types themselves.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-12-16 08:52:57 -06:00
Dan Williams d1e1ae3c34
Merge pull request #783 from dcbw/100
types changes for 1.0.0
2020-11-19 07:59:36 -06:00
Dan Williams 7555ca3250 spec: bump to 1.0.0-pre-release and remove 'version' from Result addresses
Redundant and easily determined from the address itself.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-11-18 11:10:51 -06:00
Dan Williams 6823eba0ec tests: small cleanup and removal of one useless testcase
The testcase checking if a prevResult was invalid is not actually
useful, because it was testing that an empty prevResult could be
unmarshalled. But due to an oversight, prevResults may not have
a CNIVersion key, which is all we can check for validity.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-11-11 11:03:15 -06:00
Dan Williams 3805b13a3b types: add 1.0.0
Only change this time is removal of the IPConfig "Version" field
which was deemed redundant.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-11-11 11:03:15 -06:00
Dan Williams 0050bfa528 types: implement convert module and make types use it
Moving to spec 1.0.0 requires more complicated conversions, so
put the converter determination logic in a separate module that
any of the types can call to convert between arbitrary versions.
This is necessary to ensure the types don't have import cycles.

This also implements downconversion, which we never claimed
*not* to support, but didn't implement.

It also verifies that the Result object unmarshalled by
each result type's NewResult() function is actually supported
by the result type. This is a potentially breaking change as
this was not previously done, and for example may now fail
attempts to read a cached PrevResult written by a previous
version.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-10-28 12:16:07 -05:00
Dan Williams 6fc72a49e9
Merge pull request #781 from dcbw/go-113
Bump release build Go version to 1.14
2020-08-05 14:43:21 -05:00
Dan Williams 90311ea2b0 Bump release build Go version to 1.14
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-08-05 10:58:15 -05:00
Dan Williams e91547363e
Merge pull request #780 from containernetworking/add-security-info
Add security reporting info
2020-08-05 10:48:31 -05:00
Bryan Boreham f9b5c9b162 Add security reporting info
Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2020-08-05 15:39:57 +00:00
Bryan Boreham 17b2a04fa4
Merge pull request #776 from clebio/revert_error_codes
Revert formatting of error codes
2020-07-08 16:08:16 +01:00
Caleb Hyde e5c65a5bf3 Revert formatting of error codes
In a prior change, reformatting of the errors codes within the
specification changed their values from positive to negative integers.

Signed-off-by: Caleb Hyde <caleb.hyde@gmail.com>
2020-07-03 14:49:25 -07:00
Dan Williams 6b482fdc76
Merge pull request #774 from squeed/go-mod
Switch to go modules
2020-07-01 10:13:30 -05:00
Casey Callendrello 65bf6884d1 Travis: bump go versions
Drop 1.11, 1.12, add 1.14.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-06-30 10:37:04 +02:00
Casey Callendrello a7cceb916d add go.mod
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-06-30 10:37:04 +02:00
Casey Callendrello 93a7425495 testhelpers: clean up how we build against old libcni targets
Now that go has modules, we don't need to do anything particularly
special to build against old cni revisions. Just let `go get` sort it
out for us.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-06-30 10:37:01 +02:00
97 changed files with 5954 additions and 2228 deletions

View File

@ -1,17 +0,0 @@
clone_folder: c:\gopath\src\github.com\containernetworking\cni
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
build: off
test_script:
- go get -t ./...
- go test -v ./...

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Don't rewrite line endings
*.go -text

View File

@ -0,0 +1,7 @@
FROM alpine:3.20
RUN apk add --no-cache curl jq
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -0,0 +1,11 @@
name: 'Re-Test'
description: 'Re-Runs the last workflow for a PR'
inputs:
token:
description: 'GitHub API Token'
required: true
runs:
using: 'docker'
image: 'Dockerfile'
env:
GITHUB_TOKEN: ${{ inputs.token }}

45
.github/actions/retest-action/entrypoint.sh vendored Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
set -ex
if ! jq -e '.issue.pull_request' ${GITHUB_EVENT_PATH}; then
echo "Not a PR... Exiting."
exit 0
fi
if [ "$(jq -r '.comment.body' ${GITHUB_EVENT_PATH})" != "/retest" ]; then
echo "Nothing to do... Exiting."
exit 0
fi
PR_URL=$(jq -r '.issue.pull_request.url' ${GITHUB_EVENT_PATH})
curl --request GET \
--url "${PR_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" > pr.json
ACTOR=$(jq -r '.user.login' pr.json)
BRANCH=$(jq -r '.head.ref' pr.json)
curl --request GET \
--url "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?event=pull_request&actor=${ACTOR}&branch=${BRANCH}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
RERUN_URL=$(jq -r '.rerun_url' run.json)
curl --request POST \
--url "${RERUN_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json"
REACTION_URL="$(jq -r '.comment.url' ${GITHUB_EVENT_PATH})/reactions"
curl --request POST \
--url "${REACTION_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "accept: application/vnd.github.squirrel-girl-preview+json" \
--header "content-type: application/json" \
--data '{ "content" : "rocket" }'

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"

17
.github/workflows/commands.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: commands
on:
issue_comment:
types: [created]
jobs:
retest:
if: github.repository == 'containernetworking/cni'
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Re-Test Action
uses: ./.github/actions/retest-action
with:
token: ${{ secrets.REPO_ACCESS_TOKEN }}

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

96
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,96 @@
---
name: test
on: ["push", "pull_request"]
env:
GO_VERSION: "1.22"
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
jobs:
lint:
name: Lint
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest
steps:
- name: setup go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1
with:
format: auto
config_file: .yamllint.yaml
- uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
with:
args: --verbose
version: v1.57.1
build:
name: Build all linux architectures
needs: lint
runs-on: ubuntu-latest
steps:
- name: setup go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Build on all supported architectures
run: |
set -e
for arch in ${LINUX_ARCHES}; do
echo "Building for arch $arch"
GOARCH=$arch go build ./...
done
test-linux:
name: Run tests on Linux amd64
needs: build
runs-on: ubuntu-latest
steps:
- name: setup go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install test binaries
run: |
go install github.com/mattn/goveralls@v0.0.12
go install github.com/modocache/gover@latest
- name: test
run: COVERALLS=1 ./test.sh
- env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Send coverage to coveralls
run: |
PATH=$PATH:$(go env GOPATH)/bin
gover
goveralls -coverprofile=gover.coverprofile -service=github
test-win:
name: Build and run tests on Windows
needs: build
runs-on: windows-latest
steps:
- name: setup go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: test
run: bash ./test.sh

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
.idea/
bin/
gopath/
*.sw[ponm]
.vagrant
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

View File

@ -1,38 +0,0 @@
language: go
dist: bionic
go:
- 1.11.x
- 1.12.x
- 1.13.x
env:
matrix:
- TARGET=amd64
- TARGET=arm
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
matrix:
fast_finish: true
install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
- go get -t ./...
script:
- >
if [ "${TARGET}" == "amd64" ]; then
GOARCH="${TARGET}" ./test.sh;
else
GOARCH="${TARGET}" go list ./... | xargs -n1 go build -v -o /dev/null
fi
notifications:
email: false
git:
depth: 9999999

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
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:
- Create a topic branch from where you want to base your work (usually branched from master).
- Create a topic branch from where you want to base your work (usually branched from main).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.

View File

@ -10,7 +10,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
## Plugins
* Plugin authors should aim to support these conventions where it makes sense for their plugin. This means they are more likely to "just work" with a wider range of runtimes.
* Plugins should accept arguments according to these conventions if they implement the same basic functionality as other plugins. If plugins have shared functionality that isn't coverered by these conventions then a PR should be opened against this document.
* Plugins should accept arguments according to these conventions if they implement the same basic functionality as other plugins. If plugins have shared functionality that isn't covered by these conventions then a PR should be opened against this document.
## Runtimes
* Runtime authors should follow these conventions if they want to pass additional information to plugins. This will allow the extra information to be consumed by the widest range of plugins.
@ -20,7 +20,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
Additional conventions can be created by creating PRs which modify this document.
## Dynamic Plugin specific fields (Capabilities / Runtime Configuration)
[Plugin specific fields](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
[Plugin specific fields](SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
> Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the args field may be used to pass arbitrary data which may be ignored by plugins.
A plugin can define any additional fields it needs to work properly. It should return an error if it can't act on fields that were expected or where the field values were malformed.
@ -32,7 +32,7 @@ This method of passing information to a plugin is recommended when the following
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section. Plugins can request
that the runtime insert this dynamic configuration by explicitly listing their `capabilities` in the network configuration.
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists).
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](SPEC.md#network-configuration-lists).
```json
{
"name" : "ExamplePlugin",
@ -61,13 +61,15 @@ But the runtime would fill in the mappings so the plugin itself would receive so
| ip ranges | Dynamically configure the IP range(s) for address allocation. Runtimes that manage IP pools, but not individual IP addresses, can pass these to plugins. | `ipRanges` | The same as the `ranges` key for `host-local` - a list of lists of subnets. The outer list is the number of IPs to allocate, and the inner list is a pool of subnets for each allocation. <br/><pre>[<br/> [<br/> { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" } <br/> ]<br/>]</pre> | none | CNI `host-local` plugin |
| bandwidth limits | Dynamically configure interface bandwidth limits | `bandwidth` | Desired bandwidth limits. Rates are in bits per second, burst values are in bits. <pre> { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } </pre> | none | CNI `bandwidth` plugin |
| dns | Dynamically configure dns according to runtime | `dns` | Dictionary containing a list of `servers` (string entries), a list of `searches` (string entries), a list of `options` (string entries). <pre>{ <br> "searches" : [ "internal.yoyodyne.net", "corp.tyrell.net" ] <br> "servers": [ "8.8.8.8", "10.0.0.10" ] <br />} </pre> | kubernetes | CNI `win-bridge` plugin, CNI `win-overlay` plugin |
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (string entries). <pre> [ "10.10.0.1/24", "3ffe:ffff:0:01ff::1/64" ] </pre> | none | CNI `static` plugin |
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (\<ip\>\[/\<prefix\>\]). <pre> [ "192.168.0.1", 10.10.0.1/24", "3ffe:ffff:0:01ff::2", "3ffe:ffff:0:01ff::1/64" ] </pre> The plugin may require the IP address to include a prefix length. | none | CNI `static` plugin, CNI `host-local` plugin |
| mac | Dynamically assign MAC. Runtime can pass this to plugins which need MAC as input. | `mac` | `MAC` (string entry). <pre> "c2:11:22:33:44:55" </pre> | none | CNI `tuning` plugin |
| infiniband guid | Dynamically assign Infiniband GUID to network interface. Runtime can pass this to plugins which need Infiniband GUID as input. | `infinibandGUID` | `GUID` (string entry). <pre> "c2:11:22:33:44:55:66:77" </pre> | none | CNI [`ib-sriov-cni`](https://github.com/Mellanox/ib-sriov-cni) plugin |
| device id | Provide device identifier which is associated with the network to allow the CNI plugin to perform device dependent network configurations. | `deviceID` | `deviceID` (string entry). <pre> "0000:04:00.5" </pre> | none | CNI `host-local` plugin |
| device id | Provide device identifier which is associated with the network to allow the CNI plugin to perform device dependent network configurations. | `deviceID` | `deviceID` (string entry). <pre> "0000:04:00.5" </pre> | none | CNI `host-device` plugin |
| aliases | Provide a list of names that will be mapped to the IP addresses assigned to this interface. Other containers on the same network may use one of these names to access the container.| `aliases` | List of `alias` (string entry). <pre> ["my-container", "primary-db"] </pre> | none | CNI `alias` plugin |
| cgroup path | Provide the cgroup path for pod as requested by CNI plugins. | `cgroupPath` | `cgroupPath` (string entry). <pre>"/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-burstable.slice/kubelet-kubepods-burstable-pod28ce45bc_63f8_48a3_a99b_cfb9e63c856c.slice" </pre> | none | CNI `host-local` plugin |
## "args" in network config
`args` in [network config](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) were introduced as an optional field into the `0.2.0` release of the CNI spec. The first CNI code release that it appeared in was `v0.4.0`.
`args` in [network config](SPEC.md#network-configuration) were reserved as a field in the `0.2.0` release of the CNI spec.
> args (dictionary): Optional additional arguments provided by the container runtime. For example a dictionary of labels could be passed to CNI plugins by adding them to a labels field under args.
`args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support.
@ -79,7 +81,7 @@ This method of passing information to a plugin is recommended when the informati
The conventions documented here are all namespaced under `cni` so they don't conflict with any existing `args`.
For example:
```json
```jsonc
{
"cniVersion":"0.2.0",
"name":"net",
@ -88,9 +90,9 @@ For example:
"labels": [{"key": "app", "value": "myapp"}]
}
},
<REST OF CNI CONFIG HERE>
// <REST OF CNI CONFIG HERE>
"ipam":{
<IPAM CONFIG HERE>
// <IPAM CONFIG HERE>
}
}
```
@ -98,7 +100,7 @@ For example:
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ----- | ------ | ------------ | ----------------------- | ---------------------- |
| labels | Pass`key=value` labels to plugins | <pre>"labels" : [<br /> { "key" : "app", "value" : "myapp" },<br /> { "key" : "env", "value" : "prod" }<br />] </pre> | none | none |
| ips | Request static IPs | Spec:<pre>"ips": ["\<ip\>[/\<prefix\>]", ...]</pre>Examples:<pre>"ips": ["10.2.2.42/24", "2001:db8::5"]</pre>The plugin may require the IP address to include a prefix length. | none | host-local |
| ips | Request specific IPs | Spec:<pre>"ips": ["\<ip\>[/\<prefix\>]", ...]</pre>Examples:<pre>"ips": ["10.2.2.42/24", "2001:db8::5"]</pre> The plugin may require the IP address to include a prefix length. | none | host-local, static |
## CNI_ARGS
CNI_ARGS formed part of the original CNI spec and have been present since the initial release.
@ -108,7 +110,7 @@ The use of `CNI_ARGS` is deprecated and "args" should be used instead. If a runt
| Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ------ | ------ | ---------------- | ----------------------- | ---------------------- |
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre>The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local (since version v0.2.0) supports the field for IPv4 only - [documentation](https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#supported-arguments).|
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre> The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local, static |
## Chained Plugins
If plugins are agnostic about the type of interface created, they SHOULD work in a chained mode and configure existing interfaces. Plugins MAY also create the desired interface when not run in a chain.

View File

@ -1,12 +1,45 @@
# How to upgrade to CNI Specification v0.3.1
# How to Upgrade to CNI Specification v1.0
CNI v1.0 has the following changes:
- non-List configurations are removed
- the `version` field in the `interfaces` array was redundant and is removed
## libcni Changes in CNI v1.0
**`/pkg/types/current` no longer exists**
This means that runtimes need to explicitly select a version they support.
This reduces code breakage when revendoring cni into other projects and
returns the decision on which CNI Spec versions a plugin supports to the
plugin's authors.
For example, your Go imports might look like
```go
import (
cniv1 "github.com/containernetworking/cni/pkg/types/100"
)
```
# Changes in CNI v0.4
CNI v0.4 has the following important changes:
- A new verb, "CHECK", was added. Runtimes can now ask plugins to verify the status of a container's attachment
- A new configuration flag, `disableCheck`, which indicates to the runtime that configuration should not be CHECK'ed
No changes were made to the result type.
# How to upgrade to CNI Specification v0.3.0 and later
The 0.3.0 specification contained a small error. The Result structure's `ip` field should have been renamed to `ips` to be consistent with the IPAM result structure definition; this rename was missed when updating the Result to accommodate multiple IP addresses and interfaces. All first-party CNI plugins (bridge, host-local, etc) were updated to use `ips` (and thus be inconsistent with the 0.3.0 specification) and most other plugins have not been updated to the 0.3.0 specification yet, so few (if any) users should be impacted by this change.
The 0.3.1 specification corrects the Result structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
The 0.3.1 specification corrects the `Result` structure to use the `ips` field name as originally intended. This is the only change between 0.3.0 and 0.3.1.
# How to upgrade to CNI Specification v0.3.0
Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information
Version 0.3.0 of the [CNI Specification](https://github.com/containernetworking/cni/blob/spec-v0.3.0/SPEC.md) provides rich information
about container network configuration, including details of network interfaces
and support for multiple IP addresses.
@ -31,12 +64,12 @@ ensure that the configuration files specify a `cniVersion` field and that the
version there is supported by your container runtime and CNI plugins.
Configuration files without a version field should be given version 0.2.0.
The CNI spec includes example configuration files for
[single plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations)
and for [lists of chained plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations).
[single plugins](SPEC.md#example-configurations)
and for [lists of chained plugins](SPEC.md#example-configurations).
Consult the documentation for your runtime and plugins to determine what
CNI spec versions they support. Test any plugin upgrades before deploying to
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool)
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/main/cnitool)
useful. Specifically, your configuration version should be the lowest common
version supported by your plugins.
@ -46,7 +79,7 @@ This section provides guidance for upgrading plugins to CNI Spec Version 0.3.0.
### General guidance for all plugins (language agnostic)
To provide the smoothest upgrade path, **existing plugins should support
multiple versions of the CNI spec**. In particular, plugins with existing
installed bases should add support for CNI spec version 0.3.0 while maintaining
installed bases should add support for CNI spec version 1.0.0 while maintaining
compatibility with older versions.
To do this, two changes are required. First, a plugin should advertise which
@ -55,13 +88,13 @@ command with the following JSON data:
```json
{
"cniVersion": "0.3.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0" ]
"cniVersion": "1.0.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
}
```
Second, for the `ADD` command, a plugin must respect the `cniVersion` field
provided in the [network configuration JSON](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration).
provided in the [network configuration JSON](SPEC.md#network-configuration).
That field is a request for the plugin to return results of a particular format:
- If the `cniVersion` field is not present, then spec v0.2.0 should be assumed
@ -69,11 +102,11 @@ That field is a request for the plugin to return results of a particular format:
- If the plugin doesn't support the version, the plugin must error.
- Otherwise, the plugin must return a [CNI Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
- Otherwise, the plugin must return a [CNI Result](SPEC.md#result)
in the format requested.
Result formats for older CNI spec versions are available in the
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/master/SPEC.md).
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/main/SPEC.md).
For example, suppose a plugin, via its `VERSION` response, advertises CNI specification
support for v0.2.0 and v0.3.0. When it receives `cniVersion` key of `0.2.0`,
@ -88,15 +121,15 @@ require some changes now, but should more-easily handle spec changes and
new features going forward.
For plugin authors, the biggest change is that `types.Result` is now an
interface implemented by concrete struct types in the `types/current` and
`types/020` subpackages.
interface implemented by concrete struct types in the `types/100`,
`types/040`, and `types/020` subpackages.
Internally, plugins should use the `types/current` structs, and convert
to or from specific versions when required. A typical plugin will only need
to do a single conversion. That is when it is about to complete and needs to
print the result JSON in the correct format to stdout. The library
function `types.PrintResult()` simplifies this by converting and printing in
a single call.
Internally, plugins should use the latest spec version (eg `types/100`) structs,
and convert to or from specific versions when required. A typical plugin will
only need to do a single conversion when it is about to complete and
needs to print the result JSON in the requested `cniVersion` format to stdout.
The library function `types.PrintResult()` simplifies this by converting and
printing in a single call.
Additionally, the plugin should advertise which CNI Spec versions it supports
via the 3rd argument to `skel.PluginMain()`.
@ -107,7 +140,7 @@ Here is some example code
import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
)
@ -136,7 +169,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.1.0", "0.2.0", "0.3.0"))
skel.PluginMain(cmdAdd, cmdDel, version.All)
}
```
@ -149,13 +182,13 @@ result, err := current.NewResultFromResult(ipamResult)
```
Other examples of spec v0.3.0-compatible plugins are the
[main plugins in this repo](https://github.com/containernetworking/plugins/tree/master/plugins)
[main plugins in this repo](https://github.com/containernetworking/plugins/)
## For Runtime Authors
This section provides guidance for upgrading container runtimes to support
CNI Spec Version 0.3.0.
CNI Spec Version 0.3.0 and later.
### General guidance for all runtimes (language agnostic)
@ -163,22 +196,22 @@ CNI Spec Version 0.3.0.
To provide the smoothest upgrade path and support the broadest range of CNI
plugins, **container runtimes should support multiple versions of the CNI spec**.
In particular, runtimes with existing installed bases should add support for CNI
spec version 0.3.0 while maintaining compatibility with older versions.
spec version 0.3.0 and later while maintaining compatibility with older versions.
To support multiple versions of the CNI spec, runtimes should be able to
call both new and legacy plugins, and handle the results from either.
When calling a plugin, the runtime must request that the plugin respond in a
particular format by specifying the `cniVersion` field in the
[Network Configuration](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration)
[Network Configuration](SPEC.md#network-configuration)
JSON block. The plugin will then respond with
a [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result)
a [Result](SPEC.md#result)
in the format defined by that CNI spec version, and the runtime must parse
and handle this result.
#### Handle errors due to version incompatibility
Plugins may respond with error indicating that they don't support the requested
CNI version (see [Well-known Error Codes](https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes)),
CNI version (see [Well-known Error Codes](SPEC.md#well-known-error-codes)),
e.g.
```json
{
@ -197,16 +230,17 @@ added in CNI spec v0.2.0, so older plugins may not respect it. In the absence
of a successful response to `VERSION`, assume that the plugin only supports
CNI spec v0.1.0.
#### Handle missing data in v0.3.0 results
The Result for the `ADD` command in CNI spec version 0.3.0 includes a new field
`interfaces`. An IP address in the `ip` field may describe which interface
it is assigned to, by placing a numeric index in the `interface` subfield.
#### Handle missing data in v0.3.0 and later results
The Result for the `ADD` command in CNI spec version 0.3.0 and later includes
a new field `interfaces`. An IP address in the `ip` field may describe which
interface it is assigned to, by placing a numeric index in the `interface`
subfield.
However, some plugins which are v0.3.0 compatible may nonetheless omit the
`interfaces` field and/or set the `interface` index value to `-1`. Runtimes
should gracefully handle this situation, unless they have good reason to rely
on the existence of the interface data. In that case, provide the user an
error message that helps diagnose the issue.
However, some plugins which are v0.3.0 and later compatible may nonetheless
omit the `interfaces` field and/or set the `interface` index value to `-1`.
Runtimes should gracefully handle this situation, unless they have good reason
to rely on the existence of the interface data. In that case, provide the user
an error message that helps diagnose the issue.
### Specific guidance for container runtimes written in Go
Container runtimes written in Go may leverage the Go language packages in this
@ -223,17 +257,18 @@ other packages, such as the high-level `libcni` package, have been updated to us
this interface. Concrete types are now per-version subpackages. The `types/current`
subpackage contains the latest (spec v0.3.0) types.
When up-converting older result types to spec v0.3.0, fields new in
spec v0.3.0 (like `interfaces`) may be empty. Conversely, when
down-converting v0.3.0 results to an older version, any data in those fields
will be lost.
| From | 0.1 | 0.2 | 0.3 |
|--------|-----|-----|-----|
| To 0.1 | ✔ | ✔ | x |
| To 0.2 | ✔ | ✔ | x |
| To 0.3 | ✴ | ✴ | ✔ |
When up-converting older result types to spec v0.3.0 and later, fields new in
spec v0.3.0 and later (like `interfaces`) may be empty. Conversely, when
down-converting v0.3.0 and later results to an older version, any data in
those fields will be lost.
| From | 0.1 | 0.2 | 0.3 | 0.4 | 1.0 |
|--------|-----|-----|-----|-----|-----|
| To 0.1 | ✔ | ✔ | x | x | x |
| To 0.2 | ✔ | ✔ | x | x | x |
| To 0.3 | ✴ | ✴ | ✔ | ✔ | ✔ |
| To 0.4 | ✴ | ✴ | ✔ | ✔ | ✔ |
| To 1.0 | ✴ | ✴ | ✔ | ✔ | ✔ |
Key:
> ✔ : lossless conversion <br>

View File

@ -1,8 +1,14 @@
Bruce Ma <brucema19901024@gmail.com> (@mars1024)
Bryan Boreham <bryan@weave.works> (@bboreham)
Casey Callendrello <cdc@redhat.com> (@squeed)
Dan Williams <dcbw@redhat.com> (@dcbw)
Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
Matt Dupre <matt@tigera.io> (@matthewdupre)
Casey Callendrello <cdc@isovalent.com> (@squeed)
Michael Cambria <mcambria@redhat.com> (@mccv1r0)
Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
Michael Zappa <Michael.Zappa@gmail.com> (@MikeZappa87)
Tomofumi Hayashi <s1061123@gmail.com> (@s1061123)
Lionel Jouin <lionel.jouin@est.tech> (@LionelJouin)
Ben Leggett <benjamin@edera.dev> (@bleggett)
Marcelo Guerrero <guerrero.viveros@gmail.com> (@mlguerrero12)
Doug Smith <douglas.kipp.smith@gmail.com> (@dougbtv)
Emeritus:
Dan Williams <dcbw@redhat.com> (@dcbw)
Matt Dupre <matt@tigera.io> (@matthewdupre)
Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek)

1
Makefile Normal file
View File

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

View File

@ -1,13 +1,12 @@
[![Linux Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/wtrkou8oow7x533e/branch/master?svg=true)](https://ci.appveyor.com/project/cni-bot/cni/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
![CNI Logo](logo.png)
---
# 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?
CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
@ -24,6 +23,14 @@ Here are the recordings of two sessions that the CNI maintainers hosted at KubeC
- [Introduction to CNI](https://youtu.be/YjjrQiJOyME)
- [CNI deep dive](https://youtu.be/zChkx-AB5Xc)
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
The CNI project has a [weekly meeting](https://meet.jit.si/CNIMaintainersMeeting). It takes place Mondays at 11:00 US/Eastern. All are welcome to join.
## Why develop CNI?
Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific.
@ -33,8 +40,7 @@ To avoid duplication, we think it is prudent to define a common interface betwee
## Who is using CNI?
### Container runtimes
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/admin/network-plugins/)
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/)
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
- [Apache Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
@ -43,17 +49,14 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [OpenSVC - orchestrator for legacy and containerized application stacks](https://docs.opensvc.com/latest/fr/agent.configure.cni.html)
### 3rd party plugins
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
- [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium)
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox)
- [Multus - a Multi plugin](https://github.com/Intel-Corp/multus-cni)
- [Cilium - eBPF & XDP for containers](https://github.com/cilium/cilium)
- [Multus - a Multi plugin](https://github.com/k8snetworkplumbingwg/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
- [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins)
@ -62,22 +65,19 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [Juniper Contrail](https://www.juniper.net/cloud) / [TungstenFabric](https://tungstenfabric.io) - Provides overlay SDN solution, delivering multicloud networking, hybrid cloud networking, simultaneous overlay-underlay support, network policy enforcement, network isolation, service chaining and flexible load balancing
- [Knitter - a CNI plugin supporting multiple networking for Kubernetes](https://github.com/ZTE/Knitter)
- [DANM - a CNI-compliant networking solution for TelCo workloads running on Kubernetes](https://github.com/nokia/danm)
- [VMware NSX a CNI plugin that enables automated NSX L2/L3 networking and L4/L7 Load Balancing; network isolation at the pod, node, and cluster level; and zero-trust security policy for your Kubernetes cluster.](https://docs.vmware.com/en/VMware-NSX-T/2.2/com.vmware.nsxt.ncp_kubernetes.doc/GUID-6AFA724E-BB62-4693-B95C-321E8DDEA7E1.html)
- [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override)
- [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway)
- [Cisco ACI CNI - for on-prem and cloud container networking with consistent policy and security model.](https://github.com/noironetworks/aci-containers)
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/alauda/kube-ovn)
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/kubeovn/kube-ovn)
- [Project Antrea - an Open vSwitch k8s CNI](https://github.com/vmware-tanzu/antrea)
- [OVN4NFV-K8S-Plugin - a OVN based CNI controller plugin to provide cloud native based Service function chaining (SFC), Multiple OVN overlay networking](https://github.com/opnfv/ovn4nfv-k8s-plugin)
- [Azure CNI - a CNI plugin that natively extends Azure Virtual Networks to containers](https://github.com/Azure/azure-container-networking)
- [Hybridnet - a CNI plugin designed for hybrid clouds which provides both overlay and underlay networking for containers in one or more clusters. Overlay and underlay containers can run on the same node and have cluster-wide bidirectional network connectivity.](https://github.com/alibaba/hybridnet)
- [Spiderpool - An IP Address Management (IPAM) CNI plugin of Kubernetes for managing static ip for underlay network](https://github.com/spidernet-io/spiderpool)
- [AWS VPC CNI - Networking plugin for pod networking in Kubernetes using Elastic Network Interfaces on AWS](https://github.com/aws/amazon-vpc-cni-k8s)
The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI?
### Requirements
@ -214,3 +214,7 @@ For any questions about CNI, please reach out via:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.net:6667/#containernetworking) channel on [freenode.net](https://freenode.net/)
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/). NOTE: the previous CNI Slack (containernetworking.slack.com) has been sunsetted.
## Security
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [MAINTAINERS](MAINTAINERS) file.

View File

@ -1,40 +1,19 @@
# Release process
## Resulting artifacts
Creating a new release produces the following artifacts:
- Binaries (stored in the `release-<TAG>` directory) :
- `cni-<PLATFORM>-<VERSION>.tgz` binaries
- `cni-<VERSION>.tgz` binary (copy of amd64 platform binary)
- `sha1`, `sha256` and `sha512` files for the above files.
## Preparing for a release
1. Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
- Create a draft of the release note
- Discuss the level of testing that's needed and create a test plan if sensible
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
## Creating the release artifacts
1. Make sure you are on the master branch and don't have any local uncommitted changes.
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
1. Run the release script from the root of the repository
- `scripts/release.sh`
- The script requires Docker and ensures that a consistent environment is used.
- The artifacts will now be present in the `release-<TAG>` directory.
1. Test these binaries according to the test plan.
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
- Create a draft of the release note
- Discuss the level of testing that's needed and create a test plan if sensible
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
## Publishing the release
1. Make sure you are on the master branch and don't have any local uncommitted changes.
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
1. Push the tag to git `git push origin <TAG>`
1. Create a release on Github, using the tag which was just pushed.
1. Attach all the artifacts from the release directory.
1. Add the release note to the release.
1. Announce the release on at least the CNI mailing, IRC and Slack.

1801
SPEC.md

File diff suppressed because it is too large Load Diff

25
Vagrantfile vendored
View File

@ -1,25 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-18.04"
config.vm.synced_folder ".", "/go/src/github.com/containernetworking/cni"
config.vm.provision "shell", inline: <<-SHELL
set -e -x -u
apt-get update -y || (sleep 40 && apt-get update -y)
apt-get install -y git
wget -qO- https://storage.googleapis.com/golang/go1.12.5.linux-amd64.tar.gz | tar -C /usr/local -xz
echo 'export GOPATH=/go; export PATH=/usr/local/go/bin:$GOPATH/bin:$PATH' >> /root/.bashrc
eval `tail -n1 /root/.bashrc`
go get github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/gomega/...
cd /go/src/github.com/containernetworking/cni
SHELL
end

View File

@ -7,8 +7,12 @@ add or remove an interface in an already-created network namespace.
* `NETCONFPATH`: This environment variable needs to be set to a
directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches
for CNI configuration files in this directory with the extension
`*.conf` or `*.json`. It loads all the CNI configuration files in
for CNI configuration files in this directory according to the following priorities:
1. Search files with the extension `*.conflist`, representing a list of plugin configurations.
2. If there are no `*.conflist` files in the directory, search files with the extension `*.conf` or `*.json`,
representing a single plugin configuration.
It loads all the CNI configuration files in
this directory and if it finds a CNI configuration with the `network
name` given to the cnitool it returns the corresponding CNI
configuration, else it returns `nil`.

View File

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

22
go.mod Normal file
View File

@ -0,0 +1,22 @@
module github.com/containernetworking/cni
go 1.21
require (
github.com/onsi/ginkgo/v2 v2.20.1
github.com/onsi/gomega v1.34.1
github.com/vishvananda/netns v0.0.4
)
require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

36
go.sum Normal file
View File

@ -0,0 +1,36 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -14,23 +14,33 @@
package libcni
// Note this is the actual implementation of the CNI specification, which
// is reflected in the SPEC.md file.
// it is typically bundled into runtime providers (i.e. containerd or cri-o would use this
// before calling runc or hcsshim). It is also bundled into CNI providers as well, for example,
// to add an IP to a container, to parse the configuration of the CNI and so on.
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/create"
"github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version"
)
var (
CacheDir = "/var/lib/cni"
// slightly awkward wording to preserve anyone matching on error strings
ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command")
)
const (
@ -57,17 +67,37 @@ type RuntimeConf struct {
CacheDir string
}
type NetworkConfig struct {
Network *types.NetConf
// Use PluginConfig instead of NetworkConfig, the NetworkConfig
// backwards-compat alias will be removed in a future release.
type NetworkConfig = PluginConfig
type PluginConfig struct {
Network *types.PluginConf
Bytes []byte
}
type NetworkConfigList struct {
Name string
CNIVersion string
DisableCheck bool
Plugins []*NetworkConfig
Bytes []byte
Name string
CNIVersion string
DisableCheck bool
DisableGC bool
LoadOnlyInlinedPlugins bool
Plugins []*PluginConfig
Bytes []byte
}
type NetworkAttachment struct {
ContainerID string
Network string
IfName string
Config []byte
NetNS string
CniArgs [][2]string
CapabilityArgs map[string]interface{}
}
type GCArgs struct {
ValidAttachments []types.GCAttachment
}
type CNI interface {
@ -77,14 +107,21 @@ type CNI interface {
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error)
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
GetCachedAttachments(containerID string) ([]*NetworkAttachment, error)
GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error)
}
type CNIConfig struct {
@ -115,7 +152,7 @@ func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec)
}
}
func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) {
func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) {
var err error
inject := map[string]interface{}{
@ -132,8 +169,11 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
if err != nil {
return nil, err
}
if rt != nil {
return injectRuntimeConfig(orig, rt)
}
return injectRuntimeConfig(orig, rt)
return orig, nil
}
// This function takes a libcni RuntimeConf structure and injects values into
@ -148,7 +188,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
// capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin.
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) {
func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) {
var err error
rc := make(map[string]interface{})
@ -188,6 +228,7 @@ type cachedInfo struct {
Config []byte `json:"config"`
IfName string `json:"ifName"`
NetworkName string `json:"networkName"`
NetNS string `json:"netns,omitempty"`
CniArgs [][2]string `json:"cniArgs,omitempty"`
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
RawResult map[string]interface{} `json:"result,omitempty"`
@ -222,6 +263,7 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
Config: config,
IfName: rt.IfName,
NetworkName: netName,
NetNS: rt.NetNS,
CniArgs: rt.Args,
CapabilityArgs: rt.CapabilityArgs,
}
@ -247,11 +289,11 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil {
return err
}
return ioutil.WriteFile(fname, newBytes, 0600)
return os.WriteFile(fname, newBytes, 0o600)
}
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
@ -270,7 +312,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R
if err != nil {
return nil, nil, err
}
bytes, err = ioutil.ReadFile(fname)
bytes, err = os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil, nil
@ -278,7 +320,7 @@ func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *R
unmarshaled := cachedInfo{}
if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %w", netName, err)
}
if unmarshaled.Kind != CNICacheV1 {
return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
@ -298,21 +340,14 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(fname)
data, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
}
// Read the version of the cached result
decoder := version.ConfigDecoder{}
resultCniVersion, err := decoder.Decode(data)
if err != nil {
return nil, err
}
// Ensure we can understand the result
result, err := version.NewResult(resultCniVersion, data)
// Load the cached result
result, err := create.CreateFromBytes(data)
if err != nil {
return nil, err
}
@ -322,10 +357,10 @@ func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *Runtim
// should match the config version unless the config was changed
// while the container was running.
result, err = result.GetAsVersion(cniVersion)
if err != nil && resultCniVersion != cniVersion {
return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
if err != nil {
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
}
return result, err
return result, nil
}
func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
@ -333,7 +368,7 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
if err != nil {
return nil, err
}
fdata, err := ioutil.ReadFile(fname)
fdata, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
@ -346,18 +381,11 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
newBytes, err := json.Marshal(&cachedInfo.RawResult)
if err != nil {
return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
return nil, fmt.Errorf("failed to marshal cached network %q config: %w", netName, err)
}
// Read the version of the cached result
decoder := version.ConfigDecoder{}
resultCniVersion, err := decoder.Decode(newBytes)
if err != nil {
return nil, err
}
// Ensure we can understand the result
result, err := version.NewResult(resultCniVersion, newBytes)
// Load the cached result
result, err := create.CreateFromBytes(newBytes)
if err != nil {
return nil, err
}
@ -367,10 +395,10 @@ func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf)
// should match the config version unless the config was changed
// while the container was running.
result, err = result.GetAsVersion(cniVersion)
if err != nil && resultCniVersion != cniVersion {
return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
if err != nil {
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
}
return result, err
return result, nil
}
// GetNetworkListCachedResult returns the cached Result of the previous
@ -381,7 +409,7 @@ func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *Runt
// GetNetworkCachedResult returns the cached Result of the previous
// AddNetwork() operation for a network, or an error.
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
}
@ -393,11 +421,73 @@ func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *Runt
// GetNetworkCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(net.Network.Name, rt)
}
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
// GetCachedAttachments returns a list of network attachments from the cache.
// The returned list will be filtered by the containerID if the value is not empty.
func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) {
dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results")
entries, err := os.ReadDir(dirPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
fileNames := make([]string, 0, len(entries))
for _, e := range entries {
fileNames = append(fileNames, e.Name())
}
sort.Strings(fileNames)
attachments := []*NetworkAttachment{}
for _, fname := range fileNames {
if len(containerID) > 0 {
part := fmt.Sprintf("-%s-", containerID)
pos := strings.Index(fname, part)
if pos <= 0 || pos+len(part) >= len(fname) {
continue
}
}
cacheFile := filepath.Join(dirPath, fname)
bytes, err := os.ReadFile(cacheFile)
if err != nil {
continue
}
cachedInfo := cachedInfo{}
if err := json.Unmarshal(bytes, &cachedInfo); err != nil {
continue
}
if cachedInfo.Kind != CNICacheV1 {
continue
}
if len(containerID) > 0 && cachedInfo.ContainerID != containerID {
continue
}
if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" {
continue
}
attachments = append(attachments, &NetworkAttachment{
ContainerID: cachedInfo.ContainerID,
Network: cachedInfo.NetworkName,
IfName: cachedInfo.IfName,
Config: cachedInfo.Config,
NetNS: cachedInfo.NetNS,
CniArgs: cachedInfo.CniArgs,
CapabilityArgs: cachedInfo.CapabilityArgs,
})
}
return attachments, nil
}
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
@ -428,18 +518,18 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
for _, net := range list.Plugins {
result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
if err != nil {
return nil, err
return nil, fmt.Errorf("plugin %s failed (add): %w", pluginDescription(net.Network), err)
}
}
if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
return nil, fmt.Errorf("failed to set network %q cached result: %w", list.Name, err)
}
return result, nil
}
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
@ -460,7 +550,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if !gtet {
return fmt.Errorf("configuration version %q does not support the CHECK command", list.CNIVersion)
return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp)
}
if list.DisableCheck {
@ -469,7 +559,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err)
}
for _, net := range list.Plugins {
@ -481,7 +571,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
return nil
}
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
@ -504,55 +594,69 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil {
_ = c.cacheDel(list.Name, rt)
cachedResult = nil
}
}
for i := len(list.Plugins) - 1; i >= 0; i-- {
net := list.Plugins[i]
if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
return err
return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err)
}
}
_ = c.cacheDel(list.Name, rt)
return nil
}
func pluginDescription(net *types.PluginConf) string {
if net == nil {
return "<missing>"
}
pluginType := net.Type
out := fmt.Sprintf("type=%q", pluginType)
name := net.Name
if name != "" {
out += fmt.Sprintf(" name=%q", name)
}
return out
}
// AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
if err != nil {
return nil, err
}
if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
return nil, fmt.Errorf("failed to set network %q cached result: %w", net.Network.Name, err)
}
return result, nil
}
// CheckNetwork executes the plugin with the CHECK command
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
// CHECK was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err
} else if !gtet {
return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
return fmt.Errorf("configuration version %q %w", net.Network.CNIVersion, ErrorCheckNotSupp)
}
cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
return fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err)
}
return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt)
}
// DelNetwork executes the plugin with the DEL command
func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error {
func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
@ -561,7 +665,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
} else if gtet {
cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
return fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err)
}
}
@ -612,7 +716,7 @@ func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfig
// ValidateNetwork checks that a configuration is reasonably valid.
// It uses the same logic as ValidateNetworkList)
// Returns a list of capabilities
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) {
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) {
caps := []string{}
for c, ok := range net.Network.Capabilities {
if ok {
@ -660,6 +764,129 @@ func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (vers
return invoke.GetVersionInfo(ctx, pluginPath, c.exec)
}
// GCNetworkList will do two things
// - dump the list of cached attachments, and issue deletes as necessary
// - issue a GC to the underlying plugins (if the version is high enough)
func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error {
// If DisableGC is set, then don't bother GCing at all.
if list.DisableGC {
return nil
}
// First, get the list of cached attachments
cachedAttachments, err := c.GetCachedAttachments("")
if err != nil {
return nil
}
var validAttachments map[types.GCAttachment]interface{}
if args != nil {
validAttachments = make(map[types.GCAttachment]interface{}, len(args.ValidAttachments))
for _, a := range args.ValidAttachments {
validAttachments[a] = nil
}
}
var errs []error
for _, cachedAttachment := range cachedAttachments {
if cachedAttachment.Network != list.Name {
continue
}
// we found this attachment
gca := types.GCAttachment{
ContainerID: cachedAttachment.ContainerID,
IfName: cachedAttachment.IfName,
}
if _, ok := validAttachments[gca]; ok {
continue
}
// otherwise, this attachment wasn't valid and we should issue a CNI DEL
rt := RuntimeConf{
ContainerID: cachedAttachment.ContainerID,
NetNS: cachedAttachment.NetNS,
IfName: cachedAttachment.IfName,
Args: cachedAttachment.CniArgs,
CapabilityArgs: cachedAttachment.CapabilityArgs,
}
if err := c.DelNetworkList(ctx, list, &rt); err != nil {
errs = append(errs, fmt.Errorf("failed to delete stale attachment %s %s: %w", rt.ContainerID, rt.IfName, err))
}
}
// now, if the version supports it, issue a GC
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt {
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
if args != nil {
inject["cni.dev/valid-attachments"] = args.ValidAttachments
// #1101: spec used incorrect variable name
inject["cni.dev/attachments"] = args.ValidAttachments
}
for _, plugin := range list.Plugins {
// build config here
pluginConfig, err := InjectConf(plugin, inject)
if err != nil {
errs = append(errs, fmt.Errorf("failed to generate configuration to GC plugin %s: %w", plugin.Network.Type, err))
}
if err := c.gcNetwork(ctx, pluginConfig); err != nil {
errs = append(errs, fmt.Errorf("failed to GC plugin %s: %w", plugin.Network.Type, err))
}
}
}
return errors.Join(errs...)
}
func (c *CNIConfig) gcNetwork(ctx context.Context, net *PluginConfig) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
args := c.args("GC", &RuntimeConf{})
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
}
func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error {
// If the version doesn't support status, abort.
if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt {
return nil
}
inject := map[string]interface{}{
"name": list.Name,
"cniVersion": list.CNIVersion,
}
for _, plugin := range list.Plugins {
// build config here
pluginConfig, err := InjectConf(plugin, inject)
if err != nil {
return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err)
}
if err := c.getStatusNetwork(ctx, pluginConfig); err != nil {
return err // Don't collect errors here, so we return a clean error code.
}
}
return nil
}
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *PluginConfig) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
args := c.args("STATUS", &RuntimeConf{})
return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec)
}
// =====
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{

File diff suppressed because it is too large Load Diff

View File

@ -17,18 +17,18 @@ package libcni_test
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
)
var _ = Describe("Backwards compatibility", func() {
@ -36,7 +36,7 @@ var _ = Describe("Backwards compatibility", func() {
BeforeEach(func() {
var err error
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred())
})
@ -66,6 +66,9 @@ var _ = Describe("Backwards compatibility", func() {
Expect(result).To(Equal(legacy_examples.ExpectedResult))
err = cniConfig.DelNetwork(context.TODO(), netConf, runtimeConf)
Expect(err).NotTo(HaveOccurred())
Expect(os.RemoveAll(pluginPath)).To(Succeed())
})

View File

@ -16,11 +16,16 @@ package libcni
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
type NotFoundError struct {
@ -40,10 +45,17 @@ func (e NoConfigsFoundError) Error() string {
return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
}
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
conf := &NetworkConfig{Bytes: bytes}
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
// This will not validate that the plugins actually belong to the netconfig by ensuring
// that they are loaded from a directory named after the networkName, relative to the network config.
//
// Since here we are just accepting raw bytes, the caller is responsible for ensuring that the plugin
// config provided here actually "belongs" to the networkconfig in question.
func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) {
// TODO why are we creating a struct that holds both the byte representation and the deserialized
// representation, and returning that, instead of just returning the deserialized representation?
conf := &PluginConfig{Bytes: pluginConfBytes, Network: &types.PluginConf{}}
if err := json.Unmarshal(pluginConfBytes, conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %w", err)
}
if conf.Network.Type == "" {
return nil, fmt.Errorf("error parsing configuration: missing 'type'")
@ -51,18 +63,36 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return conf, nil
}
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := ioutil.ReadFile(filename)
// Given a path to a directory containing a network configuration, and the name of a network,
// loads all plugin definitions found at path `networkConfPath/networkName/*.conf`
func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) {
var pConfs []*PluginConfig
pluginConfPath := filepath.Join(networkConfPath, networkName)
pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"})
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", filename, err)
return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err)
}
return ConfFromBytes(bytes)
for _, pluginConfFile := range pluginConfFiles {
pluginConfBytes, err := os.ReadFile(pluginConfFile)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", pluginConfFile, err)
}
pluginConf, err := NetworkPluginConfFromBytes(pluginConfBytes)
if err != nil {
return nil, err
}
pConfs = append(pConfs, pluginConf)
}
return pConfs, nil
}
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
rawList := make(map[string]interface{})
if err := json.Unmarshal(bytes, &rawList); err != nil {
return nil, fmt.Errorf("error parsing configuration list: %s", err)
if err := json.Unmarshal(confBytes, &rawList); err != nil {
return nil, fmt.Errorf("error parsing configuration list: %w", err)
}
rawName, ok := rawList["name"]
@ -83,26 +113,115 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
}
}
disableCheck := false
if rawDisableCheck, ok := rawList["disableCheck"]; ok {
disableCheck, ok = rawDisableCheck.(bool)
rawVersions, ok := rawList["cniVersions"]
if ok {
// Parse the current package CNI version
rvs, ok := rawVersions.([]interface{})
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid disableCheck type %T", rawDisableCheck)
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs)
}
vs := make([]string, 0, len(rvs))
for i, rv := range rvs {
v, ok := rv.(string)
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv)
}
gt, err := version.GreaterThan(v, version.Current())
if err != nil {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err)
} else if !gt {
// Skip versions "greater" than this implementation of the spec
vs = append(vs, v)
}
}
// if cniVersion was already set, append it to the list for sorting.
if cniVersion != "" {
gt, err := version.GreaterThan(cniVersion, version.Current())
if err != nil {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err)
} else if !gt {
// ignore any versions higher than the current implemented spec version
vs = append(vs, cniVersion)
}
}
slices.SortFunc[[]string](vs, func(v1, v2 string) int {
if v1 == v2 {
return 0
}
if gt, _ := version.GreaterThan(v1, v2); gt {
return 1
}
return -1
})
if len(vs) > 0 {
cniVersion = vs[len(vs)-1]
}
}
readBool := func(key string) (bool, error) {
rawVal, ok := rawList[key]
if !ok {
return false, nil
}
if b, ok := rawVal.(bool); ok {
return b, nil
}
s, ok := rawVal.(string)
if !ok {
return false, fmt.Errorf("error parsing configuration list: invalid type %T for %s", rawVal, key)
}
s = strings.ToLower(s)
switch s {
case "false":
return false, nil
case "true":
return true, nil
}
return false, fmt.Errorf("error parsing configuration list: invalid value %q for %s", s, key)
}
disableCheck, err := readBool("disableCheck")
if err != nil {
return nil, err
}
disableGC, err := readBool("disableGC")
if err != nil {
return nil, err
}
loadOnlyInlinedPlugins, err := readBool("loadOnlyInlinedPlugins")
if err != nil {
return nil, err
}
list := &NetworkConfigList{
Name: name,
DisableCheck: disableCheck,
CNIVersion: cniVersion,
Bytes: bytes,
Name: name,
DisableCheck: disableCheck,
DisableGC: disableGC,
LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
CNIVersion: cniVersion,
Bytes: confBytes,
}
var plugins []interface{}
plug, ok := rawList["plugins"]
if !ok {
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key")
// We can have a `plugins` list key in the main conf,
// We can also have `loadOnlyInlinedPlugins == true`
//
// If `plugins` is there, then `loadOnlyInlinedPlugins` can be true
//
// If plugins is NOT there, then `loadOnlyInlinedPlugins` cannot be true
//
// We have to have at least some plugins.
if !ok && loadOnlyInlinedPlugins {
return nil, fmt.Errorf("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key")
} else if !ok && !loadOnlyInlinedPlugins {
return list, nil
}
plugins, ok = plug.([]interface{})
if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
@ -114,32 +233,76 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
for i, conf := range plugins {
newBytes, err := json.Marshal(conf)
if err != nil {
return nil, fmt.Errorf("failed to marshal plugin config %d: %v", i, err)
return nil, fmt.Errorf("failed to marshal plugin config %d: %w", i, err)
}
netConf, err := ConfFromBytes(newBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err)
return nil, fmt.Errorf("failed to parse plugin config %d: %w", i, err)
}
list.Plugins = append(list.Plugins, netConf)
}
return list, nil
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := ioutil.ReadFile(filename)
func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", filename, err)
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
return ConfListFromBytes(bytes)
conf, err := NetworkConfFromBytes(bytes)
if err != nil {
return nil, err
}
if !conf.LoadOnlyInlinedPlugins {
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
if err != nil {
return nil, err
}
conf.Plugins = append(conf.Plugins, plugins...)
}
if len(conf.Plugins) == 0 {
// Having 0 plugins for a given network is not necessarily a problem,
// but return as error for caller to decide, since they tried to load
return nil, fmt.Errorf("no plugin configs found")
}
return conf, nil
}
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return NetworkPluginConfFromBytes(bytes)
}
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
return ConfFromBytes(bytes)
}
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
return NetworkConfFromBytes(bytes)
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
return NetworkConfFromFile(filename)
}
// ConfFiles simply returns a slice of all files in the provided directory
// with extensions matching the provided set.
func ConfFiles(dir string, extensions []string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles
files, err := ioutil.ReadDir(dir)
files, err := os.ReadDir(dir)
switch {
case err == nil: // break
case os.IsNotExist(err):
// If folder not there, return no error - only return an
// error if we cannot read contents or there are no contents.
return nil, nil
default:
return nil, err
@ -160,6 +323,7 @@ func ConfFiles(dir string, extensions []string) ([]string, error) {
return confFiles, nil
}
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func LoadConf(dir, name string) (*NetworkConfig, error) {
files, err := ConfFiles(dir, []string{".conf", ".json"})
switch {
@ -183,6 +347,15 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
}
func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return LoadNetworkConf(dir, name)
}
// LoadNetworkConf looks at all the network configs in a given dir,
// loads and parses them all, and returns the first one with an extension of `.conf`
// that matches the provided network name predicate.
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
// TODO this .conflist/.conf extension thing is confusing and inexact
// for implementors. We should pick one extension for everything and stick with it.
files, err := ConfFiles(dir, []string{".conflist"})
if err != nil {
return nil, err
@ -190,7 +363,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
sort.Strings(files)
for _, confFile := range files {
conf, err := ConfListFromFile(confFile)
conf, err := NetworkConfFromFile(confFile)
if err != nil {
return nil, err
}
@ -199,12 +372,13 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
}
}
// Try and load a network configuration file (instead of list)
// Deprecated: Try and load a network configuration file (instead of list)
// from the same name, then upconvert.
singleConf, err := LoadConf(dir, name)
if err != nil {
// A little extra logic so the error makes sense
if _, ok := err.(NoConfigsFoundError); len(files) != 0 && ok {
var ncfErr NoConfigsFoundError
if len(files) != 0 && errors.As(err, &ncfErr) {
// Config lists found but no config files found
return nil, NotFoundError{dir, name}
}
@ -214,11 +388,12 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return ConfListFromConf(singleConf)
}
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) {
// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) {
config := make(map[string]interface{})
err := json.Unmarshal(original.Bytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
return nil, fmt.Errorf("unmarshal existing network bytes: %w", err)
}
for key, value := range newValues {
@ -238,12 +413,14 @@ func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*Net
return nil, err
}
return ConfFromBytes(newBytes)
return NetworkPluginConfFromBytes(newBytes)
}
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
// with the single network as the only entry in the list.
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
//
// Deprecated: Non-conflist file formats are unsupported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfListFromConf(original *PluginConfig) (*NetworkConfigList, error) {
// Re-deserialize the config's json, then make a raw map configlist.
// This may seem a bit strange, but it's to make the Bytes fields
// actually make sense. Otherwise, the generated json is littered with

View File

@ -15,14 +15,16 @@
package libcni_test
import (
"io/ioutil"
"fmt"
"os"
"path/filepath"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Loading configuration from disk", func() {
@ -34,11 +36,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
pluginConfig = []byte(`{ "name": "some-plugin", "type": "foobar", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0o600)).To(Succeed())
})
AfterEach(func() {
@ -48,8 +50,8 @@ var _ = Describe("Loading configuration from disk", func() {
It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{
Name: "some-plugin",
Type: "foobar",
},
@ -72,13 +74,13 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() {
Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed())
pluginConfig = []byte(`{ "name": "some-plugin", "some-key": "some-value", "type": "foobar" }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0o600)).To(Succeed())
})
It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{
Name: "some-plugin",
Type: "foobar",
},
@ -96,7 +98,7 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when a config file is malformed", func() {
BeforeEach(func() {
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0o600)).To(Succeed())
})
It("returns a useful error", func() {
@ -108,10 +110,10 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when the config is in a nested subdir", func() {
BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2")
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
Expect(os.MkdirAll(subdir, 0o700)).To(Succeed())
pluginConfig = []byte(`{ "name": "deep", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0o600)).To(Succeed())
})
It("will not find the config", func() {
@ -126,11 +128,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0o600)).To(Succeed())
})
AfterEach(func() {
@ -160,12 +162,12 @@ var _ = Describe("Loading configuration from disk", func() {
var fileName, configDir string
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
fileName = filepath.Join(configDir, "50-whatever.conf")
pluginConfig := []byte(`{ "name": "some-plugin", "some-key": "some-value" }`)
Expect(ioutil.WriteFile(fileName, pluginConfig, 0600)).To(Succeed())
Expect(os.WriteFile(fileName, pluginConfig, 0o600)).To(Succeed())
})
AfterEach(func() {
@ -179,7 +181,7 @@ var _ = Describe("Loading configuration from disk", func() {
})
})
Describe("ConfFromBytes", func() {
Describe("NetworkPluginConfFromBytes", func() {
Context("when the config is missing 'type'", func() {
It("returns a useful error", func() {
_, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`))
@ -188,7 +190,7 @@ var _ = Describe("Loading configuration from disk", func() {
})
})
Describe("LoadConfList", func() {
Describe("LoadNetworkConf", func() {
var (
configDir string
configList []byte
@ -196,11 +198,11 @@ var _ = Describe("Loading configuration from disk", func() {
BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "plugin-conf")
configDir, err = os.MkdirTemp("", "plugin-conf")
Expect(err).NotTo(HaveOccurred())
configList = []byte(`{
"name": "some-list",
"name": "some-network",
"cniVersion": "0.2.0",
"disableCheck": true,
"plugins": [
@ -218,7 +220,7 @@ var _ = Describe("Loading configuration from disk", func() {
}
]
}`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
})
AfterEach(func() {
@ -226,23 +228,23 @@ var _ = Describe("Loading configuration from disk", func() {
})
It("finds the network config file for the plugin of the given type", func() {
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{
Name: "some-list",
Name: "some-network",
CNIVersion: "0.2.0",
DisableCheck: true,
Plugins: []*libcni.NetworkConfig{
Plugins: []*libcni.PluginConfig{
{
Network: &types.NetConf{Type: "host-local"},
Network: &types.PluginConf{Type: "host-local"},
Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`),
},
{
Network: &types.NetConf{Type: "bridge"},
Network: &types.PluginConf{Type: "bridge"},
Bytes: []byte(`{"mtu":1400,"type":"bridge"}`),
},
{
Network: &types.NetConf{Type: "port-forwarding"},
Network: &types.PluginConf{Type: "port-forwarding"},
Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`),
},
},
@ -253,25 +255,25 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when there is a config file with the same name as the list", func() {
BeforeEach(func() {
configFile := []byte(`{
"name": "some-list",
"name": "some-network",
"cniVersion": "0.2.0",
"type": "bridge"
}`)
Expect(ioutil.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0o600)).To(Succeed())
})
It("Loads the config list first", func() {
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(len(netConfigList.Plugins)).To(Equal(3))
Expect(netConfigList.Plugins).To(HaveLen(3))
})
It("falls back to the config file", func() {
Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed())
netConfigList, err := libcni.LoadConfList(configDir, "some-list")
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(len(netConfigList.Plugins)).To(Equal(1))
Expect(netConfigList.Plugins).To(HaveLen(1))
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge"))
})
})
@ -282,25 +284,25 @@ var _ = Describe("Loading configuration from disk", func() {
})
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin")
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir}))
})
})
Context("when there is no config for the desired plugin list", func() {
Context("when there is no config for the desired network name", func() {
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-other-plugin")
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"}))
_, err := libcni.LoadNetworkConf(configDir, "some-other-network")
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-network"}))
})
})
Context("when a config file is malformed", func() {
BeforeEach(func() {
Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0o600)).To(Succeed())
})
It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin")
_, err := libcni.LoadNetworkConf(configDir, "some-plugin")
Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`))
})
})
@ -308,7 +310,7 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when the config is in a nested subdir", func() {
BeforeEach(func() {
subdir := filepath.Join(configDir, "subdir1", "subdir2")
Expect(os.MkdirAll(subdir, 0700)).To(Succeed())
Expect(os.MkdirAll(subdir, 0o700)).To(Succeed())
configList = []byte(`{
"name": "deep",
@ -320,37 +322,309 @@ var _ = Describe("Loading configuration from disk", func() {
},
]
}`)
Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0o600)).To(Succeed())
})
It("will not find the config", func() {
_, err := libcni.LoadConfList(configDir, "deep")
_, err := libcni.LoadNetworkConf(configDir, "deep")
Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
})
})
Context("when disableCheck is a string not a boolean", func() {
It("will read a 'true' value and convert to boolean", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"disableCheck": "true",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.DisableCheck).To(BeTrue())
})
It("will read a 'false' value and convert to boolean", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"disableCheck": "false",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.DisableCheck).To(BeFalse())
})
It("will return an error on an unrecognized value", func() {
const badValue string = "adsfasdfasf"
configList = []byte(fmt.Sprintf(`{
"name": "some-network",
"cniVersion": "0.4.0",
"disableCheck": "%s",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`, badValue))
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(fmt.Sprintf("error parsing configuration list: invalid value \"%s\" for disableCheck", badValue)))
})
})
Context("for loadOnlyInlinedPlugins", func() {
It("the value will be parsed", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true,
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
})
It("the value will be false if not in config", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
})
It("will return an error on an unrecognized value", func() {
const badValue string = "sphagnum"
configList = []byte(fmt.Sprintf(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": "%s",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`, badValue))
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(fmt.Sprintf(`error parsing configuration list: invalid value "%s" for loadOnlyInlinedPlugins`, badValue)))
})
It("will return an error if `plugins` is missing and `loadOnlyInlinedPlugins` is `true`", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key"))
})
It("will return no error if `plugins` is missing and `loadOnlyInlinedPlugins` is false", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": false
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
Expect(netConfigList.Plugins).To(HaveLen(1))
})
It("will return error if `loadOnlyInlinedPlugins` is implicitly false + no conf plugin is defined, but no plugins subfolder with network name exists", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError("no plugin configs found"))
})
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined, but no plugins subfolder with network name exists", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
})
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined and network name subfolder exists, but is empty/unreadable", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
})
It("will merge loaded and inlined plugin lists if both `plugins` is set and `loadOnlyInlinedPlugins` is false", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
Expect(netConfigList.Plugins).To(HaveLen(2))
})
It("will ignore loaded plugins if `plugins` is set and `loadOnlyInlinedPlugins` is true", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true,
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
Expect(netConfigList.Plugins).To(HaveLen(1))
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("host-local"))
})
})
})
Describe("ConfListFromFile", func() {
Describe("NetworkConfFromFile", func() {
Context("when the file cannot be opened", func() {
It("returns a useful error", func() {
_, err := libcni.ConfListFromFile("/tmp/nope/not-here")
_, err := libcni.NetworkConfFromFile("/tmp/nope/not-here")
Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
})
})
})
Describe("InjectConf", func() {
var testNetConfig *libcni.NetworkConfig
var testNetConfig *libcni.PluginConfig
BeforeEach(func() {
testNetConfig = &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin", Type: "foobar"},
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`)}
testNetConfig = &libcni.PluginConfig{
Network: &types.PluginConf{Name: "some-plugin", Type: "foobar"},
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`),
}
})
Context("when function parameters are incorrect", func() {
It("returns unmarshal error", func() {
conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`)}
conf := &libcni.PluginConfig{
Network: &types.PluginConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`),
}
_, err := libcni.InjectConf(conf, map[string]interface{}{"": nil})
Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`)))
@ -373,8 +647,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{
Name: "some-plugin",
Type: "foobar",
},
@ -391,8 +665,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{
Name: "some-plugin",
Type: "foobar",
},
@ -409,8 +683,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{
Name: "some-plugin",
Type: "foobar",
},
@ -419,7 +693,6 @@ var _ = Describe("Loading configuration from disk", func() {
})
It("adds sub-fields of NetworkConfig.Network to the config", func() {
expectedPluginConfig := []byte(`{"dns":{"domain":"local","nameservers":["server1","server2"]},"name":"some-plugin","type":"bridge"}`)
servers := []string{"server1", "server2"}
newDNS := &types.DNS{Nameservers: servers, Domain: "local"}
@ -432,8 +705,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{
Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.PluginConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
Bytes: expectedPluginConfig,
}))
})
@ -441,8 +714,55 @@ var _ = Describe("Loading configuration from disk", func() {
})
})
var _ = Describe("NetworkConfFromBytes", func() {
Describe("Version selection", func() {
makeConfig := func(versions ...string) []byte {
// ugly fake json encoding, but whatever
vs := []string{}
for _, v := range versions {
vs = append(vs, fmt.Sprintf(`"%s"`, v))
}
return []byte(fmt.Sprintf(`{"name": "test", "cniVersions": [%s], "plugins": [{"type": "foo"}]}`, strings.Join(vs, ",")))
}
It("correctly selects the maximum version", func() {
conf, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.0"))
Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.1.0"))
})
It("selects the highest version supported by libcni", func() {
conf, err := libcni.NetworkConfFromBytes(makeConfig("99.0.0", "1.1.0", "0.4.0", "1.0.0"))
Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.1.0"))
})
It("fails when invalid versions are specified", func() {
_, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.f"))
Expect(err).To(HaveOccurred())
})
It("falls back to cniVersion", func() {
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersion": "1.2.3", "plugins": [{"type": "foo"}]}`))
Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.2.3"))
})
It("merges cniVersions and cniVersion", func() {
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersion": "1.0.0", "cniVersions": ["0.1.0", "0.4.0"], "plugins": [{"type": "foo"}]}`))
Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.0.0"))
})
It("handles an empty cniVersions array", func() {
conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersions": [], "plugins": [{"type": "foo"}]}`))
Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal(""))
})
})
})
var _ = Describe("ConfListFromConf", func() {
var testNetConfig *libcni.NetworkConfig
var testNetConfig *libcni.PluginConfig
BeforeEach(func() {
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`)
@ -464,16 +784,15 @@ var _ = Describe("ConfListFromConf", func() {
Expect(ncl).To(Equal(&libcni.NetworkConfigList{
Name: "some-plugin",
CNIVersion: "0.3.1",
Plugins: []*libcni.NetworkConfig{testNetConfig},
Plugins: []*libcni.PluginConfig{testNetConfig},
}))
//Test that the json unmarshals to the same data
ncl2, err := libcni.ConfListFromBytes(bytes)
// Test that the json unmarshals to the same data
ncl2, err := libcni.NetworkConfFromBytes(bytes)
Expect(err).NotTo(HaveOccurred())
ncl2.Bytes = nil
ncl2.Plugins[0].Bytes = nil
Expect(ncl2).To(Equal(ncl))
})
})

View File

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

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

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

View File

@ -17,17 +17,17 @@ package invoke_test
import (
"context"
"encoding/json"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/plugins/test/noop/debug"
)
var _ = Describe("Delegate", func() {
@ -43,14 +43,13 @@ var _ = Describe("Delegate", func() {
BeforeEach(func() {
netConf, _ = json.Marshal(map[string]string{
"name": "delegate-test",
"cniVersion": "0.4.0",
"cniVersion": version.Current(),
})
expectedResult = &current.Result{
CNIVersion: "0.4.0",
CNIVersion: current.ImplementedSpecVersion,
IPs: []*current.IPConfig{
{
Version: "4",
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.CIDRMask(24, 32),
@ -60,7 +59,7 @@ var _ = Describe("Delegate", func() {
}
expectedResultBytes, _ := json.Marshal(expectedResult)
debugFile, err := ioutil.TempFile("", "cni_debug")
debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name()
@ -224,4 +223,48 @@ var _ = Describe("Delegate", func() {
})
})
})
Describe("DelegateStatus", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "STATUS")
})
It("finds and execs the named plugin", func() {
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("STATUS"))
})
Context("if the STATUS delegation runs on an existing non-STATUS command", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE")
})
It("aborts and returns a useful error", func() {
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("STATUS"))
// check the original env
Expect(os.Getenv("CNI_COMMAND")).To(Equal("NOPE"))
})
})
Context("when the plugin cannot be found", func() {
BeforeEach(func() {
pluginName = "non-existent-plugin"
})
It("returns a useful error", func() {
err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
})

View File

@ -16,10 +16,12 @@ package invoke
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/create"
"github.com/containernetworking/cni/pkg/version"
)
@ -32,21 +34,64 @@ type Exec interface {
Decode(jsonBytes []byte) (version.PluginInfo, error)
}
// Plugin must return result in same version as specified in netconf; but
// for backwards compatibility reasons if the result version is empty use
// config version (rather than technically correct 0.1.0).
// https://github.com/containernetworking/cni/issues/895
func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(netconf)
if err != nil {
return "", nil, err
}
var rawResult map[string]interface{}
if err := json.Unmarshal(result, &rawResult); err != nil {
return "", nil, fmt.Errorf("failed to unmarshal raw result: %w", err)
}
// plugin output of "null" is successfully unmarshalled, but results in a nil
// map which causes a panic when the confVersion is assigned below.
if rawResult == nil {
rawResult = make(map[string]interface{})
}
// Manually decode Result version; we need to know whether its cniVersion
// is empty, while built-in decoders (correctly) substitute 0.1.0 for an
// empty version per the CNI spec.
if resultVerRaw, ok := rawResult["cniVersion"]; ok {
resultVer, ok := resultVerRaw.(string)
if ok && resultVer != "" {
return resultVer, result, nil
}
}
// If the cniVersion is not present or empty, assume the result is
// the same CNI spec version as the config
rawResult["cniVersion"] = confVersion
newBytes, err := json.Marshal(rawResult)
if err != nil {
return "", nil, fmt.Errorf("failed to remarshal fixed result: %w", err)
}
return confVersion, newBytes, nil
}
// For example, a testcase could pass an instance of the following fakeExec
// object to ExecPluginWithResult() to verify the incoming stdin and environment
// and provide a tailored response:
//
//import (
// import (
// "encoding/json"
// "path"
// "strings"
//)
// )
//
//type fakeExec struct {
// type fakeExec struct {
// version.PluginDecoder
//}
// }
//
//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
// net := &types.NetConf{}
// err := json.Unmarshal(stdinData, net)
// if err != nil {
@ -64,14 +109,14 @@ type Exec interface {
// }
// }
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
//}
// }
//
//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
// func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
// if len(paths) > 0 {
// return path.Join(paths[0], plugin), nil
// }
// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths)
//}
// }
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil {
@ -83,14 +128,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte
return nil, err
}
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(netconf)
resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes)
if err != nil {
return nil, err
}
return version.NewResult(confVersion, stdoutBytes)
return create.Create(resultVersion, fixedBytes)
}
func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {

View File

@ -19,13 +19,13 @@ import (
"encoding/json"
"errors"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/invoke/fakes"
"github.com/containernetworking/cni/pkg/types/current"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Executing a plugin, unit tests", func() {
@ -42,7 +42,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
BeforeEach(func() {
rawExec = &fakes.RawExec{}
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "0.3.1", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
versionDecoder = &fakes.VersionDecoder{}
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
@ -68,12 +68,13 @@ var _ = Describe("Executing a plugin, unit tests", func() {
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs).To(HaveLen(1))
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
})
It("passes its arguments through to the rawExec", func() {
invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
_, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
@ -96,11 +97,44 @@ var _ = Describe("Executing a plugin, unit tests", func() {
_, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, nil)
Expect(err).To(HaveOccurred())
})
It("assumes config version if result version is missing", func() {
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(r.Version()).To(Equal("0.3.1"))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(1))
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
})
It("assumes config version if result version is empty", func() {
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(r.Version()).To(Equal("0.3.1"))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(1))
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
})
It("assumes 0.1.0 if config and result version are empty", func() {
netconf = []byte(`{ "some": "stdin" }`)
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(r.Version()).To(Equal("0.1.0"))
})
})
Describe("without returning a result", func() {
It("passes its arguments through to the rawExec", func() {
invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
@ -131,7 +165,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
})
It("execs the plugin with the command VERSION", func() {
invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
_, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()})
@ -167,7 +202,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
})
It("sets dummy values for env vars required by very old plugins", func() {
invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
_, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred())
env := rawExec.ExecPluginCall.Received.Environ
Expect(env).To(ContainElement("CNI_NETNS=dummy"))

View File

@ -18,6 +18,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
)
// FindInPath returns the full path of the plugin by searching in the provided path
@ -26,6 +27,10 @@ func FindInPath(plugin string, paths []string) (string, error) {
return "", fmt.Errorf("no plugin name provided")
}
if strings.ContainsRune(plugin, os.PathSeparator) {
return "", fmt.Errorf("invalid plugin name: %s", plugin)
}
if len(paths) == 0 {
return "", fmt.Errorf("no paths provided")
}

View File

@ -16,14 +16,14 @@ package invoke_test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
)
var _ = Describe("FindInPath", func() {
@ -37,17 +37,17 @@ var _ = Describe("FindInPath", func() {
)
BeforeEach(func() {
tempDir, err := ioutil.TempDir("", "cni-find")
tempDir, err := os.MkdirTemp("", "cni-find")
Expect(err).NotTo(HaveOccurred())
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin")
plugin, err := os.CreateTemp(tempDir, "a-cni-plugin")
Expect(err).NotTo(HaveOccurred())
plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0]
plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name))
Expect(err).NotTo(HaveOccurred())
anotherTempDir, err = ioutil.TempDir("", "nothing-here")
anotherTempDir, err = os.MkdirTemp("", "nothing-here")
Expect(err).NotTo(HaveOccurred())
multiplePaths = []string{anotherTempDir, tempDir}
@ -99,5 +99,13 @@ var _ = Describe("FindInPath", func() {
Expect(err).To(MatchError(fmt.Sprintf("failed to find plugin %q in path %s", pluginName, pathsWithNothing)))
})
})
Context("When the plugin contains a directory separator", func() {
It("returns an error", func() {
bogusPlugin := ".." + string(os.PathSeparator) + "pluginname"
_, err := invoke.FindInPath(bogusPlugin, []string{anotherTempDir})
Expect(err).To(MatchError("invalid plugin name: " + bogusPlugin))
})
})
})
})

View File

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

View File

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

View File

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

View File

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

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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version"
@ -34,12 +35,13 @@ import (
// CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin
type CmdArgs struct {
ContainerID string
Netns string
IfName string
Args string
Path string
StdinData []byte
ContainerID string
Netns string
IfName string
Args string
Path string
NetnsOverride string
StdinData []byte
}
type dispatcher struct {
@ -55,21 +57,25 @@ type dispatcher struct {
type reqForCmdEntry map[string]bool
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
var cmd, contID, netns, ifName, args, path string
var cmd, contID, netns, ifName, args, path, netnsOverride string
vars := []struct {
name string
val *string
reqForCmd reqForCmdEntry
name string
val *string
reqForCmd reqForCmdEntry
validateFn func(string) *types.Error
}{
{
"CNI_COMMAND",
&cmd,
reqForCmdEntry{
"ADD": true,
"CHECK": true,
"DEL": true,
"ADD": true,
"CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
},
nil,
},
{
"CNI_CONTAINERID",
@ -79,6 +85,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": true,
},
utils.ValidateContainerID,
},
{
"CNI_NETNS",
@ -88,6 +95,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": false,
},
nil,
},
{
"CNI_IFNAME",
@ -97,6 +105,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": true,
"DEL": true,
},
utils.ValidateInterfaceName,
},
{
"CNI_ARGS",
@ -106,15 +115,29 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
"CHECK": false,
"DEL": false,
},
nil,
},
{
"CNI_PATH",
&path,
reqForCmdEntry{
"ADD": true,
"CHECK": true,
"DEL": true,
"ADD": true,
"CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
},
nil,
},
{
"CNI_NETNS_OVERRIDE",
&netnsOverride,
reqForCmdEntry{
"ADD": false,
"CHECK": false,
"DEL": false,
},
nil,
},
}
@ -125,6 +148,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
argsMissing = append(argsMissing, v.name)
}
} else if v.reqForCmd[cmd] && v.validateFn != nil {
if err := v.validateFn(*v.val); err != nil {
return "", nil, err
}
}
}
@ -137,18 +164,25 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
t.Stdin = bytes.NewReader(nil)
}
stdinData, err := ioutil.ReadAll(t.Stdin)
stdinData, err := io.ReadAll(t.Stdin)
if err != nil {
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
}
if cmd != "VERSION" {
if err := validateConfig(stdinData); err != nil {
return "", nil, err
}
}
cmdArgs := &CmdArgs{
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
NetnsOverride: netnsOverride,
}
return cmd, cmdArgs, nil
}
@ -163,8 +197,13 @@ func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo ver
return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
}
if toCall == nil {
return nil
}
if err = toCall(cmdArgs); err != nil {
if e, ok := err.(*types.Error); ok {
var e *types.Error
if errors.As(err, &e) {
// don't wrap Error in Error
return e
}
@ -190,32 +229,32 @@ func validateConfig(jsonBytes []byte) *types.Error {
return nil
}
func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
// Print the about string to stderr when no command is set
if err.Code == types.ErrInvalidEnvironmentVariables && t.Getenv("CNI_COMMAND") == "" && about != "" {
_, _ = fmt.Fprintln(t.Stderr, about)
_, _ = fmt.Fprintf(t.Stderr, "CNI protocol versions supported: %s\n", strings.Join(versionInfo.SupportedVersions(), ", "))
return nil
}
return err
}
if cmd != "VERSION" {
if err = validateConfig(cmdArgs.StdinData); err != nil {
return err
}
if err = utils.ValidateContainerID(cmdArgs.ContainerID); err != nil {
return err
}
if err = utils.ValidateInterfaceName(cmdArgs.IfName); err != nil {
return err
}
}
switch cmd {
case "ADD":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Add)
if err != nil {
return err
}
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
if checkErr != nil {
return checkErr
} else if isPluginNetNS {
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
}
}
case "CHECK":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
@ -231,7 +270,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Check); err != nil {
return err
}
return nil
@ -239,7 +278,62 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
case "DEL":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
err = t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Del)
if err != nil {
return err
}
if strings.ToUpper(cmdArgs.NetnsOverride) != "TRUE" && cmdArgs.NetnsOverride != "1" {
isPluginNetNS, checkErr := ns.CheckNetNS(cmdArgs.Netns)
if checkErr != nil {
return checkErr
} else if isPluginNetNS {
return types.NewError(types.ErrInvalidNetNS, "plugin's netns and netns from CNI_NETNS should not be the same", "")
}
}
case "GC":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow GC", "")
}
for _, pluginVersion := range versionInfo.SupportedVersions() {
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.GC); err != nil {
return err
}
return nil
}
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow GC", "")
case "STATUS":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow STATUS", "")
}
for _, pluginVersion := range versionInfo.SupportedVersions() {
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
if err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Status); err != nil {
return err
}
return nil
}
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow STATUS", "")
case "VERSION":
if err := versionInfo.Encode(t.Stdout); err != nil {
return types.NewError(types.ErrIOFailure, err.Error(), "")
@ -248,10 +342,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
return types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("unknown CNI_COMMAND: %v", cmd), "")
}
if err != nil {
return err
}
return nil
return err
}
// PluginMainWithError is the core "main" for a plugin. It accepts
@ -266,13 +357,63 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
//
// To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMain() instead.
//
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncsWithError instead.
func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error {
return PluginMainFuncsWithError(CNIFuncs{Add: cmdAdd, Check: cmdCheck, Del: cmdDel}, versionInfo, about)
}
// CNIFuncs contains a group of callback command funcs to be passed in as
// parameters to the core "main" for a plugin.
type CNIFuncs struct {
Add func(_ *CmdArgs) error
Del func(_ *CmdArgs) error
Check func(_ *CmdArgs) error
GC func(_ *CmdArgs) error
Status func(_ *CmdArgs) error
}
// PluginMainFuncsWithError is the core "main" for a plugin. It accepts
// callback functions defined within CNIFuncs and returns an error.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// It is the responsibility of the caller to check for non-nil error return.
//
// For a plugin to comply with the CNI spec, it must print any error to stdout
// as JSON and then exit with nonzero status code.
//
// To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMainFuncs() instead.
func PluginMainFuncsWithError(funcs CNIFuncs, versionInfo version.PluginInfo, about string) *types.Error {
return (&dispatcher{
Getenv: os.Getenv,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}).pluginMain(cmdAdd, cmdCheck, cmdDel, versionInfo, about)
}).pluginMain(funcs, versionInfo, about)
}
// PluginMainFuncs is the core "main" for a plugin which includes automatic error handling.
// This is a newer alternative func to PluginMain which abstracts CNI commands within a
// CNIFuncs interface.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// The caller can specify an "about" string, which is printed on stderr
// when no CNI_COMMAND is specified. The recommended output is "CNI plugin <foo> v<version>"
//
// When an error occurs in any func in CNIFuncs, PluginMainFuncs will print the error
// as JSON to stdout and call os.Exit(1).
//
// To have more control over error handling, use PluginMainFuncsWithError() instead.
func PluginMainFuncs(funcs CNIFuncs, versionInfo version.PluginInfo, about string) {
if e := PluginMainFuncsWithError(funcs, versionInfo, about); e != nil {
if err := e.Print(); err != nil {
log.Print("Error writing error JSON to stdout: ", err)
}
os.Exit(1)
}
}
// PluginMain is the core "main" for a plugin which includes automatic error handling.
@ -286,6 +427,8 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio
// as JSON to stdout and call os.Exit(1).
//
// To have more control over error handling, use PluginMainWithError() instead.
//
// Deprecated: Use github.com/containernetworking/cni/pkg/skel.PluginMainFuncs instead.
func PluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) {
if e := PluginMainWithError(cmdAdd, cmdCheck, cmdDel, versionInfo, about); e != nil {
if err := e.Print(); err != nil {

View File

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

View File

@ -20,13 +20,11 @@ import (
"fmt"
"strings"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
type fakeCmd struct {
@ -47,13 +45,14 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
var _ = Describe("dispatching to the correct callback", func() {
var (
environment map[string]string
stdinData string
stdout, stderr *bytes.Buffer
cmdAdd, cmdCheck, cmdDel *fakeCmd
dispatch *dispatcher
expectedCmdArgs *CmdArgs
versionInfo version.PluginInfo
environment map[string]string
stdinData string
stdout, stderr *bytes.Buffer
cmdAdd, cmdCheck, cmdDel, cmdGC *fakeCmd
dispatch *dispatcher
expectedCmdArgs *CmdArgs
versionInfo version.PluginInfo
funcs CNIFuncs
)
BeforeEach(func() {
@ -69,7 +68,7 @@ var _ = Describe("dispatching to the correct callback", func() {
stdinData = `{ "name":"skel-test", "some": "config", "cniVersion": "9.8.7" }`
stdout = &bytes.Buffer{}
stderr = &bytes.Buffer{}
versionInfo = version.PluginSupports("9.8.7")
versionInfo = version.PluginSupports("9.8.7", "10.0.0")
dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] },
Stdin: strings.NewReader(stdinData),
@ -79,6 +78,14 @@ var _ = Describe("dispatching to the correct callback", func() {
cmdAdd = &fakeCmd{}
cmdCheck = &fakeCmd{}
cmdDel = &fakeCmd{}
cmdGC = &fakeCmd{}
funcs = CNIFuncs{
Add: cmdAdd.Func,
Del: cmdDel.Func,
Check: cmdCheck.Func,
GC: cmdGC.Func,
}
expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
@ -89,10 +96,10 @@ var _ = Describe("dispatching to the correct callback", func() {
}
})
var envVarChecker = func(envVar string, isRequired bool) {
envVarChecker := func(envVar string, isRequired bool) {
delete(environment, envVar)
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
if isRequired {
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -104,8 +111,17 @@ var _ = Describe("dispatching to the correct callback", func() {
}
Context("when the CNI_COMMAND is ADD", func() {
expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "eth0",
Args: "some;extra;args",
Path: "/some/cni/path",
StdinData: []byte(stdinData),
}
It("extracts env vars and stdin data and calls cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1))
@ -116,7 +132,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("returns an error when containerID has invalid characters", func() {
environment["CNI_CONTAINERID"] = "some-%%container-id"
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -129,7 +145,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is too long", func() {
environment["CNI_IFNAME"] = "1234567890123456"
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -141,7 +157,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is .", func() {
environment["CNI_IFNAME"] = "."
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -153,7 +169,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name is ..", func() {
environment["CNI_IFNAME"] = ".."
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -165,7 +181,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters /", func() {
environment["CNI_IFNAME"] = "test/test"
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -177,7 +193,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters :", func() {
environment["CNI_IFNAME"] = "test:test"
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -189,7 +205,7 @@ var _ = Describe("dispatching to the correct callback", func() {
It("interface name contains invalid characters whitespace", func() {
environment["CNI_IFNAME"] = "test test"
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -200,7 +216,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("does not call cmdCheck or cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdCheck.CallCount).To(Equal(0))
@ -224,7 +240,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("reports that all of them are missing, not just the first", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
@ -246,8 +262,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1))
@ -261,14 +276,15 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("immediately returns a useful error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("incompatible CNI versions"))
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
})
It("does not call either callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
@ -283,7 +299,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("extracts env vars and stdin data and calls cmdCheck", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
@ -293,7 +309,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
@ -317,7 +333,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("reports that all of them are missing, not just the first", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
@ -330,8 +346,8 @@ var _ = Describe("dispatching to the correct callback", func() {
Context("when cniVersion is less than 0.4.0", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("config version does not allow CHECK"))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
@ -343,8 +359,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.4.0", "some": "config" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("plugin version does not allow CHECK"))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
@ -356,8 +372,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
@ -368,8 +384,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns invalid network config", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.4.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrInvalidNetworkConfig)))
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
@ -380,8 +396,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
@ -389,13 +405,132 @@ var _ = Describe("dispatching to the correct callback", func() {
})
})
Context("when the CNI_COMMAND is GC", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "GC"
delete(environment, "CNI_NETNS")
delete(environment, "CNI_IFNAME")
delete(environment, "CNI_CONTAINERID")
delete(environment, "CNI_ARGS")
expectedCmdArgs = &CmdArgs{
Path: "/some/cni/path",
StdinData: []byte(stdinData),
}
dispatch = &dispatcher{
Getenv: func(key string) string {
return environment[key]
},
Stdin: strings.NewReader(stdinData),
Stdout: stdout,
Stderr: stderr,
}
})
It("extracts env vars and stdin data and calls cmdGC", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(cmdGC.CallCount).To(Equal(1))
Expect(cmdGC.Received.CmdArgs).To(Equal(expectedCmdArgs))
})
It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINERID", false),
Entry("net ns", "CNI_NETNS", false),
Entry("if name", "CNI_IFNAME", false),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", true),
)
Context("when multiple required env vars are missing", func() {
BeforeEach(func() {
delete(environment, "CNI_PATH")
})
It("reports that all of them are missing, not just the first", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_PATH] missing",
}))
})
})
Context("when cniVersion is less than 1.1.0", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("config version does not allow GC"))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when plugin does not support 1.1.0", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "1.1.0", "some": "config" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("plugin version does not allow GC"))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the config has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("1.1.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the config has a bad name", func() {
It("immediately returns invalid network config", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
versionInfo = version.PluginSupports("1.1.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the plugin has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "1.1.0", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
})
Context("when the CNI_COMMAND is DEL", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "DEL"
})
It("calls cmdDel with the env vars and stdin data", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(1))
@ -403,7 +538,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("does not call cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
@ -425,17 +560,17 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("prints the version to stdout", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"cniVersion": "%s",
"supportedVersions": ["9.8.7"]
}`, current.ImplementedSpecVersion)))
"supportedVersions": ["9.8.7", "10.0.0"]
}`, version.Current())))
})
It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
@ -455,14 +590,14 @@ var _ = Describe("dispatching to the correct callback", func() {
r := &BadReader{}
dispatch.Stdin = r
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(r.ReadCount).To(Equal(0))
Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"cniVersion": "%s",
"supportedVersions": ["9.8.7"]
}`, current.ImplementedSpecVersion)))
"supportedVersions": ["9.8.7", "10.0.0"]
}`, version.Current())))
})
})
@ -472,14 +607,14 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
It("returns an error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
@ -489,7 +624,8 @@ var _ = Describe("dispatching to the correct callback", func() {
It("prints the about string when the command is blank", func() {
environment["CNI_COMMAND"] = ""
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "test framework v42")
err := dispatch.pluginMain(funcs, versionInfo, "test framework v42")
Expect(err).NotTo(HaveOccurred())
Expect(stderr.String()).To(ContainSubstring("test framework v42"))
})
})
@ -497,18 +633,18 @@ var _ = Describe("dispatching to the correct callback", func() {
Context("when the CNI_COMMAND is missing", func() {
It("prints the about string to stderr", func() {
environment = map[string]string{}
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "AWESOME PLUGIN")
err := dispatch.pluginMain(funcs, versionInfo, "AWESOME PLUGIN")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
log := stderr.String()
Expect(log).To(Equal("AWESOME PLUGIN\n"))
Expect(log).To(Equal("AWESOME PLUGIN\nCNI protocol versions supported: 9.8.7, 10.0.0\n"))
})
It("fails if there is no about string", func() {
environment = map[string]string{}
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
@ -526,14 +662,14 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{
Code: types.ErrIOFailure,
@ -552,7 +688,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("returns the error as-is", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{
Code: 1234,
@ -567,7 +703,7 @@ var _ = Describe("dispatching to the correct callback", func() {
})
It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{
Code: types.ErrInternal,

View File

@ -22,25 +22,47 @@ import (
"os"
"github.com/containernetworking/cni/pkg/types"
convert "github.com/containernetworking/cni/pkg/types/internal"
)
const ImplementedSpecVersion string = "0.2.0"
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
var supportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
// Register converters for all versions less than the implemented spec version
func init() {
convert.RegisterConverter("0.1.0", []string{ImplementedSpecVersion}, convertFrom010)
convert.RegisterConverter(ImplementedSpecVersion, []string{"0.1.0"}, convertTo010)
// Creator
convert.RegisterCreator(supportedVersions, NewResult)
}
// Compatibility types for CNI version 0.1.0 and 0.2.0
// NewResult creates a new Result object from JSON data. The JSON data
// must be compatible with the CNI versions implemented by this type.
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
for _, v := range supportedVersions {
if result.CNIVersion == v {
if result.CNIVersion == "" {
result.CNIVersion = "0.1.0"
}
return result, nil
}
}
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
supportedVersions, result.CNIVersion)
}
// GetResult converts the given Result object to the ImplementedSpecVersion
// and returns the concrete type or an error
func GetResult(r types.Result) (*Result, error) {
// We expect version 0.1.0/0.2.0 results
result020, err := r.GetAsVersion(ImplementedSpecVersion)
result020, err := convert.Convert(r, ImplementedSpecVersion)
if err != nil {
return nil, err
}
@ -51,6 +73,32 @@ func GetResult(r types.Result) (*Result, error) {
return result, nil
}
func convertFrom010(from types.Result, toVersion string) (types.Result, error) {
if toVersion != "0.2.0" {
panic("only converts to version 0.2.0")
}
fromResult := from.(*Result)
return &Result{
CNIVersion: ImplementedSpecVersion,
IP4: fromResult.IP4.Copy(),
IP6: fromResult.IP6.Copy(),
DNS: *fromResult.DNS.Copy(),
}, nil
}
func convertTo010(from types.Result, toVersion string) (types.Result, error) {
if toVersion != "0.1.0" {
panic("only converts to version 0.1.0")
}
fromResult := from.(*Result)
return &Result{
CNIVersion: "0.1.0",
IP4: fromResult.IP4.Copy(),
IP6: fromResult.IP6.Copy(),
DNS: *fromResult.DNS.Copy(),
}, nil
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
@ -60,17 +108,16 @@ type Result struct {
}
func (r *Result) Version() string {
return ImplementedSpecVersion
return r.CNIVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
for _, supportedVersion := range SupportedVersions {
if version == supportedVersion {
r.CNIVersion = version
return r, nil
}
// If the creator of the result did not set the CNIVersion, assume it
// should be the highest spec version implemented by this Result
if r.CNIVersion == "" {
r.CNIVersion = ImplementedSpecVersion
}
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
return convert.Convert(r, version)
}
func (r *Result) Print() error {
@ -93,6 +140,22 @@ type IPConfig struct {
Routes []types.Route
}
func (i *IPConfig) Copy() *IPConfig {
if i == nil {
return nil
}
var routes []types.Route
for _, fromRoute := range i.Routes {
routes = append(routes, *fromRoute.Copy())
}
return &IPConfig{
IP: i.IP,
Gateway: i.Gateway,
Routes: routes,
}
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type

View File

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

View File

@ -15,79 +15,64 @@
package types020_test
import (
"io/ioutil"
"encoding/json"
"fmt"
"net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types/create"
)
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
It("correctly encodes a 0.1.0/0.2.0 Result", func() {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil())
Expect(routegwv4).NotTo(BeNil())
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil())
Expect(routegwv4).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil())
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility
res := types020.Result{
CNIVersion: types020.ImplementedSpecVersion,
IP4: &types020.IPConfig{
IP: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
Routes: []types.Route{
{Dst: *routev4, GW: routegwv4},
},
// Set every field of the struct to ensure source compatibility
res := &types020.Result{
CNIVersion: resultCNIVersion,
IP4: &types020.IPConfig{
IP: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
Routes: []types.Route{
{Dst: *routev4, GW: routegwv4},
},
IP6: &types020.IPConfig{
IP: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
Routes: []types.Route{
{Dst: *routev6, GW: routegwv6},
},
},
IP6: &types020.IPConfig{
IP: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
Routes: []types.Route{
{Dst: *routev6, GW: routegwv6},
},
DNS: types.DNS{
Nameservers: []string{"1.2.3.4", "1::cafe"},
Domain: "acompany.com",
Search: []string{"somedomain.com", "otherdomain.net"},
Options: []string{"foo", "bar"},
},
}
},
DNS: types.DNS{
Nameservers: []string{"1.2.3.4", "1::cafe"},
Domain: "acompany.com",
Search: []string{"somedomain.com", "otherdomain.net"},
Options: []string{"foo", "bar"},
},
}
// Redirect stdout to capture JSON result
oldStdout := os.Stdout
r, w, err := os.Pipe()
Expect(err).NotTo(HaveOccurred())
os.Stdout = w
err = res.Print()
w.Close()
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(Equal(`{
"cniVersion": "0.2.0",
json := fmt.Sprintf(`{
"cniVersion": "%s",
"ip4": {
"ip": "1.2.3.30/24",
"gateway": "1.2.3.1",
@ -123,6 +108,50 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
"bar"
]
}
}`))
}`, jsonCNIVersion)
return res, json
}
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
It("correctly encodes a 0.2.0 Result", func() {
res, expectedJSON := testResult(types020.ImplementedSpecVersion, types020.ImplementedSpecVersion)
out, err := json.Marshal(res)
Expect(err).NotTo(HaveOccurred())
Expect(out).To(MatchJSON(expectedJSON))
})
It("correctly encodes a 0.1.0 Result", func() {
res, expectedJSON := testResult("0.1.0", "0.1.0")
out, err := json.Marshal(res)
Expect(err).NotTo(HaveOccurred())
Expect(out).To(MatchJSON(expectedJSON))
})
It("converts a 0.2.0 result to 0.1.0", func() {
res, expectedJSON := testResult(types020.ImplementedSpecVersion, "0.1.0")
res010, err := res.GetAsVersion("0.1.0")
Expect(err).NotTo(HaveOccurred())
out, err := json.Marshal(res010)
Expect(err).NotTo(HaveOccurred())
Expect(out).To(MatchJSON(expectedJSON))
})
It("converts a 0.1.0 result to 0.2.0", func() {
res, expectedJSON := testResult("0.1.0", types020.ImplementedSpecVersion)
res020, err := res.GetAsVersion(types020.ImplementedSpecVersion)
Expect(err).NotTo(HaveOccurred())
out, err := json.Marshal(res020)
Expect(err).NotTo(HaveOccurred())
Expect(out).To(MatchJSON(expectedJSON))
})
It("creates a 0.1.0 result passing CNIVersion ''", func() {
_, expectedJSON := testResult("", "")
resT, err := create.Create("", []byte(expectedJSON))
Expect(err).NotTo(HaveOccurred())
res010, ok := resT.(*types020.Result)
Expect(ok).To(BeTrue())
Expect(res010.CNIVersion).To(Equal("0.1.0"))
})
})

306
pkg/types/040/types.go Normal file
View File

@ -0,0 +1,306 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types040
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
convert "github.com/containernetworking/cni/pkg/types/internal"
)
const ImplementedSpecVersion string = "0.4.0"
var supportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
// Register converters for all versions less than the implemented spec version
func init() {
// Up-converters
convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x)
convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x)
convert.RegisterConverter("0.3.0", supportedVersions, convertInternal)
convert.RegisterConverter("0.3.1", supportedVersions, convertInternal)
// Down-converters
convert.RegisterConverter("0.4.0", []string{"0.3.0", "0.3.1"}, convertInternal)
convert.RegisterConverter("0.4.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("0.3.1", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("0.3.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
// Creator
convert.RegisterCreator(supportedVersions, NewResult)
}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
for _, v := range supportedVersions {
if result.CNIVersion == v {
return result, nil
}
}
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
supportedVersions, result.CNIVersion)
}
func GetResult(r types.Result) (*Result, error) {
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
func NewResultFromResult(result types.Result) (*Result, error) {
newResult, err := convert.Convert(result, ImplementedSpecVersion)
if err != nil {
return nil, err
}
return newResult.(*Result), nil
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
Interfaces []*Interface `json:"interfaces,omitempty"`
IPs []*IPConfig `json:"ips,omitempty"`
Routes []*types.Route `json:"routes,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
func convert020IPConfig(from *types020.IPConfig, ipVersion string) *IPConfig {
return &IPConfig{
Version: ipVersion,
Address: from.IP,
Gateway: from.Gateway,
}
}
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*types020.Result)
toResult := &Result{
CNIVersion: toVersion,
DNS: *fromResult.DNS.Copy(),
Routes: []*types.Route{},
}
if fromResult.IP4 != nil {
toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP4, "4"))
for _, fromRoute := range fromResult.IP4.Routes {
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
}
}
if fromResult.IP6 != nil {
toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP6, "6"))
for _, fromRoute := range fromResult.IP6.Routes {
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
}
}
return toResult, nil
}
func convertInternal(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
toResult := &Result{
CNIVersion: toVersion,
DNS: *fromResult.DNS.Copy(),
Routes: []*types.Route{},
}
for _, fromIntf := range fromResult.Interfaces {
toResult.Interfaces = append(toResult.Interfaces, fromIntf.Copy())
}
for _, fromIPC := range fromResult.IPs {
toResult.IPs = append(toResult.IPs, fromIPC.Copy())
}
for _, fromRoute := range fromResult.Routes {
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
}
return toResult, nil
}
func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
toResult := &types020.Result{
CNIVersion: toVersion,
DNS: *fromResult.DNS.Copy(),
}
for _, fromIP := range fromResult.IPs {
// Only convert the first IP address of each version as 0.2.0
// and earlier cannot handle multiple IP addresses
if fromIP.Version == "4" && toResult.IP4 == nil {
toResult.IP4 = &types020.IPConfig{
IP: fromIP.Address,
Gateway: fromIP.Gateway,
}
} else if fromIP.Version == "6" && toResult.IP6 == nil {
toResult.IP6 = &types020.IPConfig{
IP: fromIP.Address,
Gateway: fromIP.Gateway,
}
}
if toResult.IP4 != nil && toResult.IP6 != nil {
break
}
}
for _, fromRoute := range fromResult.Routes {
is4 := fromRoute.Dst.IP.To4() != nil
if is4 && toResult.IP4 != nil {
toResult.IP4.Routes = append(toResult.IP4.Routes, types.Route{
Dst: fromRoute.Dst,
GW: fromRoute.GW,
})
} else if !is4 && toResult.IP6 != nil {
toResult.IP6.Routes = append(toResult.IP6.Routes, types.Route{
Dst: fromRoute.Dst,
GW: fromRoute.GW,
})
}
}
// 0.2.0 and earlier require at least one IP address in the Result
if toResult.IP4 == nil && toResult.IP6 == nil {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return toResult, nil
}
func (r *Result) Version() string {
return r.CNIVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
// If the creator of the result did not set the CNIVersion, assume it
// should be the highest spec version implemented by this Result
if r.CNIVersion == "" {
r.CNIVersion = ImplementedSpecVersion
}
return convert.Convert(r, version)
}
func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = writer.Write(data)
return err
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
func (i *Interface) Copy() *Interface {
if i == nil {
return nil
}
newIntf := *i
return &newIntf
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// IP version, either "4" or "6"
Version string
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
func (i *IPConfig) Copy() *IPConfig {
if i == nil {
return nil
}
ipc := &IPConfig{
Version: i.Version,
Address: i.Address,
Gateway: i.Gateway,
}
if i.Interface != nil {
intf := *i.Interface
ipc.Interface = &intf
}
return ipc
}
// JSON (un)marshallable types
type ipConfig struct {
Version string `json:"version"`
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Version: c.Version,
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Version = ipc.Version
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package current_test
package types040_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestTypesCurrent(t *testing.T) {

View File

@ -12,23 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package current_test
package types040_test
import (
"encoding/json"
"github.com/containernetworking/cni/pkg/types/020"
"io/ioutil"
"io"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040"
)
func testResult() *current.Result {
func testResult() *types040.Result {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
@ -48,25 +48,25 @@ func testResult() *current.Result {
Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility
return &current.Result{
return &types040.Result{
CNIVersion: "0.3.1",
Interfaces: []*current.Interface{
Interfaces: []*types040.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
},
IPs: []*current.IPConfig{
IPs: []*types040.IPConfig{
{
Version: "4",
Interface: current.Int(0),
Interface: types040.Int(0),
Address: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
},
{
Version: "6",
Interface: current.Int(0),
Interface: types040.Int(0),
Address: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
},
@ -84,7 +84,7 @@ func testResult() *current.Result {
}
}
var _ = Describe("Current types operations", func() {
var _ = Describe("040 types operations", func() {
It("correctly encodes a 0.3.x Result", func() {
res := testResult()
@ -99,7 +99,7 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := ioutil.ReadAll(r)
out, err := io.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
@ -169,12 +169,12 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := ioutil.ReadAll(r)
out, err := io.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{
"cniVersion": "0.2.0",
"cniVersion": "0.1.0",
"ip4": {
"ip": "1.2.3.30/24",
"gateway": "1.2.3.1",
@ -257,8 +257,8 @@ var _ = Describe("Current types operations", func() {
},
}
// Convert to current
newRes, err := current.NewResultFromResult(res)
// Convert to 040
newRes, err := types040.NewResultFromResult(res)
Expect(err).NotTo(HaveOccurred())
// Convert back to 0.2.0
oldRes, err := newRes.GetAsVersion("0.2.0")
@ -314,8 +314,8 @@ var _ = Describe("Current types operations", func() {
},
}
// Convert to current
newRes, err := current.NewResultFromResult(res)
// Convert to 040
newRes, err := types040.NewResultFromResult(res)
Expect(err).NotTo(HaveOccurred())
// Convert back to 0.2.0
oldRes, err := newRes.GetAsVersion("0.2.0")
@ -330,9 +330,9 @@ var _ = Describe("Current types operations", func() {
})
It("correctly marshals and unmarshals interface index 0", func() {
ipc := &current.IPConfig{
ipc := &types040.IPConfig{
Version: "4",
Interface: current.Int(0),
Interface: types040.Int(0),
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.IPv4Mask(255, 255, 255, 0),
@ -347,21 +347,29 @@ var _ = Describe("Current types operations", func() {
"address": "10.1.2.3/24"
}`))
recovered := &current.IPConfig{}
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
recovered := &types040.IPConfig{}
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
Expect(recovered).To(Equal(ipc))
})
It("fails when downconverting a config to 0.2.0 that has no IPs", func() {
res := testResult()
res.IPs = nil
res.Routes = nil
_, err := res.GetAsVersion("0.2.0")
Expect(err).To(MatchError("cannot convert: no valid IP addresses"))
})
Context("when unmarshalling json fails", func() {
It("returns an error", func() {
recovered := &current.IPConfig{}
err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
recovered := &types040.IPConfig{}
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
})
})
It("correctly marshals a missing interface index", func() {
ipc := &current.IPConfig{
ipc := &types040.IPConfig{
Version: "4",
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),

352
pkg/types/100/types.go Normal file
View File

@ -0,0 +1,352 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types100
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
types040 "github.com/containernetworking/cni/pkg/types/040"
convert "github.com/containernetworking/cni/pkg/types/internal"
)
// The types did not change between v1.0 and v1.1
const ImplementedSpecVersion string = "1.1.0"
var supportedVersions = []string{"1.0.0", "1.1.0"}
// Register converters for all versions less than the implemented spec version
func init() {
// Up-converters
convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x)
convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x)
convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x)
convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x)
convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100)
// Down-converters
convert.RegisterConverter("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
convert.RegisterConverter("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("1.1.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
convert.RegisterConverter("1.1.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
convert.RegisterConverter("1.1.0", []string{"1.0.0"}, convertFrom100)
// Creator
convert.RegisterCreator(supportedVersions, NewResult)
}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
for _, v := range supportedVersions {
if result.CNIVersion == v {
return result, nil
}
}
return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
supportedVersions, result.CNIVersion)
}
func GetResult(r types.Result) (*Result, error) {
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
func NewResultFromResult(result types.Result) (*Result, error) {
newResult, err := convert.Convert(result, ImplementedSpecVersion)
if err != nil {
return nil, err
}
return newResult.(*Result), nil
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
Interfaces []*Interface `json:"interfaces,omitempty"`
IPs []*IPConfig `json:"ips,omitempty"`
Routes []*types.Route `json:"routes,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
// Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function
func (r *Result) MarshalJSON() ([]byte, error) {
// use type alias to escape recursion for json.Marshal() to MarshalJSON()
type fixObjType = Result
bytes, err := json.Marshal(fixObjType(*r)) //nolint:all
if err != nil {
return nil, err
}
fixupObj := make(map[string]interface{})
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
return nil, err
}
if r.DNS.IsEmpty() {
delete(fixupObj, "dns")
}
return json.Marshal(fixupObj)
}
// convertFrom100 does nothing except set the version; the types are the same
func convertFrom100(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
result := &Result{
CNIVersion: toVersion,
Interfaces: fromResult.Interfaces,
IPs: fromResult.IPs,
Routes: fromResult.Routes,
DNS: fromResult.DNS,
}
return result, nil
}
func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
result040, err := convert.Convert(from, "0.4.0")
if err != nil {
return nil, err
}
result100, err := convertFrom04x(result040, toVersion)
if err != nil {
return nil, err
}
return result100, nil
}
func convertIPConfigFrom040(from *types040.IPConfig) *IPConfig {
to := &IPConfig{
Address: from.Address,
Gateway: from.Gateway,
}
if from.Interface != nil {
intf := *from.Interface
to.Interface = &intf
}
return to
}
func convertInterfaceFrom040(from *types040.Interface) *Interface {
return &Interface{
Name: from.Name,
Mac: from.Mac,
Sandbox: from.Sandbox,
}
}
func convertFrom04x(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*types040.Result)
toResult := &Result{
CNIVersion: toVersion,
DNS: *fromResult.DNS.Copy(),
Routes: []*types.Route{},
}
for _, fromIntf := range fromResult.Interfaces {
toResult.Interfaces = append(toResult.Interfaces, convertInterfaceFrom040(fromIntf))
}
for _, fromIPC := range fromResult.IPs {
toResult.IPs = append(toResult.IPs, convertIPConfigFrom040(fromIPC))
}
for _, fromRoute := range fromResult.Routes {
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
}
return toResult, nil
}
func convertIPConfigTo040(from *IPConfig) *types040.IPConfig {
version := "6"
if from.Address.IP.To4() != nil {
version = "4"
}
to := &types040.IPConfig{
Version: version,
Address: from.Address,
Gateway: from.Gateway,
}
if from.Interface != nil {
intf := *from.Interface
to.Interface = &intf
}
return to
}
func convertInterfaceTo040(from *Interface) *types040.Interface {
return &types040.Interface{
Name: from.Name,
Mac: from.Mac,
Sandbox: from.Sandbox,
}
}
func convertTo04x(from types.Result, toVersion string) (types.Result, error) {
fromResult := from.(*Result)
toResult := &types040.Result{
CNIVersion: toVersion,
DNS: *fromResult.DNS.Copy(),
Routes: []*types.Route{},
}
for _, fromIntf := range fromResult.Interfaces {
toResult.Interfaces = append(toResult.Interfaces, convertInterfaceTo040(fromIntf))
}
for _, fromIPC := range fromResult.IPs {
toResult.IPs = append(toResult.IPs, convertIPConfigTo040(fromIPC))
}
for _, fromRoute := range fromResult.Routes {
toResult.Routes = append(toResult.Routes, fromRoute.Copy())
}
return toResult, nil
}
func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
// First convert to 0.4.0
result040, err := convertTo04x(from, "0.4.0")
if err != nil {
return nil, err
}
result02x, err := convert.Convert(result040, toVersion)
if err != nil {
return nil, err
}
return result02x, nil
}
func (r *Result) Version() string {
return r.CNIVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
// If the creator of the result did not set the CNIVersion, assume it
// should be the highest spec version implemented by this Result
if r.CNIVersion == "" {
r.CNIVersion = ImplementedSpecVersion
}
return convert.Convert(r, version)
}
func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = writer.Write(data)
return err
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Mtu int `json:"mtu,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
SocketPath string `json:"socketPath,omitempty"`
PciID string `json:"pciID,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
func (i *Interface) Copy() *Interface {
if i == nil {
return nil
}
newIntf := *i
return &newIntf
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
func (i *IPConfig) Copy() *IPConfig {
if i == nil {
return nil
}
ipc := &IPConfig{
Address: i.Address,
Gateway: i.Gateway,
}
if i.Interface != nil {
intf := *i.Interface
ipc.Interface = &intf
}
return ipc
}
// JSON (un)marshallable types
type ipConfig struct {
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

View File

@ -0,0 +1,27 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types100_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestTypesCurrent(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Current Types Suite")
}

345
pkg/types/100/types_test.go Normal file
View File

@ -0,0 +1,345 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types100_test
import (
"encoding/json"
"io"
"net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
)
func testResult() *current.Result {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil())
Expect(routegwv4).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility
return &current.Result{
CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Mtu: 1500,
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
},
},
IPs: []*current.IPConfig{
{
Interface: current.Int(0),
Address: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
},
{
Interface: current.Int(0),
Address: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
},
},
Routes: []*types.Route{
{Dst: *routev4, GW: routegwv4},
{Dst: *routev6, GW: routegwv6},
},
DNS: types.DNS{
Nameservers: []string{"1.2.3.4", "1::cafe"},
Domain: "acompany.com",
Search: []string{"somedomain.com", "otherdomain.net"},
Options: []string{"foo", "bar"},
},
}
}
var _ = Describe("Current types operations", func() {
It("correctly encodes a 1.1.0 Result", func() {
res := testResult()
// Redirect stdout to capture JSON result
oldStdout := os.Stdout
r, w, err := os.Pipe()
Expect(err).NotTo(HaveOccurred())
os.Stdout = w
err = res.Print()
w.Close()
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{
"cniVersion": "` + current.ImplementedSpecVersion + `",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"mtu": 1500,
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
}
],
"ips": [
{
"interface": 0,
"address": "1.2.3.30/24",
"gateway": "1.2.3.1"
},
{
"interface": 0,
"address": "abcd:1234:ffff::cdde/64",
"gateway": "abcd:1234:ffff::1"
}
],
"routes": [
{
"dst": "15.5.6.0/24",
"gw": "15.5.6.8"
},
{
"dst": "1111:dddd::/80",
"gw": "1111:dddd::aaaa"
}
],
"dns": {
"nameservers": [
"1.2.3.4",
"1::cafe"
],
"domain": "acompany.com",
"search": [
"somedomain.com",
"otherdomain.net"
],
"options": [
"foo",
"bar"
]
}
}`))
})
It("correctly converts a 1.0.0 Result to 1.1.0", func() {
tr, err := testResult().GetAsVersion("1.0.0")
Expect(err).NotTo(HaveOccurred())
trv1, ok := tr.(*current.Result)
Expect(ok).To(BeTrue())
// 1.0.0 and 1.1.0 should be the same except for CNI version
Expect(trv1.CNIVersion).To(Equal("1.0.0"))
// If we convert 1.0.0 back to 1.1.0 it should be identical
trv11, err := trv1.GetAsVersion("1.1.0")
Expect(err).NotTo(HaveOccurred())
Expect(trv11).To(Equal(testResult()))
})
It("correctly encodes a 0.1.0 Result", func() {
res, err := testResult().GetAsVersion("0.1.0")
Expect(err).NotTo(HaveOccurred())
// Redirect stdout to capture JSON result
oldStdout := os.Stdout
r, w, err := os.Pipe()
Expect(err).NotTo(HaveOccurred())
os.Stdout = w
err = res.Print()
w.Close()
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{
"cniVersion": "0.1.0",
"ip4": {
"ip": "1.2.3.30/24",
"gateway": "1.2.3.1",
"routes": [
{
"dst": "15.5.6.0/24",
"gw": "15.5.6.8"
}
]
},
"ip6": {
"ip": "abcd:1234:ffff::cdde/64",
"gateway": "abcd:1234:ffff::1",
"routes": [
{
"dst": "1111:dddd::/80",
"gw": "1111:dddd::aaaa"
}
]
},
"dns": {
"nameservers": [
"1.2.3.4",
"1::cafe"
],
"domain": "acompany.com",
"search": [
"somedomain.com",
"otherdomain.net"
],
"options": [
"foo",
"bar"
]
}
}`))
})
It("correctly encodes a 0.4.0 Result", func() {
res, err := testResult().GetAsVersion("0.4.0")
Expect(err).NotTo(HaveOccurred())
// Redirect stdout to capture JSON result
oldStdout := os.Stdout
r, w, err := os.Pipe()
Expect(err).NotTo(HaveOccurred())
os.Stdout = w
err = res.Print()
w.Close()
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{
"cniVersion": "0.4.0",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net"
}
],
"ips": [
{
"interface": 0,
"version": "4",
"address": "1.2.3.30/24",
"gateway": "1.2.3.1"
},
{
"interface": 0,
"version": "6",
"address": "abcd:1234:ffff::cdde/64",
"gateway": "abcd:1234:ffff::1"
}
],
"routes": [
{
"dst": "15.5.6.0/24",
"gw": "15.5.6.8"
},
{
"dst": "1111:dddd::/80",
"gw": "1111:dddd::aaaa"
}
],
"dns": {
"nameservers": [
"1.2.3.4",
"1::cafe"
],
"domain": "acompany.com",
"search": [
"somedomain.com",
"otherdomain.net"
],
"options": [
"foo",
"bar"
]
}
}`))
})
It("correctly marshals and unmarshals interface index 0", func() {
ipc := &current.IPConfig{
Interface: current.Int(0),
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
}
jsonBytes, err := json.Marshal(ipc)
Expect(err).NotTo(HaveOccurred())
Expect(jsonBytes).To(MatchJSON(`{
"interface": 0,
"address": "10.1.2.3/24"
}`))
recovered := &current.IPConfig{}
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
Expect(recovered).To(Equal(ipc))
})
Context("when unmarshalling json fails", func() {
It("returns an error", func() {
recovered := &current.IPConfig{}
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
})
})
It("correctly marshals a missing interface index", func() {
ipc := &current.IPConfig{
Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
}
json, err := json.Marshal(ipc)
Expect(err).NotTo(HaveOccurred())
Expect(json).To(MatchJSON(`{
"address": "10.1.2.3/24"
}`))
})
})

View File

@ -26,8 +26,8 @@ import (
type UnmarshallableBool bool
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns boolean true if the string is "1" or "[Tt]rue"
// Returns boolean false if the string is "0" or "[Ff]alse"
// Returns boolean true if the string is "1" or "true" or "True"
// Returns boolean false if the string is "0" or "false" or "False”
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data))
switch s {
@ -91,16 +91,26 @@ func LoadArgs(args string, container interface{}) error {
unknownArgs = append(unknownArgs, pair)
continue
}
keyFieldIface := keyField.Addr().Interface()
u, ok := keyFieldIface.(encoding.TextUnmarshaler)
var keyFieldInterface interface{}
switch {
case keyField.Kind() == reflect.Ptr:
keyField.Set(reflect.New(keyField.Type().Elem()))
keyFieldInterface = keyField.Interface()
case keyField.CanAddr() && keyField.Addr().CanInterface():
keyFieldInterface = keyField.Addr().Interface()
default:
return UnmarshalableArgsError{fmt.Errorf("field '%s' has no valid interface", keyString)}
}
u, ok := keyFieldInterface.(encoding.TextUnmarshaler)
if !ok {
return UnmarshalableArgsError{fmt.Errorf(
"ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler",
keyString, reflect.TypeOf(keyFieldIface))}
keyString, reflect.TypeOf(keyFieldInterface))}
}
err := u.UnmarshalText([]byte(valueString))
if err != nil {
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
return fmt.Errorf("ARGS: error parsing value of pair %q: %w", pair, err)
}
}

View File

@ -15,13 +15,13 @@
package types_test
import (
"net"
"reflect"
. "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/containernetworking/cni/pkg/types"
)
var _ = Describe("UnmarshallableBool UnmarshalText", func() {
@ -126,7 +126,37 @@ var _ = Describe("LoadArgs", func() {
}{}
err := LoadArgs("IP=10.0.0.0/24", &conf)
Expect(err).To(HaveOccurred())
})
})
Context("When loading known arguments", func() {
It("should succeed if argument is marshallable value type", func() {
conf := struct {
IP net.IP
CommonArgs
}{}
err := LoadArgs("IP=10.0.0.0", &conf)
Expect(err).NotTo(HaveOccurred())
Expect(conf.IP.String()).To(Equal("10.0.0.0"))
})
It("should succeed if argument is marshallable pointer type", func() {
conf := struct {
IP *net.IP
CommonArgs
}{}
err := LoadArgs("IP=10.0.0.0", &conf)
Expect(err).NotTo(HaveOccurred())
Expect(conf.IP.String()).To(Equal("10.0.0.0"))
})
It("should fail if argument is pointer of marshallable pointer type", func() {
conf := struct {
IP **net.IP
CommonArgs
}{}
err := LoadArgs("IP=10.0.0.0", &conf)
Expect(err).To(HaveOccurred())
})
})
})

View File

@ -0,0 +1,59 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package create
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/types"
_ "github.com/containernetworking/cni/pkg/types/020"
_ "github.com/containernetworking/cni/pkg/types/040"
_ "github.com/containernetworking/cni/pkg/types/100"
convert "github.com/containernetworking/cni/pkg/types/internal"
)
// DecodeVersion returns the CNI version from CNI configuration or result JSON,
// or an error if the operation could not be performed.
func DecodeVersion(jsonBytes []byte) (string, error) {
var conf struct {
CNIVersion string `json:"cniVersion"`
}
err := json.Unmarshal(jsonBytes, &conf)
if err != nil {
return "", fmt.Errorf("decoding version from network config: %w", err)
}
if conf.CNIVersion == "" {
return "0.1.0", nil
}
return conf.CNIVersion, nil
}
// Create creates a CNI Result using the given JSON with the expected
// version, or an error if the creation could not be performed
func Create(version string, bytes []byte) (types.Result, error) {
return convert.Create(version, bytes)
}
// CreateFromBytes creates a CNI Result from the given JSON, automatically
// detecting the CNI spec version of the result. An error is returned if the
// operation could not be performed.
func CreateFromBytes(bytes []byte) (types.Result, error) {
version, err := DecodeVersion(bytes)
if err != nil {
return nil, err
}
return convert.Create(version, bytes)
}

View File

@ -1,276 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package current
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
)
const ImplementedSpecVersion string = "0.4.0"
var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}
func GetResult(r types.Result) (*Result, error) {
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
var resultConverters = []struct {
versions []string
convert func(types.Result) (*Result, error)
}{
{types020.SupportedVersions, convertFrom020},
{SupportedVersions, convertFrom030},
}
func convertFrom020(result types.Result) (*Result, error) {
oldResult, err := types020.GetResult(result)
if err != nil {
return nil, err
}
newResult := &Result{
CNIVersion: ImplementedSpecVersion,
DNS: oldResult.DNS,
Routes: []*types.Route{},
}
if oldResult.IP4 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "4",
Address: oldResult.IP4.IP,
Gateway: oldResult.IP4.Gateway,
})
for _, route := range oldResult.IP4.Routes {
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: route.GW,
})
}
}
if oldResult.IP6 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "6",
Address: oldResult.IP6.IP,
Gateway: oldResult.IP6.Gateway,
})
for _, route := range oldResult.IP6.Routes {
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: route.GW,
})
}
}
return newResult, nil
}
func convertFrom030(result types.Result) (*Result, error) {
newResult, ok := result.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
newResult.CNIVersion = ImplementedSpecVersion
return newResult, nil
}
func NewResultFromResult(result types.Result) (*Result, error) {
version := result.Version()
for _, converter := range resultConverters {
for _, supportedVersion := range converter.versions {
if version == supportedVersion {
return converter.convert(result)
}
}
}
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
Interfaces []*Interface `json:"interfaces,omitempty"`
IPs []*IPConfig `json:"ips,omitempty"`
Routes []*types.Route `json:"routes,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
// Convert to the older 0.2.0 CNI spec Result type
func (r *Result) convertTo020() (*types020.Result, error) {
oldResult := &types020.Result{
CNIVersion: types020.ImplementedSpecVersion,
DNS: r.DNS,
}
for _, ip := range r.IPs {
// Only convert the first IP address of each version as 0.2.0
// and earlier cannot handle multiple IP addresses
if ip.Version == "4" && oldResult.IP4 == nil {
oldResult.IP4 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
} else if ip.Version == "6" && oldResult.IP6 == nil {
oldResult.IP6 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
}
if oldResult.IP4 != nil && oldResult.IP6 != nil {
break
}
}
for _, route := range r.Routes {
is4 := route.Dst.IP.To4() != nil
if is4 && oldResult.IP4 != nil {
oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
} else if !is4 && oldResult.IP6 != nil {
oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
}
}
if oldResult.IP4 == nil && oldResult.IP6 == nil {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return oldResult, nil
}
func (r *Result) Version() string {
return ImplementedSpecVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
switch version {
case "0.3.0", "0.3.1", ImplementedSpecVersion:
r.CNIVersion = version
return r, nil
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
return r.convertTo020()
}
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
}
func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = writer.Write(data)
return err
}
// Convert this old version result to the current CNI version result
func (r *Result) Convert() (*Result, error) {
return r, nil
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// IP version, either "4" or "6"
Version string
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
// JSON (un)marshallable types
type ipConfig struct {
Version string `json:"version"`
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Version: c.Version,
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Version = ipc.Version
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

View File

@ -0,0 +1,92 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
)
// ConvertFn should convert from the given arbitrary Result type into a
// Result implementing CNI specification version passed in toVersion.
// The function is guaranteed to be passed a Result type matching the
// fromVersion it was registered with, and is guaranteed to be
// passed a toVersion matching one of the toVersions it was registered with.
type ConvertFn func(from types.Result, toVersion string) (types.Result, error)
type converter struct {
// fromVersion is the CNI Result spec version that convertFn accepts
fromVersion string
// toVersions is a list of versions that convertFn can convert to
toVersions []string
convertFn ConvertFn
}
var converters []*converter
func findConverter(fromVersion, toVersion string) *converter {
for _, c := range converters {
if c.fromVersion == fromVersion {
for _, v := range c.toVersions {
if v == toVersion {
return c
}
}
}
}
return nil
}
// Convert converts a CNI Result to the requested CNI specification version,
// or returns an error if the conversion could not be performed or failed
func Convert(from types.Result, toVersion string) (types.Result, error) {
if toVersion == "" {
toVersion = "0.1.0"
}
fromVersion := from.Version()
// Shortcut for same version
if fromVersion == toVersion {
return from, nil
}
// Otherwise find the right converter
c := findConverter(fromVersion, toVersion)
if c == nil {
return nil, fmt.Errorf("no converter for CNI result version %s to %s",
fromVersion, toVersion)
}
return c.convertFn(from, toVersion)
}
// RegisterConverter registers a CNI Result converter. SHOULD NOT BE CALLED
// EXCEPT FROM CNI ITSELF.
func RegisterConverter(fromVersion string, toVersions []string, convertFn ConvertFn) {
// Make sure there is no converter already registered for these
// from and to versions
for _, v := range toVersions {
if findConverter(fromVersion, v) != nil {
panic(fmt.Sprintf("converter already registered for %s to %s",
fromVersion, v))
}
}
converters = append(converters, &converter{
fromVersion: fromVersion,
toVersions: toVersions,
convertFn: convertFn,
})
}

View File

@ -0,0 +1,66 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
)
type ResultFactoryFunc func([]byte) (types.Result, error)
type creator struct {
// CNI Result spec versions that createFn can create a Result for
versions []string
createFn ResultFactoryFunc
}
var creators []*creator
func findCreator(version string) *creator {
for _, c := range creators {
for _, v := range c.versions {
if v == version {
return c
}
}
}
return nil
}
// Create creates a CNI Result using the given JSON, or an error if the creation
// could not be performed
func Create(version string, bytes []byte) (types.Result, error) {
if c := findCreator(version); c != nil {
return c.createFn(bytes)
}
return nil, fmt.Errorf("unsupported CNI result version %q", version)
}
// RegisterCreator registers a CNI Result creator. SHOULD NOT BE CALLED
// EXCEPT FROM CNI ITSELF.
func RegisterCreator(versions []string, createFn ResultFactoryFunc) {
// Make sure there is no creator already registered for these versions
for _, v := range versions {
if findCreator(v) != nil {
panic(fmt.Sprintf("creator already registered for %s", v))
}
}
creators = append(creators, &creator{
versions: versions,
createFn: createFn,
})
}

View File

@ -56,35 +56,74 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil
}
// NetConf describes a network.
type NetConf struct {
// Use PluginConf instead of NetConf, the NetConf
// backwards-compat alias will be removed in a future release.
type NetConf = PluginConf
// PluginConf describes a plugin configuration for a specific network.
type PluginConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM IPAM `json:"ipam,omitempty"`
DNS DNS `json:"dns"`
DNS DNS `json:"dns,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult Result `json:"-"`
// ValidAttachments is only supplied when executing a GC operation
ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
}
// GCAttachment is the parameters to a GC call -- namely,
// the container ID and ifname pair that represents a
// still-valid attachment.
type GCAttachment struct {
ContainerID string `json:"containerID"`
IfName string `json:"ifname"`
}
// Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function
func (n *PluginConf) MarshalJSON() ([]byte, error) {
bytes, err := json.Marshal(*n)
if err != nil {
return nil, err
}
fixupObj := make(map[string]interface{})
if err := json.Unmarshal(bytes, &fixupObj); err != nil {
return nil, err
}
if n.DNS.IsEmpty() {
delete(fixupObj, "dns")
}
return json.Marshal(fixupObj)
}
type IPAM struct {
Type string `json:"type,omitempty"`
}
// IsEmpty returns true if IPAM structure has no value, otherwise return false
func (i *IPAM) IsEmpty() bool {
return i.Type == ""
}
// NetConfList describes an ordered list of networks.
type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
DisableGC bool `json:"disableGC,omitempty"`
Plugins []*PluginConf `json:"plugins,omitempty"`
}
type ResultFactoryFunc func([]byte) (Result, error)
// Result is an interface that provides the result of plugin execution
type Result interface {
// The highest CNI specification result version the result supports
@ -118,17 +157,79 @@ type DNS struct {
Options []string `json:"options,omitempty"`
}
// IsEmpty returns true if DNS structure has no value, otherwise return false
func (d *DNS) IsEmpty() bool {
if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 {
return true
}
return false
}
func (d *DNS) Copy() *DNS {
if d == nil {
return nil
}
to := &DNS{Domain: d.Domain}
to.Nameservers = append(to.Nameservers, d.Nameservers...)
to.Search = append(to.Search, d.Search...)
to.Options = append(to.Options, d.Options...)
return to
}
type Route struct {
Dst net.IPNet
GW net.IP
Dst net.IPNet
GW net.IP
MTU int
AdvMSS int
Priority int
Table *int
Scope *int
}
func (r *Route) String() string {
return fmt.Sprintf("%+v", *r)
table := "<nil>"
if r.Table != nil {
table = fmt.Sprintf("%d", *r.Table)
}
scope := "<nil>"
if r.Scope != nil {
scope = fmt.Sprintf("%d", *r.Scope)
}
return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope)
}
func (r *Route) Copy() *Route {
if r == nil {
return nil
}
route := &Route{
Dst: r.Dst,
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Scope: r.Scope,
}
if r.Table != nil {
table := *r.Table
route.Table = &table
}
if r.Scope != nil {
scope := *r.Scope
route.Scope = &scope
}
return route
}
// Well known error codes
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
// see https://github.com/containernetworking/cni/blob/main/SPEC.md#error
const (
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
@ -138,7 +239,10 @@ const (
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11
ErrPluginNotAvailable uint = 50
ErrLimitedConnectivity uint = 51
ErrInternal uint = 999
)
@ -173,8 +277,13 @@ func (e *Error) Print() error {
// JSON (un)marshallable types
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
MTU int `json:"mtu,omitempty"`
AdvMSS int `json:"advmss,omitempty"`
Priority int `json:"priority,omitempty"`
Table *int `json:"table,omitempty"`
Scope *int `json:"scope,omitempty"`
}
func (r *Route) UnmarshalJSON(data []byte) error {
@ -185,13 +294,24 @@ func (r *Route) UnmarshalJSON(data []byte) error {
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
r.MTU = rt.MTU
r.AdvMSS = rt.AdvMSS
r.Priority = rt.Priority
r.Table = rt.Table
r.Scope = rt.Scope
return nil
}
func (r Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
Dst: IPNet(r.Dst),
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Table: r.Table,
Scope: r.Scope,
}
return json.Marshal(rt)

View File

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

View File

@ -18,14 +18,15 @@ import (
"encoding/json"
"net"
"github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
types040 "github.com/containernetworking/cni/pkg/types/040"
current "github.com/containernetworking/cni/pkg/types/100"
)
var _ = Describe("Types", func() {
Describe("ParseCIDR", func() {
DescribeTable("Parse and stringify",
func(input, expectedIP string, expectedMask int) {
@ -87,14 +88,19 @@ var _ = Describe("Types", func() {
IP: net.ParseIP("1.2.3.0"),
Mask: net.CIDRMask(24, 32),
},
GW: net.ParseIP("1.2.3.1"),
GW: net.ParseIP("1.2.3.1"),
MTU: 1500,
AdvMSS: 1340,
Priority: 100,
Table: types040.Int(50),
Scope: types040.Int(253),
}
})
It("marshals and unmarshals to JSON", func() {
jsonBytes, err := json.Marshal(example)
Expect(err).NotTo(HaveOccurred())
Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1" }`))
Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1", "mtu": 1500, "advmss": 1340, "priority": 100, "table": 50, "scope": 253 }`))
var unmarshaled types.Route
Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
@ -110,7 +116,7 @@ var _ = Describe("Types", func() {
})
It("formats as a string with a hex mask", func() {
Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1}`))
Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1 MTU:1500 AdvMSS:1340 Priority:100 Table:50 Scope:253}`))
})
})
@ -143,4 +149,53 @@ var _ = Describe("Types", func() {
Expect(err).To(Equal(example))
})
})
Describe("Result conversion", func() {
var result *current.Result
BeforeEach(func() {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
result = &current.Result{
CNIVersion: "1.0.0",
Interfaces: []*current.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
},
},
IPs: []*current.IPConfig{
{
Interface: current.Int(0),
Address: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
},
{
Interface: current.Int(0),
Address: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
},
},
DNS: types.DNS{
Nameservers: []string{"1.2.3.4", "1::cafe"},
Domain: "acompany.com",
Search: []string{"somedomain.com", "otherdomain.net"},
Options: []string{"foo", "bar"},
},
}
})
It("can create a CNIVersion '' (0.1.0) result", func() {
newResult, err := result.GetAsVersion("")
Expect(err).NotTo(HaveOccurred())
Expect(newResult.Version()).To(Equal("0.1.0"))
})
})
})

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
func ValidateContainerID(containerID string) *types.Error {
if containerID == "" {
return types.NewError(types.ErrUnknownContainer, "missing containerID", "")
}
@ -48,7 +47,6 @@ func ValidateContainerID(containerID string) *types.Error {
// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters
func ValidateNetworkName(networkName string) *types.Error {
if networkName == "" {
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "")
}
@ -58,11 +56,11 @@ func ValidateNetworkName(networkName string) *types.Error {
return nil
}
// ValidateInterfaceName will validate the interface name based on the three rules below
// ValidateInterfaceName will validate the interface name based on the four rules below
// 1. The name must not be empty
// 2. The name must be less than 16 characters
// 3. The name must not be "." or ".."
// 3. The name must not contain / or : or any whitespace characters
// 4. The name must not contain / or : or any whitespace characters
// ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024
func ValidateInterfaceName(ifName string) *types.Error {
if len(ifName) == 0 {

View File

@ -15,23 +15,12 @@
package version
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/types/create"
)
// ConfigDecoder can decode the CNI version available in network config data
type ConfigDecoder struct{}
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
var conf struct {
CNIVersion string `json:"cniVersion"`
}
err := json.Unmarshal(jsonBytes, &conf)
if err != nil {
return "", fmt.Errorf("decoding version from network config: %s", err)
}
if conf.CNIVersion == "" {
return "0.1.0", nil
}
return conf.CNIVersion, nil
return create.DecodeVersion(jsonBytes)
}

View File

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

View File

@ -16,7 +16,6 @@ package legacy_examples
import (
"fmt"
"io/ioutil"
"os"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
@ -108,9 +107,9 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
return nil, fmt.Errorf("unknown example net config template %q", name)
}
debugFile, err := ioutil.TempFile("", "cni_debug")
debugFile, err := os.CreateTemp("", "cni_debug")
if err != nil {
return nil, fmt.Errorf("failed to create noop plugin debug file: %v", err)
return nil, fmt.Errorf("failed to create noop plugin debug file: %w", err)
}
debugFilePath := debugFile.Name()
@ -119,7 +118,7 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
}
if err := debug.WriteDebug(debugFilePath); err != nil {
os.Remove(debugFilePath)
return nil, fmt.Errorf("failed to write noop plugin debug file %q: %v", debugFilePath, err)
return nil, fmt.Errorf("failed to write noop plugin debug file %q: %w", debugFilePath, err)
}
conf := &ExampleNetConf{
Config: fmt.Sprintf(template.conf, debugFilePath),
@ -144,12 +143,12 @@ var V010_Runtime = ExampleRuntime{
NetConfs: []string{"unversioned", "0.1.0"},
Example: Example{
Name: "example_invoker_v010",
CNIRepoGitRef: "c0d34c69", //version with ns.Do
CNIRepoGitRef: "c0d34c69", // version with ns.Do
PluginSource: `package main
import (
"fmt"
"io/ioutil"
"io"
"os"
"github.com/containernetworking/cni/libcni"
@ -161,7 +160,7 @@ func main(){
}
func exec() int {
confBytes, err := ioutil.ReadAll(os.Stdin)
confBytes, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Printf("could not read netconfig from stdin: %+v", err)
return 1

View File

@ -17,8 +17,8 @@
package legacy_examples
import (
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
"sync"
@ -39,8 +39,10 @@ type Example struct {
PluginSource string
}
var buildDir = ""
var buildDirLock sync.Mutex
var (
buildDir = ""
buildDirLock sync.Mutex
)
func ensureBuildDirExists() error {
buildDirLock.Lock()
@ -51,7 +53,7 @@ func ensureBuildDirExists() error {
}
var err error
buildDir, err = ioutil.TempDir("", "cni-example-plugins")
buildDir, err = os.MkdirTemp("", "cni-example-plugins")
return err
}
@ -120,6 +122,7 @@ func main() { skel.PluginMain(c, c) }
// As we change the CNI spec, the Result type and this value may change.
// The text of the example plugins should not.
var ExpectedResult = &types020.Result{
CNIVersion: "0.1.0",
IP4: &types020.IPConfig{
IP: net.IPNet{
IP: net.ParseIP("10.1.2.3"),
@ -127,7 +130,7 @@ var ExpectedResult = &types020.Result{
},
Gateway: net.ParseIP("10.1.2.1"),
Routes: []types.Route{
types.Route{
{
Dst: net.IPNet{
IP: net.ParseIP("0.0.0.0"),
Mask: net.CIDRMask(0, 32),

View File

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

View File

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

View File

@ -68,7 +68,7 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
var info pluginInfo
err := json.Unmarshal(jsonBytes, &info)
if err != nil {
return nil, fmt.Errorf("decoding version info: %s", err)
return nil, fmt.Errorf("decoding version info: %w", err)
}
if info.CNIVersion_ == "" {
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
@ -86,8 +86,8 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
// minor, and micro numbers or returns an error
func ParseVersion(version string) (int, int, int, error) {
var major, minor, micro int
if version == "" {
return -1, -1, -1, fmt.Errorf("invalid version %q: the version is empty", version)
if version == "" { // special case: no version declared == v0.1.0
return 0, 1, 0, nil
}
parts := strings.Split(version, ".")
@ -97,20 +97,20 @@ func ParseVersion(version string) (int, int, int, error) {
major, err := strconv.Atoi(parts[0])
if err != nil {
return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %v", parts[0], err)
return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %w", parts[0], err)
}
if len(parts) >= 2 {
minor, err = strconv.Atoi(parts[1])
if err != nil {
return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %v", parts[1], err)
return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %w", parts[1], err)
}
}
if len(parts) >= 3 {
micro, err = strconv.Atoi(parts[2])
if err != nil {
return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %v", parts[2], err)
return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %w", parts[2], err)
}
}
@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
}
return false, nil
}
// GreaterThan returns true if the first version is greater than the second
func GreaterThan(version, otherVersion string) (bool, error) {
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
if err != nil {
return false, err
}
secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
if err != nil {
return false, err
}
if firstMajor > secondMajor {
return true, nil
} else if firstMajor == secondMajor {
if firstMinor > secondMinor {
return true, nil
} else if firstMinor == secondMinor && firstMicro > secondMicro {
return true, nil
}
}
return false, nil
}

View File

@ -15,9 +15,10 @@
package version_test
import (
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Decoding versions reported by a plugin", func() {
@ -91,8 +92,16 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
Expect(micro).To(Equal(3))
})
It("parses an empty string as v0.1.0", func() {
major, minor, micro, err := version.ParseVersion("")
Expect(err).NotTo(HaveOccurred())
Expect(major).To(Equal(0))
Expect(minor).To(Equal(1))
Expect(micro).To(Equal(0))
})
It("returns an error for malformed versions", func() {
badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5", ""}
badVersions := []string{"asdfasdf", "asdf.", ".asdfas", "asdf.adsf.", "0.", "..", "1.2.3.4.5"}
for _, v := range badVersions {
_, _, _, err := version.ParseVersion(v)
Expect(err).To(HaveOccurred())
@ -112,19 +121,19 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
// Make sure the first is greater than the second
gt, err := version.GreaterThanOrEqualTo(v[0], v[1])
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(true))
Expect(gt).To(BeTrue())
// And the opposite
gt, err = version.GreaterThanOrEqualTo(v[1], v[0])
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(false))
Expect(gt).To(BeFalse())
}
})
It("returns true when versions are the same", func() {
gt, err := version.GreaterThanOrEqualTo("1.2.3", "1.2.3")
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(true))
Expect(gt).To(BeTrue())
})
It("returns an error for malformed versions", func() {

View File

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

View File

@ -22,17 +22,12 @@ package testhelpers
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
const packageBaseName = "github.com/containernetworking/cni"
func run(cmd *exec.Cmd) error {
out, err := cmd.CombinedOutput()
if err != nil {
@ -42,115 +37,75 @@ func run(cmd *exec.Cmd) error {
return nil
}
func goBuildEnviron(gopath string) []string {
environ := os.Environ()
for i, kvp := range environ {
if strings.HasPrefix(kvp, "GOPATH=") {
environ[i] = "GOPATH=" + gopath
return environ
// unset GOPATH if it's set, so we use modules
func goBuildEnviron() []string {
out := []string{}
for _, kvp := range os.Environ() {
if !strings.HasPrefix(kvp, "GOPATH=") {
out = append(out, kvp)
}
}
environ = append(environ, "GOPATH="+gopath)
return environ
return out
}
func buildGoProgram(gopath, packageName, outputFilePath string) error {
cmd := exec.Command("go", "build", "-o", outputFilePath, packageName)
cmd.Env = goBuildEnviron(gopath)
func buildGoProgram(modPath, outputFilePath string) error {
cmd := exec.Command("go", "build", "-o", outputFilePath, ".")
cmd.Dir = modPath
cmd.Env = goBuildEnviron()
return run(cmd)
}
func createSingleFilePackage(gopath, packageName string, fileContents []byte) error {
dirName := filepath.Join(gopath, "src", packageName)
err := os.MkdirAll(dirName, 0700)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(dirName, "main.go"), fileContents, 0600)
func modInit(path, name string) error {
cmd := exec.Command("go", "mod", "init", name)
cmd.Dir = path
return run(cmd)
}
func removePackage(gopath, packageName string) error {
dirName := filepath.Join(gopath, "src", packageName)
return os.RemoveAll(dirName)
// addLibcni will execute `go mod edit -replace` to fix libcni at a specified version
func addLibcni(path, gitRef string) error {
cmd := exec.Command("go", "mod", "edit", "-replace=github.com/containernetworking/cni=github.com/containernetworking/cni@"+gitRef)
cmd.Dir = path
return run(cmd)
}
func isRepoRoot(path string) bool {
_, err := ioutil.ReadDir(filepath.Join(path, ".git"))
return (err == nil) && (filepath.Base(path) == "cni")
}
func LocateCurrentGitRepo() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for i := 0; i < 5; i++ {
if isRepoRoot(dir) {
return dir, nil
}
dir, err = filepath.Abs(filepath.Dir(dir))
if err != nil {
return "", fmt.Errorf("abs(dir(%q)): %s", dir, err)
}
}
return "", fmt.Errorf("unable to find cni repo root, landed at %q", dir)
}
func gitCloneThisRepo(cloneDestination string) error {
err := os.MkdirAll(cloneDestination, 0700)
if err != nil {
return err
}
currentGitRepo, err := LocateCurrentGitRepo()
if err != nil {
return err
}
return run(exec.Command("git", "clone", currentGitRepo, cloneDestination))
}
func gitCheckout(localRepo string, gitRef string) error {
return run(exec.Command("git", "-C", localRepo, "checkout", gitRef))
// modTidy will execute `go mod tidy` to ensure all necessary dependencies
func modTidy(path string) error {
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = path
return run(cmd)
}
// BuildAt builds the go programSource using the version of the CNI library
// at gitRef, and saves the resulting binary file at outputFilePath
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
tempGoPath, err := ioutil.TempDir("", "cni-git-")
tempDir, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "cni-test-")
if err != nil {
return err
}
defer os.RemoveAll(tempGoPath)
defer os.RemoveAll(tempDir)
cloneDestination := filepath.Join(tempGoPath, "src", packageBaseName)
err = gitCloneThisRepo(cloneDestination)
if err != nil {
modName := filepath.Base(tempDir)
if err := modInit(tempDir, modName); err != nil {
return err
}
err = gitCheckout(cloneDestination, gitRef)
if err != nil {
if err := addLibcni(tempDir, gitRef); err != nil {
return err
}
rand.Seed(time.Now().UnixNano())
testPackageName := fmt.Sprintf("test-package-%x", rand.Int31())
err = createSingleFilePackage(tempGoPath, testPackageName, programSource)
if err != nil {
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0o600); err != nil {
return err
}
defer removePackage(tempGoPath, testPackageName)
err = buildGoProgram(tempGoPath, testPackageName, outputFilePath)
if err != nil {
if err := modTidy(tempDir); err != nil {
return err
}
err = buildGoProgram(tempDir, outputFilePath)
if err != nil {
return fmt.Errorf("failed to build: %w", err)
}
return nil
}

View File

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

View File

@ -4,24 +4,22 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testhelpers_test
package testhelpers
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/containernetworking/cni/pkg/version/testhelpers"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -44,7 +42,7 @@ func main() { skel.PluginMain(c, c) }
gitRef = "f4364185253"
var err error
outputDir, err = ioutil.TempDir("", "bin")
outputDir, err = os.MkdirTemp("", "bin")
Expect(err).NotTo(HaveOccurred())
outputFilePath = filepath.Join(outputDir, "some-binary")
if runtime.GOOS == "windows" {
@ -59,7 +57,7 @@ func main() { skel.PluginMain(c, c) }
It("builds the provided source code using the CNI library at the given git ref", func() {
Expect(outputFilePath).NotTo(BeAnExistingFile())
err := testhelpers.BuildAt(programSource, gitRef, outputFilePath)
err := BuildAt(programSource, gitRef, outputFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(outputFilePath).To(BeAnExistingFile())
@ -71,40 +69,3 @@ func main() { skel.PluginMain(c, c) }
Expect(output).To(ContainSubstring("unknown CNI_COMMAND: VERSION"))
})
})
var _ = Describe("LocateCurrentGitRepo", func() {
It("returns the path to the root of the CNI git repo", func() {
path, err := testhelpers.LocateCurrentGitRepo()
Expect(err).NotTo(HaveOccurred())
AssertItIsTheCNIRepoRoot(path)
})
Context("when run from a different directory", func() {
BeforeEach(func() {
os.Chdir("..")
})
It("still finds the CNI repo root", func() {
path, err := testhelpers.LocateCurrentGitRepo()
Expect(err).NotTo(HaveOccurred())
AssertItIsTheCNIRepoRoot(path)
})
})
})
func AssertItIsTheCNIRepoRoot(path string) {
Expect(path).To(BeADirectory())
files, err := ioutil.ReadDir(path)
Expect(err).NotTo(HaveOccurred())
names := []string{}
for _, file := range files {
names = append(names, file.Name())
}
Expect(names).To(ContainElement("SPEC.md"))
Expect(names).To(ContainElement("libcni"))
Expect(names).To(ContainElement("cnitool"))
}

View File

@ -19,13 +19,12 @@ import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/types/create"
)
// Current reports the version of the CNI spec implemented by this library
func Current() string {
return "0.4.0"
return "1.1.0"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -35,48 +34,56 @@ func Current() string {
//
// Any future CNI spec versions which meet this definition should be added to
// this list.
var Legacy = PluginSupports("0.1.0", "0.2.0")
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0")
var (
Legacy = PluginSupports("0.1.0", "0.2.0")
All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0")
)
var resultFactories = []struct {
supportedVersions []string
newResult types.ResultFactoryFunc
}{
{current.SupportedVersions, current.NewResult},
{types020.SupportedVersions, types020.NewResult},
// VersionsFrom returns a list of versions starting from min, inclusive
func VersionsStartingFrom(min string) PluginInfo {
out := []string{}
// cheat, just assume ordered
ok := false
for _, v := range All.SupportedVersions() {
if !ok && v == min {
ok = true
}
if ok {
out = append(out, v)
}
}
return PluginSupports(out...)
}
// Finds a Result object matching the requested version (if any) and asks
// that object to parse the plugin result, returning an error if parsing failed.
func NewResult(version string, resultBytes []byte) (types.Result, error) {
reconciler := &Reconciler{}
for _, resultFactory := range resultFactories {
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
if err == nil {
// Result supports this version
return resultFactory.newResult(resultBytes)
}
}
return nil, fmt.Errorf("unsupported CNI result version %q", version)
return create.Create(version, resultBytes)
}
// ParsePrevResult parses a prevResult in a NetConf structure and sets
// the NetConf's PrevResult member to the parsed Result object.
func ParsePrevResult(conf *types.NetConf) error {
func ParsePrevResult(conf *types.PluginConf) error {
if conf.RawPrevResult == nil {
return nil
}
// Prior to 1.0.0, Result types may not marshal a CNIVersion. Since the
// result version must match the config version, if the Result's version
// is empty, inject the config version.
if ver, ok := conf.RawPrevResult["CNIVersion"]; !ok || ver == "" {
conf.RawPrevResult["CNIVersion"] = conf.CNIVersion
}
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return fmt.Errorf("could not serialize prevResult: %v", err)
return fmt.Errorf("could not serialize prevResult: %w", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes)
conf.PrevResult, err = create.Create(conf.CNIVersion, resultBytes)
if err != nil {
return fmt.Errorf("could not parse prevResult: %v", err)
return fmt.Errorf("could not parse prevResult: %w", err)
}
return nil

View File

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

View File

@ -19,24 +19,31 @@ import (
"net"
"reflect"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
cniv1 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Version operations", func() {
It("computes a list of versions correctly", func() {
actual := version.VersionsStartingFrom("0.3.1")
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0", "1.1.0"}))
})
Context("when a prevResult is available", func() {
It("parses the prevResult", func() {
rawBytes := []byte(`{
"cniVersion": "0.3.0",
"cniVersion": "1.0.0",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net"
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
}
],
"ips": [
@ -52,8 +59,8 @@ var _ = Describe("Version operations", func() {
err := json.Unmarshal(rawBytes, &raw)
Expect(err).NotTo(HaveOccurred())
conf := &types.NetConf{
CNIVersion: "0.3.0",
conf := &types.PluginConf{
CNIVersion: "1.0.0",
Name: "foobar",
Type: "baz",
RawPrevResult: raw,
@ -61,20 +68,20 @@ var _ = Describe("Version operations", func() {
err = version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
expectedResult := &current.Result{
CNIVersion: "0.3.0",
Interfaces: []*current.Interface{
expectedResult := &cniv1.Result{
CNIVersion: "1.0.0",
Interfaces: []*cniv1.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
},
},
IPs: []*current.IPConfig{
IPs: []*cniv1.IPConfig{
{
Version: "4",
Interface: current.Int(0),
Interface: cniv1.Int(0),
Address: net.IPNet{
IP: net.ParseIP("1.2.3.30"),
Mask: net.IPv4Mask(255, 255, 255, 0),
@ -87,8 +94,8 @@ var _ = Describe("Version operations", func() {
})
It("fails if the prevResult version is unknown", func() {
conf := &types.NetConf{
CNIVersion: "0.3.0",
conf := &types.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
@ -97,28 +104,32 @@ var _ = Describe("Version operations", func() {
}
err := version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
Expect(err).To(MatchError(`could not parse prevResult: result type supports [1.0.0 1.1.0] but unmarshalled CNIVersion is "5678.456"`))
})
It("fails if the prevResult is invalid", func() {
conf := &types.NetConf{
CNIVersion: "0.3.0",
It("fails if the prevResult version does not match the prevResult version", func() {
conf := &types.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
"adsfasdfasdfasdfasdfaf": nil,
"cniVersion": "0.2.0",
"ip4": map[string]interface{}{
"ip": "1.2.3.30/24",
"gateway": "1.2.3.1",
},
},
}
err := version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0 1.1.0] but unmarshalled CNIVersion is \"0.2.0\""))
})
})
Context("when a prevResult is not available", func() {
It("does not fail", func() {
conf := &types.NetConf{
CNIVersion: "0.3.0",
conf := &types.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
}
@ -128,4 +139,22 @@ var _ = Describe("Version operations", func() {
Expect(conf.PrevResult).To(BeNil())
})
})
Context("version parsing", func() {
It("parses versions correctly", func() {
v1 := "1.1.0"
v2 := "1.1.1"
check := func(a, b string, want bool) {
GinkgoHelper()
gt, err := version.GreaterThan(a, b)
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(want))
}
check(v1, v2, false)
check(v2, v1, true)
check(v2, v2, false)
})
})
})

72
plugins/debug/README.md Normal file
View File

@ -0,0 +1,72 @@
# debug plugin
## Overview
This plugin aims to help debugging or troubleshooting in CNI plugin development.
## Example Configuration
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"plugins": [
{
"type": "ptp",
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "172.16.30.0/24",
"routes": [
{
"dst": "0.0.0.0/0"
}
]
}
},
{
"type": "debug",
"cniOutput": "/tmp/cni_output.txt",
"addHooks": [
[ "sh", "-c", "ip link set $CNI_IFNAME promisc on" ]
]
},
{
"type": "portmap",
"capabilities": {"portMappings": true},
"externalSetMarkChain": "KUBE-MARK-MASQ"
}
]
}
```
## Config Reference
* `cniOutput` (string, optional): output CNI request into file.
* `addHooks` (string array, optional): commands executed in container network namespace at interface add.
(note: but just execute it and does not catch command failure)
* `delHooks` (string array, optional): commands executed in container network namespace at interface delete.
(note: but just execute it and does not catch command failure)
* `checkHooks` (string array, optional): commands executed in container network namespace at interface check.
(note: but just execute it and does not catch command failure)
### Sample CNI Ouput
```
CmdAdd
ContainerID: cnitool-20c433bb2b1d6ede56d6
Netns: /var/run/netns/cnitest
IfName: eth0
Args:
Path: /opt/cni/bin
StdinData: {"cniOutput":"/tmp/cni_output.txt","cniVersion":"0.3.1","name":"test","prevResult":{"cniVersion":"0.3.1","interfaces":[{"name":"veth92e295cc","mac":"56:22:7f:b7:5b:75"},{"name":"eth0","mac":"46:b3:f3:77:bf:21","sandbox":"/var/run/netns/cnitest"}],"ips":[{"version":"4","interface":1,"address":"10.1.1.2/24","gateway":"10.1.1.1"}],"dns":{"nameservers":["10.64.255.25","8.8.8.8"]}},"type":"none"}
----------------------
CmdDel
ContainerID: cnitool-20c433bb2b1d6ede56d6
Netns: /var/run/netns/cnitest
IfName: eth0
Args:
Path: /opt/cni/bin
StdinData: {"cniOutput":"/tmp/cni_output.txt","cniVersion":"0.3.1","name":"test","type":"none"}
----------------------
```

15
plugins/debug/go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/containernetworking/cni/plugins/debug
go 1.21
require (
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.4.0
)
require (
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/sys v0.15.0 // indirect
)
replace github.com/containernetworking/cni => ../..

26
plugins/debug/go.sum Normal file
View File

@ -0,0 +1,26 @@
github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7wwSv9iTbXzzic=
github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

148
plugins/debug/main.go Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2021 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
type100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
)
type NetConf struct {
types.NetConf
CNIOutput string `json:"cniOutput,omitempty"`
AddHooks [][]string `json:"addHooks,omitempty"`
DelHooks [][]string `json:"delHooks,omitempty"`
CheckHooks [][]string `json:"checkHooks,omitempty"`
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("none"))
}
func outputCmdArgs(fp io.Writer, args *skel.CmdArgs) {
fmt.Fprintf(fp, `ContainerID: %s
Netns: %s
IfName: %s
Args: %s
Path: %s
StdinData: %s
----------------------
`,
args.ContainerID,
args.Netns,
args.IfName,
args.Args,
args.Path,
string(args.StdinData))
}
func parseConf(data []byte) (*NetConf, error) {
conf := &NetConf{}
if err := json.Unmarshal(data, &conf); err != nil {
return nil, fmt.Errorf("failed to parse")
}
return conf, nil
}
func getResult(netConf *NetConf) *type100.Result {
if netConf.RawPrevResult == nil {
return &type100.Result{}
}
version.ParsePrevResult(&netConf.NetConf)
result, _ := type100.NewResultFromResult(netConf.PrevResult)
return result
}
func executeHooks(netnsName string, hooks [][]string) {
netns, err := ns.GetNS(netnsName)
if err != nil {
return
}
defer netns.Close()
netns.Do(func(_ ns.NetNS) error {
for _, hookStrs := range hooks {
hookCmd := hookStrs[0]
hookArgs := hookStrs[1:]
output, err := exec.Command(hookCmd, hookArgs...).Output()
if err != nil {
fmt.Fprintf(os.Stderr, "OUTPUT: %v", output)
fmt.Fprintf(os.Stderr, "ERR: %v", err)
}
}
return nil
})
}
func cmdAdd(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
defer fp.Close()
fmt.Fprintf(fp, "CmdAdd\n")
outputCmdArgs(fp, args)
}
// call hooks
if netConf.AddHooks != nil {
executeHooks(args.Netns, netConf.AddHooks)
}
return types.PrintResult(getResult(netConf), netConf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
defer fp.Close()
fmt.Fprintf(fp, "CmdDel\n")
outputCmdArgs(fp, args)
}
// call hooks
if netConf.DelHooks != nil {
executeHooks(args.Netns, netConf.DelHooks)
}
return types.PrintResult(&type100.Result{}, netConf.CNIVersion)
}
func cmdCheck(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
defer fp.Close()
fmt.Fprintf(fp, "CmdCheck\n")
outputCmdArgs(fp, args)
}
// call hooks
if netConf.CheckHooks != nil {
executeHooks(args.Netns, netConf.CheckHooks)
}
return types.PrintResult(&type100.Result{}, netConf.CNIVersion)
}

View File

@ -17,7 +17,7 @@ package debug
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/containernetworking/cni/pkg/skel"
)
@ -29,6 +29,7 @@ type Debug struct {
// Report* fields allow the test to control the behavior of the no-op plugin
ReportResult string
ReportError string
ReportErrorCode uint
ReportStderr string
ReportVersionSupport []string
ExitWithCode int
@ -40,9 +41,18 @@ type Debug struct {
CmdArgs skel.CmdArgs
}
// CmdLogEntry records a single CNI command as well as its args
type CmdLogEntry struct {
Command string
CmdArgs skel.CmdArgs
}
// CmdLog records a list of CmdLogEntry received by the noop plugin
type CmdLog []CmdLogEntry
// ReadDebug will return a debug file recorded by the noop plugin
func ReadDebug(debugFilePath string) (*Debug, error) {
debugBytes, err := ioutil.ReadFile(debugFilePath)
debugBytes, err := os.ReadFile(debugFilePath)
if err != nil {
return nil, err
}
@ -63,10 +73,41 @@ func (debug *Debug) WriteDebug(debugFilePath string) error {
return err
}
err = ioutil.WriteFile(debugFilePath, debugBytes, 0600)
err = os.WriteFile(debugFilePath, debugBytes, 0o600)
if err != nil {
return err
}
return nil
}
// WriteCommandLog appends the executed cni command to the record file
func WriteCommandLog(path string, entry CmdLogEntry) error {
buf, err := os.ReadFile(path)
if err != nil {
return err
}
var cmds CmdLog
if len(buf) > 0 {
if err = json.Unmarshal(buf, &cmds); err != nil {
return err
}
}
cmds = append(cmds, entry)
if buf, err = json.Marshal(&cmds); err != nil {
return nil
}
return os.WriteFile(path, buf, 0o644)
}
func ReadCommandLog(path string) (CmdLog, error) {
buf, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cmds CmdLog
if err = json.Unmarshal(buf, &cmds); err != nil {
return nil, err
}
return cmds, nil
}

View File

@ -23,29 +23,31 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"os"
"strings"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
)
type NetConf struct {
types.NetConf
DebugFile string `json:"debugFile"`
PrevResult *current.Result `json:"prevResult,omitempty"`
types.PluginConf
DebugFile string `json:"debugFile"`
CommandLog string `json:"commandLog"`
}
func loadConf(bytes []byte) (*NetConf, error) {
n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v %q", err, string(bytes))
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes))
}
if err := version.ParsePrevResult(&n.PluginConf); err != nil {
return nil, err
}
return n, nil
}
@ -115,38 +117,57 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
return err
}
if netConf.CommandLog != "" {
if err = noop_debug.WriteCommandLog(
netConf.CommandLog,
noop_debug.CmdLogEntry{
Command: command,
CmdArgs: *args,
}); err != nil {
return err
}
}
if debug.ReportStderr != "" {
if _, err = os.Stderr.WriteString(debug.ReportStderr); err != nil {
return err
}
}
if debug.ReportError != "" {
return errors.New(debug.ReportError)
} else if debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS" {
switch {
case debug.ReportError != "":
ec := debug.ReportErrorCode
if ec == 0 {
ec = types.ErrInternal
}
return &types.Error{
Msg: debug.ReportError,
Code: ec,
}
case debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS":
prevResult := netConf.PrevResult
if debug.ReportResult == "INJECT-DNS" {
prevResult, err = current.NewResultFromResult(netConf.PrevResult)
newResult, err := current.NewResultFromResult(netConf.PrevResult)
if err != nil {
return err
}
prevResult.DNS.Nameservers = []string{"1.2.3.4"}
newResult.DNS.Nameservers = []string{"1.2.3.4"}
prevResult = newResult
}
// Must print the prevResult as the CNIVersion of the config
newResult, err := prevResult.GetAsVersion(netConf.CNIVersion)
if err != nil {
return fmt.Errorf("failed to convert result to config %q: %v", netConf.CNIVersion, err)
return fmt.Errorf("failed to convert result to config %q: %w", netConf.CNIVersion, err)
}
resultBytes, err := json.Marshal(newResult)
if err != nil {
return fmt.Errorf("failed to marshal new result: %v", err)
return fmt.Errorf("failed to marshal new result: %w", err)
}
_, err = os.Stdout.WriteString(string(resultBytes))
if err != nil {
return err
}
} else if debug.ReportResult != "" {
case debug.ReportResult != "":
_, err = os.Stdout.WriteString(debug.ReportResult)
if err != nil {
return err
@ -160,7 +181,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
}
func debugGetSupportedVersions(stdinData []byte) []string {
vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"}
vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"}
cniArgs := os.Getenv("CNI_ARGS")
if cniArgs == "" {
return vers
@ -193,9 +214,17 @@ func cmdDel(args *skel.CmdArgs) error {
return debugBehavior(args, "DEL")
}
func cmdGC(args *skel.CmdArgs) error {
return debugBehavior(args, "GC")
}
func cmdStatus(args *skel.CmdArgs) error {
return debugBehavior(args, "STATUS")
}
func saveStdin() ([]byte, error) {
// Read original stdin
stdinData, err := ioutil.ReadAll(os.Stdin)
stdinData, err := io.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
@ -224,5 +253,11 @@ func main() {
}
supportedVersions := debugGetSupportedVersions(stdinData)
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0")
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
GC: cmdGC,
Status: cmdStatus,
}, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0")
}

View File

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

View File

@ -16,18 +16,18 @@ package main_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("No-op plugin", func() {
@ -46,7 +46,7 @@ var _ = Describe("No-op plugin", func() {
ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"},
}
debugFile, err := ioutil.TempFile("", "cni_debug")
debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name()
@ -107,6 +107,7 @@ var _ = Describe("No-op plugin", func() {
"some":"stdin-json",
"cniVersion": "0.3.1",
"prevResult": {
"cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.15/24"}]
}
}`)
@ -125,6 +126,7 @@ var _ = Describe("No-op plugin", func() {
"some":"stdin-json",
"cniVersion": "0.4.0",
"prevResult": {
"cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.15/24"}]
}
}`)
@ -143,7 +145,7 @@ var _ = Describe("No-op plugin", func() {
"some":"stdin-json",
"cniVersion": "0.4.0",
"prevResult": {
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}
@ -153,7 +155,7 @@ var _ = Describe("No-op plugin", func() {
Expect(err).NotTo(HaveOccurred())
Eventually(session).Should(gexec.Exit(0))
Expect(session.Out.Contents()).To(MatchJSON(`{
"cniVersion": "0.4.0",
"cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {"nameservers": ["1.2.3.4"]}
}`))
@ -228,7 +230,7 @@ var _ = Describe("No-op plugin", func() {
})
Context("when the ReportResult debug field is set", func() {
var expectedResultString = fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
expectedResultString := fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
BeforeEach(func() {
debug.ReportResult = expectedResultString

View File

@ -2,7 +2,36 @@
if [[ ${DEBUG} -gt 0 ]]; then set -x; fi
NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}
NETCONFPATH="${NETCONFPATH-/etc/cni/net.d}"
function exec_list() {
plist="$1"
name="$2"
cniVersion="$3"
echo "$plist" | jq -c '.[]' | while read -r conf; do
plugin_bin="$(echo "$conf" | jq -r '.type')"
conf="$(echo "$conf" | jq -r ".name = \"$name\" | .cniVersion = \"$cniVersion\"")"
if [ -n "$res" ]; then
conf="$(echo "$conf" | jq -r ".prevResult=$res")"
fi
if ! res=$(echo "$conf" | $plugin_bin); then
error "$name" "$res"
elif [[ ${DEBUG} -gt 0 ]]; then
echo "${res}" | jq -r .
fi
done
}
function error () {
name="$1"
res="$2"
err_msg=$(echo "$res" | jq -r '.msg')
if [ -z "$errmsg" ]; then
err_msg=$res
fi
echo "${name} : error executing $CNI_COMMAND: $err_msg"
exit 1
}
function exec_plugins() {
i=0
@ -13,25 +42,24 @@ function exec_plugins() {
export CNI_CONTAINERID=$contid
export CNI_NETNS=$netns
for netconf in $(echo $NETCONFPATH/*.conf | sort); do
name=$(jq -r '.name' <$netconf)
plugin=$(jq -r '.type' <$netconf)
export CNI_IFNAME=$(printf eth%d $i)
for netconf in $(echo "$NETCONFPATH"/*.conf | sort); do
export CNI_IFNAME=$(printf eth%d $i)
name=$(jq -r '.name' <"$netconf")
cniVersion=$(jq -r '.cniVersion' <"$netconf")
plist=$(jq '.plugins | select(.!=null)' <"$netconf")
if [ -n "$plist" ]; then
exec_list "$plist" "$name" "$cniVersion"
else
plugin=$(jq -r '.type' <"$netconf")
res=$($plugin <$netconf)
if [ $? -ne 0 ]; then
errmsg=$(echo $res | jq -r '.msg')
if [ -z "$errmsg" ]; then
errmsg=$res
fi
if ! res=$($plugin <"$netconf"); then
error "$name" "$res"
elif [[ ${DEBUG} -gt 0 ]]; then
echo "${res}" | jq -r .
fi
fi
echo "${name} : error executing $CNI_COMMAND: $errmsg"
exit 1
elif [[ ${DEBUG} -gt 0 ]]; then
echo ${res} | jq -r .
fi
let "i=i+1"
(( i++ )) || true
done
}

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.12-alpine \
/bin/sh -xe -c "\
apk --no-cache add bash tar;
cd /opt/src; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x; do \
rm -f ${OUTPUT_DIR}/* ; \
CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \
for format in tgz; do \
FILENAME=cni-\$arch-${TAG}.\$format; \
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
if [ \"\$arch\" == \"amd64\" ]; then \
cp \$FILEPATH ${RELEASE_DIR}/cni-${TAG}.\$format; \
fi; \
done; \
done;
cd ${RELEASE_DIR};
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
cd ..
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"

25
test.sh
View File

@ -7,38 +7,17 @@ cd "$(dirname $0)"
PKGS=${PKGS:-$(go list ./... | xargs echo)}
echo -n "Running tests "
function testrun {
bash -c "umask 0; PATH=$PATH go test $@"
}
if [ ! -z "${COVERALLS:-""}" ]; then
# coverage profile only works per-package
echo "with coverage profile generation..."
i=0
for t in ${PKGS}; do
testrun "-covermode set -coverprofile ${i}.coverprofile ${t}"
go test -covermode set -coverprofile ${i}.coverprofile "${t}"
i=$((i+1))
done
gover
goveralls -service=travis-ci -coverprofile=gover.coverprofile
else
echo "without coverage profile generation..."
for t in ${PKGS}; do
testrun "${t}"
done
fi
echo "Checking gofmt..."
fmtRes=$(go fmt ${PKGS})
if [ -n "${fmtRes}" ]; then
echo -e "go fmt checking failed:\n${fmtRes}"
exit 255
fi
echo "Checking govet..."
vetRes=$(go vet ${PKGS})
if [ -n "${vetRes}" ]; then
echo -e "go vet checking failed:\n${vetRes}"
exit 255
go test ${PKGS}
fi
echo "Checking license header..."