Compare commits

...

654 Commits
v0.6.0 ... 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
Casey Callendrello 11db36c11b
Merge pull request #768 from asears/md-doc-lint-updates-3
Fix linting issues in docs, add headers to json example, update errors into table
2020-06-10 17:43:06 +02:00
Casey Callendrello 9ec274acd6
Merge branch 'master' into md-doc-lint-updates-3 2020-06-10 17:38:01 +02:00
Bryan Boreham 0016c95d2e
Merge pull request #769 from caboteria/readme-tweaks
Readme tweaks
2020-06-10 16:36:33 +01:00
toby cabot c78d465e9f Replace 2019 conference announcement with links to the recordings
While KubeCon/CloudNativeCon 2019 is in the past the recordings of the
sessions are helpful.

Signed-off-by: toby cabot <toby@caboteria.org>
2020-06-07 18:12:05 -04:00
toby cabot fc1de42c01 Sync contact methods in README and CONTRIBUTING
Now that the CNI slack has been in use for ~7 months it's no longer
necessary to have it as a top-level banner headline.  It should be in
CONTRIBUTING.md, though, with the other project contact channels.

Signed-off-by: toby cabot <toby@caboteria.org>
2020-06-07 18:12:05 -04:00
asears c815aca145
Update the SPEC, CODE-OF-CONDUCT, CONTRIBUTING, RELEASING with minor formatting changes and linting updates.
Signed-off-by: asears <asears@users.noreply.github.com>
2020-06-03 21:41:13 -04:00
Casey Callendrello f92762a8bf
Merge pull request #765 from rkamudhan/patch-4
adding OVN4NFV-K8s-Plugin as 3rd party plugin
2020-05-13 17:13:39 +02:00
Kuralamudhan Ramakrishnan 17a6379deb Update README.md
Signed-off-by: r.kuralamudhan <kuralamudhan.ramakrishnan@intel.com>
2020-05-06 21:12:32 -07:00
Kuralamudhan Ramakrishnan 956c943a7e adding OVN4NFV-K8s-Plugin as 3rd party plugin
Adding [OVN4NFV-K8S-Plugin](https://github.com/opnfv/ovn4nfv-k8s-plugin) is
OVN based CNI controller plugin to provide cloud-native based Service function
chaining(SFC), Multiple OVN overlay networking, dynamic subnet creation,
dynamic creation of virtual networks, VLAN Provider network,
Direct provider network and pluggable with other Multi-network plugins,
ideal for edge-based cloud-native workloads in Multi-cluster networking

Signed-off-by: r.kuralamudhan <kuralamudhan.ramakrishnan@intel.com>
2020-05-06 21:11:32 -07:00
Bryan Boreham 77436456f2
Merge pull request #763 from saschagrunert/version-retry
Retry exec commands on text file busy
2020-04-22 16:55:15 +01:00
Dan Williams acc3dadce9
Merge pull request #764 from adrianchiris/typo-infiniband-guid
Typo fixes for infiniband GUID
2020-04-22 10:53:43 -05:00
Sascha Grunert e2a736653a
Retry exec commands on text file busy
This makes the plugin validation a bit more robust by retrying the
execution if the plugin is in text file busy state. This can happen if
we write the config and the plugin at once. To avoid such races we now
try up to 5 seconds for the plugin validation.

Signed-off-by: Sascha Grunert <sgrunert@suse.com>
2020-04-16 09:11:52 +02:00
Adrian Chiris 76b18ea294 Typo fixes for infiniband GUID
- Remove underscore in the Area description of the entry
- Modify the capability name to be camelCase instead of underscores
  to be consistent with other capabilities (e.g `portMappings`, `ipRanges`)

As there was no CNI released and no implementations of this capability
(That i know of) i reckon we can do the attribute name adjustment.

Signed-off-by: Adrian Chiris <adrianc@mellanox.com>
2020-04-16 10:08:07 +03:00
Casey Callendrello bf84331c94
Merge pull request #761 from adrianchiris/device-id_runtime_conf
Add DeviceID attribute to RuntimeConfig
2020-04-08 17:33:34 +02:00
Michael C. Cambria 7d9ec90bff
Merge pull request #760 from dcbw/really-return-stderr
invoke: capture and return stderr if plugin exits unexpectedly
2020-04-08 11:32:44 -04:00
Dan Williams 44dabed6ce invoke: capture and return stderr if plugin exits unexpectedly
The way raw_exec invokes the command doesn't actually pass back
stderr, despite ExitError having that capability.  c.Run()
internally calls c.Wait() but that doesn't capture stderr and
insert it into the returned ExitError. So we have to do that
ourselves.

Fixes: https://github.com/containernetworking/cni/issues/732
Fixes: https://github.com/containernetworking/cni/pull/759

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-04-08 09:29:41 -05:00
Adrian Chiris 279bc6c21b Add DeviceID attribute to RuntimeConfig
This commit attempts to standardize the consumption
of a device identifier by CNI's for networks that
is associated with a device resource and require its
identifier to perform configurations.

Signed-off-by: Adrian Chiris <adrianc@mellanox.com>
2020-04-08 12:42:13 +03:00
Dan Williams fdcc7b15d7 test: allow specific package testing with PKGS=<x> ./test.sh
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-04-01 11:52:44 -05:00
Dan Williams cf734d46be
Merge pull request #742 from adrianchiris/master
Add GUID to well known Capabilities
2020-04-01 09:22:49 -05:00
Adrian Chiris ca0082f790 Add Infiniband GUID to well known Capabilities
This commit extends CONVENTION.md Well-known Capabilities
to include `infiniband_guid`. this shall be used by CNI plugins
who wish to set Infiniband GUID address for a network interface.

Change-Id: I3716e0a9ea660937a73feee00ea9c5b1a92ab478
Signed-off-by: Adrian Chiris <adrianc@mellanox.com>
2020-03-12 17:41:08 +02:00
Dan Williams 4fae32b849
Merge pull request #755 from libesz/fix-conflist-samples
Remove extra ',' chars which makes conflist examples invalid.
2020-03-04 10:16:08 -06:00
Gergo Huszty 38353fabbb Remove extra ',' chars which makes conflist examples invalid.
Signed-off-by: Gergo Huszty <gergo.huszty@ibm.com>
2020-02-28 15:18:06 +01:00
Dan Williams 56ace59a9e
Merge pull request #752 from dcbw/maintainers-remove-stefan
maintainers: remove Stefan per personal request
2020-02-19 10:46:25 -06:00
Dan Williams 134f603c67 maintainers: remove Stefan per personal request
We contacted Stefan and he said he could be dropped as a CNI maintainer.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-02-19 10:28:21 -06:00
Piotr Skamruk c252795066
Merge pull request #751 from dcbw/mcambria-maintainer
Add Michael Cambria as a CNI maintainer
2020-02-13 11:05:30 +01:00
Dan Williams 1435c6bddb Add Michael Cambria as a CNI maintainer
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-02-12 10:11:19 -06:00
Piotr Skamruk f69d1f2cab
Merge pull request #745 from truongnh1992/insert_link
Update link freenode.org to freenode.net
2020-01-30 11:15:46 +01:00
Nguyen Hai Truong 6b46a03d2b Update link freenode.org to freenode.net
The domain freenode.org is no longer up-to-date,
use freenode.net instead

Signed-off-by: Nguyen Hai Truong <truongnh@vn.fujitsu.com>
2020-01-30 16:30:46 +07:00
Bryan Boreham 0adeb0ed54
Merge pull request #744 from containernetworking/update-1-0-roadmap
Update roadmap for 1.0 plans
2020-01-29 16:54:22 +00:00
Bryan Boreham 075e303ba8 Update roadmap for 1.0 plans
Signed-off-by: Bryan Boreham <bryan@weave.works>
2020-01-29 16:20:26 +00:00
Bryan Boreham d0fd3ff4e9
Merge pull request #583 from jellonek/resultasstring
Update Result.String implementation to work as declared in interface
2020-01-22 16:31:49 +00:00
Bryan Boreham c2b68cd595
Merge pull request #738 from mars1024/black-box-testing
pkg/utils: utility package should use black-box testing
2020-01-22 16:31:07 +00:00
Bryan Boreham 5c2bade00b
Merge pull request #740 from gavinfish/json_comments
Change language identifier to jsonc for json with comments
2020-01-19 19:01:04 +00:00
gavinfish 0b1c6497ed Change language identifier to jsonc for json with comments
Signed-off-by: gavinfish <drfish.me@gmail.com>
2020-01-17 13:59:17 +08:00
Piotr Skamruk 704c56d291 Update tests
Signed-off-by: Piotr Skamruk <piotr.skamruk@gmail.com>
2020-01-15 12:26:12 +01:00
Piotr Skamruk f208f19fd3 Remove Result.String
Closes #581

Signed-off-by: Piotr Skamruk <piotr.skamruk@gmail.com>
2020-01-15 12:26:12 +01:00
Bruce Ma cc6e8afa5a pkg/utils: utility package should use black-box testing
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-01-10 20:36:12 +08:00
Dan Williams a09ec7a992
Merge pull request #736 from q384566678/add-utils-test
pkg/utils: add utils_test
2020-01-08 10:22:03 -06:00
Piotr Skamruk 6680f870ab
Merge pull request #735 from zjj2wry/intercepte-stderr
intercept netplugin std error
2020-01-08 17:12:57 +01:00
Zhou Hao 025e32f89e pkg/utils: add utils_test
Signed-off-by: Zhou Hao <zhouhao@cn.fujitsu.com>
2019-12-27 11:32:54 +08:00
郑佳金 6f29b01658 intercept netplugin std error
Signed-off-by: 郑佳金 <zhengjiajin2016@gmail.com>
Signed-off-by: 郑佳金 <zhengjiajin2016@gmail.com>
2019-12-19 14:08:13 +08:00
Bryan Boreham 4c077d2fec
Merge pull request #730 from oilbeater/add-kube-ovn
Add Kube-OVN to CNI list
2019-12-11 16:08:43 +00:00
Bryan Boreham d65360849f
Merge branch 'master' into add-kube-ovn 2019-12-11 16:08:22 +00:00
Casey Callendrello 340972aad4
Merge pull request #731 from yasensim/master
Add Project Antrea in CNI list
2019-12-11 17:07:30 +01:00
oilbeater 777584fb17 Add Kube-OVN to CNI list
Signed-off-by: oilbeater <liumengxinfly@gmail.com>
2019-12-05 14:24:05 +08:00
Bryan Boreham 7aed59f421
Merge pull request #727 from rktidwell/master
DOCUMENTATION: Address incomplete instructions in CONTRIBUTING.md
2019-12-04 16:34:45 +00:00
Dan Williams dce771e2a0
Merge pull request #725 from containernetworking/new-maintainers
Add Bruce Ma and Piotr Skarmuk as maintainers
2019-12-04 10:09:07 -06:00
yasensim e00fa533de appending antrea to the cni list
Signed-off-by: yasensim <yasensim@gmail.com>
2019-12-03 20:45:50 -08:00
yasensim 7815be7f0a Add Project Antrea in CNI list
Signed-off-by: yasensim <yasensim@gmail.com>
2019-12-02 17:27:29 -08:00
Matt Dupre dcb71ae8c9
Merge pull request #729 from weibeld/master
Add table of contents to SPEC.md
2019-11-27 11:39:56 -05:00
Daniel Weibel f2fa4a3fa0 Add table of contents
Generated wit markdown-toc:

markdown-toc --no-firsth1 --maxdepth=2 SPEC.md

Signed-off-by: Daniel Weibel <danielmweibel@gmail.com>
2019-11-26 19:37:48 +08:00
Ryan Tidwell b36de6e00b
DOCUMENTATION: Address incomplete instructions in CONTRIBUTING.md
This change addresses a few missing steps and out-of-date items in the
contributor docs such as fetching proper dependencies and removing
references to paths that no longer exist.

Fixes #726

Signed-off-by: Ryan Tidwell <rtidwell@suse.com>
2019-11-25 09:34:48 -06:00
Bryan Boreham ade6ad342d
Merge pull request #728 from noironetworks/cisco-cni
Adding Cisco ACI to CNI plugin list
2019-11-25 12:26:09 +00:00
Sumit Naiksatam c35ac21f14 Adding Cisco ACI to CNI plugin list
Signed-off-by: Sumit Naiksatam <sumitnaiksatam@gmail.com>
2019-11-22 10:36:38 -08:00
Bryan Boreham 3eb88acd2a Add Bruce Ma and Piotr Skarmuk as maintainers
Signed-off-by: Bryan Boreham <bryan@weave.works>
2019-11-16 11:45:09 +00:00
Bryan Boreham 46b9d6fa20
Merge pull request #724 from squeed/readme-update
Update README to sunset slack and call out kubecon presentations
2019-11-13 19:28:37 +00:00
Casey Callendrello 6c6a3156c3 Update README to sunset slack and call out kubecon presentations
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2019-11-13 17:36:00 +01:00
Piotr Skamruk c2508f1804
Merge pull request #722 from containernetworking/note-binaries
Add a note to README about where to find the binaries
2019-11-07 12:41:35 +01:00
Bryan Boreham b89eff5321 Add a note to README about where to find the binaries
Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2019-11-06 16:56:52 +00:00
Dan Williams 2d4bc3d57a
Merge pull request #721 from mccv1r0/issue720
When CNI version isn't supplied in config, use default.
2019-10-16 11:11:06 -05:00
Michael Cambria 4eec64802d When the CNI version isn't supplied in config, use default.
Signed-off-by: Michael Cambria <mccv1r0@gmail.com>
2019-10-16 11:20:00 -04:00
Bryan Boreham 63202159f1
Merge pull request #712 from mars1024/modify/ifname_validation
add interface name validation
2019-10-09 16:15:59 +01:00
Bruce Ma eefc06974c SPEC: update validation rules for interface name in docs and address some comments
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-08 12:01:17 +08:00
Piotr Skamruk 904ce461b0
Merge pull request #715 from mars1024/typo/spec
spec: fix some typo in docs
2019-10-03 22:07:54 +02:00
Dan Williams 8f2d48e6e1
Merge pull request #716 from yuxiaobo96/cni-update3
Correct word spelling mistakes
2019-10-02 10:17:22 -05:00
Bruce Ma b5188cf64f spec: fix some typo in docs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-29 14:11:31 +08:00
yuxiaobo c94fcd742c Perfect annotation: Correct word spelling mistakes
Signed-off-by: yuxiaobo <yuxiaobogo@163.com>
2019-09-26 15:34:44 +08:00
Bryan Boreham 02a25a7635
Merge pull request #713 from janisz/patch-1
Bump golang
2019-09-25 16:08:28 +01:00
Piotr Skamruk 15d37883e6
Merge pull request #710 from hwdef/add-err-handle-p/t/noop
add err handling in plugins/test/noop/
2019-09-24 18:46:20 +02:00
Bruce Ma 7be1ac932d add interface name validation to libcni and skel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-20 11:30:22 +08:00
Bruce Ma 9f4a623222 utils: add validation function for interface name
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-20 11:29:05 +08:00
janisz 2a82881b00
Bump golang
Refs:
* https://github.com/containernetworking/plugins/pull/386
* https://golang.org/doc/go1.13

Signed-off-by: Tomasz Janiszewski <janiszt@gmail.com>
2019-09-19 10:15:35 +02:00
Casey Callendrello bd4e822851
Merge pull request #711 from johscheuer/update-cnitool-docs
Update cnitool docs
2019-09-18 14:21:55 +02:00
Johannes M. Scheuermann c0f784d16d Update cnitool docs
Signed-off-by: Johannes M. Scheuermann <joh.scheuer@gmail.com>
2019-09-17 21:45:26 +02:00
hwdef 94399d569c add err handling in plugins/test/noop/
Signed-off-by: hwdef <hwdef97@gmail.com>
2019-09-13 01:30:09 +08:00
Piotr Skamruk 83439463f7
Merge pull request #704 from dcbw/cache-ifname
libcni: also cache IfName
2019-09-04 17:32:31 +02:00
Bryan Boreham bd64c46461
Merge pull request #698 from mccv1r0/issue679
validate containerID and networkName
2019-09-04 16:29:24 +01:00
Dan Williams 9d4429ded4
Merge pull request #705 from dcbw/fix-cache-result-key
libcni: fix cache file 'result' key name
2019-09-04 10:18:19 -05:00
Michael Cambria d8dfb56fd8 validate containerID and networkName
ensure they contain only letters and numbers
2019-09-03 17:54:22 -04:00
Dan Williams e4a11bae33 libcni: cache file operations require full uniqueness tuple in RuntimeConf
We can't generate a cache file path without all of network name,
container ID, and interface name in the RuntimeConf. They should
be there already anyway (otherwise errors will occur) but we should
return those errors earlier.
2019-09-03 11:19:18 -05:00
Dan Williams a83f3cb0f4 libcni: also cache IfName, network name, and container ID
We should have all components of the uniqueness tuple in the cache file
so that they can be read out of the file without trying to parse the
cache file name.
2019-09-03 10:04:18 -05:00
Dan Williams 894863c200 libcni: fix cache file 'result' key name 2019-08-29 20:39:33 -05:00
Dan Williams 13b6ad6447
Merge pull request #699 from mars1024/cleanup/skel
skel: remove needless functions and types
2019-08-21 10:26:33 -05:00
Bruce Ma f3654f3d2d skel: remove needless functions and types
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-16 18:18:23 +08:00
Dan Williams 8c6c47d1c7
Merge pull request #686 from mars1024/cleanup/skel_error
skel: clean up errors in skel and add some well-known error codes
2019-08-07 10:13:50 -05:00
Bruce Ma 3e79703c66 modify some well-known errors
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-01 11:21:26 +08:00
Dan Williams d19c2358a5
Merge pull request #691 from mars1024/docs/add_some_capabilities
docs: add ips and mac to well-known capabilities and fix some typo
2019-07-25 11:11:49 -05:00
Dan Williams d34f7f4ead
Merge pull request #692 from mars1024/bugfix/validatePlugin
libcni: find plugin in exec
2019-07-25 11:11:35 -05:00
Bruce Ma 1318d7c94c libcni: find plugin in exec
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-25 10:16:23 +08:00
Piotr Skamruk df124b22e4
Merge pull request #678 from mccv1r0/pr661
libcni: add config caching [v2]
2019-07-20 11:32:07 +02:00
Michael Cambria 9af40ed73a libcni: add config caching
Cache the config JSON, CNI_ARGs, and CapabilityArgs on ADD operations,
    and remove them on DEL. This allows runtimes to retrieve the cached
    information to ensure that the pod is given the same config and options
    on DEL as it was given on ADD.

    Add versioning to beginning of cache file
    Combine cached configuration and results into one file

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2019-07-19 17:49:42 -04:00
Casey Callendrello 461cf2b48f
Merge pull request #690 from BSWANG/master
readme: add Alibaba Cloud CNI plugin 'Terway' to the list
2019-07-17 11:20:17 -04:00
Casey Callendrello b2c510b758
Merge pull request #689 from rosenhouse/bump-linux-version
bump linux to Bionic 18.04 in Travis and Vagrant
2019-07-17 11:12:12 -04:00
Bruce Ma 722a488070 docs: add ips and mac to well-known capabilities and fix some typo
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-16 22:50:35 +08:00
Bruce Ma 227c438835 SPEC: add some well-known error codes
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-15 22:50:17 +08:00
Bruce Ma ba034ef5da testcases: make testcase use suitable error code
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-15 22:33:23 +08:00
Bruce Ma 4b29940c46 skel: clean up typed Errors in skel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-15 21:36:18 +08:00
Bruce Ma 50192c0c4c types : add NewError method
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-15 20:04:01 +08:00
bingshen.wbs 0af0477c17 readme: add Alibaba Cloud CNI plugin 'Terway' to the list
Signed-off-by: bingshen.wbs <bingshen.wbs@alibaba-inc.com>
2019-07-11 13:55:48 +08:00
Gabe Rosenhouse b92d83cb59 bump linux to Bionic 18.04 in Travis and Vagrant 2019-07-10 08:32:50 -07:00
Dan Williams 220a58f522
Merge pull request #688 from dholbach/patch-1
add missing comma, fixes syntax error
2019-07-10 10:03:39 -05:00
Daniel Holbach a48337ac26 add missing commas, fix syntax error
Bryan spotted the second one - well spotted
2019-07-05 11:39:39 +02:00
Matt Dupre dc71cd2ba6
Merge pull request #681 from mars1024/clean_up
clean up : fix staticcheck warnings
2019-07-03 16:03:56 +01:00
Michael Cambria 5077b14734 Add stringifyArgs and parseArgs functions to utils.go
Signed-off-by: Michael Cambria <mcambria@redhat.com>
2019-06-28 11:56:55 -04:00
Dan Williams ef03d0458c
Merge pull request #682 from dcbw/cachedir
libcni: add InitCNIConfigWithCacheDir() and deprecate RuntimeConfig.CacheDir
2019-06-26 08:03:07 -05:00
Dan Williams 80ad241eb6 libcni: add InitCNIConfigWithCacheDir() and deprecate RuntimeConfig.CacheDir
CacheDir is a property of the entire CNIConfig object and there is no
point to having it be different between container invocations. It was
originally put in RuntimeConfig to make re-vendoring easier so that
users wouldn't have to update calls to InitCNIConfig.
2019-06-20 09:39:25 -05:00
Bruce Ma 5dbeae87ff clean up : remove useless variable
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-19 23:53:31 +08:00
Bruce Ma a03dc28d25 clean up : fix staticcheck warnings
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-19 19:45:29 +08:00
Casey Callendrello dc953e2fd9
Merge pull request #668 from adelina-t/patch-1
Docs: Small typo fixes in main README.md
2019-06-12 17:24:20 +02:00
Casey Callendrello 4cfb7b5689
Merge pull request #663 from mars1024/modify/cniargs_for_delegate
delegate : allow delegation funcs override CNI_COMMAND env automatically in heritance
2019-06-05 17:31:12 +02:00
Dan Williams 33bb6067c0
Merge pull request #672 from dcbw/go-v1.12
Release: bump go to v1.12
2019-06-03 08:52:59 -05:00
Dan Williams e3a51e54fd Release: bump go to v1.12 2019-05-29 11:32:55 -05:00
Dan Williams d11ef1fb4e
Merge pull request #669 from mccv1r0/gnlcached
Add GetNetworkListCachedResult method to CNI interface
2019-05-29 10:30:36 -05:00
Michael Cambria 53e9245e7b add GetNetworkListCachedResult to CNI interface 2019-05-17 11:59:05 -04:00
Bruce Ma 1475a6e3c0 add some testcases for invoke/args
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-16 17:14:00 +08:00
Bruce Ma e67d6cba0f delegate : allow delegation funcs override CNI_COMMAND env automatically in heritance
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-16 16:49:18 +08:00
Bryan Boreham 5bab2c0253
Merge pull request #662 from mars1024/bugfix/cniargs_override
invoke : ensure custom envs of CNIArgs are prepended to process envs
2019-05-15 16:51:49 +01:00
Adelina Tuvenie cbca75246a Docs: Small typo fixes in main README.md 2019-05-15 18:29:54 +03:00
Dan Williams a94ff7bdaf
Merge pull request #666 from s1061123/add_route_override
Add cni-route-override to CNI plugin list
2019-05-15 10:27:46 -05:00
Tomofumi Hayashi 0469a05ab5 Add cni-route-override to CNI plugin list 2019-05-09 15:36:31 +09:00
Bruce Ma e40cce2373 add deduplication in return of CNIArgs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-09 13:57:16 +08:00
Bruce Ma ea274f7629 ensure custom envs of CNIArgs are prepended to process envs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-05 12:34:14 +08:00
Dan Williams f726df9103
Merge pull request #659 from Colstuwjx/cnitool-check-example
Upgrade to cni spec v0.4.0.
2019-05-01 10:05:57 -05:00
Colstuwjx ffc61ed2d3 Upgrade to cni spec v0.4.0. 2019-04-29 18:20:30 +08:00
Casey Callendrello 7d76556571
Merge pull request #658 from squeed/bump-spec-040
SPEC: add references to spec version 0.4.0
2019-04-26 18:00:55 +02:00
Casey Callendrello c47f15614b SPEC: add references to spec version 0.4.0 2019-04-26 14:04:09 +02:00
Dan Williams ec5ec68fa4
Merge pull request #655 from danwinship/error-no-error
Return a better error when the plugin returns none
2019-04-17 10:22:34 -05:00
Dan Williams 530eed4d27
Merge pull request #651 from SataQiu/fix-spell-20190404
Fix some spell errors
2019-04-15 16:45:14 -05:00
Dan Williams 121ab0058f
Merge pull request #652 from SataQiu/fix-link-20190409
Make link point to the correct version
2019-04-15 16:44:47 -05:00
Dan Williams de8cf72507
Merge pull request #653 from SataQiu/fix-spell-error
Fix spell error for configuration json
2019-04-15 16:44:03 -05:00
Dan Winship 59d8d02f67 Return a better error when the plugin returns none 2019-04-12 14:19:01 -04:00
SataQiu 8361cffee2 make link point to the correct version 2019-04-09 17:46:58 +08:00
SataQiu 4461d54929 fix spell error for configuration json 2019-04-09 17:09:24 +08:00
SataQiu 207be7e31e fix some spell errors 2019-04-04 23:36:59 +08:00
Bryan Boreham 0471e018e5
Merge pull request #647 from xichengliudui/fix404notfound
update spec-upgrades.md make it point to a specific web page
2019-04-03 17:02:41 +01:00
Dan Williams c9c5031a1b
Merge pull request #650 from xichengliudui/patch-2
Fix 404 not found
2019-04-03 10:05:58 -05:00
lIuDuI 6bddfcd3bb
Fix 404 not found 2019-03-30 19:09:43 +08:00
Dan Williams fbb95fff8a
Merge pull request #597 from dcbw/conventions-ip-prefix
CONVENTIONS: add support for IP prefixes
2019-03-20 10:58:02 -05:00
Matt Dupre f7e07eec38
Merge pull request #622 from trungnvfet/fix_http_https
Change http to https for security links
2019-03-20 15:52:47 +00:00
Bryan Boreham c976b421df
Merge pull request #627 from longkb/update_deprecated_link
Update deprecated link in doc
2019-03-20 15:51:05 +00:00
Dan Williams 139f460f90
Merge pull request #632 from hnwolf/fix_removed_document_link
Remove incorrect document link
2019-03-20 10:49:31 -05:00
Dan Williams fe0a28d82a
Merge pull request #639 from nagiesek/dnsCapabilityUpdate
Update conventions.md to include dns capability
2019-03-20 10:48:37 -05:00
Dan Williams 2c4013efcd
Merge pull request #621 from truongnh1992/fix-typo
doc: fix typo
2019-03-20 10:43:25 -05:00
Dan Williams 0dba47b1f9
Merge pull request #641 from dcbw/spec-clarify-routes
SPEC.md: clarify meaning of 'routes'
2019-03-20 10:41:23 -05:00
Dan Williams 4554856998 SPEC.md: clarify meaning of 'routes'
Routes are expected to be relevant to the sandbox interface.
2019-03-20 10:38:40 -05:00
Dan Williams f4fdaa2304
Merge pull request #617 from luksa/convert_from_020_gw
Don't copy gw from IP4.Gateway to Route.GW When converting from 0.2.0
2019-03-20 10:35:18 -05:00
1693291525@qq.com f55f75bf0e update spec-upgrades.md 2019-03-20 02:24:27 -04:00
Marko Lukša 83ed2459f7 Add tests provided by Dan Williams (@dbcw) 2019-03-15 10:04:20 +01:00
Marko Lukša 16d174d296 Don't copy gw from IP4.Gateway to Route.GW When converting from 0.2.0
When converting from 0.2.0 to 0.3.x, we used to copy the Gateway defined
under IP4.Gateway to each route that had GW set to nil. This is wrong
because the result of converting from 0.2.0 to 0.3.x and back to 0.2.0
doesn't match the original config.

Also, the spec says the following about routes.gw: "IP of the gateway.
If omitted, a default gateway is assumed (as determined by the CNI
plugin)."

So, it should be up to the CNI plugin to determine what to do when the
gateway in a route isn't specified. Therefore, when converting from
0.2.0 to 0.3.x, we should NOT populate the gateway field.
2019-03-15 10:01:46 +01:00
Kim Bao Long f00ca5f42b Update deprecated link in doc
Currently, the modified **plugins/main** was removed from master branch.
It's only available in **v0.3.0** tag. So this commit aims to update the
deprecated link to the working one.

Signed-off-by: Kim Bao Long <longkb@vn.fujitsu.com>
2019-03-14 08:13:14 +07:00
Casey Callendrello 6896907ab4
Merge pull request #626 from opensvc/arnaudveron-opensvc-addon
Add OpenSVC to the list of container runtimes
2019-03-13 16:42:41 +01:00
Casey Callendrello 3d19d747b6
Merge pull request #635 from longkb/remove_the_deprecated_links
Remove deprecated link in SPEC.md
2019-03-13 16:17:05 +01:00
Dan Williams 203f70b6bb
Merge pull request #636 from andrey-ko/result-print-to
add PrintTo method to Result interface
2019-03-13 10:14:36 -05:00
Dan Williams 2e00c58a3b
Merge pull request #637 from ajacoutot/typo
invoke: fix typo (opensbd -> openbsd)
2019-03-13 10:11:52 -05:00
Nathan Gieseker 5761cff2ef Update conventions.md to include dns capability 2019-03-12 21:44:55 -07:00
Antoine Jacoutot b3342a6e36 invoke: fix typo (opensbd -> openbsd) 2019-02-28 10:06:17 +01:00
akolomentsev 667a69ea9d add PrintTo method to Result interface, in order to allow to marshal result to any writer, not only stdout
Signed-off-by: Andrey Kolomentsev <andrey.kolomentsev@gmail.com>
2019-02-27 20:13:32 -08:00
Nguyen Hung Phuong b69a8d52b6 Remove incorrect document link 2019-02-26 08:39:29 +07:00
Kim Bao Long 8574263c4e Remove deprecated link in SPEC.md
Current, the URLs that link docs of **rkt_networking_proposal** and
**rkt_networking_design** are already dead. So this commit aims to
clean up them from **SPEC.md**.

Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com>
Signed-off-by: Kim Bao Long <longkb@vn.fujitsu.com>
2019-02-25 11:22:43 +07:00
Arnaud Veron b1838c7f90
Add OpenSVC to the list of container runtimes 2019-02-21 15:49:41 +01:00
Nguyen Hai Truong f36276e1bc doc: fix typo
Although it is spelling mistakes, it might make
affects while reading.

Signed-off-by: Nguyen Hai Truong <truongnh@vn.fujitsu.com>
2019-02-20 20:11:01 -08:00
Matt Dupre 8fa373b4fd
Merge pull request #624 from annp1987/remove_duplicate_worlds
Remove duplicate word 'the the'.
2019-02-20 16:30:19 +00:00
Matt Dupre 405805692c
Merge pull request #625 from huynq0911/fix_http_https
Change http to https for security links
2019-02-20 16:29:46 +00:00
Nguyen Quang Huy c96f9d1d95 Change http to https for security links
For security, we should change http into https links.
2019-02-20 15:07:48 +07:00
Nguyen Phuong An 5ba9bb8fa0 Remove duplicate word 'the the'.
Signed-off-by: Nguyen Phuong An <AnNP@vn.fujitsu.com>
2019-02-20 13:56:29 +07:00
Nguyen Van Trung ab0ee6fa98 Change http to https for security links
Signed-off-by: Nguyen Van Trung <trungnv@vn.fujitsu.com>
2019-02-15 10:18:04 +07:00
Bryan Boreham 7aacc67f18
Merge pull request #619 from annp1987/master
Use /usr/bin/env bash in shebang for docker-run.sh
2019-02-13 16:57:51 +00:00
Nguyen Phuong An bfcd424c0e Use /usr/bin/env bash in shebang for docker-run.sh 2019-02-13 15:26:08 +07:00
Bryan Boreham 51885a9b69
Merge pull request #618 from SchSeba/small_fix
Fix cnitool readme
2019-02-07 17:16:36 +00:00
Sebastian Sch c9bc5b79ec Fix cnitool readme.
build.sh script doesn't exist anymore change to build_linux.sh or
build_windows.sh
2019-02-07 12:18:41 +02:00
Dan Williams 8e8abe455b
Merge pull request #616 from JoeWrightss/patch-4
Fix some typos in SPEC.md
2019-02-06 10:15:30 -06:00
Bryan Boreham 4425e7e337
Merge pull request #615 from squeed/update-maint
Update Casey's email address.
2019-01-31 14:47:32 +00:00
Casey Callendrello 93bfd62fb1 Update Casey's email address. 2019-01-31 15:26:25 +01:00
zhoulin xie 06b37e1ff5 Fix some typos in SPEC.md
Signed-off-by: zhoulin xie <zhoulin.xie@daocloud.io>
2019-01-31 22:25:12 +08:00
Dan Williams 0cf415d434
Merge pull request #613 from bradmbock/add-vmwnsx-cni
Added VMware NSX CNI plugin to CNI plugin list.
2019-01-30 10:33:20 -06:00
Dan Williams 0126b26304
Merge pull request #614 from yeya24/patch/fixtypo
fix some typos
2019-01-30 10:05:05 -06:00
yeya24 afdc49d0a6 fix some typos 2019-01-29 22:55:01 +08:00
Bradley Bock d6ffc8d74f Added VMware NSX CNI plugin to CNI plugin list. 2019-01-28 14:17:37 -08:00
Bryan Boreham 762bb8ae5d
Merge pull request #602 from liucimin/fix_version_ParseVersion_function
fix the ParseVersion when the version is  will get fnAtoi
2019-01-23 16:29:37 +00:00
Matt Dupre 1c35013cf4
Merge pull request #612 from JoeWrightss/patch-3
Fix some typos in comment
2019-01-23 16:07:50 +00:00
zhoulin xie ce8938617e Fix some typos in comment
Signed-off-by: zhoulin xie <zhoulin.xie@daocloud.io>
2019-01-17 19:38:40 +08:00
Casey Callendrello ce46ad0130
Merge pull request #609 from containernetworking/community-20190130
Announce next community meeting in Jan 2019
2019-01-09 21:41:49 -08:00
Bryan Boreham b74b969c00 Announce next community meeting in Jan 2019
Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2019-01-09 16:34:24 +00:00
Dan Williams e2bdb13345
Merge pull request #607 from rst0git/master
readme: Update CNI plugins install example
2019-01-09 09:52:05 -06:00
liucimin 94bf13891a fix the ParseVersion when the version is will get fnAtoi 2018-12-25 14:31:39 +08:00
Radostin Stoyanov d6ed2fd5a3 readme: Update CNI plugins install example
Commit containernetworking/plugins@4e1f780 (Split build.sh into two
OS-specific scripts) removes the build.sh file. Now we have
build_linux.sh and build_windows.sh

Signed-off-by: Radostin Stoyanov <rstoyanov1@gmail.com>
2018-12-20 21:53:17 +00:00
Casey Callendrello 213b3fdea2
Merge pull request #600 from nak3/valid-args-num
Validate correct number of args in cnitool
2018-12-19 11:51:28 -05:00
Dan Williams b7ae50cbb4
Merge pull request #604 from JoeWrightss/patch-1
Fix some typos
2018-12-19 10:40:27 -06:00
Dan Williams 3de1875e27
Merge pull request #603 from containernetworking/bboreham-patch-1
Update roadmap with change from GET to CHECK
2018-12-19 10:40:04 -06:00
JoeWrightss 3a3a701be0 Fix some typos
Signed-off-by: JoeWrightss <zhoulin.xie@daocloud.io>
2018-12-07 23:19:51 +08:00
Bryan Boreham 34a6112b3b
Update roadmap with change from GET to CHECK 2018-12-06 12:42:47 +00:00
Kenjiro Nakayama 3d6117d63e Validate correct number of args in cnitool
Currently, cnitool command needs 4 args - e.g cnitool add <net>
<netns>. This patch changes to the number of valide args to 4 from 3.
2018-11-29 17:20:24 +09:00
Dan Williams 66dafdf3e9 CONVENTIONS: add support for IP prefixes
While many plugins already have IPAM range info not all do. Without
a range/subnet in configuration, passing a plain IP address in
CNI_ARGS or via 'args' does not provide enough information.
2018-11-28 16:04:33 -06:00
Dan Williams fdcc5db51b
Merge pull request #596 from nak3/update-go-version
Update supported Go version statement
2018-11-28 10:08:47 -06:00
Kenjiro Nakayama 297457de48 Update supported Go version statement 2018-11-28 16:13:03 +09:00
Dan Williams dba13c4822
Merge pull request #593 from Levovar/add_danm
README: expands list of 3rd party plugins with DANM
2018-11-14 10:12:16 -06:00
Levente Kale c744c26d2e README: expands list of 3rd party plugins with a new option
DANM -a K8s networking solution using CNI- was recently made opensource.
PR expands the list of 3rd party plugins with the reference to the project.
2018-11-10 13:19:32 +01:00
Dan Williams 951c0fb806
Merge pull request #592 from rosenhouse/bump-to-go-1.11
bump travis and vagrantfile to go 1.11
2018-11-07 09:28:20 -06:00
Gabe Rosenhouse b271ce351d bump travis and vagrantfile to go 1.11 2018-10-31 08:39:54 -07:00
Dan Williams 47f042a96e
Merge pull request #590 from containernetworking/move-del-text
Move spec text about DEL and errors
2018-10-31 10:33:24 -05:00
Bryan Boreham a8a44a721a
Merge pull request #591 from containernetworking/eagain
Add a well-known error for "try again"
2018-10-28 18:18:44 +02:00
Bryan Boreham cc562d1b44 Add a well-known error for "try again" 2018-10-17 15:37:38 +00:00
Bryan Boreham 24aa1f4030 Move spec text about DEL and errors
This paragraph was under a heading specifically about configuration
lists, but it applies to all DEL operations whether in a list or not.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2018-10-17 15:31:04 +00:00
Dan Williams 1dba71945f
Merge pull request #588 from GodloveD/add-singularity-to-readme
Added Singularity to the README.md.
2018-10-17 10:25:04 -05:00
GodloveD f301e8fe68 Added Singularity to the README.md. It began supporting cni in 3.0. 2018-10-09 17:08:42 -04:00
Dan Williams 777324ca6b
Merge pull request #584 from dcbw/get-to-check
skel,invoke,libcni: GET -> CHECK
2018-09-26 11:43:02 -05:00
Dan Williams df75bcfdbe libcni: caching should use new uniqueness tuple (netname, containerid, ifname) 2018-09-26 11:41:14 -05:00
Dan Williams 89d8d97746 types,libcni: implement DisableCheck 2018-09-26 11:20:47 -05:00
Dan Williams 68c963649f libcni: add GetNetwork[List]CachedResult
Now that GET no longer exists and no longer returns the
cached result, provide a public method to return the cached
result that runtimes can use.
2018-09-26 11:03:05 -05:00
Dan Williams 42e857f0a2 skel,invoke,libcni: GET -> CHECK
Major change is that CHECK no longer returns a Result, per the spec.
2018-09-26 10:51:49 -05:00
Dan Williams f145359def
Merge pull request #568 from liucimin/feature_exec_timeout
libcni/invoke: add context argument to enable cancelation and timeouts
2018-09-26 10:19:17 -05:00
liucimin c5ab1c3fc6 add the timeout context 2018-09-25 09:52:59 +08:00
Dan Williams 7418eded11
Merge pull request #587 from mccv1r0/cnitool
cnitool: Honor interface name supplied via CNI_IFNAME environment variable.
2018-09-24 16:13:51 -05:00
Michael Cambria 626458fb67 gofmt needed 2018-09-24 14:51:38 -04:00
Michael Cambria 01eda4e0cf Use interface name supplied via CNI_IFNAME environment variable, when set. Default to eth0 otherwise 2018-09-24 13:29:28 -04:00
Dan Williams 93b8b11111
Merge pull request #579 from matthewdupre/check-spec
Replace GET action in spec with proposed CHECK action
2018-09-12 10:55:48 -05:00
Casey Callendrello d59bfbf33e SPEC: CHECK: clarify difference between "tracked" and "non-tracked" resources 2018-09-12 17:13:07 +02:00
Gabe Rosenhouse 47cf2da00b
Merge pull request #578 from lifubang/loopback020
when type is loopback, IP4 or IP6 is always nil
2018-08-22 08:22:31 -07:00
Matt Dupre 57da9a82b0 Have GET action not return anything on success 2018-08-22 15:37:56 +01:00
Matt Dupre 97b1f58534 Replace GET action in spec with proposed CHECK action 2018-08-15 15:46:46 +01:00
Lifubang 5ef0e9aa4c when loopback, IP4 or IP6 is always nil
Signed-off-by: Lifubang <lifubang@aliyun.com>
2018-08-13 18:50:23 +08:00
Dan Williams 779dc12496
Merge pull request #570 from dcbw/prev-result-helpers
types/version: add helper to parse PrevResult
2018-08-09 13:33:51 -05:00
Dan Williams d2836a7b59 types/version: add helper to parse PrevResult
Plugins can use the new helpers to parse a prevResult.  When parsing
their NetConf structure, assuming it has a types.NetConf embedded:

  type MyConf struct {
    types.NetConf
    ...
  }

  conf := MyConf{}
  if err := json.Unmarshal(stdin, &conf); err != nil {
    return nil, fmt.Errorf("failed to parse network configuration: %v", err)
  }

  if err := version.ParsePrevResult(&conf.NetConf); err != nil {
    return nil, err
  }

and then when the plugin wishes to access the PrevResult it can transform
conf.PrevResult into any Result version it wants:

  if conf.PrevResult != nil {
    res, err := current.NewResultFromResult(conf.PrevResult)
    if err != nil {
      return nil, fmt.Errorf("could not convert result to current version: %v", err)
    }
    for _, ip := range res.IPs {
      ...
    }
  }
2018-08-09 13:27:57 -05:00
Dan Williams cbd2718984
Merge pull request #572 from dcbw/skel-about
skel: only print about message, not errors
2018-08-08 10:46:45 -05:00
Dan Williams 9c9cc5365d
Merge pull request #574 from xiangpengzhao/add-knitter
List Knitter as 3rd party plugin
2018-08-08 10:32:51 -05:00
Dan Williams 0750650021
Merge pull request #576 from acmcodercom/master
Add name field in 99-loopback.conf because network name is required.
2018-08-08 10:32:19 -05:00
Lifubang 9fc6eb434c Add name field in 99-loopback.conf because network name is required. 2018-08-08 10:30:45 +08:00
xiangpengzhao 478f7b417e List Knitter as 3rd party plugin 2018-08-03 14:16:29 +08:00
Dan Williams 0f6a509e4b
Merge pull request #565 from dcbw/spec-members
spec: be explicit about optional and required structure members
2018-08-02 12:44:13 -05:00
Dan Williams 2ce2c24cc2 skel: only print about message, not errors
Instead of:

CNI_COMMAND env variable missing
<about message here>
{
    "code": 100,
    "msg": "required env variables missing"
}

print:
<about message here>
2018-07-26 10:51:40 -05:00
Dan Williams e67bb289cc
Merge pull request #566 from dineshb-jnpr/master
Add Juniper Contrail to 3rd party plugin list
2018-07-05 16:07:35 -05:00
dineshb-jnpr 9bd516a786 Add Juniper Contrail to 3rd party plugin list
This change is to update the 3rd Party plugin section of README
to include and list the K8s CNI solution from Juniper Networks, INC.
2018-07-02 17:23:29 -07:00
Dan Williams 9a34081fef spec: be explicit about optional and require structure members
Fixes: #564
2018-06-28 13:12:35 -05:00
Dan Williams d974c1dd54
Merge pull request #562 from jingax10/master
Fix broken build for cni introduced by https://github.com/containernetworking/cni/pull/524
2018-06-28 12:02:30 -05:00
Jing Ai 96343561e0 Fix broken build for cni introduced by https://github.com/containernetworking/cni/pull/524. 2018-06-20 22:42:04 -07:00
Dan Williams aed7a2f2a1
Merge pull request #524 from squeed/validate
libcni: add ValidateNetwork and ValidateNetworkList functions
2018-06-20 10:39:28 -05:00
Casey Callendrello 07c1a6da47
Merge pull request #558 from dcbw/libcni-exec-interface
libcni: make exec handling an interface for better downstream testing
2018-06-13 17:08:47 +02:00
Casey Callendrello b34795b3ce libcni: add ValidateNetwork and ValidateNetworkList functions
These perform some static validation on a network configuration,
ensuring it is generally executable.
2018-06-13 16:35:17 +02:00
Dan Williams ffc1e7b446 invoke: fix an existing gofmt complaint 2018-06-11 15:33:53 -05:00
Dan Williams 09dac4ecdc libcni: make exec handling an interface for better downstream testing
When a downstream project just uses libcni, it takes a lot of harness
code to set up binaries on-disk that get called.  Instead we can handle
that by passing an execer interface to libcni APIs that gets called
instead, which the downstreams can use to avoid execing thing entirely
and instead check arguments/environment in code.

Downstream users that currently create a CNIConfig object like this:

    cniconf := libcni.CNIConfig{Path: []string{"foobar"}}

Can now fake out all on-disk binary exec/find operations with:

    cniconf := libcni.NewCNIConfig([]string{"foobar"}, <exec interface>)
2018-06-11 15:33:53 -05:00
Dan Williams 86431d5c16
Merge pull request #557 from containernetworking/if-name-matters
Network interface name matters, updated
2018-05-31 13:41:59 -05:00
Bryan Boreham 6106bbf116 Use explicit identifiers in key tuple 2018-05-30 15:49:04 +00:00
Bryan Boreham df12d88b58 A couple of tests should be testing 0.3.1 explicitly 2018-05-30 15:40:47 +00:00
Casey Callendrello 3ffb039b29
Merge pull request #554 from squeed/skel-plugin-version
skel: add support for plugin version string
2018-05-30 17:34:47 +02:00
Mike Spreitzer 7f05cbb33b First draft that passes tests 2018-05-30 15:16:38 +00:00
Mike Spreitzer b43173e5d2 start drafting change to make ifname matter 2018-05-30 15:11:19 +00:00
Casey Callendrello 35d510c0ee
Merge pull request #556 from squeed/bw-convention
Conventions: add bandwidth limits
2018-05-30 17:07:14 +02:00
Casey Callendrello d2be2fa823 Conventions: add bandwidth limits 2018-05-25 19:09:56 +02:00
Casey Callendrello 197eadbe52 skel: add support for plugin version string 2018-05-24 14:26:42 +02:00
Casey Callendrello 85488fbd8d
Merge pull request #490 from dcbw/get-action
Add a GET action to the CNI spec, packages, and sample plugin
2018-05-23 17:22:32 +02:00
Dan Williams ef2ae62d8f
Merge pull request #553 from dcbw/ovn-kube
README: add ovn-kubernetes to the plugin list
2018-05-22 11:32:32 -05:00
Dan Williams fed8bad0cb libcni: add GET support and result caching
Per the spec, a Result from ADD is cached and provided to GET and DEL
commands.
2018-05-21 16:05:31 -05:00
Dan Williams 49ebeedfbb libcni: use Add/DelNetworkList to implement Add/DelNetwork 2018-05-21 16:05:31 -05:00
Dan Williams 23a5d8e617 spec/invoke/skel: add GET command 2018-05-21 16:05:31 -05:00
Dan Williams 7b8b5da47b spec/pkg: bump version to 0.4.0 2018-05-16 10:21:17 -05:00
Dan Williams eea7a08b3c pkg/version: add some version-handling utility functions 2018-05-16 10:21:15 -05:00
Dan Williams 4372fe2566 README: add ovn-kubernetes to plugin list 2018-05-15 20:34:17 -05:00
Matt Dupre a700ea864b
Merge pull request #543 from qianzhangxa/remove-version-param
spec: remove the version parameter from ADD and DEL commands.
2018-03-07 16:29:29 +00:00
Bryan Boreham 974e2cf668
Merge pull request #546 from mhausenblas/patch-1
fixes the title in specification
2018-03-07 16:20:52 +00:00
Michael Hausenblas cd2d779931
fixes the title in specification
Might be an historical artefact but I believe the correct full name for CNI should be Container Network Interface 

CC: @bboreham
2018-03-06 09:54:24 +00:00
Dan Williams 9aab8b3e9f
Merge pull request #538 from rosenhouse/bump-golang-1.10
travis: bump golang versions
2018-02-28 10:02:33 -06:00
Gabe Rosenhouse 05a3c9c213 travis: bump golang versions
- test against Go 1.10
- stop testing against Go 1.8

since Go language maintainers no longer support 1.8
see: https://golang.org/doc/devel/release.html#policy
2018-02-28 07:00:16 -08:00
Qian Zhang e3b496a3b6 spec: remove the version parameter from ADD and DEL commands.
The CNI spec version passed from runtime to plugin is the `cniVersion`
field in the network configuration rather than a parameter to the ADD
and DEL command. So, remove the version parameter from the spec, and
also describe how the `cniVersion` field in the network configuration
should be used by the plugin.

Fixes #542
2018-02-27 09:59:57 +08:00
Gabe Rosenhouse 142cde0c76
Merge pull request #536 from dcbw/require-network-name
pkg/skel: return error if JSON config has no network name
2018-02-17 19:21:24 -08:00
Gabe Rosenhouse 826f754e82
Merge pull request #535 from dcbw/no-sudo-required
tests: sudo not actually required
2018-02-17 18:55:25 -08:00
Dan Williams d4bae8512a pkg/skel: return error if JSON config has no network name
SPEC.md has indicated that 'name' is required since at least mid-2015,
but that was not enforced in pkg/skel or our unit tests.  That could
lead to configs passing no network name and plugins like host-local
that require the name failing or causing odd behavior.

Since the name is required by the spec, require it in the code too.
2018-02-16 16:14:30 -06:00
Dan Williams c3fc2ae6e4 tests: sudo not actually required
The legacy example plugin and its libcni backwards_compatibility_test.go
just use the noop plugin, which doesn't do anything with the netns path
or make any interfaces inside the netns.  So we can remove anything
netns related from the test, which means we no longer need sudo.
2018-02-16 11:46:03 -06:00
Dan Williams 6cddc9a378
Merge pull request #529 from joerg84/patch-1
Described Mesos as Apache project.
2018-02-07 09:47:58 -06:00
Joerg Schad 55d1c96643
Described Mesos as Apache project. 2018-01-24 21:34:26 +01:00
Casey Callendrello 693cc869fd
Merge pull request #525 from squeed/range-arg
CONVENTIONS: add new `ipRanges` capability.
2018-01-24 18:39:33 +01:00
Gabe Rosenhouse 1773a1f24c
Merge pull request #528 from huangjiuyuan/fix-typo
Fix typo in CONVENTIONS.md
2018-01-19 14:30:55 -08:00
Gabe Rosenhouse d59d5c6cda
Merge pull request #96 from zachgersh/make-ipam-type
pkg/types: Makes IPAM concrete type
2018-01-17 10:42:08 -08:00
huangjiuyuan bb3445a503 Fix typo in CONVENTIONS.md 2018-01-11 10:24:03 +08:00
Bryan Boreham 9ded5d186f
Merge pull request #527 from Intel-Corp/dev/bonding-cni-3rd-party-readme-pr
Adding the Bonding CNI plugin as 3rd party plugin
2018-01-10 16:53:04 +00:00
Kuralamudhan Ramakrishnan 6787bb6591
Update README.md 2017-12-15 18:20:33 +00:00
Kuralamudhan Ramakrishnan 7099c732cb
Adding the Bonding CNI plugin as 3rd party plugin
- Bonding CNI provides a method for aggregating multiple network interfaces into a single logical "bonded" interface.
- Linx Bonding drivers provides various flavour of bonded interface depending on the mode (bonding policies), such as round robin, active aggregation according to the 802.3 ad specification
For more information on the bonding driver. Please refer to [kernel doc(https://www.kernel.org/doc/Documentation/networking/bonding.txt)
2017-12-15 16:41:43 +00:00
Gabe Rosenhouse 97ce633ca1
Merge pull request #526 from davidk01/patch-1
kurma is no longer maintained
2017-12-13 08:24:23 -08:00
david karapetyan 74b3024fa8
kurma is no longer maintained
Kurma is a derelict project so should be removed
2017-12-12 14:09:12 -08:00
Casey Callendrello 68ae5290ea CONVENTIONS: add new `ipRanges` capability.
Also, restructure the document slightly.
2017-12-08 16:28:59 +01:00
Casey Callendrello 9b747fabbd
Merge pull request #516 from rosenhouse/add-appveyor-badge
README: add badge for appveyor (Windows CI) status
2017-12-07 11:51:46 +01:00
Gabriel Rosenhouse f8fb397969 README: add badge for appveyor (Windows CI) 2017-12-03 14:13:21 -08:00
Matt Dupre 869505bebd
Merge pull request #517 from matthewdupre/maintainers-update
Maintainers: replace Tom Denham with Matt Dupre
2017-11-21 17:21:36 +00:00
Gabe Rosenhouse 1f824fbf2c
Merge pull request #521 from rosenhouse/types-tests
backfill some unit tests
2017-11-15 08:08:07 -08:00
Gabriel Rosenhouse cf260a13d7 pks/types/current: backfill a couple more unit tests 2017-11-15 00:33:43 -08:00
Gabriel Rosenhouse 61a8bf2ee1 pkg/types: non-pointer Route marshals as JSON
also: backfill unit tests for JSON and string formatting of custom types
2017-11-15 00:33:43 -08:00
Gabe Rosenhouse a88bf49d86
Merge pull request #520 from aaithal/addECSToReadme
readme: add Amazon ECS to runtimes and plugins list
2017-11-15 00:31:47 -08:00
Aithal 0844a98e03 readme: add Amazon ECS to the list 2017-11-14 15:09:32 -08:00
Gabe Rosenhouse eeded9f55e
Merge pull request #518 from porridge/patch-1
Fix indentation
2017-11-07 20:02:14 -08:00
Marcin Owsiany 525880d8bc
Fix indentation 2017-11-07 20:30:02 +01:00
Matt Dupre 464d07ffdb Maintainers: replace Tom Denham with Matt Dupre 2017-11-01 15:34:48 +00:00
Gabe Rosenhouse 384d8c0b52
Merge pull request #514 from rosenhouse/no-vendor
Remove vendor directory
2017-11-01 08:11:26 -07:00
Gabriel Rosenhouse fa8f141f29 Remove vendor dir, simplify build & test tooling
"Libraries should never vendor their dependencies"
  https://peter.bourgon.org/go-best-practices-2016/#dependency-management
The only thing in there were test-support packages anyhow

Remove Godeps folder, nothing to manage!

Remove build.sh
- not much here to build now that we've split out plugins
   - for non-AMD64 platforms, travis just runs go build directly

Clean up test.sh
- no vendor --> simpler logic for enumerating packages
- use "bash strict mode"
   http://redsymbol.net/articles/unofficial-bash-strict-mode
- coveralls token not required for public repos

Simplify .travis.yml, update to work without vendor
- fewer env vars
- go get -t ./...

Clean up Vagrantfile
- no godep required anymore
- bump Golang version 1.9.2
2017-10-31 22:32:18 -07:00
Gabriel Rosenhouse 7f03ee4537 libcni: fix go vet error 2017-10-31 22:24:53 -07:00
Casey Callendrello 4228ca8928 Merge pull request #508 from squeed/cnitool-cid
cnitool: generate container id from the netns path
2017-10-25 14:58:57 +02:00
Gabe Rosenhouse 97314481b4 Merge pull request #515 from rosenhouse/appveyor
Add CI for Windows
2017-10-23 08:19:28 -07:00
Gabriel Rosenhouse 449ac26e38 ci: add appveyor file for Windows CI coverage 2017-10-22 23:21:14 -07:00
Gabriel Rosenhouse 36a22467d9 Windows compatibility: all tests pass! 2017-10-22 23:21:08 -07:00
Gabe Rosenhouse 40837d1ff0 Merge pull request #510 from squeed/containerid
spec, skel: Make the container ID required and unique.
2017-10-21 12:29:23 -07:00
Casey Callendrello b9995395c0 cnitool: generate container id from the netns path, add docs
We shouldn't be creating networks with a blank containerid. Let's
synthesize one from the netns path.

Also, add a basic README.
2017-10-20 14:28:11 +02:00
Casey Callendrello a0a74bbcd3 spec, skel: Make the container ID required and unique.
Previously, the spec did not require the container id to be set.
However, almost every plugin relies on it being unique, including the
CNI-maintained plugins. So, change the spec to require the use of
containerid and that plugins should treat it as a primary key.
2017-10-20 14:26:18 +02:00
Casey Callendrello 03e5b5ff9e Add "spec containes unreleased changes" warning. 2017-10-20 14:26:18 +02:00
Gabe Rosenhouse 30c6934e5c Merge pull request #499 from rosenhouse/golang-to-1.9
Golang versions: add 1.9, drop 1.7
2017-10-18 15:34:51 -07:00
Gabe Rosenhouse 43744a3fce pkg/invoke: fix test pollution 2017-10-18 14:51:35 -07:00
Gabe Rosenhouse baecfa6fda Golang versions: add 1.9.1, drop 1.7 2017-10-18 14:51:33 -07:00
Casey Callendrello ff7c3e02e3 Merge pull request #509 from containernetworking/update-community-sync
Add 2017-10-04 community sync
2017-09-22 11:39:36 +02:00
Casey Callendrello 4b9e11a526 Add 2017-10-04 community sync 2017-09-22 11:39:09 +02:00
Dan Williams b30c419f96 Merge pull request #497 from squeed/args-add-ip
Conventions: add the "ips" arg
2017-09-20 09:50:34 -05:00
Gabe Rosenhouse 6614adf4a4 Merge pull request #505 from dcbw/op-ordering
spec: add notes about ADD/DEL ordering
2017-09-18 20:20:24 -07:00
Dan Williams c0ca8f4db9 spec: add notes about ADD/DEL ordering 2017-09-18 12:25:30 -05:00
Dan Williams b7c7ec76ed Merge pull request #504 from dcbw/gov-voting
governance: add notes about public voting
2017-09-18 12:24:09 -05:00
Gabe Rosenhouse 5da3960e2b Merge pull request #506 from Intel-Corp/dev/vhostuser3rdparty-pr
README: List vhostuser CNI as a 3rd party plugin
2017-09-18 09:34:26 -07:00
kuralamudhan ramakrishnan a35068121c README: List vhostuser as a 3rd party plugin 2017-09-16 00:47:37 +01:00
Dan Williams a2d35cb6a1 Merge pull request #500 from matthewdupre/spec-language
Use more RFC2119 style language in specification (must, should...)
2017-09-14 14:46:52 -05:00
Dan Williams 27a848cc18 Merge pull request #502 from anfernee/version
skel: VERSION shouldn't block on stdin
2017-09-14 13:25:24 -05:00
Dan Williams 5c552f3ffc governance: add notes about public voting 2017-09-13 17:24:40 -05:00
Yongkun Anfernee Gui f5302a2aa9 VERSION shouldn't block on stdin
Right now, I have to do the following
  $ echo | CNI_COMMAND=VERSION bridge
or
  CNI_COMMAND=VERSION bridge
  ^D
to be able to get the result back.

But VERSION doesn't need to read from stdin.
2017-09-07 22:48:10 -07:00
Gabe Rosenhouse 909fe7d10d Merge pull request #501 from rosenhouse/test-all-packages
Test all non-vendored packages
2017-08-30 19:32:53 -07:00
Gabe Rosenhouse a2b0a28ce5 Test all non-vendored packages 2017-08-30 14:59:16 -07:00
Casey Callendrello 98ace24557 Merge pull request #493 from dcbw/governance
docs: add Governance procedure documentation
2017-08-30 17:14:54 +02:00
Gabe Rosenhouse a677030029 Merge pull request #492 from dcbw/roadmap
roadmap: add 1.0 items and clean things up
2017-08-30 08:12:30 -07:00
Dan Williams 3f72c535ea docs: add Governance procedure documentation 2017-08-30 10:10:11 -05:00
Matt Dupre fa69c6eb6a Use more RFC2119 style language in specification (must, should...) 2017-08-30 15:35:18 +01:00
Gabe Rosenhouse 3477001a1b Merge pull request #489 from squeed/clean-build
Release: clean the builddir when building.
2017-08-23 09:25:12 -07:00
Casey Callendrello 0a0d0da638 Conventions: add the "ips" arg 2017-08-22 18:10:07 +02:00
Dan Williams 80c688516d Merge pull request #494 from dcbw/validate-type
libcni: return error if Type is empty
2017-08-18 12:50:31 -05:00
Dan Williams 9c87b066ce Merge pull request #495 from rosenhouse/spec-list-older-versions
Spec lists git tags pointing to old spec versions
2017-08-18 12:49:39 -05:00
Gabriel Rosenhouse 31e696618c spec: table of git tags to older versions 2017-08-17 20:43:48 -07:00
Dan Williams 95599124da libcni: return error if Type is empty
A network config missing Type is pretty useless since it means we can't
find the plugin to execute.  So return an error if Type isn't given.
2017-08-17 22:34:33 -05:00
Dan Williams 5adb89b6d2 roadmap: add 1.0 items and clean things up 2017-08-16 13:14:10 -05:00
Casey Callendrello 6c680c05dd Release: clean the builddir when building. 2017-08-14 14:27:44 +02:00
zachgersh 832a52582b pkg/types: split out NetConf.IPAM into own type 2017-06-08 20:42:22 -07:00
249 changed files with 9620 additions and 15644 deletions

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

4
.gitignore vendored
View File

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

30
.golangci.yml Normal file
View File

@ -0,0 +1,30 @@
linters:
enable:
- contextcheck
- errcheck
- errorlint
- gci
- ginkgolinter
- gocritic
- gofumpt
- govet
- ineffassign
- misspell
- nolintlint
- nonamedreturns
- predeclared
- staticcheck
- typecheck
- unconvert
- unused
- whitespace
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/containernetworking)
run:
timeout: 5m

View File

@ -1,41 +0,0 @@
language: go
sudo: required
dist: trusty
go:
- 1.7.x
- 1.8.x
env:
global:
- TOOLS_CMD=golang.org/x/tools/cmd
- PATH=$GOROOT/bin:$PATH
- GO15VENDOREXPERIMENT=1
matrix:
- TARGET=amd64
- TARGET=arm
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
matrix:
fast_finish: true
install:
- go get ${TOOLS_CMD}/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
script:
- >
if [ "${TARGET}" == "amd64" ]; then
GOARCH="${TARGET}" ./test.sh;
else
GOARCH="${TARGET}" ./build.sh;
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,4 +1,3 @@
## Community Code of Conduct # Community Code of Conduct
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).

View File

@ -7,18 +7,19 @@ it easier to get your contribution accepted.
We gratefully welcome improvements to documentation as well as to code. We gratefully welcome improvements to documentation as well as to code.
# Certificate of Origin ## Certificate of Origin
By contributing to this project you agree to the Developer Certificate of By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details. contribution. See the [DCO](DCO) file for details.
# Email and Chat ## Email and Chat
The project uses the the cni-dev email list and IRC chat: The project uses the cni-dev email list, IRC chat, and Slack:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev) - Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org - 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.
Please avoid emailing maintainers found in the MAINTAINERS file directly. They Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists. are very busy and read the mailing lists.
@ -33,19 +34,20 @@ are very busy and read the mailing lists.
This is a rough outline of how to prepare a contribution: This is a rough outline of how to prepare a contribution:
- Create a topic branch from where you want to base your work (usually branched from master). - Create a topic branch from where you want to base your work (usually branched from main).
- Make commits of logical units. - Make commits of logical units.
- Make sure your commit messages are in the proper format (see below). - Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository. - Push your changes to a topic branch in your fork of the repository.
- If you changed code: - If you changed code:
- add automated tests to cover your changes, using the [Ginkgo](http://onsi.github.io/ginkgo/) & [Gomega](http://onsi.github.io/gomega/) style - add automated tests to cover your changes, using the [Ginkgo](https://onsi.github.io/ginkgo/) & [Gomega](https://onsi.github.io/gomega/) style
- if the package did not previously have any test coverage, add it to the list - if the package did not previously have any test coverage, add it to the list
of `TESTABLE` packages in the `test.sh` script. of `TESTABLE` packages in the `test.sh` script.
- run the full test script and ensure it passes - run the full test script and ensure it passes
- Make sure any new code files have a license header (this is now enforced by automated tests) - Make sure any new code files have a license header (this is now enforced by automated tests)
- Submit a pull request to the original repository. - Submit a pull request to the original repository.
## How to run the test suite ## How to run the test suite
We generally require test coverage of any new features or bug fixes. We generally require test coverage of any new features or bug fixes.
Here's how you can run the test suite on any system (even Mac or Windows) using Here's how you can run the test suite on any system (even Mac or Windows) using
@ -62,19 +64,19 @@ cd /go/src/github.com/containernetworking/cni
./test.sh ./test.sh
# to focus on a particular test suite # to focus on a particular test suite
cd plugins/main/loopback cd libcni
go test go test
``` ```
# Acceptance policy ## Acceptance policy
These things will make a PR more likely to be accepted: These things will make a PR more likely to be accepted:
* a well-described requirement - a well-described requirement
* tests for new code - tests for new code
* tests for old code! - tests for old code!
* new code and tests follow the conventions in old code and tests - new code and tests follow the conventions in old code and tests
* a good commit message (see below) - a good commit message (see below)
In general, we will merge a PR once two maintainers have endorsed it. In general, we will merge a PR once two maintainers have endorsed it.
Trivial changes (e.g., corrections to spelling) may get waved through. Trivial changes (e.g., corrections to spelling) may get waved through.
@ -86,7 +88,7 @@ We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why. the body of the commit should describe the why.
``` ```md
scripts: add the test-cluster command scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and this uses tmux to setup a test cluster that you can easily kill and
@ -97,7 +99,7 @@ Fixes #38
The format can be described more formally as follows: The format can be described more formally as follows:
``` ```md
<subsystem>: <what changed> <subsystem>: <what changed>
<BLANK LINE> <BLANK LINE>
<why this change was made> <why this change was made>
@ -111,6 +113,7 @@ This allows the message to be easier to read on GitHub as well as in various
git tools. git tools.
## 3rd party plugins ## 3rd party plugins
So you've built a CNI plugin. Where should it live? So you've built a CNI plugin. Where should it live?
Short answer: We'd be happy to link to it from our [list of 3rd party plugins](README.md#3rd-party-plugins). Short answer: We'd be happy to link to it from our [list of 3rd party plugins](README.md#3rd-party-plugins).

View File

@ -10,7 +10,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
## Plugins ## 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. * 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 ## 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. * 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.
@ -19,23 +19,20 @@ Establishing these conventions allows plugins to work across multiple runtimes.
# Current conventions # Current conventions
Additional conventions can be created by creating PRs which modify this document. Additional conventions can be created by creating PRs which modify this document.
## Plugin specific fields ## Dynamic Plugin specific fields (Capabilities / Runtime Configuration)
[Plugin specific fields](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release. [Plugin specific fields](SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
> Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the args field may be used to pass arbitrary data which may be ignored by plugins. > Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the args field may be used to pass arbitrary data which may be ignored by plugins.
A plugin can define any additional fields it needs to work properly. It is expected that it will return an error if it can't act on fields that were expected or where the field values were malformed. A plugin can define any additional fields it needs to work properly. It should return an error if it can't act on fields that were expected or where the field values were malformed.
This method of passing information to a plugin is recommended when the following conditions hold This method of passing information to a plugin is recommended when the following conditions hold:
* The configuration has specific meaning to the plugin (i.e. it's not just general meta data) * The configuration has specific meaning to the plugin (i.e. it's not just general meta data)
* the plugin is expected to act on the configuration or return an error if it can't * the plugin is expected to act on the configuration or return an error if it can't
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section. 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.
| Area | Purpose | Spec and Example | Runtime implementations | Plugin Implementations | 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).
| ----- | ------- | ---------------- | ----------------------- | --------------------- |
| port mappings | Pass mapping from ports on the host to ports in the container network namespace. | Operators can ask runtimes to pass port mapping information to plugins, by setting the following in the CNI config <pre>"capabilities": {"portMappings": true} </pre> Runtimes should fill in the actual port mappings when the config is passed to plugins. It should be placed in a new section of the config "runtimeConfig" e.g. <pre>"runtimeConfig": {<br /> "portMappings" : [<br /> { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },<br /> { "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }<br /> ]<br />}</pre> | none | none |
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).
```json ```json
{ {
"name" : "ExamplePlugin", "name" : "ExamplePlugin",
@ -57,20 +54,34 @@ But the runtime would fill in the mappings so the plugin itself would receive so
} }
``` ```
### Well-known Capabilities
| Area | Purpose | Capability | Spec and Example | Runtime implementations | Plugin Implementations |
| ----- | ------- | -----------| ---------------- | ----------------------- | --------------------- |
| port mappings | Pass mapping from ports on the host to ports in the container network namespace. | `portMappings` | A list of portmapping entries.<br/> <pre>[<br/> { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" },<br /> { "hostPort": 8000, "containerPort": 8001, "protocol": "udp" }<br /> ]<br /></pre> | kubernetes | CNI `portmap` plugin |
| 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` (\<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-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
`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 (dictionary): Optional additional arguments provided by the container runtime. For example a dictionary of labels could be passed to CNI plugins by adding them to a labels field under args.
`args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support. `args` provide a way of providing more structured data than the flat strings that CNI_ARGS can support.
`args` should be used for _optional_ meta-data. Runtimes can place additional data in `args` and plugins that don't understand that data should just ignore it. Runtimes should not require that a plugin understands or consumes that data provided, and so a runtime should not expect to receive an error if the data could not be acted on. `args` should be used for _optional_ meta-data. Runtimes can place additional data in `args` and plugins that don't understand that data should just ignore it. Runtimes should not require that a plugin understands or consumes that data provided, and so a runtime should not expect to receive an error if the data could not be acted on.
This method of passing information to a plugin is recommended when the information is optional and the plugin can choose to ignore it. It's often that case that such information is passed to all plugins by the runtime whithout regard for whether the plugin can understand it. This method of passing information to a plugin is recommended when the information is optional and the plugin can choose to ignore it. It's often that case that such information is passed to all plugins by the runtime without regard for whether the plugin can understand it.
The conventions documented here are all namepaced under `cni` so they don't conflict with any existing `args`. The conventions documented here are all namespaced under `cni` so they don't conflict with any existing `args`.
For example: For example:
```json ```jsonc
{ {
"cniVersion":"0.2.0", "cniVersion":"0.2.0",
"name":"net", "name":"net",
@ -79,9 +90,9 @@ For example:
"labels": [{"key": "app", "value": "myapp"}] "labels": [{"key": "app", "value": "myapp"}]
} }
}, },
<REST OF CNI CONFIG HERE> // <REST OF CNI CONFIG HERE>
"ipam":{ "ipam":{
<IPAM CONFIG HERE> // <IPAM CONFIG HERE>
} }
} }
``` ```
@ -89,16 +100,17 @@ For example:
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations | | 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 | | 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 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
CNI_ARGS formed part of the original CNI spec and have been present since the initial release. CNI_ARGS formed part of the original CNI spec and have been present since the initial release.
> `CNI_ARGS`: Extra arguments passed in by the user at invocation time. Alphanumeric key-value pairs separated by semicolons; for example, "FOO=BAR;ABC=123" > `CNI_ARGS`: Extra arguments passed in by the user at invocation time. Alphanumeric key-value pairs separated by semicolons; for example, "FOO=BAR;ABC=123"
The use of `CNI_ARGS` is deprecated and "args" should be used instead. The use of `CNI_ARGS` is deprecated and "args" should be used instead. If a runtime passes an equivalent key via `args` (eg the `ips` `args` Area and the `CNI_ARGS` `IP` Field) and the plugin understands `args`, the plugin must ignore the CNI_ARGS Field.
| Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations | | Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ------ | ------ | ---------------- | ----------------------- | ---------------------- | | ------ | ------ | ---------------- | ----------------------- | ---------------------- |
| IP | Request a specific IP from IPAM plugins | IP=192.168.10.4 | *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/cni/blob/master/Documentation/host-local.md#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 ## 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. 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,9 +1,11 @@
# Overview # Overview
The `cnitool` is a utility that can be used to test a CNI plugin The `cnitool` is a utility that can be used to test a CNI plugin
without the need for a container runtime. The `cnitool` takes a without the need for a container runtime. The `cnitool` takes a
`network name` and a `network namespace` and a command to `ADD` or `network name` and a `network namespace` and a command to `ADD` or
`DEL`,.i.e, attach or detach containers from a network. The `cnitool` `DEL`,.i.e, attach or detach containers from a network. The `cnitool`
relies on the following environment variables to operate properly: relies on the following environment variables to operate properly:
* `NETCONFPATH`: This environment variable needs to be set to a * `NETCONFPATH`: This environment variable needs to be set to a
directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches
for CNI configuration files in this directory with the extension for CNI configuration files in this directory with the extension
@ -11,6 +13,7 @@ relies on the following environment variables to operate properly:
this directory and if it finds a CNI configuration with the `network this directory and if it finds a CNI configuration with the `network
name` given to the cnitool it returns the corresponding CNI name` given to the cnitool it returns the corresponding CNI
configuration, else it returns `nil`. configuration, else it returns `nil`.
* `CNI_PATH`: For a given CNI configuration `cnitool` will search for * `CNI_PATH`: For a given CNI configuration `cnitool` will search for
the corresponding CNI plugin in this path. the corresponding CNI plugin in this path.
For the full documentation of `cnitool` see the [cnitool docs](../cnitool/README.md)

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.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](https://github.com/containernetworking/cni/blob/spec-v0.3.0/SPEC.md) provides rich information
Version 0.3.0 of the [CNI Specification](../SPEC.md) provides rich information
about container network configuration, including details of network interfaces about container network configuration, including details of network interfaces
and support for multiple IP addresses. 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. version there is supported by your container runtime and CNI plugins.
Configuration files without a version field should be given version 0.2.0. Configuration files without a version field should be given version 0.2.0.
The CNI spec includes example configuration files for The CNI spec includes example configuration files for
[single plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations) [single plugins](SPEC.md#example-configurations)
and for [lists of chained plugins](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations). and for [lists of chained plugins](SPEC.md#example-configurations).
Consult the documentation for your runtime and plugins to determine what Consult the documentation for your runtime and plugins to determine what
CNI spec versions they support. Test any plugin upgrades before deploying to CNI spec versions they support. Test any plugin upgrades before deploying to
production. You may find [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool) production. You may find [cnitool](https://github.com/containernetworking/cni/tree/main/cnitool)
useful. Specifically, your configuration version should be the lowest common useful. Specifically, your configuration version should be the lowest common
version supported by your plugins. version supported by your plugins.
@ -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) ### General guidance for all plugins (language agnostic)
To provide the smoothest upgrade path, **existing plugins should support To provide the smoothest upgrade path, **existing plugins should support
multiple versions of the CNI spec**. In particular, plugins with existing 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. compatibility with older versions.
To do this, two changes are required. First, a plugin should advertise which To do this, two changes are required. First, a plugin should advertise which
@ -55,13 +88,13 @@ command with the following JSON data:
```json ```json
{ {
"cniVersion": "0.3.0", "cniVersion": "1.0.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.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 Second, for the `ADD` command, a plugin must respect the `cniVersion` field
provided in the [network configuration JSON](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration). provided in the [network configuration JSON](SPEC.md#network-configuration).
That field is a request for the plugin to return results of a particular format: That field is a request for the plugin to return results of a particular format:
- If the `cniVersion` field is not present, then spec v0.2.0 should be assumed - If the `cniVersion` field is not present, then spec v0.2.0 should be assumed
@ -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. - If the plugin doesn't support the version, the plugin must error.
- Otherwise, the plugin must return a [CNI Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result) - Otherwise, the plugin must return a [CNI Result](SPEC.md#result)
in the format requested. in the format requested.
Result formats for older CNI spec versions are available in the Result formats for older CNI spec versions are available in the
[git history for SPEC.md](https://github.com/containernetworking/cni/commits/master/SPEC.md). [git history for SPEC.md](https://github.com/containernetworking/cni/commits/main/SPEC.md).
For example, suppose a plugin, via its `VERSION` response, advertises CNI specification For example, suppose a plugin, via its `VERSION` response, advertises CNI specification
support for v0.2.0 and v0.3.0. When it receives `cniVersion` key of `0.2.0`, support for v0.2.0 and v0.3.0. When it receives `cniVersion` key of `0.2.0`,
@ -82,21 +115,21 @@ the plugin must return result JSON conforming to CNI spec version 0.2.0.
### Specific guidance for plugins written in Go ### Specific guidance for plugins written in Go
Plugins written in Go may leverage the Go language packages in this repository Plugins written in Go may leverage the Go language packages in this repository
to ease the process of upgrading and supporting multiple versions. CNI to ease the process of upgrading and supporting multiple versions. CNI
[Library and Plugins Release v0.5.0](https://github.com/containernetworking/cni/releases) [Library and Plugins Release v0.5.0](https://github.com/containernetworking/cni/releases/tag/v0.5.0)
includes important changes to the Golang APIs. Plugins using these APIs will includes important changes to the Golang APIs. Plugins using these APIs will
require some changes now, but should more-easily handle spec changes and require some changes now, but should more-easily handle spec changes and
new features going forward. new features going forward.
For plugin authors, the biggest change is that `types.Result` is now an For plugin authors, the biggest change is that `types.Result` is now an
interface implemented by concrete struct types in the `types/current` and interface implemented by concrete struct types in the `types/100`,
`types/020` subpackages. `types/040`, and `types/020` subpackages.
Internally, plugins should use the `types/current` structs, and convert Internally, plugins should use the latest spec version (eg `types/100`) structs,
to or from specific versions when required. A typical plugin will only need and convert to or from specific versions when required. A typical plugin will
to do a single conversion. That is when it is about to complete and needs to only need to do a single conversion when it is about to complete and
print the result JSON in the correct format to stdout. The library needs to print the result JSON in the requested `cniVersion` format to stdout.
function `types.PrintResult()` simplifies this by converting and printing in The library function `types.PrintResult()` simplifies this by converting and
a single call. printing in a single call.
Additionally, the plugin should advertise which CNI Spec versions it supports Additionally, the plugin should advertise which CNI Spec versions it supports
via the 3rd argument to `skel.PluginMain()`. via the 3rd argument to `skel.PluginMain()`.
@ -107,7 +140,7 @@ Here is some example code
import ( import (
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
) )
@ -136,7 +169,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
func main() { 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 Other examples of spec v0.3.0-compatible plugins are the
[main plugins in this repo](https://github.com/containernetworking/cni/tree/master/plugins/main) [main plugins in this repo](https://github.com/containernetworking/plugins/)
## For Runtime Authors ## For Runtime Authors
This section provides guidance for upgrading container runtimes to support 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) ### 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 To provide the smoothest upgrade path and support the broadest range of CNI
plugins, **container runtimes should support multiple versions of the CNI spec**. plugins, **container runtimes should support multiple versions of the CNI spec**.
In particular, runtimes with existing installed bases should add support for CNI 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 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. call both new and legacy plugins, and handle the results from either.
When calling a plugin, the runtime must request that the plugin respond in a When calling a plugin, the runtime must request that the plugin respond in a
particular format by specifying the `cniVersion` field in the particular format by specifying the `cniVersion` field in the
[Network Configuration](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) [Network Configuration](SPEC.md#network-configuration)
JSON block. The plugin will then respond with JSON block. The plugin will then respond with
a [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result) a [Result](SPEC.md#result)
in the format defined by that CNI spec version, and the runtime must parse in the format defined by that CNI spec version, and the runtime must parse
and handle this result. and handle this result.
#### Handle errors due to version incompatibility #### Handle errors due to version incompatibility
Plugins may respond with error indicating that they don't support the requested Plugins may respond with error indicating that they don't support the requested
CNI version (see [Well-known Error Codes](https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes)), CNI version (see [Well-known Error Codes](SPEC.md#well-known-error-codes)),
e.g. e.g.
```json ```json
{ {
@ -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 of a successful response to `VERSION`, assume that the plugin only supports
CNI spec v0.1.0. CNI spec v0.1.0.
#### Handle missing data in v0.3.0 results #### Handle missing data in v0.3.0 and later results
The Result for the `ADD` command in CNI spec version 0.3.0 includes a new field The Result for the `ADD` command in CNI spec version 0.3.0 and later includes
`interfaces`. An IP address in the `ip` field may describe which interface a new field `interfaces`. An IP address in the `ip` field may describe which
it is assigned to, by placing a numeric index in the `interface` subfield. 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 However, some plugins which are v0.3.0 and later compatible may nonetheless
`interfaces` field and/or set the `interface` index value to `-1`. Runtimes omit the `interfaces` field and/or set the `interface` index value to `-1`.
should gracefully handle this situation, unless they have good reason to rely Runtimes should gracefully handle this situation, unless they have good reason
on the existence of the interface data. In that case, provide the user an to rely on the existence of the interface data. In that case, provide the user
error message that helps diagnose the issue. an error message that helps diagnose the issue.
### Specific guidance for container runtimes written in Go ### Specific guidance for container runtimes written in Go
Container runtimes written in Go may leverage the Go language packages in this 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` this interface. Concrete types are now per-version subpackages. The `types/current`
subpackage contains the latest (spec v0.3.0) types. subpackage contains the latest (spec v0.3.0) types.
When up-converting older result types to spec v0.3.0, fields new in When up-converting older result types to spec v0.3.0 and later, fields new in
spec v0.3.0 (like `interfaces`) may be empty. Conversely, when spec v0.3.0 and later (like `interfaces`) may be empty. Conversely, when
down-converting v0.3.0 results to an older version, any data in those fields down-converting v0.3.0 and later results to an older version, any data in
will be lost. those fields will be lost.
| From | 0.1 | 0.2 | 0.3 |
|--------|-----|-----|-----|
| To 0.1 | ✔ | ✔ | x |
| To 0.2 | ✔ | ✔ | x |
| To 0.3 | ✴ | ✴ | ✔ |
| 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: Key:
> ✔ : lossless conversion <br> > ✔ : lossless conversion <br>
@ -249,7 +284,7 @@ work with the fields exposed by that struct:
```go ```go
// runtime invokes the plugin to get the opaque types.Result // runtime invokes the plugin to get the opaque types.Result
// this may conform to any CNI spec version // this may conform to any CNI spec version
resultInterface, err := libcni.AddNetwork(netConf, runtimeConf) resultInterface, err := libcni.AddNetwork(ctx, netConf, runtimeConf)
// upconvert result to the current 0.3.0 spec // upconvert result to the current 0.3.0 spec
result, err := current.NewResultFromResult(resultInterface) result, err := current.NewResultFromResult(resultInterface)

44
GOVERNANCE.md Normal file
View File

@ -0,0 +1,44 @@
# CNI Governance
This document defines project governance for the project.
## Voting
The CNI project employs "organization voting" to ensure no single organization can dominate the project.
Individuals not associated with or employed by a company or organization are allowed one organization vote.
Each company or organization (regardless of the number of maintainers associated with or employed by that company/organization) receives one organization vote.
In other words, if two maintainers are employed by Company X, two by Company Y, two by Company Z, and one maintainer is an un-affiliated individual, a total of four "organization votes" are possible; one for X, one for Y, one for Z, and one for the un-affiliated individual.
Any maintainer from an organization may cast the vote for that organization.
For formal votes, a specific statement of what is being voted on should be added to the relevant github issue or PR, and a link to that issue or PR added to the maintainers meeting agenda document.
Maintainers should indicate their yes/no vote on that issue or PR, and after a suitable period of time, the votes will be tallied and the outcome noted.
## Changes in Maintainership
New maintainers are proposed by an existing maintainer and are elected by a 2/3 majority organization vote.
Maintainers can be removed by a 2/3 majority organization vote.
## Approving PRs
Non-specification-related PRs may be merged after receiving at least two organization votes.
Changes to the CNI Specification also follow the normal PR approval process (eg, 2 organization votes), but any maintainer can request that the approval require a 2/3 majority organization vote.
## Github Project Administration
Maintainers will be added to the containernetworking GitHub organization and added to the GitHub cni-maintainers team, and made a GitHub maintainer of that team.
After 6 months a maintainer will be made an "owner" of the GitHub organization.
## Changes in Governance
All changes in Governance require a 2/3 majority organization vote.
## Other Changes
Unless specified above, all other changes to the project require a 2/3 majority organization vote.
Additionally, any maintainer may request that any change require a 2/3 majority organization vote.

160
Godeps/Godeps.json generated
View File

@ -1,160 +0,0 @@
{
"ImportPath": "github.com/containernetworking/cni",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/config",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/types",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/format",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gbytes",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gexec",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/assertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/types",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
}
]
}

5
Godeps/Readme generated
View File

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

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

1
Makefile Normal file
View File

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

View File

@ -1,19 +1,12 @@
[![Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
[![Slack Status](https://cryptic-tundra-43194.herokuapp.com/badge.svg)](https://cryptic-tundra-43194.herokuapp.com/)
![CNI Logo](logo.png) ![CNI Logo](logo.png)
--- ---
# Community Sync Meeting
There is a community sync meeting for users and developers every 1-2 months. The next meeting will help on a Google Hangout and the link is in the [agenda](https://docs.google.com/document/d/10ECyT2mBGewsJUcmYmS8QNo1AcNgy2ZIe2xS7lShYhE/edit?usp=sharing) (Notes from previous meeting are also in this doc). The next meeting will be held on *Wednesday, June 21th* at *3:00pm UTC* [Add to Calendar]https://www.worldtimebuddy.com/?qm=1&lid=100,5,2643743,5391959&h=100&date=2017-6-21&sln=15-16).
---
# CNI - the Container Network Interface # CNI - the Container Network Interface
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2446/badge)](https://bestpractices.coreinfrastructure.org/projects/2446)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/containernetworking/cni/badge)](https://securityscorecards.dev/viewer/?uri=github.com/containernetworking/cni)
## What is CNI? ## What is CNI?
CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins. CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
@ -25,6 +18,19 @@ As well as the [specification](SPEC.md), this repository contains the Go source
The template code makes it straight-forward to create a CNI plugin for an existing container networking project. The template code makes it straight-forward to create a CNI plugin for an existing container networking project.
CNI also makes a good framework for creating a new container networking project from scratch. CNI also makes a good framework for creating a new container networking project from scratch.
Here are the recordings of two sessions that the CNI maintainers hosted at KubeCon/CloudNativeCon 2019:
- [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? ## Why develop CNI?
Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific. Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific.
@ -34,40 +40,49 @@ To avoid duplication, we think it is prudent to define a common interface betwee
## Who is using CNI? ## Who is using CNI?
### Container runtimes ### Container runtimes
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html) - [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/)
- [Kurma - container runtime](http://kurma.io/)
- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/)
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md) - [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release) - [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
- [Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md) - [Apache Mesos - a distributed systems kernel](https://github.com/apache/mesos/blob/master/docs/cni.md)
- [Amazon ECS - a highly scalable, high performance container management service](https://aws.amazon.com/ecs/)
- [Singularity - container platform optimized for HPC, EPC, and AI](https://github.com/sylabs/singularity)
- [OpenSVC - orchestrator for legacy and containerized application stacks](https://docs.opensvc.com/latest/fr/agent.configure.cni.html)
### 3rd party plugins ### 3rd party plugins
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni) - [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin) - [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
- [SR-IOV](https://github.com/hustcat/sriov-cni) - [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium) - [Cilium - eBPF & XDP for containers](https://github.com/cilium/cilium)
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox) - [Multus - a Multi plugin](https://github.com/k8snetworkplumbingwg/multus-cni)
- [Multus - a Multi plugin](https://github.com/Intel-Corp/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube) - [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie) - [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni) - [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni) - [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
- [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)
- [Bonding CNI - a Link aggregating plugin to address failover and high availability network](https://github.com/Intel-Corp/bond-cni)
- [ovn-kubernetes - an container network plugin built on Open vSwitch (OVS) and Open Virtual Networking (OVN) with support for both Linux and Windows](https://github.com/openvswitch/ovn-kubernetes)
- [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)
- [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override)
- [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway)
- [Cisco ACI CNI - for on-prem and cloud container networking with consistent policy and security model.](https://github.com/noironetworks/aci-containers)
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/kubeovn/kube-ovn)
- [Project Antrea - an Open vSwitch k8s CNI](https://github.com/vmware-tanzu/antrea)
- [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). The CNI team also maintains some [core plugins in a separate repository](https://github.com/containernetworking/plugins).
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI? ## How do I use CNI?
### Requirements ### Requirements
The CNI spec is language agnostic. To use the Go language libraries in this repository, you'll need a recent version of Go. Our [automated tests](https://travis-ci.org/containernetworking/cni/builds) cover Go versions 1.7 and 1.8. The CNI spec is language agnostic. To use the Go language libraries in this repository, you'll need a recent version of Go. You can find the Go versions covered by our [automated tests](https://travis-ci.org/containernetworking/cni/builds) in [.travis.yaml](.travis.yml).
### Reference Plugins ### Reference Plugins
@ -104,6 +119,7 @@ EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF $ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{ {
"cniVersion": "0.2.0", "cniVersion": "0.2.0",
"name": "lo",
"type": "loopback" "type": "loopback"
} }
EOF EOF
@ -115,7 +131,7 @@ Next, build the plugins:
```bash ```bash
$ cd $GOPATH/src/github.com/containernetworking/plugins $ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build.sh $ ./build_linux.sh # or build_windows.sh
``` ```
Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network: Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network:
@ -175,17 +191,30 @@ lo Link encap:Local Loopback
## What might CNI do in the future? ## What might CNI do in the future?
CNI currently covers a wide range of needs for network configuration due to it simple model and API. CNI currently covers a wide range of needs for network configuration due to its simple model and API.
However, in the future CNI might want to branch out into other directions: However, in the future CNI might want to branch out into other directions:
- Dynamic updates to existing network configuration - Dynamic updates to existing network configuration
- Dynamic policies for network bandwidth and firewall rules - Dynamic policies for network bandwidth and firewall rules
If these topics of are interest, please contact the team via the mailing list or IRC and find some like-minded people in the community to put a proposal together. If these topics are of interest, please contact the team via the mailing list or IRC and find some like-minded people in the community to put a proposal together.
## Where are the binaries?
The plugins moved to a separate repo:
https://github.com/containernetworking/plugins, and the releases there
include binaries and checksums.
Prior to release 0.7.0 the `cni` release also included a `cnitool`
binary; as this is a developer tool we suggest you build it yourself.
## Contact ## Contact
For any questions about CNI, please reach out on the mailing list: For any questions about CNI, please reach out via:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev) - Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org - IRC: #[containernetworking](irc://irc.freenode.net:6667/#containernetworking) channel on [freenode.net](https://freenode.net/)
- Slack: [containernetworking.slack.com](https://cryptic-tundra-43194.herokuapp.com) - 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,34 +1,19 @@
# Release process # Release process
## Resulting artifacts
Creating a new release produces the following artifacts:
- Binaries (stored in the `release-<TAG>` directory) :
- `cni-<PLATFORM>-<VERSION>.tgz` binaries
- `cni-<VERSION>.tgz` binary (copy of amd64 platform binary)
- `sha1`, `sha256` and `sha512` files for the above files.
## Preparing for a release ## Preparing for a release
1. Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
- 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 Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
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) - Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
1. Run the release script from the root of the repository - Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
- `scripts/release.sh` - Create a draft of the release note
- The script requires Docker and ensures that a consistent environment is used. - Discuss the level of testing that's needed and create a test plan if sensible
- The artifacts will now be present in the `release-<TAG>` directory. - Check what version of `go` is used in the build container, updating it if there's a new stable release.
1. Test these binaries according to the test plan.
## Publishing the release ## Publishing the release
1. Make sure you are on the master branch and don't have any local uncommitted changes.
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
1. Push the tag to git `git push origin <TAG>` 1. Push the tag to git `git push origin <TAG>`
1. Create a release on Github, using the tag which was just pushed. 1. Create a release on Github, using the tag which was just pushed.
1. Attach all the artifacts from the release directory.
1. Add the release note to the release. 1. Add the release note to the release.
1. Announce the release on at least the CNI mailing, IRC and Slack. 1. Announce the release on at least the CNI mailing, IRC and Slack.

View File

@ -5,29 +5,14 @@ The list below is not complete, and we advise to get the current project state f
## CNI Milestones ## CNI Milestones
### [v0.2.0](https://github.com/containernetworking/cni/milestones/v0.2.0)
* Signed release binaries
* Introduction of a testing strategy/framework
### [v0.3.0](https://github.com/containernetworking/cni/milestones/v0.3.0)
* Further increase test coverage
* Simpler default route handling in bridge plugin
* Clarify project description, documentation and contribution guidelines
### [v0.4.0](https://github.com/containernetworking/cni/milestones/v0.4.0)
* Further increase test coverage
* Simpler bridging of host interface
* Improve IPAM allocator predictability
* Allow in- and output of arbitrary K/V pairs for plugins
### [v1.0.0](https://github.com/containernetworking/cni/milestones/v1.0.0) ### [v1.0.0](https://github.com/containernetworking/cni/milestones/v1.0.0)
- Plugin composition functionality - Targeted for April 2020
- IPv6 support - More precise specification language
- Stable SPEC - Stable SPEC
- Strategy and tooling for backwards compatibility
- Complete test coverage - Complete test coverage
- Integrate build artefact generation with CI
### Beyond v1.0.0
- Conformance test suite for CNI plugins (both reference and 3rd party)
- Signed release binaries

1509
SPEC.md

File diff suppressed because it is too large Load Diff

26
Vagrantfile vendored
View File

@ -1,26 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-16.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.8.3.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/tools/godep
cd /go/src/github.com/containernetworking/cni
godep restore
SHELL
end

View File

@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -e
ORG_PATH="github.com/containernetworking"
REPO_PATH="${ORG_PATH}/cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOPATH=${PWD}/gopath
echo "Building API"
go build "$@" ${REPO_PATH}/libcni
echo "Building reference CLI"
go build -o ${PWD}/bin/cnitool "$@" ${REPO_PATH}/cnitool
echo "Building plugins"
PLUGINS="plugins/test/*"
for d in $PLUGINS; do
if [ -d $d ]; then
plugin=$(basename $d)
echo " " $plugin
go build -o ${PWD}/bin/$plugin "$@" ${REPO_PATH}/$d
fi
done

77
cnitool/README.md Normal file
View File

@ -0,0 +1,77 @@
# cnitool
`cnitool` is a simple program that executes a CNI configuration. It will
add or remove an interface in an already-created network namespace.
## Environment Variables
* `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 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`.
* `CNI_PATH`: For a given CNI configuration `cnitool` will search for
the corresponding CNI plugin in this path.
## Example invocation
First, install cnitool:
```bash
go get github.com/containernetworking/cni
go install github.com/containernetworking/cni/cnitool
```
Then, check out and build the plugins. All commands should be run from this directory.
```bash
git clone https://github.com/containernetworking/plugins.git
cd plugins
./build_linux.sh
# or
./build_windows.sh
```
Create a network configuration
```bash
echo '{"cniVersion":"0.4.0","name":"myptp","type":"ptp","ipMasq":true,"ipam":{"type":"host-local","subnet":"172.16.29.0/24","routes":[{"dst":"0.0.0.0/0"}]}}' | sudo tee /etc/cni/net.d/10-myptp.conf
```
Create a network namespace. This will be called `testing`:
```bash
sudo ip netns add testing
```
Add the container to the network:
```bash
sudo CNI_PATH=./bin cnitool add myptp /var/run/netns/testing
```
Check whether the container's networking is as expected (ONLY for spec v0.4.0+):
```bash
sudo CNI_PATH=./bin cnitool check myptp /var/run/netns/testing
```
Test that it works:
```bash
sudo ip -n testing addr
sudo ip netns exec testing ping -c 1 4.2.2.2
```
And clean up:
```bash
sudo CNI_PATH=./bin cnitool del myptp /var/run/netns/testing
sudo ip netns del testing
```

View File

@ -15,6 +15,8 @@
package main package main
import ( import (
"context"
"crypto/sha512"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -24,16 +26,21 @@ import (
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
) )
// Protocol parameters are passed to the plugins via OS environment variables.
const ( const (
EnvCNIPath = "CNI_PATH" EnvCNIPath = "CNI_PATH"
EnvNetDir = "NETCONFPATH" EnvNetDir = "NETCONFPATH"
EnvCapabilityArgs = "CAP_ARGS" EnvCapabilityArgs = "CAP_ARGS"
EnvCNIArgs = "CNI_ARGS" EnvCNIArgs = "CNI_ARGS"
EnvCNIIfname = "CNI_IFNAME"
DefaultNetDir = "/etc/cni/net.d" DefaultNetDir = "/etc/cni/net.d"
CmdAdd = "add" CmdAdd = "add"
CmdDel = "del" CmdCheck = "check"
CmdDel = "del"
CmdGC = "gc"
CmdStatus = "status"
) )
func parseArgs(args string) ([][2]string, error) { func parseArgs(args string) ([][2]string, error) {
@ -53,16 +60,15 @@ func parseArgs(args string) ([][2]string, error) {
} }
func main() { func main() {
if len(os.Args) < 3 { if len(os.Args) < 4 {
usage() usage()
return
} }
netdir := os.Getenv(EnvNetDir) netdir := os.Getenv(EnvNetDir)
if netdir == "" { if netdir == "" {
netdir = DefaultNetDir netdir = DefaultNetDir
} }
netconf, err := libcni.LoadConfList(netdir, os.Args[2]) netconf, err := libcni.LoadNetworkConf(netdir, os.Args[2])
if err != nil { if err != nil {
exit(err) exit(err)
} }
@ -84,38 +90,60 @@ func main() {
} }
} }
netns := os.Args[3] ifName, ok := os.LookupEnv(EnvCNIIfname)
if !ok {
cninet := &libcni.CNIConfig{ ifName = "eth0"
Path: filepath.SplitList(os.Getenv(EnvCNIPath)),
} }
netns := os.Args[3]
netns, err = filepath.Abs(netns)
if err != nil {
exit(err)
}
// Generate the containerid by hashing the netns path
s := sha512.Sum512([]byte(netns))
containerID := fmt.Sprintf("cnitool-%x", s[:10])
cninet := libcni.NewCNIConfig(filepath.SplitList(os.Getenv(EnvCNIPath)), nil)
rt := &libcni.RuntimeConf{ rt := &libcni.RuntimeConf{
ContainerID: "cni", ContainerID: containerID,
NetNS: netns, NetNS: netns,
IfName: "eth0", IfName: ifName,
Args: cniArgs, Args: cniArgs,
CapabilityArgs: capabilityArgs, CapabilityArgs: capabilityArgs,
} }
switch os.Args[1] { switch os.Args[1] {
case CmdAdd: case CmdAdd:
result, err := cninet.AddNetworkList(netconf, rt) result, err := cninet.AddNetworkList(context.TODO(), netconf, rt)
if result != nil { if result != nil {
_ = result.Print() _ = result.Print()
} }
exit(err) exit(err)
case CmdCheck:
err := cninet.CheckNetworkList(context.TODO(), netconf, rt)
exit(err)
case CmdDel: case CmdDel:
exit(cninet.DelNetworkList(netconf, rt)) exit(cninet.DelNetworkList(context.TODO(), netconf, rt))
case CmdGC:
// Currently just invoke GC without args, hence all network interface should be GC'ed!
exit(cninet.GCNetworkList(context.TODO(), netconf, nil))
case CmdStatus:
exit(cninet.GetStatusNetworkList(context.TODO(), netconf))
} }
} }
func usage() { func usage() {
exe := filepath.Base(os.Args[0]) exe := filepath.Base(os.Args[0])
fmt.Fprintf(os.Stderr, "%s: Add or remove network interfaces from a network namespace\n", exe) fmt.Fprintf(os.Stderr, "%s: Add, check, remove, gc or status network interfaces from a network namespace\n", exe)
fmt.Fprintf(os.Stderr, " %s %s <net> <netns>\n", exe, CmdAdd) fmt.Fprintf(os.Stderr, " %s add <net> <netns>\n", exe)
fmt.Fprintf(os.Stderr, " %s %s <net> <netns>\n", exe, CmdDel) 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) 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,15 +14,43 @@
package libcni 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 ( import (
"context"
"encoding/json"
"errors"
"fmt"
"os" "os"
"path/filepath"
"sort"
"strings" "strings"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types" "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" "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 (
CNICacheV1 = "cniCacheV1"
)
// A RuntimeConf holds the arguments to one invocation of a CNI plugin
// excepting the network configuration, with the nested exception that
// the `runtimeConfig` from the network configuration is included
// here.
type RuntimeConf struct { type RuntimeConf struct {
ContainerID string ContainerID string
NetNS string NetNS string
@ -34,41 +62,102 @@ type RuntimeConf struct {
// in this map which match the capabilities of the plugin are passed // in this map which match the capabilities of the plugin are passed
// to the plugin // to the plugin
CapabilityArgs map[string]interface{} CapabilityArgs map[string]interface{}
// DEPRECATED. Will be removed in a future release.
CacheDir string
} }
type NetworkConfig struct { // Use PluginConfig instead of NetworkConfig, the NetworkConfig
Network *types.NetConf // backwards-compat alias will be removed in a future release.
type NetworkConfig = PluginConfig
type PluginConfig struct {
Network *types.PluginConf
Bytes []byte Bytes []byte
} }
type NetworkConfigList struct { type NetworkConfigList struct {
Name string Name string
CNIVersion string CNIVersion string
Plugins []*NetworkConfig DisableCheck bool
Bytes []byte 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 { type CNI interface {
AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
DelNetwork(net *NetworkConfig, rt *RuntimeConf) 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 *PluginConfig) ([]string, error)
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
GetCachedAttachments(containerID string) ([]*NetworkAttachment, error)
GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error)
} }
type CNIConfig struct { type CNIConfig struct {
Path []string Path []string
exec invoke.Exec
cacheDir string
} }
// CNIConfig implements the CNI interface // CNIConfig implements the CNI interface
var _ CNI = &CNIConfig{} var _ CNI = &CNIConfig{}
func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { // NewCNIConfig returns a new CNIConfig object that will search for plugins
// in the given paths and use the given exec interface to run those plugins,
// or if the exec interface is not given, will use a default exec handler.
func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
return NewCNIConfigWithCacheDir(path, "", exec)
}
// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins
// in the given paths use the given exec interface to run those plugins,
// or if the exec interface is not given, will use a default exec handler.
// The given cache directory will be used for temporary data storage when needed.
func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig {
return &CNIConfig{
Path: path,
cacheDir: cacheDir,
exec: exec,
}
}
func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) {
var err error var err error
inject := map[string]interface{}{ inject := map[string]interface{}{
"name": list.Name, "name": name,
"cniVersion": list.CNIVersion, "cniVersion": cniVersion,
} }
// Add previous plugin result // Add previous plugin result
if prevResult != nil { if prevResult != nil {
@ -80,8 +169,11 @@ func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult typ
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rt != nil {
return injectRuntimeConfig(orig, rt)
}
return injectRuntimeConfig(orig, rt) return orig, nil
} }
// This function takes a libcni RuntimeConf structure and injects values into // This function takes a libcni RuntimeConf structure and injects values into
@ -92,11 +184,11 @@ func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult typ
// These capabilities arguments are filtered through the plugin's advertised // These capabilities arguments are filtered through the plugin's advertised
// capabilities from its config JSON, and any keys in the CapabilityArgs // capabilities from its config JSON, and any keys in the CapabilityArgs
// matching plugin capabilities are added to the "runtimeConfig" dictionary // matching plugin capabilities are added to the "runtimeConfig" dictionary
// sent to the plugin via JSON on stdin. For exmaple, if the plugin's // sent to the plugin via JSON on stdin. For example, if the plugin's
// capabilities include "portMappings", and the CapabilityArgs map includes a // capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig" // "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin. // dictionary to be passed to the plugin's stdin.
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) { func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) {
var err error var err error
rc := make(map[string]interface{}) rc := make(map[string]interface{})
@ -119,45 +211,359 @@ func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig,
return orig, nil return orig, nil
} }
// ensure we have a usable exec if the CNIConfig was not given one
func (c *CNIConfig) ensureExec() invoke.Exec {
if c.exec == nil {
c.exec = &invoke.DefaultExec{
RawExec: &invoke.RawExec{Stderr: os.Stderr},
PluginDecoder: version.PluginDecoder{},
}
}
return c.exec
}
type cachedInfo struct {
Kind string `json:"kind"`
ContainerID string `json:"containerId"`
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"`
Result types.Result `json:"-"`
}
// getCacheDir returns the cache directory in this order:
// 1) global cacheDir from CNIConfig object
// 2) deprecated cacheDir from RuntimeConf object
// 3) fall back to default cache directory
func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string {
if c.cacheDir != "" {
return c.cacheDir
}
if rt.CacheDir != "" {
return rt.CacheDir
}
return CacheDir
}
func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) {
if netName == "" || rt.ContainerID == "" || rt.IfName == "" {
return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName)
}
return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil
}
func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error {
cached := cachedInfo{
Kind: CNICacheV1,
ContainerID: rt.ContainerID,
Config: config,
IfName: rt.IfName,
NetworkName: netName,
NetNS: rt.NetNS,
CniArgs: rt.Args,
CapabilityArgs: rt.CapabilityArgs,
}
// We need to get type.Result into cachedInfo as JSON map
// Marshal to []byte, then Unmarshal into cached.RawResult
data, err := json.Marshal(result)
if err != nil {
return err
}
err = json.Unmarshal(data, &cached.RawResult)
if err != nil {
return err
}
newBytes, err := json.Marshal(&cached)
if err != nil {
return err
}
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil {
return err
}
return os.WriteFile(fname, newBytes, 0o600)
}
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
// Ignore error
return nil
}
return os.Remove(fname)
}
func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
var bytes []byte
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, nil, err
}
bytes, err = os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil, nil
}
unmarshaled := cachedInfo{}
if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
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)
}
newRt := *rt
if unmarshaled.CniArgs != nil {
newRt.Args = unmarshaled.CniArgs
}
newRt.CapabilityArgs = unmarshaled.CapabilityArgs
return unmarshaled.Config, &newRt, nil
}
func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, err
}
data, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
}
// Load the cached result
result, err := create.CreateFromBytes(data)
if err != nil {
return nil, err
}
// Convert to the config version to ensure plugins get prevResult
// in the same version as the config. The cached result version
// should match the config version unless the config was changed
// while the container was running.
result, err = result.GetAsVersion(cniVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
}
return result, nil
}
func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
fname, err := c.getCacheFilePath(netName, rt)
if err != nil {
return nil, err
}
fdata, err := os.ReadFile(fname)
if err != nil {
// Ignore read errors; the cached result may not exist on-disk
return nil, nil
}
cachedInfo := cachedInfo{}
if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 {
return c.getLegacyCachedResult(netName, cniVersion, rt)
}
newBytes, err := json.Marshal(&cachedInfo.RawResult)
if err != nil {
return nil, fmt.Errorf("failed to marshal cached network %q config: %w", netName, err)
}
// Load the cached result
result, err := create.CreateFromBytes(newBytes)
if err != nil {
return nil, err
}
// Convert to the config version to ensure plugins get prevResult
// in the same version as the config. The cached result version
// should match the config version unless the config was changed
// while the container was running.
result, err = result.GetAsVersion(cniVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err)
}
return result, nil
}
// GetNetworkListCachedResult returns the cached Result of the previous
// AddNetworkList() operation for a network list, or an error.
func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
return c.getCachedResult(list.Name, list.CNIVersion, rt)
}
// GetNetworkCachedResult returns the cached Result of the previous
// AddNetwork() operation for a network, or an error.
func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
}
// GetNetworkListCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(list.Name, rt)
}
// GetNetworkCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(net.Network.Name, rt)
}
// 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 {
return nil, err
}
if err := utils.ValidateContainerID(rt.ContainerID); err != nil {
return nil, err
}
if err := utils.ValidateNetworkName(name); err != nil {
return nil, err
}
if err := utils.ValidateInterfaceName(rt.IfName); err != nil {
return nil, err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
return nil, err
}
return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
}
// AddNetworkList executes a sequence of plugins with the ADD command // AddNetworkList executes a sequence of plugins with the ADD command
func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
var prevResult types.Result var err error
var result types.Result
for _, net := range list.Plugins { for _, net := range list.Plugins {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("plugin %s failed (add): %w", pluginDescription(net.Network), err)
}
newConf, err := buildOneConfig(list, net, prevResult, rt)
if err != nil {
return nil, err
}
prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
if err != nil {
return nil, err
} }
} }
return prevResult, nil if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %w", list.Name, err)
}
return result, nil
} }
// DelNetworkList executes a sequence of plugins with the DEL command func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error { c.ensureExec()
for i := len(list.Plugins) - 1; i >= 0; i-- { pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
net := list.Plugins[i] if err != nil {
return err
}
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil { if err != nil {
return err return err
} }
newConf, err := buildOneConfig(list, net, nil, rt) return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec)
if err != nil { }
return err
}
if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil { // CheckNetworkList executes a sequence of plugins with the CHECK command
func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
// CHECK was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if !gtet {
return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp)
}
if list.DisableCheck {
return nil
}
cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err)
}
for _, net := range list.Plugins {
if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
return err return err
} }
} }
@ -165,45 +571,320 @@ func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) err
return nil return nil
} }
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 {
return err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
return err
}
return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
}
// DelNetworkList executes a sequence of plugins with the DEL command
func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
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 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 // AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
net, err = injectRuntimeConfig(net, rt) if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
if err != nil { return nil, fmt.Errorf("failed to set network %q cached result: %w", net.Network.Name, err)
return nil, err
} }
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)) return result, nil
}
// CheckNetwork executes the plugin with the CHECK command
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 %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: %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 // DelNetwork executes the plugin with the DEL command
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err)
}
}
if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
return err
}
_ = c.cacheDel(net.Network.Name, rt)
return nil
}
// ValidateNetworkList checks that a configuration is reasonably valid.
// - all the specified plugins exist on disk
// - every plugin supports the desired version.
//
// Returns a list of all capabilities supported by the configuration, or error
func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) {
version := list.CNIVersion
// holding map for seen caps (in case of duplicates)
caps := map[string]interface{}{}
errs := []error{}
for _, net := range list.Plugins {
if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil {
errs = append(errs, err)
}
for c, enabled := range net.Network.Capabilities {
if !enabled {
continue
}
caps[c] = struct{}{}
}
}
if len(errs) > 0 {
return nil, fmt.Errorf("%v", errs)
}
// make caps list
cc := make([]string, 0, len(caps))
for c := range caps {
cc = append(cc, c)
}
return cc, nil
}
// ValidateNetwork checks that a configuration is reasonably valid.
// It uses the same logic as ValidateNetworkList)
// Returns a list of capabilities
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) {
caps := []string{}
for c, ok := range net.Network.Capabilities {
if ok {
caps = append(caps, c)
}
}
if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil {
return nil, err
}
return caps, nil
}
// validatePlugin checks that an individual plugin's configuration is sane
func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(pluginName, c.Path)
if err != nil { if err != nil {
return err return err
} }
if expectedVersion == "" {
expectedVersion = "0.1.0"
}
net, err = injectRuntimeConfig(net, rt) vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec)
if err != nil { if err != nil {
return err return err
} }
for _, vers := range vi.SupportedVersions() {
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) if vers == expectedVersion {
return nil
}
}
return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion)
} }
// GetVersionInfo reports which versions of the CNI spec are supported by // GetVersionInfo reports which versions of the CNI spec are supported by
// the given plugin. // the given plugin.
func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) {
pluginPath, err := invoke.FindInPath(pluginType, c.Path) c.ensureExec()
pluginPath, err := c.exec.FindInPath(pluginType, c.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return invoke.GetVersionInfo(pluginPath) 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)
} }
// ===== // =====

File diff suppressed because it is too large Load Diff

View File

@ -15,20 +15,35 @@
package libcni_test package libcni_test
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples" "github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
) )
var _ = Describe("Backwards compatibility", func() { var _ = Describe("Backwards compatibility", func() {
var cacheDirPath string
BeforeEach(func() {
var err error
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(os.RemoveAll(cacheDirPath)).To(Succeed())
})
It("correctly handles the response from a legacy plugin", func() { It("correctly handles the response from a legacy plugin", func() {
example := legacy_examples.V010 example := legacy_examples.V010
pluginPath, err := example.Build() pluginPath, err := example.Build()
@ -44,17 +59,23 @@ var _ = Describe("Backwards compatibility", func() {
IfName: "eth0", IfName: "eth0",
} }
cniConfig := &libcni.CNIConfig{Path: []string{filepath.Dir(pluginPath)}} cniConfig := libcni.NewCNIConfigWithCacheDir([]string{filepath.Dir(pluginPath)}, cacheDirPath, nil)
result, err := cniConfig.AddNetwork(netConf, runtimeConf) result, err := cniConfig.AddNetwork(context.TODO(), netConf, runtimeConf)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(legacy_examples.ExpectedResult)) Expect(result).To(Equal(legacy_examples.ExpectedResult))
err = cniConfig.DelNetwork(context.TODO(), netConf, runtimeConf)
Expect(err).NotTo(HaveOccurred())
Expect(os.RemoveAll(pluginPath)).To(Succeed()) Expect(os.RemoveAll(pluginPath)).To(Succeed())
}) })
It("correctly handles the request from a runtime with an older libcni", func() { It("correctly handles the request from a runtime with an older libcni", func() {
if runtime.GOOS == "windows" {
Skip("cannot build old runtime on windows")
}
example := legacy_examples.V010_Runtime example := legacy_examples.V010_Runtime
binPath, err := example.Build() binPath, err := example.Build()

View File

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

View File

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

View File

@ -15,15 +15,13 @@
package libcni_test package libcni_test
import ( import (
"fmt" "encoding/json"
"path/filepath" "path/filepath"
"strings" "testing"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
"testing"
) )
func TestLibcni(t *testing.T) { func TestLibcni(t *testing.T) {
@ -31,32 +29,31 @@ func TestLibcni(t *testing.T) {
RunSpecs(t, "Libcni Suite") RunSpecs(t, "Libcni Suite")
} }
var plugins = map[string]string{ var pluginPackages = map[string]string{
"noop": "github.com/containernetworking/cni/plugins/test/noop", "noop": "github.com/containernetworking/cni/plugins/test/noop",
"sleep": "github.com/containernetworking/cni/plugins/test/sleep",
} }
var pluginPaths map[string]string var (
var pluginDirs []string // array of plugin dirs pluginPaths map[string]string
pluginDirs []string // array of plugin dirs
)
var _ = SynchronizedBeforeSuite(func() []byte { var _ = SynchronizedBeforeSuite(func() []byte {
dirs := make([]string, 0, len(plugins)) paths := map[string]string{}
for name, packagePath := range pluginPackages {
for name, packagePath := range plugins {
execPath, err := gexec.Build(packagePath) execPath, err := gexec.Build(packagePath)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
dirs = append(dirs, fmt.Sprintf("%s=%s", name, execPath)) paths[name] = execPath
} }
crossNodeData, err := json.Marshal(paths)
Expect(err).NotTo(HaveOccurred())
return []byte(strings.Join(dirs, ":")) return crossNodeData
}, func(crossNodeData []byte) { }, func(crossNodeData []byte) {
pluginPaths = make(map[string]string) Expect(json.Unmarshal(crossNodeData, &pluginPaths)).To(Succeed())
for _, str := range strings.Split(string(crossNodeData), ":") { for _, pluginPath := range pluginPaths {
kvs := strings.SplitN(str, "=", 2) pluginDirs = append(pluginDirs, filepath.Dir(pluginPath))
if len(kvs) != 2 {
Fail("Invalid inter-node data...")
}
pluginPaths[kvs[0]] = kvs[1]
pluginDirs = append(pluginDirs, filepath.Dir(kvs[1]))
} }
}) })

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

@ -15,6 +15,7 @@
package invoke package invoke
import ( import (
"fmt"
"os" "os"
"strings" "strings"
) )
@ -22,6 +23,8 @@ import (
type CNIArgs interface { type CNIArgs interface {
// For use with os/exec; i.e., return nil to inherit the // For use with os/exec; i.e., return nil to inherit the
// environment from this process // environment from this process
// For use in delegation; inherit the environment from this
// process and allow overrides
AsEnv() []string AsEnv() []string
} }
@ -29,7 +32,7 @@ type inherited struct{}
var inheritArgsFromEnv inherited var inheritArgsFromEnv inherited
func (_ *inherited) AsEnv() []string { func (*inherited) AsEnv() []string {
return nil return nil
} }
@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs) pluginArgsStr = stringify(args.PluginArgs)
} }
// Ensure that the custom values are first, so any value present in // Duplicated values which come first will be overridden, so we must put the
// the process environment won't override them. // custom values in the end to avoid being overridden by the process environments.
env = append([]string{ env = append(env,
"CNI_COMMAND=" + args.Command, "CNI_COMMAND="+args.Command,
"CNI_CONTAINERID=" + args.ContainerID, "CNI_CONTAINERID="+args.ContainerID,
"CNI_NETNS=" + args.NetNS, "CNI_NETNS="+args.NetNS,
"CNI_ARGS=" + pluginArgsStr, "CNI_ARGS="+pluginArgsStr,
"CNI_IFNAME=" + args.IfName, "CNI_IFNAME="+args.IfName,
"CNI_PATH=" + args.Path, "CNI_PATH="+args.Path,
}, env...) )
return env return dedupEnv(env)
} }
// taken from rkt/networking/net_plugin.go // taken from rkt/networking/net_plugin.go
@ -80,3 +83,46 @@ func stringify(pluginArgs [][2]string) string {
return strings.Join(entries, ";") return strings.Join(entries, ";")
} }
// DelegateArgs implements the CNIArgs interface
// used for delegation to inherit from environments
// and allow some overrides like CNI_COMMAND
var _ CNIArgs = &DelegateArgs{}
type DelegateArgs struct {
Command string
}
func (d *DelegateArgs) AsEnv() []string {
env := os.Environ()
// The custom values should come in the end to override the existing
// process environment of the same key.
env = append(env,
"CNI_COMMAND="+d.Command,
)
return dedupEnv(env)
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
out := make([]string, 0, len(env))
envMap := map[string]string{}
for _, kv := range env {
// find the first "=" in environment, if not, just keep it
eq := strings.Index(kv, "=")
if eq < 0 {
out = append(out, kv)
continue
}
envMap[kv[:eq]] = kv[eq+1:]
}
for k, v := range envMap {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
return out
}

View File

@ -17,44 +17,123 @@ package invoke_test
import ( import (
"os" "os"
"github.com/containernetworking/cni/pkg/invoke" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
) )
var _ = Describe("Args", func() { var _ = Describe("CNIArgs AsEnv", func() {
Describe("AsEnv", func() { Describe("Args AsEnv", func() {
It("places the CNI_ environment variables ahead of any ambient variables", func() { BeforeEach(func() {
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_IFNAME", "eth0")
os.Setenv("CNI_CONTAINERID", "id")
os.Setenv("CNI_ARGS", "args")
os.Setenv("CNI_NETNS", "testns")
os.Setenv("CNI_PATH", "testpath")
})
It("places the CNI environment variables in the end to be prepended", func() {
args := invoke.Args{ args := invoke.Args{
Command: "ADD", Command: "ADD",
ContainerID: "some-container-id", ContainerID: "some-container-id",
NetNS: "/some/netns/path", NetNS: "/some/netns/path",
PluginArgs: [][2]string{ PluginArgs: [][2]string{
[2]string{"KEY1", "VALUE1"}, {"KEY1", "VALUE1"},
[2]string{"KEY2", "VALUE2"}, {"KEY2", "VALUE2"},
}, },
IfName: "eth7", IfName: "eth7",
Path: "/some/cni/path", Path: "/some/cni/path",
} }
const numCNIEnvVars = 6
latentVars := os.Environ() latentEnvs := os.Environ()
numLatentEnvs := len(latentEnvs)
cniEnv := args.AsEnv() cniEnvs := args.AsEnv()
Expect(cniEnv).To(HaveLen(len(latentVars) + numCNIEnvVars)) Expect(cniEnvs).To(HaveLen(numLatentEnvs))
Expect(cniEnv[0:numCNIEnvVars]).To(Equal([]string{
"CNI_COMMAND=ADD",
"CNI_CONTAINERID=some-container-id",
"CNI_NETNS=/some/netns/path",
"CNI_ARGS=KEY1=VALUE1;KEY2=VALUE2",
"CNI_IFNAME=eth7",
"CNI_PATH=/some/cni/path",
}))
for i := range latentVars { Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(BeTrue())
Expect(cniEnv[numCNIEnvVars+i]).To(Equal(latentVars[i])) 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(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() {
os.Unsetenv("CNI_COMMAND")
os.Unsetenv("CNI_IFNAME")
os.Unsetenv("CNI_CONTAINERID")
os.Unsetenv("CNI_ARGS")
os.Unsetenv("CNI_NETNS")
os.Unsetenv("CNI_PATH")
})
})
Describe("DelegateArgs AsEnv", func() {
BeforeEach(func() {
os.Unsetenv("CNI_COMMAND")
})
It("override CNI_COMMAND if it already exists in environment variables", func() {
os.Setenv("CNI_COMMAND", "DEL")
delegateArgs := invoke.DelegateArgs{
Command: "ADD",
} }
latentEnvs := os.Environ()
numLatentEnvs := len(latentEnvs)
cniEnvs := delegateArgs.AsEnv()
Expect(cniEnvs).To(HaveLen(numLatentEnvs))
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() {
delegateArgs := invoke.DelegateArgs{
Command: "ADD",
}
latentEnvs := os.Environ()
numLatentEnvs := len(latentEnvs)
cniEnvs := delegateArgs.AsEnv()
Expect(cniEnvs).To(HaveLen(numLatentEnvs + 1))
Expect(inStringSlice("CNI_COMMAND=ADD", cniEnvs)).To(BeTrue())
})
AfterEach(func() {
os.Unsetenv("CNI_COMMAND")
})
})
Describe("inherited AsEnv", func() {
It("return nil string slice if we call AsEnv of inherited", func() {
inheritedArgs := invoke.ArgsFromEnv()
var nilSlice []string = nil
Expect(inheritedArgs.AsEnv()).To(Equal(nilSlice))
}) })
}) })
}) })
func inStringSlice(in string, slice []string) bool {
for _, s := range slice {
if in == s {
return true
}
}
return false
}

View File

@ -15,39 +15,75 @@
package invoke package invoke
import ( import (
"fmt" "context"
"os" "os"
"path/filepath" "path/filepath"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
) )
func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) { func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
if os.Getenv("CNI_COMMAND") != "ADD" { if exec == nil {
return nil, fmt.Errorf("CNI_COMMAND is not ADD") exec = defaultExec
} }
paths := filepath.SplitList(os.Getenv("CNI_PATH")) paths := filepath.SplitList(os.Getenv("CNI_PATH"))
pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
return "", nil, err
}
pluginPath, err := FindInPath(delegatePlugin, paths) return pluginPath, exec, nil
}
// DelegateAdd calls the given delegate plugin with the CNI ADD action and
// JSON configuration
func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv()) // DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)
} }
func DelegateDel(delegatePlugin string, netconf []byte) error { // DelegateCheck calls the given delegate plugin with the CNI CHECK action and
if os.Getenv("CNI_COMMAND") != "DEL" { // JSON configuration
return fmt.Errorf("CNI_COMMAND is not DEL") func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
} return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK")
}
paths := filepath.SplitList(os.Getenv("CNI_PATH")) func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) error {
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil { if err != nil {
return err return err
} }
return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv()) 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 {
return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL")
}
// 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
func delegateArgs(action string) *DelegateArgs {
return &DelegateArgs{
Command: action,
}
} }

View File

@ -15,18 +15,19 @@
package invoke_test package invoke_test
import ( import (
"context"
"encoding/json" "encoding/json"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"github.com/containernetworking/cni/pkg/invoke" . "github.com/onsi/ginkgo/v2"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/plugins/test/noop/debug"
) )
var _ = Describe("Delegate", func() { var _ = Describe("Delegate", func() {
@ -36,18 +37,19 @@ var _ = Describe("Delegate", func() {
debugFileName string debugFileName string
debugBehavior *debug.Debug debugBehavior *debug.Debug
expectedResult *current.Result expectedResult *current.Result
ctx context.Context
) )
BeforeEach(func() { BeforeEach(func() {
netConf, _ = json.Marshal(map[string]string{ netConf, _ = json.Marshal(map[string]string{
"cniVersion": "0.3.1", "name": "delegate-test",
"cniVersion": version.Current(),
}) })
expectedResult = &current.Result{ expectedResult = &current.Result{
CNIVersion: "0.3.1", CNIVersion: current.ImplementedSpecVersion,
IPs: []*current.IPConfig{ IPs: []*current.IPConfig{
&current.IPConfig{ {
Version: "4",
Address: net.IPNet{ Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"), IP: net.ParseIP("10.1.2.3"),
Mask: net.CIDRMask(24, 32), Mask: net.CIDRMask(24, 32),
@ -57,7 +59,7 @@ var _ = Describe("Delegate", func() {
} }
expectedResultBytes, _ := json.Marshal(expectedResult) expectedResultBytes, _ := json.Marshal(expectedResult)
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name() debugFileName = debugFile.Name()
@ -66,15 +68,20 @@ var _ = Describe("Delegate", func() {
} }
Expect(debugBehavior.WriteDebug(debugFileName)).To(Succeed()) Expect(debugBehavior.WriteDebug(debugFileName)).To(Succeed())
pluginName = "noop" pluginName = "noop"
ctx = context.TODO()
os.Setenv("CNI_ARGS", "DEBUG="+debugFileName) os.Setenv("CNI_ARGS", "DEBUG="+debugFileName)
os.Setenv("CNI_PATH", filepath.Dir(pathToPlugin)) os.Setenv("CNI_PATH", filepath.Dir(pathToPlugin))
os.Setenv("CNI_NETNS", "/tmp/some/netns/path") os.Setenv("CNI_NETNS", "/tmp/some/netns/path")
os.Setenv("CNI_IFNAME", "eth7") os.Setenv("CNI_IFNAME", "eth7")
os.Setenv("CNI_CONTAINERID", "container")
}) })
AfterEach(func() { AfterEach(func() {
os.RemoveAll(debugFileName) os.RemoveAll(debugFileName)
for _, k := range []string{"CNI_COMMAND", "CNI_ARGS", "CNI_PATH", "CNI_NETNS", "CNI_IFNAME"} {
os.Unsetenv(k)
}
}) })
Describe("DelegateAdd", func() { Describe("DelegateAdd", func() {
@ -83,7 +90,7 @@ var _ = Describe("Delegate", func() {
}) })
It("finds and execs the named plugin", func() { It("finds and execs the named plugin", func() {
result, err := invoke.DelegateAdd(pluginName, netConf) result, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(expectedResult)) Expect(result).To(Equal(expectedResult))
@ -93,14 +100,23 @@ var _ = Describe("Delegate", func() {
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7")) Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
}) })
Context("if the delegation isn't part of an existing ADD command", func() { Context("if the ADD delegation runs on an existing non-ADD command, ", func() {
BeforeEach(func() { BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE") os.Setenv("CNI_COMMAND", "NOPE")
}) })
It("aborts and returns a useful error", func() { It("aborts and returns a useful error", func() {
_, err := invoke.DelegateAdd(pluginName, netConf) result, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError("CNI_COMMAND is not ADD")) Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal(expectedResult))
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("ADD"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
// check the original env
Expect(os.Getenv("CNI_COMMAND")).To(Equal("NOPE"))
}) })
}) })
@ -110,7 +126,53 @@ var _ = Describe("Delegate", func() {
}) })
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := invoke.DelegateAdd(pluginName, netConf) _, err := invoke.DelegateAdd(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
Describe("DelegateCheck", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "CHECK")
})
It("finds and execs the named plugin", func() {
err := invoke.DelegateCheck(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("CHECK"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
})
Context("if the CHECK delegation runs on an existing non-CHECK command", func() {
BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE")
})
It("aborts and returns a useful error", func() {
err := invoke.DelegateCheck(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("CHECK"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
// 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.DelegateCheck(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin"))) Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
}) })
}) })
@ -122,7 +184,7 @@ var _ = Describe("Delegate", func() {
}) })
It("finds and execs the named plugin", func() { It("finds and execs the named plugin", func() {
err := invoke.DelegateDel(pluginName, netConf) err := invoke.DelegateDel(ctx, pluginName, netConf, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName) pluginInvocation, err := debug.ReadDebug(debugFileName)
@ -131,14 +193,22 @@ var _ = Describe("Delegate", func() {
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7")) Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
}) })
Context("if the delegation isn't part of an existing DEL command", func() { Context("if the DEL delegation runs on an existing non-DEL command", func() {
BeforeEach(func() { BeforeEach(func() {
os.Setenv("CNI_COMMAND", "NOPE") os.Setenv("CNI_COMMAND", "NOPE")
}) })
It("aborts and returns a useful error", func() { It("aborts and returns a useful error", func() {
err := invoke.DelegateDel(pluginName, netConf) err := invoke.DelegateDel(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError("CNI_COMMAND is not DEL")) Expect(err).NotTo(HaveOccurred())
pluginInvocation, err := debug.ReadDebug(debugFileName)
Expect(err).NotTo(HaveOccurred())
Expect(pluginInvocation.Command).To(Equal("DEL"))
Expect(pluginInvocation.CmdArgs.IfName).To(Equal("eth7"))
// check the original env
Expect(os.Getenv("CNI_COMMAND")).To(Equal("NOPE"))
}) })
}) })
@ -148,7 +218,51 @@ var _ = Describe("Delegate", func() {
}) })
It("returns a useful error", func() { It("returns a useful error", func() {
err := invoke.DelegateDel(pluginName, netConf) err := invoke.DelegateDel(ctx, pluginName, netConf, nil)
Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
})
})
})
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"))) Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
}) })
}) })

View File

@ -15,57 +15,132 @@
package invoke package invoke
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/create"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
) )
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { // Exec is an interface encapsulates all operations that deal with finding
return defaultPluginExec.WithResult(pluginPath, netconf, args) // and executing a CNI plugin. Tests may provide a fake implementation
// to avoid writing fake plugins to temporary directories during the test.
type Exec interface {
ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error)
FindInPath(plugin string, paths []string) (string, error)
Decode(jsonBytes []byte) (version.PluginInfo, error)
} }
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { // Plugin must return result in same version as specified in netconf; but
return defaultPluginExec.WithoutResult(pluginPath, netconf, args) // 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 GetVersionInfo(pluginPath string) (version.PluginInfo, error) { func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
return defaultPluginExec.GetVersionInfo(pluginPath)
}
var defaultPluginExec = &PluginExec{
RawExec: &RawExec{Stderr: os.Stderr},
VersionDecoder: &version.PluginDecoder{},
}
type PluginExec struct {
RawExec interface {
ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
}
VersionDecoder interface {
Decode(jsonBytes []byte) (version.PluginInfo, error)
}
}
func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) {
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
if err != nil {
return nil, err
}
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{} versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(netconf) 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 (
// "encoding/json"
// "path"
// "strings"
// )
//
// type fakeExec struct {
// version.PluginDecoder
// }
//
// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
// net := &types.NetConf{}
// err := json.Unmarshal(stdinData, net)
// if err != nil {
// return nil, fmt.Errorf("failed to unmarshal configuration: %v", err)
// }
// pluginName := path.Base(pluginPath)
// if pluginName != net.Type {
// return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type)
// }
// for _, e := range environ {
// // Check environment for forced failure request
// parts := strings.Split(e, "=")
// if len(parts) > 0 && parts[0] == "FAIL" {
// return nil, fmt.Errorf("failed to execute plugin %s", pluginName)
// }
// }
// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil
// }
//
// func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
// if len(paths) > 0 {
// return path.Join(paths[0], plugin), nil
// }
// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths)
// }
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil {
exec = defaultExec
}
stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return version.NewResult(confVersion, stdoutBytes) resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes)
if err != nil {
return nil, err
}
return create.Create(resultVersion, fixedBytes)
} }
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {
_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) if exec == nil {
exec = defaultExec
}
_, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
return err return err
} }
@ -73,7 +148,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr
// For recent-enough plugins, it uses the information returned by the VERSION // For recent-enough plugins, it uses the information returned by the VERSION
// command. For older plugins which do not recognize that command, it reports // command. For older plugins which do not recognize that command, it reports
// version 0.1.0 // version 0.1.0
func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) { func GetVersionInfo(ctx context.Context, pluginPath string, exec Exec) (version.PluginInfo, error) {
if exec == nil {
exec = defaultExec
}
args := &Args{ args := &Args{
Command: "VERSION", Command: "VERSION",
@ -83,7 +161,7 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
Path: "dummy", Path: "dummy",
} }
stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current())) stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv()) stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, stdin, args.AsEnv())
if err != nil { if err != nil {
if err.Error() == "unknown CNI_COMMAND: VERSION" { if err.Error() == "unknown CNI_COMMAND: VERSION" {
return version.PluginSupports("0.1.0"), nil return version.PluginSupports("0.1.0"), nil
@ -91,5 +169,19 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
return nil, err return nil, err
} }
return e.VersionDecoder.Decode(stdoutBytes) return exec.Decode(stdoutBytes)
}
// DefaultExec is an object that implements the Exec interface which looks
// for and executes plugins from disk.
type DefaultExec struct {
*RawExec
version.PluginDecoder
}
// DefaultExec implements the Exec interface
var _ Exec = &DefaultExec{}
var defaultExec = &DefaultExec{
RawExec: &RawExec{Stderr: os.Stderr},
} }

View File

@ -15,37 +15,42 @@
package invoke_test package invoke_test
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/invoke/fakes" "github.com/containernetworking/cni/pkg/invoke/fakes"
"github.com/containernetworking/cni/pkg/types/current" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("Executing a plugin, unit tests", func() { var _ = Describe("Executing a plugin, unit tests", func() {
var ( var (
pluginExec *invoke.PluginExec pluginExec invoke.Exec
rawExec *fakes.RawExec rawExec *fakes.RawExec
versionDecoder *fakes.VersionDecoder versionDecoder *fakes.VersionDecoder
pluginPath string pluginPath string
netconf []byte netconf []byte
cniargs *fakes.CNIArgs cniargs *fakes.CNIArgs
ctx context.Context
) )
BeforeEach(func() { BeforeEach(func() {
rawExec = &fakes.RawExec{} 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 = &fakes.VersionDecoder{}
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0") versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
pluginExec = &invoke.PluginExec{ pluginExec = &struct {
*fakes.RawExec
*fakes.VersionDecoder
}{
RawExec: rawExec, RawExec: rawExec,
VersionDecoder: versionDecoder, VersionDecoder: versionDecoder,
} }
@ -53,21 +58,23 @@ var _ = Describe("Executing a plugin, unit tests", func() {
netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`) netconf = []byte(`{ "some": "stdin", "cniVersion": "0.3.1" }`)
cniargs = &fakes.CNIArgs{} cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"} cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
ctx = context.TODO()
}) })
Describe("returning a result", func() { Describe("returning a result", func() {
It("unmarshals the result bytes into the Result type", func() { It("unmarshals the result bytes into the Result type", func() {
r, err := pluginExec.WithResult(pluginPath, netconf, cniargs) r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r) result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(len(result.IPs)).To(Equal(1)) Expect(result.IPs).To(HaveLen(1))
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4")) Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
}) })
It("passes its arguments through to the rawExec", func() { It("passes its arguments through to the rawExec", func() {
pluginExec.WithResult(pluginPath, netconf, cniargs) _, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
@ -78,15 +85,56 @@ var _ = Describe("Executing a plugin, unit tests", func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana") rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
}) })
It("returns the error", func() { It("returns the error", func() {
_, err := pluginExec.WithResult(pluginPath, netconf, cniargs) _, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).To(MatchError("banana")) Expect(err).To(MatchError("banana"))
}) })
}) })
It("returns an error using the default exec interface", func() {
// pluginPath should not exist on-disk so we expect an error.
// This test simply tests that the default exec handler
// is run when the exec interface is nil.
_, 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() { Describe("without returning a result", func() {
It("passes its arguments through to the rawExec", func() { It("passes its arguments through to the rawExec", func() {
pluginExec.WithoutResult(pluginPath, netconf, cniargs) err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf)) Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"})) Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
@ -97,10 +145,18 @@ var _ = Describe("Executing a plugin, unit tests", func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana") rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
}) })
It("returns the error", func() { It("returns the error", func() {
err := pluginExec.WithoutResult(pluginPath, netconf, cniargs) err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, pluginExec)
Expect(err).To(MatchError("banana")) Expect(err).To(MatchError("banana"))
}) })
}) })
It("returns an error using the default exec interface", func() {
// pluginPath should not exist on-disk so we expect an error.
// This test simply tests that the default exec handler
// is run when the exec interface is nil.
err := invoke.ExecPluginWithoutResult(ctx, pluginPath, netconf, cniargs, nil)
Expect(err).To(HaveOccurred())
})
}) })
Describe("discovering the plugin version", func() { Describe("discovering the plugin version", func() {
@ -109,7 +165,8 @@ var _ = Describe("Executing a plugin, unit tests", func() {
}) })
It("execs the plugin with the command VERSION", func() { It("execs the plugin with the command VERSION", func() {
pluginExec.GetVersionInfo(pluginPath) _, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred())
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION")) Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()}) expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()})
@ -117,7 +174,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
}) })
It("decodes and returns the version info", func() { It("decodes and returns the version info", func() {
versionInfo, err := pluginExec.GetVersionInfo(pluginPath) versionInfo, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(versionInfo.SupportedVersions()).To(Equal([]string{"0.42.0"})) Expect(versionInfo.SupportedVersions()).To(Equal([]string{"0.42.0"}))
Expect(versionDecoder.DecodeCall.Received.JSONBytes).To(MatchJSON(`{ "some": "version-info" }`)) Expect(versionDecoder.DecodeCall.Received.JSONBytes).To(MatchJSON(`{ "some": "version-info" }`))
@ -128,7 +185,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
rawExec.ExecPluginCall.Returns.Error = errors.New("banana") rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
}) })
It("returns the error", func() { It("returns the error", func() {
_, err := pluginExec.GetVersionInfo(pluginPath) _, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).To(MatchError("banana")) Expect(err).To(MatchError("banana"))
}) })
}) })
@ -139,13 +196,14 @@ var _ = Describe("Executing a plugin, unit tests", func() {
}) })
It("interprets the error as a 0.1.0 version", func() { It("interprets the error as a 0.1.0 version", func() {
versionInfo, err := pluginExec.GetVersionInfo(pluginPath) versionInfo, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(versionInfo.SupportedVersions()).To(ConsistOf("0.1.0")) Expect(versionInfo.SupportedVersions()).To(ConsistOf("0.1.0"))
}) })
It("sets dummy values for env vars required by very old plugins", func() { It("sets dummy values for env vars required by very old plugins", func() {
pluginExec.GetVersionInfo(pluginPath) _, err := invoke.GetVersionInfo(ctx, pluginPath, pluginExec)
Expect(err).NotTo(HaveOccurred())
env := rawExec.ExecPluginCall.Received.Environ env := rawExec.ExecPluginCall.Received.Environ
Expect(env).To(ContainElement("CNI_NETNS=dummy")) Expect(env).To(ContainElement("CNI_NETNS=dummy"))

View File

@ -14,6 +14,8 @@
package fakes package fakes
import "context"
type RawExec struct { type RawExec struct {
ExecPluginCall struct { ExecPluginCall struct {
Received struct { Received struct {
@ -26,11 +28,27 @@ type RawExec struct {
Error error Error error
} }
} }
FindInPathCall struct {
Received struct {
Plugin string
Paths []string
}
Returns struct {
Path string
Error error
}
}
} }
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
e.ExecPluginCall.Received.PluginPath = pluginPath e.ExecPluginCall.Received.PluginPath = pluginPath
e.ExecPluginCall.Received.StdinData = stdinData e.ExecPluginCall.Received.StdinData = stdinData
e.ExecPluginCall.Received.Environ = environ e.ExecPluginCall.Received.Environ = environ
return e.ExecPluginCall.Returns.ResultBytes, e.ExecPluginCall.Returns.Error return e.ExecPluginCall.Returns.ResultBytes, e.ExecPluginCall.Returns.Error
} }
func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
e.FindInPathCall.Received.Plugin = plugin
e.FindInPathCall.Received.Paths = paths
return e.FindInPathCall.Returns.Path, e.FindInPathCall.Returns.Error
}

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// FindInPath returns the full path of the plugin by searching in the provided path // 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") 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 { if len(paths) == 0 {
return "", fmt.Errorf("no paths provided") return "", fmt.Errorf("no paths provided")
} }

View File

@ -16,14 +16,14 @@ package invoke_test
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/containernetworking/cni/pkg/invoke" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
) )
var _ = Describe("FindInPath", func() { var _ = Describe("FindInPath", func() {
@ -37,17 +37,17 @@ var _ = Describe("FindInPath", func() {
) )
BeforeEach(func() { BeforeEach(func() {
tempDir, err := ioutil.TempDir("", "cni-find") tempDir, err := os.MkdirTemp("", "cni-find")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin") plugin, err := os.CreateTemp(tempDir, "a-cni-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0] plugin2Name := "a-plugin-with-extension" + invoke.ExecutableFileExtensions[0]
plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name)) plugin2, err := os.Create(filepath.Join(tempDir, plugin2Name))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
anotherTempDir, err = ioutil.TempDir("", "nothing-here") anotherTempDir, err = os.MkdirTemp("", "nothing-here")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
multiplePaths = []string{anotherTempDir, tempDir} multiplePaths = []string{anotherTempDir, tempDir}
@ -99,5 +99,13 @@ var _ = Describe("FindInPath", func() {
Expect(err).To(MatchError(fmt.Sprintf("failed to find plugin %q in path %s", pluginName, pathsWithNothing))) 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

@ -15,17 +15,17 @@
package invoke_test package invoke_test
import ( import (
"io/ioutil" "context"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/pkg/version/testhelpers" "github.com/containernetworking/cni/pkg/version/testhelpers"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
) )
var _ = Describe("GetVersion, integration tests", func() { var _ = Describe("GetVersion, integration tests", func() {
@ -35,9 +35,12 @@ var _ = Describe("GetVersion, integration tests", func() {
) )
BeforeEach(func() { BeforeEach(func() {
pluginDir, err := ioutil.TempDir("", "plugins") pluginDir, err := os.MkdirTemp("", "plugins")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pluginPath = filepath.Join(pluginDir, "test-plugin") pluginPath = filepath.Join(pluginDir, "test-plugin")
if runtime.GOOS == "windows" {
pluginPath += ".exe"
}
}) })
AfterEach(func() { AfterEach(func() {
@ -47,7 +50,7 @@ var _ = Describe("GetVersion, integration tests", func() {
DescribeTable("correctly reporting plugin versions", DescribeTable("correctly reporting plugin versions",
func(gitRef string, pluginSource string, expectedVersions version.PluginInfo) { func(gitRef string, pluginSource string, expectedVersions version.PluginInfo) {
Expect(testhelpers.BuildAt([]byte(pluginSource), gitRef, pluginPath)).To(Succeed()) Expect(testhelpers.BuildAt([]byte(pluginSource), gitRef, pluginPath)).To(Succeed())
versionInfo, err := invoke.GetVersionInfo(pluginPath) versionInfo, err := invoke.GetVersionInfo(context.TODO(), pluginPath, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(versionInfo.SupportedVersions()).To(ConsistOf(expectedVersions.SupportedVersions())) Expect(versionInfo.SupportedVersions()).To(ConsistOf(expectedVersions.SupportedVersions()))
@ -68,15 +71,36 @@ var _ = Describe("GetVersion, integration tests", func() {
version.PluginSupports("0.2.0", "0.999.0"), version.PluginSupports("0.2.0", "0.999.0"),
), ),
Entry("historical: before CHECK was introduced",
git_ref_v031, plugin_source_v020_custom_versions,
version.PluginSupports("0.2.0", "0.999.0"),
),
// this entry tracks the current behavior. Before you change it, ensure // this entry tracks the current behavior. Before you change it, ensure
// that its previous behavior is captured in the most recent "historical" entry // that its previous behavior is captured in the most recent "historical" entry
Entry("current", Entry("current",
"HEAD", plugin_source_v020_custom_versions, "HEAD", plugin_source_v040_check,
version.PluginSupports("0.2.0", "0.999.0"), version.PluginSupports("0.2.0", "0.4.0", "0.999.0"),
), ),
) )
}) })
// A 0.4.0 plugin that supports CHECK
const plugin_source_v040_check = `package main
import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/version"
"fmt"
)
func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
func main() { skel.PluginMain(c, c, c, version.PluginSupports("0.2.0", "0.4.0", "0.999.0"), "") }
`
const git_ref_v031 = "909fe7d"
// a 0.2.0 plugin that can report its own versions // a 0.2.0 plugin that can report its own versions
const plugin_source_v020_custom_versions = `package main const plugin_source_v020_custom_versions = `package main
@ -103,5 +127,7 @@ func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil }
func main() { skel.PluginMain(c, c) } func main() { skel.PluginMain(c, c) }
` `
const git_ref_v010 = "2c482f4" const (
const git_ref_v020_no_custom_versions = "349d66d" git_ref_v010 = "2c482f4"
git_ref_v020_no_custom_versions = "349d66d"
)

View File

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

View File

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

View File

@ -16,10 +16,13 @@ package invoke
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os/exec" "os/exec"
"strings"
"time"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
) )
@ -28,32 +31,58 @@ type RawExec struct {
Stderr io.Writer Stderr io.Writer
} }
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
c := exec.CommandContext(ctx, pluginPath)
c.Env = environ
c.Stdin = bytes.NewBuffer(stdinData)
c.Stdout = stdout
c.Stderr = stderr
c := exec.Cmd{ // Retry the command on "text file busy" errors
Env: environ, for i := 0; i <= 5; i++ {
Path: pluginPath, err := c.Run()
Args: []string{pluginPath},
Stdin: bytes.NewBuffer(stdinData), // Command succeeded
Stdout: stdout, if err == nil {
Stderr: e.Stderr, break
} }
if err := c.Run(); err != nil {
return nil, pluginErr(err, stdout.Bytes()) // If the plugin is currently about to be written, then we wait a
// second and try it again
if strings.Contains(err.Error(), "text file busy") {
time.Sleep(time.Second)
continue
}
// All other errors except than the busy text file
return nil, e.pluginErr(err, stdout.Bytes(), stderr.Bytes())
} }
// Copy stderr to caller's buffer in case plugin printed to both
// stdout and stderr for some reason. Ignore failures as stderr is
// only informational.
if e.Stderr != nil && stderr.Len() > 0 {
_, _ = stderr.WriteTo(e.Stderr)
}
return stdout.Bytes(), nil return stdout.Bytes(), nil
} }
func pluginErr(err error, output []byte) error { func (e *RawExec) pluginErr(err error, stdout, stderr []byte) error {
if _, ok := err.(*exec.ExitError); ok { emsg := types.Error{}
emsg := types.Error{} if len(stdout) == 0 {
if perr := json.Unmarshal(output, &emsg); perr != nil { if len(stderr) == 0 {
emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr) emsg.Msg = fmt.Sprintf("netplugin failed with no error message: %v", err)
} else {
emsg.Msg = fmt.Sprintf("netplugin failed: %q", string(stderr))
} }
return &emsg } else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(stdout), perr)
} }
return &emsg
return err }
func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
return FindInPath(plugin, paths)
} }

View File

@ -16,15 +16,14 @@ package invoke_test
import ( import (
"bytes" "bytes"
"io/ioutil" "context"
"os" "os"
"github.com/containernetworking/cni/pkg/invoke" . "github.com/onsi/ginkgo/v2"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
) )
var _ = Describe("RawExec", func() { var _ = Describe("RawExec", func() {
@ -34,12 +33,13 @@ var _ = Describe("RawExec", func() {
environ []string environ []string
stdin []byte stdin []byte
execer *invoke.RawExec execer *invoke.RawExec
ctx context.Context
) )
const reportResult = `{ "some": "result" }` const reportResult = `{ "some": "result" }`
BeforeEach(func() { BeforeEach(func() {
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed()) Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name() debugFileName = debugFile.Name()
@ -58,8 +58,9 @@ var _ = Describe("RawExec", func() {
"CNI_PATH=/some/bin/path", "CNI_PATH=/some/bin/path",
"CNI_IFNAME=some-eth0", "CNI_IFNAME=some-eth0",
} }
stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.3.1"}`) stdin = []byte(`{"name": "raw-exec-test", "some":"stdin-json", "cniVersion": "0.3.1"}`)
execer = &invoke.RawExec{} execer = &invoke.RawExec{}
ctx = context.TODO()
}) })
AfterEach(func() { AfterEach(func() {
@ -67,7 +68,7 @@ var _ = Describe("RawExec", func() {
}) })
It("runs the plugin with the given stdin and environment", func() { It("runs the plugin with the given stdin and environment", func() {
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ) _, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
debug, err := noop_debug.ReadDebug(debugFileName) debug, err := noop_debug.ReadDebug(debugFileName)
@ -78,7 +79,7 @@ var _ = Describe("RawExec", func() {
}) })
It("returns the resulting stdout as bytes", func() { It("returns the resulting stdout as bytes", func() {
resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ) resultBytes, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultBytes).To(BeEquivalentTo(reportResult)) Expect(resultBytes).To(BeEquivalentTo(reportResult))
@ -93,7 +94,7 @@ var _ = Describe("RawExec", func() {
}) })
It("forwards any stderr bytes to the Stderr writer", func() { It("forwards any stderr bytes to the Stderr writer", func() {
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ) _, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(stderrBuffer.String()).To(Equal("some stderr message")) Expect(stderrBuffer.String()).To(Equal("some stderr message"))
@ -102,20 +103,45 @@ var _ = Describe("RawExec", func() {
Context("when the plugin errors", func() { Context("when the plugin errors", func() {
BeforeEach(func() { BeforeEach(func() {
debug.ReportError = "banana" debug.ReportResult = ""
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
}) })
It("wraps and returns the error", func() { Context("and writes valid error JSON to stdout", func() {
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ) It("wraps and returns the error", func() {
debug.ReportError = "banana"
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
_, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("banana"))
})
})
Context("and writes to stderr", func() {
It("returns an error message with stderr output", func() {
debug.ExitWithCode = 1
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
_, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(`netplugin failed: "some stderr message"`))
})
})
})
Context("when the plugin errors with no output on stdout or stderr", func() {
It("returns the exec error message", func() {
debug.ExitWithCode = 1
debug.ReportResult = ""
debug.ReportStderr = ""
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
_, err := execer.ExecPlugin(ctx, pathToPlugin, stdin, environ)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err).To(MatchError("banana")) Expect(err).To(MatchError("netplugin failed with no error message: exit status 1"))
}) })
}) })
Context("when the system is unable to execute the plugin", func() { Context("when the system is unable to execute the plugin", func() {
It("returns the error", func() { It("returns the error", func() {
_, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ) _, err := execer.ExecPlugin(ctx, "/tmp/some/invalid/plugin/path", stdin, environ)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path"))) Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
}) })

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

@ -17,25 +17,31 @@
package skel package skel
import ( import (
"bytes"
"encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"strings"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
) )
// CmdArgs captures all the arguments passed in to the plugin // CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin // via both env vars and stdin
type CmdArgs struct { type CmdArgs struct {
ContainerID string ContainerID string
Netns string Netns string
IfName string IfName string
Args string Args string
Path string Path string
StdinData []byte NetnsOverride string
StdinData []byte
} }
type dispatcher struct { type dispatcher struct {
@ -50,147 +56,297 @@ type dispatcher struct {
type reqForCmdEntry map[string]bool type reqForCmdEntry map[string]bool
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
var cmd, contID, netns, ifName, args, path string var cmd, contID, netns, ifName, args, path, netnsOverride string
vars := []struct { vars := []struct {
name string name string
val *string val *string
reqForCmd reqForCmdEntry reqForCmd reqForCmdEntry
validateFn func(string) *types.Error
}{ }{
{ {
"CNI_COMMAND", "CNI_COMMAND",
&cmd, &cmd,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"DEL": true, "CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
}, },
nil,
}, },
{ {
"CNI_CONTAINERID", "CNI_CONTAINERID",
&contID, &contID,
reqForCmdEntry{ reqForCmdEntry{
"ADD": false, "ADD": true,
"DEL": false, "CHECK": true,
"DEL": true,
}, },
utils.ValidateContainerID,
}, },
{ {
"CNI_NETNS", "CNI_NETNS",
&netns, &netns,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"DEL": false, "CHECK": true,
"DEL": false,
}, },
nil,
}, },
{ {
"CNI_IFNAME", "CNI_IFNAME",
&ifName, &ifName,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"DEL": true, "CHECK": true,
"DEL": true,
}, },
utils.ValidateInterfaceName,
}, },
{ {
"CNI_ARGS", "CNI_ARGS",
&args, &args,
reqForCmdEntry{ reqForCmdEntry{
"ADD": false, "ADD": false,
"DEL": false, "CHECK": false,
"DEL": false,
}, },
nil,
}, },
{ {
"CNI_PATH", "CNI_PATH",
&path, &path,
reqForCmdEntry{ reqForCmdEntry{
"ADD": true, "ADD": true,
"DEL": true, "CHECK": true,
"DEL": true,
"GC": true,
"STATUS": true,
}, },
nil,
},
{
"CNI_NETNS_OVERRIDE",
&netnsOverride,
reqForCmdEntry{
"ADD": false,
"CHECK": false,
"DEL": false,
},
nil,
}, },
} }
argsMissing := false argsMissing := make([]string, 0)
for _, v := range vars { for _, v := range vars {
*v.val = t.Getenv(v.name) *v.val = t.Getenv(v.name)
if *v.val == "" { if *v.val == "" {
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" { if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
fmt.Fprintf(t.Stderr, "%v env variable missing\n", v.name) argsMissing = append(argsMissing, v.name)
argsMissing = true }
} else if v.reqForCmd[cmd] && v.validateFn != nil {
if err := v.validateFn(*v.val); err != nil {
return "", nil, err
} }
} }
} }
if argsMissing { if len(argsMissing) > 0 {
return "", nil, fmt.Errorf("required env variables missing") joined := strings.Join(argsMissing, ",")
return "", nil, types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("required env variables [%s] missing", joined), "")
} }
stdinData, err := ioutil.ReadAll(t.Stdin) if cmd == "VERSION" {
t.Stdin = bytes.NewReader(nil)
}
stdinData, err := io.ReadAll(t.Stdin)
if err != nil { if err != nil {
return "", nil, fmt.Errorf("error reading from stdin: %v", err) return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
}
if cmd != "VERSION" {
if err := validateConfig(stdinData); err != nil {
return "", nil, err
}
} }
cmdArgs := &CmdArgs{ cmdArgs := &CmdArgs{
ContainerID: contID, ContainerID: contID,
Netns: netns, Netns: netns,
IfName: ifName, IfName: ifName,
Args: args, Args: args,
Path: path, Path: path,
StdinData: stdinData, StdinData: stdinData,
NetnsOverride: netnsOverride,
} }
return cmd, cmdArgs, nil return cmd, cmdArgs, nil
} }
func createTypedError(f string, args ...interface{}) *types.Error { func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) *types.Error {
return &types.Error{
Code: 100,
Msg: fmt.Sprintf(f, args...),
}
}
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error {
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil { if err != nil {
return err return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} }
verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo) verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)
if verErr != nil { if verErr != nil {
return &types.Error{ return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
Code: types.ErrIncompatibleCNIVersion,
Msg: "incompatible CNI versions",
Details: verErr.Details(),
}
}
return toCall(cmdArgs)
}
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
return createTypedError(err.Error())
} }
switch cmd { if toCall == nil {
case "ADD": return nil
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
case "DEL":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
case "VERSION":
err = versionInfo.Encode(t.Stdout)
default:
return createTypedError("unknown CNI_COMMAND: %v", cmd)
} }
if err != nil { if err = toCall(cmdArgs); err != nil {
if e, ok := err.(*types.Error); ok { var e *types.Error
if errors.As(err, &e) {
// don't wrap Error in Error // don't wrap Error in Error
return e return e
} }
return createTypedError(err.Error()) return types.NewError(types.ErrInternal, err.Error(), "")
}
return nil
}
func validateConfig(jsonBytes []byte) *types.Error {
var conf struct {
Name string `json:"name"`
}
if err := json.Unmarshal(jsonBytes, &conf); err != nil {
return types.NewError(types.ErrDecodingFailure, fmt.Sprintf("error unmarshall network config: %v", err), "")
}
if conf.Name == "" {
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name", "")
}
if err := utils.ValidateNetworkName(conf.Name); err != nil {
return err
} }
return nil return nil
} }
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
}
switch cmd {
case "ADD":
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 {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "0.4.0"); err != nil {
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow CHECK", "")
}
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.Check); err != nil {
return err
}
return nil
}
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
case "DEL":
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(), "")
}
default:
return types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("unknown CNI_COMMAND: %v", cmd), "")
}
return err
}
// PluginMainWithError is the core "main" for a plugin. It accepts // PluginMainWithError is the core "main" for a plugin. It accepts
// callback functions for add and del CNI commands and returns an error. // callback functions for add, check, and del CNI commands and returns an error.
// //
// The caller must also specify what CNI spec versions the plugin supports. // The caller must also specify what CNI spec versions the plugin supports.
// //
@ -201,25 +357,80 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionIn
// //
// To let this package automatically handle errors and call os.Exit(1) for you, // To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMain() instead. // use PluginMain() instead.
func PluginMainWithError(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { //
// 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{ return (&dispatcher{
Getenv: os.Getenv, Getenv: os.Getenv,
Stdin: os.Stdin, Stdin: os.Stdin,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, Stderr: os.Stderr,
}).pluginMain(cmdAdd, cmdDel, versionInfo) }).pluginMain(funcs, versionInfo, about)
} }
// PluginMain is the core "main" for a plugin which includes automatic error handling. // 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 must also specify what CNI spec versions the plugin supports.
// //
// When an error occurs in either cmdAdd or cmdDel, PluginMain will print the error // 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). // as JSON to stdout and call os.Exit(1).
// //
// To have more control over error handling, use PluginMainWithError() instead. // To have more control over error handling, use PluginMainFuncsWithError() instead.
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) { func PluginMainFuncs(funcs CNIFuncs, versionInfo version.PluginInfo, about string) {
if e := PluginMainWithError(cmdAdd, cmdDel, versionInfo); e != nil { 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.
//
// 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 either cmdAdd, cmdCheck, or cmdDel, PluginMain will print the error
// 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 { if err := e.Print(); err != nil {
log.Print("Error writing error JSON to stdout: ", err) log.Print("Error writing error JSON to stdout: ", err)
} }

View File

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

View File

@ -17,14 +17,14 @@ package skel
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
) )
type fakeCmd struct { type fakeCmd struct {
@ -45,13 +45,14 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
var _ = Describe("dispatching to the correct callback", func() { var _ = Describe("dispatching to the correct callback", func() {
var ( var (
environment map[string]string environment map[string]string
stdinData string stdinData string
stdout, stderr *bytes.Buffer stdout, stderr *bytes.Buffer
cmdAdd, cmdDel *fakeCmd cmdAdd, cmdCheck, cmdDel, cmdGC *fakeCmd
dispatch *dispatcher dispatch *dispatcher
expectedCmdArgs *CmdArgs expectedCmdArgs *CmdArgs
versionInfo version.PluginInfo versionInfo version.PluginInfo
funcs CNIFuncs
) )
BeforeEach(func() { BeforeEach(func() {
@ -64,10 +65,10 @@ var _ = Describe("dispatching to the correct callback", func() {
"CNI_PATH": "/some/cni/path", "CNI_PATH": "/some/cni/path",
} }
stdinData = `{ "some": "config", "cniVersion": "9.8.7" }` stdinData = `{ "name":"skel-test", "some": "config", "cniVersion": "9.8.7" }`
stdout = &bytes.Buffer{} stdout = &bytes.Buffer{}
stderr = &bytes.Buffer{} stderr = &bytes.Buffer{}
versionInfo = version.PluginSupports("9.8.7") versionInfo = version.PluginSupports("9.8.7", "10.0.0")
dispatch = &dispatcher{ dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] }, Getenv: func(key string) string { return environment[key] },
Stdin: strings.NewReader(stdinData), Stdin: strings.NewReader(stdinData),
@ -75,7 +76,16 @@ var _ = Describe("dispatching to the correct callback", func() {
Stderr: stderr, Stderr: stderr,
} }
cmdAdd = &fakeCmd{} cmdAdd = &fakeCmd{}
cmdCheck = &fakeCmd{}
cmdDel = &fakeCmd{} cmdDel = &fakeCmd{}
cmdGC = &fakeCmd{}
funcs = CNIFuncs{
Add: cmdAdd.Func,
Del: cmdDel.Func,
Check: cmdCheck.Func,
GC: cmdGC.Func,
}
expectedCmdArgs = &CmdArgs{ expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id", ContainerID: "some-container-id",
Netns: "/some/netns/path", Netns: "/some/netns/path",
@ -86,41 +96,136 @@ var _ = Describe("dispatching to the correct callback", func() {
} }
}) })
var envVarChecker = func(envVar string, isRequired bool) { envVarChecker := func(envVar string, isRequired bool) {
delete(environment, envVar) delete(environment, envVar)
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
if isRequired { if isRequired {
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: 100, Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables missing", Msg: "required env variables [" + envVar + "] missing",
})) }))
Expect(stderr.String()).To(ContainSubstring(envVar + " env variable missing\n"))
} else { } else {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
} }
Context("when the CNI_COMMAND is ADD", func() { Context("when the CNI_COMMAND is ADD", func() {
expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "eth0",
Args: "some;extra;args",
Path: "/some/cni/path",
StdinData: []byte(stdinData),
}
It("extracts env vars and stdin data and calls cmdAdd", func() { It("extracts env vars and stdin data and calls cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1)) Expect(cmdAdd.CallCount).To(Equal(1))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs)) Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
}) })
It("does not call cmdDel", func() { It("returns an error when containerID has invalid characters", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) environment["CNI_CONTAINERID"] = "some-%%container-id"
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "invalid characters in containerID",
Details: "some-%%container-id",
}))
})
Context("return errors when interface name is invalid", func() {
It("interface name is too long", func() {
environment["CNI_IFNAME"] = "1234567890123456"
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name is too long",
Details: "interface name should be less than 16 characters",
}))
})
It("interface name is .", func() {
environment["CNI_IFNAME"] = "."
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name is . or ..",
Details: "",
}))
})
It("interface name is ..", func() {
environment["CNI_IFNAME"] = ".."
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name is . or ..",
Details: "",
}))
})
It("interface name contains invalid characters /", func() {
environment["CNI_IFNAME"] = "test/test"
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name contains / or : or whitespace characters",
Details: "",
}))
})
It("interface name contains invalid characters :", func() {
environment["CNI_IFNAME"] = "test:test"
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name contains / or : or whitespace characters",
Details: "",
}))
})
It("interface name contains invalid characters whitespace", func() {
environment["CNI_IFNAME"] = "test test"
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "interface name contains / or : or whitespace characters",
Details: "",
}))
})
})
It("does not call cmdCheck or cmdDel", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
DescribeTable("required / optional env vars", envVarChecker, DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true), Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINERID", false), Entry("container id", "CNI_CONTAINERID", true),
Entry("net ns", "CNI_NETNS", true), Entry("net ns", "CNI_NETNS", true),
Entry("if name", "CNI_IFNAME", true), Entry("if name", "CNI_IFNAME", true),
Entry("args", "CNI_ARGS", false), Entry("args", "CNI_ARGS", false),
@ -135,28 +240,29 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("reports that all of them are missing, not just the first", func() { It("reports that all of them are missing, not just the first", func() {
Expect(dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)).NotTo(Succeed()) err := dispatch.pluginMain(funcs, versionInfo, "")
log := stderr.String() Expect(err).To(HaveOccurred())
Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n"))
Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n"))
Expect(log).To(ContainSubstring("CNI_PATH env variable missing\n"))
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing",
}))
}) })
}) })
Context("when the stdin data is missing the required cniVersion config", func() { Context("when the stdin data is missing the required cniVersion config", func() {
BeforeEach(func() { BeforeEach(func() {
dispatch.Stdin = strings.NewReader(`{ "some": "config" }`) dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "some": "config" }`)
}) })
Context("when the plugin supports version 0.1.0", func() { Context("when the plugin supports version 0.1.0", func() {
BeforeEach(func() { BeforeEach(func() {
versionInfo = version.PluginSupports("0.1.0") versionInfo = version.PluginSupports("0.1.0")
expectedCmdArgs.StdinData = []byte(`{ "some": "config" }`) expectedCmdArgs.StdinData = []byte(`{ "name": "skel-test", "some": "config" }`)
}) })
It("infers the config is 0.1.0 and calls the cmdAdd callback", func() { It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1)) Expect(cmdAdd.CallCount).To(Equal(1))
@ -170,28 +276,261 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("immediately returns a useful error", func() { It("immediately returns a useful error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("incompatible CNI versions")) Expect(err.Msg).To(Equal("incompatible CNI versions"))
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`)) Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
}) })
It("does not call either callback", func() { It("does not call either callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
}) })
}) })
}) })
Context("when the CNI_COMMAND is CHECK", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "CHECK"
})
It("extracts env vars and stdin data and calls cmdCheck", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(1))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(cmdCheck.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", true),
Entry("net ns", "CNI_NETNS", true),
Entry("if name", "CNI_IFNAME", true),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", true),
)
Context("when multiple required env vars are missing", func() {
BeforeEach(func() {
delete(environment, "CNI_NETNS")
delete(environment, "CNI_IFNAME")
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_NETNS,CNI_IFNAME,CNI_PATH] missing",
}))
})
})
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(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))
Expect(cmdDel.CallCount).To(Equal(0))
})
})
Context("when plugin does not support 0.4.0", 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(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))
Expect(cmdDel.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("0.1.0", "0.2.0", "0.3.0")
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))
})
})
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("0.1.0", "0.2.0", "0.3.0", "0.4.0")
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))
})
})
Context("when the plugin has a bad version", 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(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))
})
})
})
Context("when the CNI_COMMAND is GC", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "GC"
delete(environment, "CNI_NETNS")
delete(environment, "CNI_IFNAME")
delete(environment, "CNI_CONTAINERID")
delete(environment, "CNI_ARGS")
expectedCmdArgs = &CmdArgs{
Path: "/some/cni/path",
StdinData: []byte(stdinData),
}
dispatch = &dispatcher{
Getenv: func(key string) string {
return environment[key]
},
Stdin: strings.NewReader(stdinData),
Stdout: stdout,
Stderr: stderr,
}
})
It("extracts env vars and stdin data and calls cmdGC", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(cmdGC.CallCount).To(Equal(1))
Expect(cmdGC.Received.CmdArgs).To(Equal(expectedCmdArgs))
})
It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINERID", false),
Entry("net ns", "CNI_NETNS", false),
Entry("if name", "CNI_IFNAME", false),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", true),
)
Context("when multiple required env vars are missing", func() {
BeforeEach(func() {
delete(environment, "CNI_PATH")
})
It("reports that all of them are missing, not just the first", func() {
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_PATH] missing",
}))
})
})
Context("when cniVersion is less than 1.1.0", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "0.3.0", "some": "config" }`)
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("config version does not allow GC"))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when plugin does not support 1.1.0", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "name": "skel-test", "cniVersion": "1.1.0", "some": "config" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes
Expect(err.Msg).To(Equal("plugin version does not allow GC"))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the config has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("1.1.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the config has a bad name", func() {
It("immediately returns invalid network config", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "te%%st" }`)
versionInfo = version.PluginSupports("1.1.0")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrInvalidNetworkConfig))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
Context("when the plugin has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "1.1.0", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err.Code).To(Equal(types.ErrDecodingFailure))
Expect(cmdGC.CallCount).To(Equal(0))
})
})
})
Context("when the CNI_COMMAND is DEL", func() { Context("when the CNI_COMMAND is DEL", func() {
BeforeEach(func() { BeforeEach(func() {
environment["CNI_COMMAND"] = "DEL" environment["CNI_COMMAND"] = "DEL"
}) })
It("calls cmdDel with the env vars and stdin data", func() { It("calls cmdDel with the env vars and stdin data", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(1)) Expect(cmdDel.CallCount).To(Equal(1))
@ -199,7 +538,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call cmdAdd", func() { It("does not call cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -207,7 +546,7 @@ var _ = Describe("dispatching to the correct callback", func() {
DescribeTable("required / optional env vars", envVarChecker, DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true), Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINERID", false), Entry("container id", "CNI_CONTAINERID", true),
Entry("net ns", "CNI_NETNS", false), Entry("net ns", "CNI_NETNS", false),
Entry("if name", "CNI_IFNAME", true), Entry("if name", "CNI_IFNAME", true),
Entry("args", "CNI_ARGS", false), Entry("args", "CNI_ARGS", false),
@ -221,17 +560,17 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("prints the version to stdout", func() { It("prints the version to stdout", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{ Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"cniVersion": "0.3.1", "cniVersion": "%s",
"supportedVersions": ["9.8.7"] "supportedVersions": ["9.8.7", "10.0.0"]
}`)) }`, version.Current())))
}) })
It("does not call cmdAdd or cmdDel", func() { It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
@ -247,20 +586,18 @@ var _ = Describe("dispatching to the correct callback", func() {
Entry("path", "CNI_PATH", false), Entry("path", "CNI_PATH", false),
) )
Context("when the stdin is empty", func() { It("does not read from Stdin", func() {
BeforeEach(func() { r := &BadReader{}
dispatch.Stdin = strings.NewReader("") dispatch.Stdin = r
})
It("succeeds without error", func() { err := dispatch.pluginMain(funcs, versionInfo, "")
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{ Expect(r.ReadCount).To(Equal(0))
"cniVersion": "0.3.1", Expect(stdout).To(MatchJSON(fmt.Sprintf(`{
"supportedVersions": ["9.8.7"] "cniVersion": "%s",
}`)) "supportedVersions": ["9.8.7", "10.0.0"]
}) }`, version.Current())))
}) })
}) })
@ -270,20 +607,53 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call any cmd callback", func() { It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
It("returns an error", func() { It("returns an error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: 100, Code: types.ErrInvalidEnvironmentVariables,
Msg: "unknown CNI_COMMAND: NOPE", Msg: "unknown CNI_COMMAND: NOPE",
})) }))
}) })
It("prints the about string when the command is blank", func() {
environment["CNI_COMMAND"] = ""
err := dispatch.pluginMain(funcs, versionInfo, "test framework v42")
Expect(err).NotTo(HaveOccurred())
Expect(stderr.String()).To(ContainSubstring("test framework v42"))
})
})
Context("when the CNI_COMMAND is missing", func() {
It("prints the about string to stderr", func() {
environment = map[string]string{}
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\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(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(err).To(Equal(&types.Error{
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_COMMAND] missing",
}))
})
}) })
Context("when stdin cannot be read", func() { Context("when stdin cannot be read", func() {
@ -292,17 +662,17 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("does not call any cmd callback", func() { It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0)) Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0)) Expect(cmdDel.CallCount).To(Equal(0))
}) })
It("wraps and returns the error", func() { It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: 100, Code: types.ErrIOFailure,
Msg: "error reading from stdin: banana", Msg: "error reading from stdin: banana",
})) }))
}) })
@ -318,7 +688,7 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("returns the error as-is", func() { It("returns the error as-is", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: 1234, Code: 1234,
@ -333,10 +703,10 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
It("wraps and returns the error", func() { It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo) err := dispatch.pluginMain(funcs, versionInfo, "")
Expect(err).To(Equal(&types.Error{ Expect(err).To(Equal(&types.Error{
Code: 100, Code: types.ErrInternal,
Msg: "potato", Msg: "potato",
})) }))
}) })
@ -346,10 +716,12 @@ var _ = Describe("dispatching to the correct callback", func() {
// BadReader is an io.Reader which always errors // BadReader is an io.Reader which always errors
type BadReader struct { type BadReader struct {
Error error Error error
ReadCount int
} }
func (r *BadReader) Read(buffer []byte) (int, error) { func (r *BadReader) Read(buffer []byte) (int, error) {
r.ReadCount++
if r.Error != nil { if r.Error != nil {
return 0, r.Error return 0, r.Error
} }

View File

@ -17,29 +17,52 @@ package types020
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
convert "github.com/containernetworking/cni/pkg/types/internal"
) )
const ImplementedSpecVersion string = "0.2.0" 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 // 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) { func NewResult(data []byte) (types.Result, error) {
result := &Result{} result := &Result{}
if err := json.Unmarshal(data, result); err != nil { if err := json.Unmarshal(data, result); err != nil {
return nil, err 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) { func GetResult(r types.Result) (*Result, error) {
// We expect version 0.1.0/0.2.0 results result020, err := convert.Convert(r, ImplementedSpecVersion)
result020, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -50,6 +73,32 @@ func GetResult(r types.Result) (*Result, error) {
return result, nil 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 // Result is what gets returned from the plugin (via stdout) to the caller
type Result struct { type Result struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
@ -59,42 +108,31 @@ type Result struct {
} }
func (r *Result) Version() string { func (r *Result) Version() string {
return ImplementedSpecVersion return r.CNIVersion
} }
func (r *Result) GetAsVersion(version string) (types.Result, error) { func (r *Result) GetAsVersion(version string) (types.Result, error) {
for _, supportedVersion := range SupportedVersions { // If the creator of the result did not set the CNIVersion, assume it
if version == supportedVersion { // should be the highest spec version implemented by this Result
r.CNIVersion = version if r.CNIVersion == "" {
return r, nil 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 { func (r *Result) Print() error {
return r.PrintTo(os.Stdout)
}
func (r *Result) PrintTo(writer io.Writer) error {
data, err := json.MarshalIndent(r, "", " ") data, err := json.MarshalIndent(r, "", " ")
if err != nil { if err != nil {
return err return err
} }
_, err = os.Stdout.Write(data) _, err = writer.Write(data)
return err return err
} }
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
}
if r.IP6 != nil {
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface // IPConfig contains values necessary to configure an interface
type IPConfig struct { type IPConfig struct {
IP net.IPNet IP net.IPNet
@ -102,6 +140,22 @@ type IPConfig struct {
Routes []types.Route 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 // net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type // for our custom IPNet type

View File

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

View File

@ -15,81 +15,64 @@
package types020_test package types020_test
import ( import (
"io/ioutil" "encoding/json"
"fmt"
"net" "net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/create"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() { func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
It("correctly encodes a 0.1.0/0.2.0 Result", func() { ipv4, err := types.ParseCIDR("1.2.3.30/24")
ipv4, err := types.ParseCIDR("1.2.3.30/24") Expect(err).NotTo(HaveOccurred())
Expect(err).NotTo(HaveOccurred()) Expect(ipv4).NotTo(BeNil())
Expect(ipv4).NotTo(BeNil())
routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24") routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil()) Expect(routev4).NotTo(BeNil())
Expect(routegwv4).NotTo(BeNil()) Expect(routegwv4).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64") ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil()) Expect(ipv6).NotTo(BeNil())
routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80") routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil()) Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil()) Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility // Set every field of the struct to ensure source compatibility
res := types020.Result{ res := &types020.Result{
CNIVersion: types020.ImplementedSpecVersion, CNIVersion: resultCNIVersion,
IP4: &types020.IPConfig{ IP4: &types020.IPConfig{
IP: *ipv4, IP: *ipv4,
Gateway: net.ParseIP("1.2.3.1"), Gateway: net.ParseIP("1.2.3.1"),
Routes: []types.Route{ Routes: []types.Route{
{Dst: *routev4, GW: routegwv4}, {Dst: *routev4, GW: routegwv4},
},
}, },
IP6: &types020.IPConfig{ },
IP: *ipv6, IP6: &types020.IPConfig{
Gateway: net.ParseIP("abcd:1234:ffff::1"), IP: *ipv6,
Routes: []types.Route{ Gateway: net.ParseIP("abcd:1234:ffff::1"),
{Dst: *routev6, GW: routegwv6}, Routes: []types.Route{
}, {Dst: *routev6, GW: routegwv6},
}, },
DNS: types.DNS{ },
Nameservers: []string{"1.2.3.4", "1::cafe"}, DNS: types.DNS{
Domain: "acompany.com", Nameservers: []string{"1.2.3.4", "1::cafe"},
Search: []string{"somedomain.com", "otherdomain.net"}, Domain: "acompany.com",
Options: []string{"foo", "bar"}, Search: []string{"somedomain.com", "otherdomain.net"},
}, Options: []string{"foo", "bar"},
} },
}
Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}")) json := fmt.Sprintf(`{
"cniVersion": "%s",
// 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",
"ip4": { "ip4": {
"ip": "1.2.3.30/24", "ip": "1.2.3.30/24",
"gateway": "1.2.3.1", "gateway": "1.2.3.1",
@ -125,6 +108,50 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
"bar" "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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package current_test package types040_test
import ( import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing" "testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
func TestTypesCurrent(t *testing.T) { func TestTypesCurrent(t *testing.T) {

387
pkg/types/040/types_test.go Normal file
View File

@ -0,0 +1,387 @@
// 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_test
import (
"encoding/json"
"io"
"net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040"
)
func testResult() *types040.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 &types040.Result{
CNIVersion: "0.3.1",
Interfaces: []*types040.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
},
IPs: []*types040.IPConfig{
{
Version: "4",
Interface: types040.Int(0),
Address: *ipv4,
Gateway: net.ParseIP("1.2.3.1"),
},
{
Version: "6",
Interface: types040.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("040 types operations", func() {
It("correctly encodes a 0.3.x 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": "0.3.1",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net"
}
],
"ips": [
{
"version": "4",
"interface": 0,
"address": "1.2.3.30/24",
"gateway": "1.2.3.1"
},
{
"version": "6",
"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 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 round-trips a 0.2.0 Result with route gateways", func() {
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
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},
},
},
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"},
},
}
// 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")
Expect(err).NotTo(HaveOccurred())
// Match JSON so we can figure out what's wrong if something fails the test
origJson, err := json.Marshal(res)
Expect(err).NotTo(HaveOccurred())
oldJson, err := json.Marshal(oldRes)
Expect(err).NotTo(HaveOccurred())
Expect(oldJson).To(MatchJSON(origJson))
})
It("correctly round-trips a 0.2.0 Result without route gateways", func() {
ipv4, err := types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
_, routev4, err := net.ParseCIDR("15.5.6.0/24")
Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
_, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).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},
},
},
IP6: &types020.IPConfig{
IP: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"),
Routes: []types.Route{
{Dst: *routev6},
},
},
DNS: types.DNS{
Nameservers: []string{"1.2.3.4", "1::cafe"},
Domain: "acompany.com",
Search: []string{"somedomain.com", "otherdomain.net"},
Options: []string{"foo", "bar"},
},
}
// 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")
Expect(err).NotTo(HaveOccurred())
// Match JSON so we can figure out what's wrong if something fails the test
origJson, err := json.Marshal(res)
Expect(err).NotTo(HaveOccurred())
oldJson, err := json.Marshal(oldRes)
Expect(err).NotTo(HaveOccurred())
Expect(oldJson).To(MatchJSON(origJson))
})
It("correctly marshals and unmarshals interface index 0", func() {
ipc := &types040.IPConfig{
Version: "4",
Interface: types040.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(`{
"version": "4",
"interface": 0,
"address": "10.1.2.3/24"
}`))
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 := &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 := &types040.IPConfig{
Version: "4",
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(`{
"version": "4",
"address": "10.1.2.3/24"
}`))
})
})

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

View File

@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package current_test package types100_test
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io"
"net" "net"
"os" "os"
"github.com/containernetworking/cni/pkg/types" . "github.com/onsi/ginkgo/v2"
"github.com/containernetworking/cni/pkg/types/current"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
) )
func testResult() *current.Result { func testResult() *current.Result {
@ -45,26 +45,26 @@ func testResult() *current.Result {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil()) Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil()) Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility // Set every field of the struct to ensure source compatibility
return &current.Result{ return &current.Result{
CNIVersion: "0.3.1", CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{ Interfaces: []*current.Interface{
{ {
Name: "eth0", Name: "eth0",
Mac: "00:11:22:33:44:55", Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net", Mtu: 1500,
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
}, },
}, },
IPs: []*current.IPConfig{ IPs: []*current.IPConfig{
{ {
Version: "4",
Interface: current.Int(0), Interface: current.Int(0),
Address: *ipv4, Address: *ipv4,
Gateway: net.ParseIP("1.2.3.1"), Gateway: net.ParseIP("1.2.3.1"),
}, },
{ {
Version: "6",
Interface: current.Int(0), Interface: current.Int(0),
Address: *ipv6, Address: *ipv6,
Gateway: net.ParseIP("abcd:1234:ffff::1"), Gateway: net.ParseIP("abcd:1234:ffff::1"),
@ -84,7 +84,7 @@ func testResult() *current.Result {
} }
var _ = Describe("Current types operations", func() { var _ = Describe("Current types operations", func() {
It("correctly encodes a 0.3.x Result", func() { It("correctly encodes a 1.1.0 Result", func() {
res := testResult() res := testResult()
// Redirect stdout to capture JSON result // Redirect stdout to capture JSON result
@ -98,28 +98,29 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{ Expect(string(out)).To(MatchJSON(`{
"cniVersion": "0.3.1", "cniVersion": "` + current.ImplementedSpecVersion + `",
"interfaces": [ "interfaces": [
{ {
"name": "eth0", "name": "eth0",
"mac": "00:11:22:33:44:55", "mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net" "mtu": 1500,
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
} }
], ],
"ips": [ "ips": [
{ {
"version": "4",
"interface": 0, "interface": 0,
"address": "1.2.3.30/24", "address": "1.2.3.30/24",
"gateway": "1.2.3.1" "gateway": "1.2.3.1"
}, },
{ {
"version": "6",
"interface": 0, "interface": 0,
"address": "abcd:1234:ffff::cdde/64", "address": "abcd:1234:ffff::cdde/64",
"gateway": "abcd:1234:ffff::1" "gateway": "abcd:1234:ffff::1"
@ -153,12 +154,26 @@ var _ = Describe("Current types operations", func() {
}`)) }`))
}) })
It("correctly converts a 1.0.0 Result to 1.1.0", func() {
tr, err := testResult().GetAsVersion("1.0.0")
Expect(err).NotTo(HaveOccurred())
trv1, ok := tr.(*current.Result)
Expect(ok).To(BeTrue())
// 1.0.0 and 1.1.0 should be the same except for CNI version
Expect(trv1.CNIVersion).To(Equal("1.0.0"))
// If we convert 1.0.0 back to 1.1.0 it should be identical
trv11, err := trv1.GetAsVersion("1.1.0")
Expect(err).NotTo(HaveOccurred())
Expect(trv11).To(Equal(testResult()))
})
It("correctly encodes a 0.1.0 Result", func() { It("correctly encodes a 0.1.0 Result", func() {
res, err := testResult().GetAsVersion("0.1.0") res, err := testResult().GetAsVersion("0.1.0")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(res.String()).To(Equal("IP4:{IP:{IP:1.2.3.30 Mask:ffffff00} Gateway:1.2.3.1 Routes:[{Dst:{IP:15.5.6.0 Mask:ffffff00} GW:15.5.6.8}]}, IP6:{IP:{IP:abcd:1234:ffff::cdde Mask:ffffffffffffffff0000000000000000} Gateway:abcd:1234:ffff::1 Routes:[{Dst:{IP:1111:dddd:: Mask:ffffffffffffffffffff000000000000} GW:1111:dddd::aaaa}]}, DNS:{Nameservers:[1.2.3.4 1::cafe] Domain:acompany.com Search:[somedomain.com otherdomain.net] Options:[foo bar]}"))
// Redirect stdout to capture JSON result // Redirect stdout to capture JSON result
oldStdout := os.Stdout oldStdout := os.Stdout
r, w, err := os.Pipe() r, w, err := os.Pipe()
@ -170,12 +185,12 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// parse the result // parse the result
out, err := ioutil.ReadAll(r) out, err := io.ReadAll(r)
os.Stdout = oldStdout os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{ Expect(string(out)).To(MatchJSON(`{
"cniVersion": "0.2.0", "cniVersion": "0.1.0",
"ip4": { "ip4": {
"ip": "1.2.3.30/24", "ip": "1.2.3.30/24",
"gateway": "1.2.3.1", "gateway": "1.2.3.1",
@ -214,9 +229,78 @@ var _ = Describe("Current types operations", func() {
}`)) }`))
}) })
It("correctly marshals interface index 0", func() { 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{ ipc := &current.IPConfig{
Version: "4",
Interface: current.Int(0), Interface: current.Int(0),
Address: net.IPNet{ Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"), IP: net.ParseIP("10.1.2.3"),
@ -224,18 +308,28 @@ var _ = Describe("Current types operations", func() {
}, },
} }
json, err := json.Marshal(ipc) jsonBytes, err := json.Marshal(ipc)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(json).To(MatchJSON(`{ Expect(jsonBytes).To(MatchJSON(`{
"version": "4",
"interface": 0, "interface": 0,
"address": "10.1.2.3/24" "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() { It("correctly marshals a missing interface index", func() {
ipc := &current.IPConfig{ ipc := &current.IPConfig{
Version: "4",
Address: net.IPNet{ Address: net.IPNet{
IP: net.ParseIP("10.1.2.3"), IP: net.ParseIP("10.1.2.3"),
Mask: net.IPv4Mask(255, 255, 255, 0), Mask: net.IPv4Mask(255, 255, 255, 0),
@ -245,7 +339,6 @@ var _ = Describe("Current types operations", func() {
json, err := json.Marshal(ipc) json, err := json.Marshal(ipc)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(json).To(MatchJSON(`{ Expect(json).To(MatchJSON(`{
"version": "4",
"address": "10.1.2.3/24" "address": "10.1.2.3/24"
}`)) }`))
}) })

View File

@ -26,8 +26,8 @@ import (
type UnmarshallableBool bool type UnmarshallableBool bool
// UnmarshalText implements the encoding.TextUnmarshaler interface. // UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns boolean true if the string is "1" or "[Tt]rue" // Returns boolean true if the string is "1" or "true" or "True"
// Returns boolean false if the string is "0" or "[Ff]alse" // Returns boolean false if the string is "0" or "false" or "False”
func (b *UnmarshallableBool) UnmarshalText(data []byte) error { func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data)) s := strings.ToLower(string(data))
switch s { switch s {
@ -36,7 +36,7 @@ func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
case "0", "false": case "0", "false":
*b = false *b = false
default: default:
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s) return fmt.Errorf("boolean unmarshal error: invalid input %s", s)
} }
return nil return nil
} }
@ -91,16 +91,26 @@ func LoadArgs(args string, container interface{}) error {
unknownArgs = append(unknownArgs, pair) unknownArgs = append(unknownArgs, pair)
continue 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 { if !ok {
return UnmarshalableArgsError{fmt.Errorf( return UnmarshalableArgsError{fmt.Errorf(
"ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler", "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)) err := u.UnmarshalText([]byte(valueString))
if err != nil { 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 package types_test
import ( import (
"net"
"reflect" "reflect"
. "github.com/containernetworking/cni/pkg/types" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
. "github.com/containernetworking/cni/pkg/types"
) )
var _ = Describe("UnmarshallableBool UnmarshalText", func() { var _ = Describe("UnmarshallableBool UnmarshalText", func() {
@ -126,7 +126,37 @@ var _ = Describe("LoadArgs", func() {
}{} }{}
err := LoadArgs("IP=10.0.0.0/24", &conf) err := LoadArgs("IP=10.0.0.0/24", &conf)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
})
})
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,300 +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"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
)
const ImplementedSpecVersion string = "0.3.1"
var SupportedVersions = []string{"0.3.0", 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 {
gw := route.GW
if gw == nil {
gw = oldResult.IP4.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: 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 {
gw := route.GW
if gw == nil {
gw = oldResult.IP6.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
})
}
}
if len(newResult.IPs) == 0 {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
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", 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 {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if len(r.Interfaces) > 0 {
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
}
if len(r.IPs) > 0 {
str += fmt.Sprintf("IP:%+v, ", r.IPs)
}
if len(r.Routes) > 0 {
str += fmt.Sprintf("Routes:%+v, ", r.Routes)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// Convert this old version result to the current CNI version result
func (r *Result) Convert() (*Result, error) {
return r, nil
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// IP version, either "4" or "6"
Version string
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
// JSON (un)marshallable types
type ipConfig struct {
Version string `json:"version"`
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Version: c.Version,
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Version = ipc.Version
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

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

@ -16,8 +16,8 @@ package types
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
) )
@ -56,32 +56,77 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// NetConf describes a network. // Use PluginConf instead of NetConf, the NetConf
type NetConf struct { // backwards-compat alias will be removed in a future release.
type NetConf = PluginConf
// PluginConf describes a plugin configuration for a specific network.
type PluginConf struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"` Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM struct { IPAM IPAM `json:"ipam,omitempty"`
Type string `json:"type,omitempty"` DNS DNS `json:"dns,omitempty"`
} `json:"ipam,omitempty"`
DNS DNS `json:"dns"` 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. // NetConfList describes an ordered list of networks.
type NetConfList struct { type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Plugins []*NetConf `json:"plugins,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 // Result is an interface that provides the result of plugin execution
type Result interface { type Result interface {
// The highest CNI specification result verison the result supports // The highest CNI specification result version the result supports
// without having to convert // without having to convert
Version() string Version() string
@ -92,8 +137,8 @@ type Result interface {
// Prints the result in JSON format to stdout // Prints the result in JSON format to stdout
Print() error Print() error
// Returns a JSON string representation of the result // Prints the result in JSON format to provided writer
String() string PrintTo(writer io.Writer) error
} }
func PrintResult(result Result, version string) error { func PrintResult(result Result, version string) error {
@ -112,21 +157,93 @@ type DNS struct {
Options []string `json:"options,omitempty"` Options []string `json:"options,omitempty"`
} }
// IsEmpty returns true if DNS structure has no value, otherwise return false
func (d *DNS) IsEmpty() bool {
if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 {
return true
}
return false
}
func (d *DNS) Copy() *DNS {
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 { type Route struct {
Dst net.IPNet Dst net.IPNet
GW net.IP GW net.IP
MTU int
AdvMSS int
Priority int
Table *int
Scope *int
} }
func (r *Route) String() string { func (r *Route) String() string {
return fmt.Sprintf("%+v", *r) table := "<nil>"
if r.Table != nil {
table = fmt.Sprintf("%d", *r.Table)
}
scope := "<nil>"
if r.Scope != nil {
scope = fmt.Sprintf("%d", *r.Scope)
}
return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope)
}
func (r *Route) Copy() *Route {
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 // Well known error codes
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes // see https://github.com/containernetworking/cni/blob/main/SPEC.md#error
const ( const (
ErrUnknown uint = iota // 0 ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1 ErrIncompatibleCNIVersion // 1
ErrUnsupportedField // 2 ErrUnsupportedField // 2
ErrUnknownContainer // 3
ErrInvalidEnvironmentVariables // 4
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11
ErrPluginNotAvailable uint = 50
ErrLimitedConnectivity uint = 51
ErrInternal uint = 999
) )
type Error struct { type Error struct {
@ -135,6 +252,14 @@ type Error struct {
Details string `json:"details,omitempty"` Details string `json:"details,omitempty"`
} }
func NewError(code uint, msg, details string) *Error {
return &Error{
Code: code,
Msg: msg,
Details: details,
}
}
func (e *Error) Error() string { func (e *Error) Error() string {
details := "" details := ""
if e.Details != "" { if e.Details != "" {
@ -152,8 +277,13 @@ func (e *Error) Print() error {
// JSON (un)marshallable types // JSON (un)marshallable types
type route struct { type route struct {
Dst IPNet `json:"dst"` Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"` GW net.IP `json:"gw,omitempty"`
MTU int `json:"mtu,omitempty"`
AdvMSS int `json:"advmss,omitempty"`
Priority int `json:"priority,omitempty"`
Table *int `json:"table,omitempty"`
Scope *int `json:"scope,omitempty"`
} }
func (r *Route) UnmarshalJSON(data []byte) error { func (r *Route) UnmarshalJSON(data []byte) error {
@ -164,13 +294,24 @@ func (r *Route) UnmarshalJSON(data []byte) error {
r.Dst = net.IPNet(rt.Dst) r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW r.GW = rt.GW
r.MTU = rt.MTU
r.AdvMSS = rt.AdvMSS
r.Priority = rt.Priority
r.Table = rt.Table
r.Scope = rt.Scope
return nil return nil
} }
func (r *Route) MarshalJSON() ([]byte, error) { func (r Route) MarshalJSON() ([]byte, error) {
rt := route{ rt := route{
Dst: IPNet(r.Dst), Dst: IPNet(r.Dst),
GW: r.GW, GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Table: r.Table,
Scope: r.Scope,
} }
return json.Marshal(rt) return json.Marshal(rt)
@ -184,6 +325,3 @@ func prettyPrint(obj interface{}) error {
_, err = os.Stdout.Write(data) _, err = os.Stdout.Write(data)
return err return err
} }
// NotImplementedError is used to indicate that a method is not implemented for the given platform
var NotImplementedError = errors.New("Not Implemented")

View File

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

201
pkg/types/types_test.go Normal file
View File

@ -0,0 +1,201 @@
// Copyright 2017 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types_test
import (
"encoding/json"
"net"
. "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) {
ipn, err := types.ParseCIDR(input)
Expect(err).NotTo(HaveOccurred())
Expect(ipn.String()).To(Equal(input))
Expect(ipn.IP.String()).To(Equal(expectedIP))
ones, _ := ipn.Mask.Size()
Expect(ones).To(Equal(expectedMask))
},
Entry("ipv4", "1.2.3.4/24", "1.2.3.4", 24),
Entry("ipv6", "2001:db8::/32", "2001:db8::", 32),
)
It("returns an error when given invalid inputs", func() {
ipn, err := types.ParseCIDR("1.2.3/45")
Expect(ipn).To(BeNil())
Expect(err).To(MatchError("invalid CIDR address: 1.2.3/45"))
})
})
Describe("custom IPNet type", func() {
It("marshals and unmarshals to JSON as a string", func() {
ipn := types.IPNet{
IP: net.ParseIP("1.2.3.4"),
Mask: net.CIDRMask(24, 32),
}
jsonBytes, err := json.Marshal(ipn)
Expect(err).NotTo(HaveOccurred())
Expect(jsonBytes).To(MatchJSON(`"1.2.3.4/24"`))
var unmarshaled types.IPNet
Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
Expect(unmarshaled).To(Equal(ipn))
})
Context("when the json data is not syntactically valid", func() {
Specify("UnmarshalJSON returns an error", func() {
ipn := new(types.IPNet)
err := ipn.UnmarshalJSON([]byte("1"))
Expect(err).To(MatchError("json: cannot unmarshal number into Go value of type string"))
})
})
Context("when the json data is not semantically valid", func() {
Specify("UnmarshalJSON returns an error", func() {
ipn := new(types.IPNet)
err := ipn.UnmarshalJSON([]byte(`"1.2.3.4/99"`))
Expect(err).To(MatchError("invalid CIDR address: 1.2.3.4/99"))
})
})
})
Describe("custom Route type", func() {
var example types.Route
BeforeEach(func() {
example = types.Route{
Dst: net.IPNet{
IP: net.ParseIP("1.2.3.0"),
Mask: net.CIDRMask(24, 32),
},
GW: net.ParseIP("1.2.3.1"),
MTU: 1500,
AdvMSS: 1340,
Priority: 100,
Table: types040.Int(50),
Scope: types040.Int(253),
}
})
It("marshals and unmarshals to JSON", func() {
jsonBytes, err := json.Marshal(example)
Expect(err).NotTo(HaveOccurred())
Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1", "mtu": 1500, "advmss": 1340, "priority": 100, "table": 50, "scope": 253 }`))
var unmarshaled types.Route
Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
Expect(unmarshaled).To(Equal(example))
})
Context("when the json data is not valid", func() {
Specify("UnmarshalJSON returns an error", func() {
route := new(types.Route)
err := route.UnmarshalJSON([]byte(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.x" }`))
Expect(err).To(MatchError("invalid IP address: 1.2.3.x"))
})
})
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 MTU:1500 AdvMSS:1340 Priority:100 Table:50 Scope:253}`))
})
})
Describe("Error type", func() {
var example *types.Error
BeforeEach(func() {
example = &types.Error{
Code: 1234,
Msg: "some message",
Details: "some details",
}
})
Describe("Error() method (basic string)", func() {
It("returns a formatted string", func() {
Expect(example.Error()).To(Equal("some message; some details"))
})
Context("when details are not present", func() {
BeforeEach(func() {
example.Details = ""
})
It("returns only the message", func() {
Expect(example.Error()).To(Equal("some message"))
})
})
})
It("NewError method", func() {
err := types.NewError(1234, "some message", "some details")
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"))
})
})
})

82
pkg/utils/utils.go Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2019 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 utils
import (
"bytes"
"fmt"
"regexp"
"unicode"
"github.com/containernetworking/cni/pkg/types"
)
const (
// cniValidNameChars is the regexp used to validate valid characters in
// containerID and networkName
cniValidNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.\-]`
// maxInterfaceNameLength is the length max of a valid interface name
maxInterfaceNameLength = 15
)
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", "")
}
if !cniReg.MatchString(containerID) {
return types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", containerID)
}
return nil
}
// 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:", "")
}
if !cniReg.MatchString(networkName) {
return types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", networkName)
}
return nil
}
// 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 ".."
// 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 {
return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is empty", "")
}
if len(ifName) > maxInterfaceNameLength {
return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is too long", fmt.Sprintf("interface name should be less than %d characters", maxInterfaceNameLength+1))
}
if ifName == "." || ifName == ".." {
return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is . or ..", "")
}
for _, r := range bytes.Runes([]byte(ifName)) {
if r == '/' || r == ':' || unicode.IsSpace(r) {
return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name contains / or : or whitespace characters", "")
}
}
return nil
}

131
pkg/utils/utils_test.go Normal file
View File

@ -0,0 +1,131 @@
// Copyright 2019 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 utils_test
import (
"reflect"
"testing"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
)
func TestValidateContainerID(t *testing.T) {
testData := []struct {
description string
containerID string
err *types.Error
}{
{
description: "empty containerID",
containerID: "",
err: types.NewError(types.ErrUnknownContainer, "missing containerID", ""),
},
{
description: "invalid characters in containerID",
containerID: "1234%%%",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", "1234%%%"),
},
{
description: "normal containerID",
containerID: "a51debf7e1eb",
err: nil,
},
}
for _, tt := range testData {
err := utils.ValidateContainerID(tt.containerID)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("Expected '%v' but got '%v'", tt.err, err)
}
}
}
func TestValidateNetworkName(t *testing.T) {
testData := []struct {
description string
networkName string
err *types.Error
}{
{
description: "empty networkName",
networkName: "",
err: types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", ""),
},
{
description: "invalid characters in networkName",
networkName: "1234%%%",
err: types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", "1234%%%"),
},
{
description: "normal networkName",
networkName: "eth0",
err: nil,
},
}
for _, tt := range testData {
err := utils.ValidateNetworkName(tt.networkName)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("Expected '%v' but got '%v'", tt.err, err)
}
}
}
func TestValidateInterfaceName(t *testing.T) {
testData := []struct {
description string
interfaceName string
err *types.Error
}{
{
description: "empty interfaceName",
interfaceName: "",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is empty", ""),
},
{
description: "more than 16 characters in interfaceName",
interfaceName: "testnamemorethan16",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is too long", "interface name should be less than 16 characters"),
},
{
description: "interfaceName is .",
interfaceName: ".",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is . or ..", ""),
},
{
description: "interfaceName contains /",
interfaceName: "/testname",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "interface name contains / or : or whitespace characters", ""),
},
{
description: "interfaceName contains whitespace characters",
interfaceName: "test name",
err: types.NewError(types.ErrInvalidEnvironmentVariables, "interface name contains / or : or whitespace characters", ""),
},
{
description: "normal interfaceName",
interfaceName: "testname",
err: nil,
},
}
for _, tt := range testData {
err := utils.ValidateInterfaceName(tt.interfaceName)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("Expected '%v' but got '%v'", tt.err, err)
}
}
}

View File

@ -15,23 +15,12 @@
package version package version
import ( import (
"encoding/json" "github.com/containernetworking/cni/pkg/types/create"
"fmt"
) )
// ConfigDecoder can decode the CNI version available in network config data // ConfigDecoder can decode the CNI version available in network config data
type ConfigDecoder struct{} type ConfigDecoder struct{}
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) { func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
var conf struct { return create.DecodeVersion(jsonBytes)
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
} }

View File

@ -15,10 +15,10 @@
package version_test package version_test
import ( import (
"github.com/containernetworking/cni/pkg/version" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
) )
var _ = Describe("Decoding the version of network config", func() { var _ = Describe("Decoding the version of network config", func() {
@ -32,7 +32,7 @@ var _ = Describe("Decoding the version of network config", func() {
configBytes = []byte(`{ "cniVersion": "4.3.2" }`) configBytes = []byte(`{ "cniVersion": "4.3.2" }`)
}) })
Context("when the version is explict", func() { Context("when the version is explicit", func() {
It("returns the version", func() { It("returns the version", func() {
version, err := decoder.Decode(configBytes) version, err := decoder.Decode(configBytes)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())

View File

@ -16,7 +16,6 @@ package legacy_examples
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
@ -108,9 +107,9 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
return nil, fmt.Errorf("unknown example net config template %q", name) return nil, fmt.Errorf("unknown example net config template %q", name)
} }
debugFile, err := ioutil.TempFile("", "cni_debug") debugFile, err := os.CreateTemp("", "cni_debug")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create noop plugin debug file: %v", err) return nil, fmt.Errorf("failed to create noop plugin debug file: %w", err)
} }
debugFilePath := debugFile.Name() debugFilePath := debugFile.Name()
@ -119,7 +118,7 @@ func (e *ExampleRuntime) GenerateNetConf(name string) (*ExampleNetConf, error) {
} }
if err := debug.WriteDebug(debugFilePath); err != nil { if err := debug.WriteDebug(debugFilePath); err != nil {
os.Remove(debugFilePath) 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{ conf := &ExampleNetConf{
Config: fmt.Sprintf(template.conf, debugFilePath), Config: fmt.Sprintf(template.conf, debugFilePath),
@ -139,21 +138,19 @@ func (c *ExampleNetConf) Cleanup() {
} }
// V010_Runtime creates a simple noop network configuration, then // V010_Runtime creates a simple noop network configuration, then
// executes libcni against the the noop test plugin. // executes libcni against the noop test plugin.
var V010_Runtime = ExampleRuntime{ var V010_Runtime = ExampleRuntime{
NetConfs: []string{"unversioned", "0.1.0"}, NetConfs: []string{"unversioned", "0.1.0"},
Example: Example{ Example: Example{
Name: "example_invoker_v010", Name: "example_invoker_v010",
CNIRepoGitRef: "c0d34c69", //version with ns.Do CNIRepoGitRef: "c0d34c69", // version with ns.Do
PluginSource: `package main PluginSource: `package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"net"
"os" "os"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/libcni"
) )
@ -163,7 +160,7 @@ func main(){
} }
func exec() int { func exec() int {
confBytes, err := ioutil.ReadAll(os.Stdin) confBytes, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
fmt.Printf("could not read netconfig from stdin: %+v", err) fmt.Printf("could not read netconfig from stdin: %+v", err)
return 1 return 1
@ -181,19 +178,10 @@ func exec() int {
return 1 return 1
} }
targetNs, err := ns.NewNS()
if err != nil {
fmt.Printf("Could not create ns: %+v", err)
return 1
}
defer targetNs.Close()
ifName := "eth0"
runtimeConf := &libcni.RuntimeConf{ runtimeConf := &libcni.RuntimeConf{
ContainerID: "some-container-id", ContainerID: "some-container-id",
NetNS: targetNs.Path(), NetNS: "/some/netns/path",
IfName: ifName, IfName: "eth0",
} }
cniConfig := &libcni.CNIConfig{Path: os.Args[1:]} cniConfig := &libcni.CNIConfig{Path: os.Args[1:]}
@ -223,18 +211,6 @@ func exec() int {
return 5 return 5
} }
err = targetNs.Do(func(ns.NetNS) error {
_, err := net.InterfaceByName(ifName)
if err == nil {
return fmt.Errorf("interface was not deleted")
}
return nil
})
if err != nil {
fmt.Println(err)
return 6
}
return 0 return 0
} }
`, `,

View File

@ -17,9 +17,10 @@
package legacy_examples package legacy_examples
import ( import (
"io/ioutil"
"net" "net"
"os"
"path/filepath" "path/filepath"
"runtime"
"sync" "sync"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
@ -38,8 +39,10 @@ type Example struct {
PluginSource string PluginSource string
} }
var buildDir = "" var (
var buildDirLock sync.Mutex buildDir = ""
buildDirLock sync.Mutex
)
func ensureBuildDirExists() error { func ensureBuildDirExists() error {
buildDirLock.Lock() buildDirLock.Lock()
@ -50,7 +53,7 @@ func ensureBuildDirExists() error {
} }
var err error var err error
buildDir, err = ioutil.TempDir("", "cni-example-plugins") buildDir, err = os.MkdirTemp("", "cni-example-plugins")
return err return err
} }
@ -61,6 +64,9 @@ func (e Example) Build() (string, error) {
} }
outBinPath := filepath.Join(buildDir, e.Name) outBinPath := filepath.Join(buildDir, e.Name)
if runtime.GOOS == "windows" {
outBinPath += ".exe"
}
if err := testhelpers.BuildAt([]byte(e.PluginSource), e.CNIRepoGitRef, outBinPath); err != nil { if err := testhelpers.BuildAt([]byte(e.PluginSource), e.CNIRepoGitRef, outBinPath); err != nil {
return "", err return "", err
@ -116,6 +122,7 @@ func main() { skel.PluginMain(c, c) }
// As we change the CNI spec, the Result type and this value may change. // As we change the CNI spec, the Result type and this value may change.
// The text of the example plugins should not. // The text of the example plugins should not.
var ExpectedResult = &types020.Result{ var ExpectedResult = &types020.Result{
CNIVersion: "0.1.0",
IP4: &types020.IPConfig{ IP4: &types020.IPConfig{
IP: net.IPNet{ IP: net.IPNet{
IP: net.ParseIP("10.1.2.3"), IP: net.ParseIP("10.1.2.3"),
@ -123,7 +130,7 @@ var ExpectedResult = &types020.Result{
}, },
Gateway: net.ParseIP("10.1.2.1"), Gateway: net.ParseIP("10.1.2.1"),
Routes: []types.Route{ Routes: []types.Route{
types.Route{ {
Dst: net.IPNet{ Dst: net.IPNet{
IP: net.ParseIP("0.0.0.0"), IP: net.ParseIP("0.0.0.0"),
Mask: net.CIDRMask(0, 32), Mask: net.CIDRMask(0, 32),

View File

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

View File

@ -17,10 +17,12 @@ package legacy_examples_test
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version/legacy_examples" "github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("The v0.1.0 Example", func() { var _ = Describe("The v0.1.0 Example", func() {
@ -29,7 +31,11 @@ var _ = Describe("The v0.1.0 Example", func() {
pluginPath, err := example.Build() pluginPath, err := example.Build()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(filepath.Base(pluginPath)).To(Equal(example.Name)) expectedBaseName := example.Name
if runtime.GOOS == "windows" {
expectedBaseName += ".exe"
}
Expect(filepath.Base(pluginPath)).To(Equal(expectedBaseName))
Expect(os.RemoveAll(pluginPath)).To(Succeed()) Expect(os.RemoveAll(pluginPath)).To(Succeed())
}) })

View File

@ -18,6 +18,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"strconv"
"strings"
) )
// PluginInfo reports information about CNI versioning // PluginInfo reports information about CNI versioning
@ -66,7 +68,7 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
var info pluginInfo var info pluginInfo
err := json.Unmarshal(jsonBytes, &info) err := json.Unmarshal(jsonBytes, &info)
if err != nil { if err != nil {
return nil, fmt.Errorf("decoding version info: %s", err) return nil, fmt.Errorf("decoding version info: %w", err)
} }
if info.CNIVersion_ == "" { if info.CNIVersion_ == "" {
return nil, fmt.Errorf("decoding version info: missing field cniVersion") return nil, fmt.Errorf("decoding version info: missing field cniVersion")
@ -79,3 +81,88 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
} }
return &info, nil return &info, nil
} }
// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major,
// minor, and micro numbers or returns an error
func ParseVersion(version string) (int, int, int, error) {
var major, minor, micro int
if version == "" { // special case: no version declared == v0.1.0
return 0, 1, 0, nil
}
parts := strings.Split(version, ".")
if len(parts) >= 4 {
return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version)
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %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: %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: %w", parts[2], err)
}
}
return major, minor, micro, nil
}
// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro
// numbers, and compares them to determine whether the first version is greater
// than or equal to the second
func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
if err != nil {
return false, err
}
secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
if err != nil {
return false, err
}
if firstMajor > secondMajor {
return true, nil
} else if firstMajor == secondMajor {
if firstMinor > secondMinor {
return true, nil
} else if firstMinor == secondMinor && firstMicro >= secondMicro {
return true, nil
}
}
return false, nil
}
// GreaterThan returns true if the first version is greater than the second
func GreaterThan(version, otherVersion string) (bool, error) {
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
if err != nil {
return false, err
}
secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
if err != nil {
return false, err
}
if firstMajor > secondMajor {
return true, nil
} else if firstMajor == secondMajor {
if firstMinor > secondMinor {
return true, nil
} else if firstMinor == secondMinor && firstMicro > secondMicro {
return true, nil
}
}
return false, nil
}

View File

@ -15,9 +15,10 @@
package version_test package version_test
import ( import (
"github.com/containernetworking/cni/pkg/version" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
) )
var _ = Describe("Decoding versions reported by a plugin", func() { var _ = Describe("Decoding versions reported by a plugin", func() {
@ -82,4 +83,68 @@ var _ = Describe("Decoding versions reported by a plugin", func() {
}) })
}) })
Describe("ParseVersion", func() {
It("parses a valid version correctly", func() {
major, minor, micro, err := version.ParseVersion("1.2.3")
Expect(err).NotTo(HaveOccurred())
Expect(major).To(Equal(1))
Expect(minor).To(Equal(2))
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"}
for _, v := range badVersions {
_, _, _, err := version.ParseVersion(v)
Expect(err).To(HaveOccurred())
}
})
})
Describe("GreaterThanOrEqualTo", func() {
It("correctly compares versions", func() {
versions := [][2]string{
{"1.2.34", "1.2.14"},
{"2.5.4", "2.4.4"},
{"1.2.3", "0.2.3"},
{"0.4.0", "0.3.1"},
}
for _, v := range versions {
// Make sure the first is greater than the second
gt, err := version.GreaterThanOrEqualTo(v[0], v[1])
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(BeTrue())
// And the opposite
gt, err = version.GreaterThanOrEqualTo(v[1], v[0])
Expect(err).NotTo(HaveOccurred())
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(BeTrue())
})
It("returns an error for malformed versions", func() {
versions := [][2]string{
{"1.2.34", "asdadf"},
{"adsfad", "2.5.4"},
}
for _, v := range versions {
_, err := version.GreaterThanOrEqualTo(v[0], v[1])
Expect(err).To(HaveOccurred())
}
})
})
}) })

View File

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

View File

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

View File

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

View File

@ -4,23 +4,22 @@
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package testhelpers_test package testhelpers
import ( import (
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"github.com/containernetworking/cni/pkg/version/testhelpers" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -43,9 +42,12 @@ func main() { skel.PluginMain(c, c) }
gitRef = "f4364185253" gitRef = "f4364185253"
var err error var err error
outputDir, err = ioutil.TempDir("", "bin") outputDir, err = os.MkdirTemp("", "bin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
outputFilePath = filepath.Join(outputDir, "some-binary") outputFilePath = filepath.Join(outputDir, "some-binary")
if runtime.GOOS == "windows" {
outputFilePath += ".exe"
}
}) })
AfterEach(func() { AfterEach(func() {
@ -55,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() { It("builds the provided source code using the CNI library at the given git ref", func() {
Expect(outputFilePath).NotTo(BeAnExistingFile()) Expect(outputFilePath).NotTo(BeAnExistingFile())
err := testhelpers.BuildAt(programSource, gitRef, outputFilePath) err := BuildAt(programSource, gitRef, outputFilePath)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(outputFilePath).To(BeAnExistingFile()) Expect(outputFilePath).To(BeAnExistingFile())
@ -67,40 +69,3 @@ func main() { skel.PluginMain(c, c) }
Expect(output).To(ContainSubstring("unknown CNI_COMMAND: VERSION")) 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

@ -15,16 +15,16 @@
package version package version
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020" "github.com/containernetworking/cni/pkg/types/create"
"github.com/containernetworking/cni/pkg/types/current"
) )
// Current reports the version of the CNI spec implemented by this library // Current reports the version of the CNI spec implemented by this library
func Current() string { func Current() string {
return "0.3.1" return "1.1.0"
} }
// Legacy PluginInfo describes a plugin that is backwards compatible with the // Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -34,28 +34,57 @@ func Current() string {
// //
// Any future CNI spec versions which meet this definition should be added to // Any future CNI spec versions which meet this definition should be added to
// this list. // this list.
var Legacy = PluginSupports("0.1.0", "0.2.0") var (
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1") 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 { // VersionsFrom returns a list of versions starting from min, inclusive
supportedVersions []string func VersionsStartingFrom(min string) PluginInfo {
newResult types.ResultFactoryFunc out := []string{}
}{ // cheat, just assume ordered
{current.SupportedVersions, current.NewResult}, ok := false
{types020.SupportedVersions, types020.NewResult}, 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 // 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. // that object to parse the plugin result, returning an error if parsing failed.
func NewResult(version string, resultBytes []byte) (types.Result, error) { func NewResult(version string, resultBytes []byte) (types.Result, error) {
reconciler := &Reconciler{} return create.Create(version, resultBytes)
for _, resultFactory := range resultFactories { }
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
if err == nil { // ParsePrevResult parses a prevResult in a NetConf structure and sets
// Result supports this version // the NetConf's PrevResult member to the parsed Result object.
return resultFactory.newResult(resultBytes) func ParsePrevResult(conf *types.PluginConf) error {
} if conf.RawPrevResult == nil {
return nil
} }
return nil, fmt.Errorf("unsupported CNI result version %q", version) // 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: %w", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = create.Create(conf.CNIVersion, resultBytes)
if err != nil {
return fmt.Errorf("could not parse prevResult: %w", err)
}
return nil
} }

View File

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

160
pkg/version/version_test.go Normal file
View File

@ -0,0 +1,160 @@
// Copyright 2018 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version_test
import (
"encoding/json"
"net"
"reflect"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
cniv1 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Version operations", func() {
It("computes a list of versions correctly", func() {
actual := version.VersionsStartingFrom("0.3.1")
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0", "1.1.0"}))
})
Context("when a prevResult is available", func() {
It("parses the prevResult", func() {
rawBytes := []byte(`{
"cniVersion": "1.0.0",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
}
],
"ips": [
{
"version": "4",
"interface": 0,
"address": "1.2.3.30/24",
"gateway": "1.2.3.1"
}
]
}`)
var raw map[string]interface{}
err := json.Unmarshal(rawBytes, &raw)
Expect(err).NotTo(HaveOccurred())
conf := &types.PluginConf{
CNIVersion: "1.0.0",
Name: "foobar",
Type: "baz",
RawPrevResult: raw,
}
err = version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
expectedResult := &cniv1.Result{
CNIVersion: "1.0.0",
Interfaces: []*cniv1.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
},
},
IPs: []*cniv1.IPConfig{
{
Interface: cniv1.Int(0),
Address: net.IPNet{
IP: net.ParseIP("1.2.3.30"),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
Gateway: net.ParseIP("1.2.3.1"),
},
},
}
Expect(reflect.DeepEqual(conf.PrevResult, expectedResult)).To(BeTrue())
})
It("fails if the prevResult version is unknown", func() {
conf := &types.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
"cniVersion": "5678.456",
},
}
err := version.ParsePrevResult(conf)
Expect(err).To(MatchError(`could not parse prevResult: result type supports [1.0.0 1.1.0] but unmarshalled CNIVersion is "5678.456"`))
})
It("fails if the prevResult version does not match the prevResult version", func() {
conf := &types.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
"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).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.PluginConf{
CNIVersion: version.Current(),
Name: "foobar",
Type: "baz",
}
err := version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
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 ( import (
"encoding/json" "encoding/json"
"io/ioutil" "os"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
) )
@ -29,19 +29,30 @@ type Debug struct {
// Report* fields allow the test to control the behavior of the no-op plugin // Report* fields allow the test to control the behavior of the no-op plugin
ReportResult string ReportResult string
ReportError string ReportError string
ReportErrorCode uint
ReportStderr string ReportStderr string
ReportVersionSupport []string ReportVersionSupport []string
ExitWithCode int
// Command stores the CNI command that the plugin received // Command stores the CNI command that the plugin received
Command string Command string
// CmdArgs stores the CNI Args and Env Vars that the plugin recieved // CmdArgs stores the CNI Args and Env Vars that the plugin received
CmdArgs skel.CmdArgs CmdArgs skel.CmdArgs
} }
// CmdLogEntry records a single CNI command as well as its args
type CmdLogEntry struct {
Command string
CmdArgs skel.CmdArgs
}
// CmdLog records a list of CmdLogEntry received by the noop plugin
type CmdLog []CmdLogEntry
// ReadDebug will return a debug file recorded by the noop plugin // ReadDebug will return a debug file recorded by the noop plugin
func ReadDebug(debugFilePath string) (*Debug, error) { func ReadDebug(debugFilePath string) (*Debug, error) {
debugBytes, err := ioutil.ReadFile(debugFilePath) debugBytes, err := os.ReadFile(debugFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -62,10 +73,41 @@ func (debug *Debug) WriteDebug(debugFilePath string) error {
return err return err
} }
err = ioutil.WriteFile(debugFilePath, debugBytes, 0600) err = os.WriteFile(debugFilePath, debugBytes, 0o600)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
// WriteCommandLog appends the executed cni command to the record file
func WriteCommandLog(path string, entry CmdLogEntry) error {
buf, err := os.ReadFile(path)
if err != nil {
return err
}
var cmds CmdLog
if len(buf) > 0 {
if err = json.Unmarshal(buf, &cmds); err != nil {
return err
}
}
cmds = append(cmds, entry)
if buf, err = json.Marshal(&cmds); err != nil {
return nil
}
return os.WriteFile(path, buf, 0o644)
}
func ReadCommandLog(path string) (CmdLog, error) {
buf, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cmds CmdLog
if err = json.Unmarshal(buf, &cmds); err != nil {
return nil, err
}
return cmds, nil
}

Some files were not shown because too many files have changed in this diff Show More