Compare commits

..

No commits in common. "main" and "v1.0.0-rc1" have entirely different histories.

88 changed files with 1070 additions and 3422 deletions

View File

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

View File

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

View File

@ -1,27 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "docker"
directory: "/.github/actions/retest-action"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
groups:
golang:
patterns:
- "*"
- package-ecosystem: "gomod"
directory: "/plugins/debug"
schedule:
interval: "weekly"

View File

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

View File

@ -1,40 +0,0 @@
name: Scorecard supply-chain security
on:
branch_protection_rule:
push:
branches:
- main
schedule:
- cron: 29 15 * * 0
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
permissions:
id-token: write
security-events: write
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7
with:
sarif_file: results.sarif

View File

@ -4,45 +4,19 @@ name: test
on: ["push", "pull_request"]
env:
GO_VERSION: "1.22"
GO_VERSION: "1.15"
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
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@v2
- name: Build on all supported architectures
run: |
@ -54,27 +28,27 @@ jobs:
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
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@v2
- name: Install test binaries
env:
GO111MODULE: off
run: |
go install github.com/mattn/goveralls@v0.0.12
go install github.com/modocache/gover@latest
go get github.com/mattn/goveralls
go get github.com/modocache/gover
- name: test
run: COVERALLS=1 ./test.sh
- env:
- name: Send coverage to coveralls
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Send coverage to coveralls
run: |
PATH=$PATH:$(go env GOPATH)/bin
gover
@ -82,15 +56,12 @@ jobs:
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
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@v2
- name: test
run: bash ./test.sh

3
.gitignore vendored
View File

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

View File

@ -1,30 +0,0 @@
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,10 +0,0 @@
---
extends: default
rules:
document-start: disable
line-length: disable
truthy:
ignore: |
.github/workflows/*.yml
.github/workflows/*.yaml

View File

@ -1,3 +1,3 @@
# Community Code of Conduct
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
CNI follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@ -34,7 +34,7 @@ are very busy and read the mailing lists.
This is a rough outline of how to prepare a contribution:
- Create a topic branch from where you want to base your work (usually branched from main).
- Create a topic branch from where you want to base your work (usually branched from master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.

View File

@ -20,7 +20,7 @@ Establishing these conventions allows plugins to work across multiple runtimes.
Additional conventions can be created by creating PRs which modify this document.
## Dynamic Plugin specific fields (Capabilities / Runtime Configuration)
[Plugin specific fields](SPEC.md#network-configuration) formed part of the original CNI spec and have been present since the initial release.
[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.
> Plugins may define additional fields that they accept and may generate an error if called with unknown fields. The exception to this is the args field may be used to pass arbitrary data which may be ignored by plugins.
A plugin can define any additional fields it needs to work properly. It should return an error if it can't act on fields that were expected or where the field values were malformed.
@ -32,7 +32,7 @@ This method of passing information to a plugin is recommended when the following
Dynamic information (i.e. data that a runtime fills out) should be placed in a `runtimeConfig` section. Plugins can request
that the runtime insert this dynamic configuration by explicitly listing their `capabilities` in the network configuration.
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](SPEC.md#network-configuration-lists).
For example, the configuration for a port mapping plugin might look like this to an operator (it should be included as part of a [network configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists).
```json
{
"name" : "ExamplePlugin",
@ -61,15 +61,14 @@ But the runtime would fill in the mappings so the plugin itself would receive so
| ip ranges | Dynamically configure the IP range(s) for address allocation. Runtimes that manage IP pools, but not individual IP addresses, can pass these to plugins. | `ipRanges` | The same as the `ranges` key for `host-local` - a list of lists of subnets. The outer list is the number of IPs to allocate, and the inner list is a pool of subnets for each allocation. <br/><pre>[<br/> [<br/> { "subnet": "10.1.2.0/24", "rangeStart": "10.1.2.3", "rangeEnd": 10.1.2.99", "gateway": "10.1.2.254" } <br/> ]<br/>]</pre> | none | CNI `host-local` plugin |
| bandwidth limits | Dynamically configure interface bandwidth limits | `bandwidth` | Desired bandwidth limits. Rates are in bits per second, burst values are in bits. <pre> { "ingressRate": 2048, "ingressBurst": 1600, "egressRate": 4096, "egressBurst": 1600 } </pre> | none | CNI `bandwidth` plugin |
| dns | Dynamically configure dns according to runtime | `dns` | Dictionary containing a list of `servers` (string entries), a list of `searches` (string entries), a list of `options` (string entries). <pre>{ <br> "searches" : [ "internal.yoyodyne.net", "corp.tyrell.net" ] <br> "servers": [ "8.8.8.8", "10.0.0.10" ] <br />} </pre> | kubernetes | CNI `win-bridge` plugin, CNI `win-overlay` plugin |
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (\<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 |
| ips | Dynamically allocate IPs for container interface. Runtime which has the ability of address allocation can pass these to plugins. | `ips` | A list of `IP` (string entries). <pre> [ "10.10.0.1/24", "3ffe:ffff:0:01ff::1/64" ] </pre> | none | CNI `static` plugin |
| 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](SPEC.md#network-configuration) were reserved as a field in the `0.2.0` release of the CNI spec.
`args` in [network config](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) were reserved as a field in the `0.2.0` release of the CNI spec.
> args (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.
@ -100,7 +99,7 @@ For example:
| Area | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ----- | ------ | ------------ | ----------------------- | ---------------------- |
| labels | Pass`key=value` labels to plugins | <pre>"labels" : [<br /> { "key" : "app", "value" : "myapp" },<br /> { "key" : "env", "value" : "prod" }<br />] </pre> | none | none |
| ips | Request 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 |
| ips | Request static IPs | Spec:<pre>"ips": ["\<ip\>[/\<prefix\>]", ...]</pre>Examples:<pre>"ips": ["10.2.2.42/24", "2001:db8::5"]</pre>The plugin may require the IP address to include a prefix length. | none | host-local |
## CNI_ARGS
CNI_ARGS formed part of the original CNI spec and have been present since the initial release.
@ -110,7 +109,7 @@ The use of `CNI_ARGS` is deprecated and "args" should be used instead. If a runt
| Field | Purpose| Spec and Example | Runtime implementations | Plugin Implementations |
| ------ | ------ | ---------------- | ----------------------- | ---------------------- |
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre> The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local, static |
| IP | Request a specific IP from IPAM plugins | Spec:<pre>IP=\<ip\>[/\<prefix\>]</pre>Example: <pre>IP=192.168.10.4/24</pre>The plugin may require the IP addresses to include a prefix length. | *rkt* supports passing additional arguments to plugins and the [documentation](https://coreos.com/rkt/docs/latest/networking/overriding-defaults.html) suggests IP can be used. | host-local (since version v0.2.0) supports the field for IPv4 only - [documentation](https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#supported-arguments).|
## Chained Plugins
If plugins are agnostic about the type of interface created, they SHOULD work in a chained mode and configure existing interfaces. Plugins MAY also create the desired interface when not run in a chain.

View File

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

View File

@ -1,14 +1,8 @@
Bruce Ma <brucema19901024@gmail.com> (@mars1024)
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:
Bryan Boreham <bryan@weave.works> (@bboreham)
Casey Callendrello <cdc@redhat.com> (@squeed)
Dan Williams <dcbw@redhat.com> (@dcbw)
Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
Matt Dupre <matt@tigera.io> (@matthewdupre)
Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek)
Michael Cambria <mcambria@redhat.com> (@mccv1r0)
Piotr Skamruk <piotr.skamruk@gmail.com> (@jellonek)

View File

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

View File

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

View File

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

322
SPEC.md
View File

@ -8,7 +8,6 @@
- [Configuration format](#configuration-format)
- [Plugin configuration objects:](#plugin-configuration-objects)
- [Example configuration](#example-configuration)
- [Version considerations](#version-considerations)
- [Section 2: Execution Protocol](#section-2-execution-protocol)
- [Overview](#overview-1)
- [Parameters](#parameters)
@ -17,25 +16,21 @@
- [`ADD`: Add container to network, or apply modifications](#add-add-container-to-network-or-apply-modifications)
- [`DEL`: Remove container from network, or un-apply modifications](#del-remove-container-from-network-or-un-apply-modifications)
- [`CHECK`: Check container's networking is as expected](#check-check-containers-networking-is-as-expected)
- [`STATUS`: Check plugin status](#status-check-plugin-status)
- [`VERSION`: probe plugin version support](#version-probe-plugin-version-support)
- [`GC`: Clean up any stale resources](#gc-clean-up-any-stale-resources)
- [Section 3: Execution of Network Configurations](#section-3-execution-of-network-configurations)
- [Lifecycle \& Ordering](#lifecycle--ordering)
- [Lifecycle & Ordering](#lifecycle--ordering)
- [Attachment Parameters](#attachment-parameters)
- [Adding an attachment](#adding-an-attachment)
- [Deleting an attachment](#deleting-an-attachment)
- [Checking an attachment](#checking-an-attachment)
- [Garbage-collecting a network](#garbage-collecting-a-network)
- [Deriving request configuration from plugin configuration](#deriving-request-configuration-from-plugin-configuration)
- [Deriving execution configuration from plugin configuration](#deriving-execution-configuration-from-plugin-configuration)
- [Deriving `runtimeConfig`](#deriving-runtimeconfig)
- [Section 4: Plugin Delegation](#section-4-plugin-delegation)
- [Delegated Plugin protocol](#delegated-plugin-protocol)
- [Delegated plugin execution procedure](#delegated-plugin-execution-procedure)
- [Section 5: Result Types](#section-5-result-types)
- [ADD Success](#add-success)
- [Success](#success)
- [Delegated plugins (IPAM)](#delegated-plugins-ipam)
- [VERSION Success](#version-success)
- [Error](#error)
- [Version](#version-1)
- [Appendix: Examples](#appendix-examples)
@ -45,7 +40,7 @@
## Version
This is CNI **spec** version **1.1.0**.
This is CNI **spec** version **1.0.0 pre-release** and is still under active development.
Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)).
@ -55,7 +50,6 @@ Released versions of the spec are available as Git tags.
| tag | spec permalink | major changes |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | --------------------------------- |
| [`spec-v1.0.0`](https://github.com/containernetworking/cni/releases/tag/spec-v1.0.0) | [spec at v1.0.0](https://github.com/containernetworking/cni/blob/spec-v1.0.0/SPEC.md) | Removed non-list configurations; removed `version` field of `interfaces` array |
| [`spec-v0.4.0`](https://github.com/containernetworking/cni/releases/tag/spec-v0.4.0) | [spec at v0.4.0](https://github.com/containernetworking/cni/blob/spec-v0.4.0/SPEC.md) | Introduce the CHECK command and passing prevResult on DEL |
| [`spec-v0.3.1`](https://github.com/containernetworking/cni/releases/tag/spec-v0.3.1) | [spec at v0.3.1](https://github.com/containernetworking/cni/blob/spec-v0.3.1/SPEC.md) | none (typo fix only) |
| [`spec-v0.3.0`](https://github.com/containernetworking/cni/releases/tag/spec-v0.3.0) | [spec at v0.3.0](https://github.com/containernetworking/cni/blob/spec-v0.3.0/SPEC.md) | rich result type, plugin chaining |
@ -77,7 +71,7 @@ For the purposes of this proposal, we define three terms very specifically:
This document aims to specify the interface between "runtimes" and "plugins". The key words "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may" and "optional" are used as specified in [RFC 2119][rfc-2119].
[namespaces]: http://man7.org/linux/man-pages/man7/namespaces.7.html
[namespaces]: http://man7.org/linux/man-pages/man7/namespaces.7.html
[rfc-2119]: https://www.ietf.org/rfc/rfc2119.txt
@ -96,7 +90,7 @@ The CNI specification defines:
CNI defines a network configuration format for administrators. It contains
directives for both the container runtime as well as the plugins to consume. At
plugin execution time, this configuration format is interpreted by the runtime and
plugin execution time, this configuration format is interpreted by the runtime and
transformed in to a form to be passed to the plugins.
In general, the network configuration is intended to be static. It can conceptually
@ -107,18 +101,14 @@ require this.
A network configuration consists of a JSON object with the following keys:
- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. Currently "1.1.0"
- `cniVersions` (string list): List of all CNI versions which this configuration supports. See [version selection](#version-selection) below.
- `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-). Must not contain characters disallowed in file paths.
- `cniVersion` (string): [Semantic Version 2.0](https://semver.org) of CNI specification to which this configuration list and all the individual configurations conform. Currently "1.0.0"
- `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-).
- `disableCheck` (boolean): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors.
- `disableGC` (boolean): Either `true` or `false`. If `disableGC` is `true`, runtimes must not call `GC` for this network configuration list. This allows an administrator to prevent `GC`ing when it is known that garbage collection may have undesired effects (e.g. shared configuration between multiple runtimes).
- `loadOnlyInlinedPlugins` (boolean): Either `true` or `false`. If `false` (default), indicates [plugin configuration objects](#plugin-configuration-objects) can be aggregated from multiple sources. Any valid plugin configuration objects aggregated from other sources must be appended to the final list of `plugins` for that network name. If set to `true`, indicates that valid plugin configuration objects aggregated from sources other than the main network configuration will be ignored. If `plugins` is not present in the network configuration, `loadOnlyInlinedPlugins` cannot be set to `true`.
- `plugins` (list): A list of inlined [plugin configuration objects](#plugin-configuration-objects). If this key is populated with inlined plugin objects, and `loadOnlyInlinedPlugins` is true, the final set of plugins for a network must consist of all the plugin objects in this list, merged with all the plugins loaded from the sibling folder with the same name as the network.
- `plugins` (list): A list of CNI plugins and their configuration, which is a list of plugin configuration objects.
#### Plugin configuration objects:
Runtimes may aggregate plugin configuration objects from multiple sources, and must unambiguously associate each loaded plugin configuration object with a single, valid network configuration. All aggregated plugin configuration objects must be validated, and each plugin with a valid configuration object must be invoked.
Plugin configuration objects may contain additional fields beyond the ones defined here. The runtime MUST pass through these fields, unchanged, to the invoked plugin, as defined in section 3.
Plugin configuration objects may contain additional fields than the ones defined here.
The runtime MUST pass through these fields, unchanged, to the plugin, as defined in section 3.
**Required keys:**
- `type` (string): Matches the name of the CNI plugin binary on disk. Must not contain characters disallowed in file paths for the system (e.g. / or \\).
@ -146,14 +136,12 @@ Plugins that consume any of these configuration keys should respect their intend
- `options` (list of strings, optional): list of options that can be passed to the resolver
**Other keys:**
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
#### Example configuration
The following is an example JSON representation of a network configuration `dbnet` with three plugin configurations (`bridge`, `tuning`, and `portmap`).
```jsonc
{
"cniVersion": "1.1.0",
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
"cniVersion": "1.0.0",
"name": "dbnet",
"plugins": [
{
@ -161,7 +149,7 @@ The following is an example JSON representation of a network configuration `dbne
// plugin specific parameters
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
// ipam specific
@ -178,7 +166,7 @@ The following is an example JSON representation of a network configuration `dbne
{
"type": "tuning",
"capabilities": {
"mac": true
"mac": true,
},
"sysctl": {
"net.core.somaxconn": "500"
@ -192,15 +180,6 @@ The following is an example JSON representation of a network configuration `dbne
}
```
### Version considerations
CNI runtimes, plugins, and network configurations may support multiple CNI specification versions independently. Plugins indicate their set of supported versions through the VERSION command, while network configurations indicate their set of supported versions through the `cniVersion` and `cniVersions` fields.
CNI runtimes MUST select the highest supported version from the set of network configuration versions given by the `cniVersion` and `cniVersions` fields. Runtimes MAY consider the set of supported plugin versions as reported by the VERSION command when determining available versions.
The CNI protocol follows Semantic Versioning principles, so the configuration format MUST remain backwards and forwards compatible within major versions.
## Section 2: Execution Protocol
### Overview
@ -216,25 +195,25 @@ a [result](#Section-5-Result-Types) on stdout on success, or an error on stderr
Parameters define invocation-specific settings, whereas configuration is, with some exceptions, the same for any given network.
The runtime must execute the plugin in the runtime's networking domain. (For most cases, this means the root network namespace / `dom0`).
The runtime must execute the plugin in the runtime's networking domain. (For most cases, this means the root network namespace / `dom0`).
### Parameters
Protocol parameters are passed to the plugins via OS environment variables.
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `CHECK`, `GC`, or `VERSION`.
- `CNI_CONTAINERID`: Container ID. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty. Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-).
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `CHECK`, or `VERSION`.
- `CNI_CONTAINERID`: Container ID. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty. Must start with a alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-).
- `CNI_NETNS`: A reference to the container's "isolation domain". If using network namespaces, then a path to the network namespace (e.g. `/run/netns/[nsname]`)
- `CNI_IFNAME`: Name of the interface to create inside the container; if the plugin is unable to use this interface name it must return an error.
- `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_PATH`: List of paths to search for CNI plugin executables. Paths are separated by an OS-specific list separator; for example ':' on Linux and ';' on Windows
### Errors
A plugin must exit with a return code of 0 on success, and non-zero on failure. If the plugin encounters an error, it should output an ["error" result structure](#Error) (see below).
A plugin must exit with a return code of 0 on success, and non-zero on failure. If the plugin encounters an error, the it should output an ["error" result structure](#Error) (see below).
### CNI operations
CNI defines 5 operations: `ADD`, `DEL`, `CHECK`, `GC`, and `VERSION`. These are passed to the plugin via the `CNI_COMMAND` environment variable.
CNI defines 4 operations: `ADD`, `DEL`, `CHECK`, and `VERSION`. These are passed to the plugin via the `CNI_COMMAND` environment variable.
#### `ADD`: Add container to network, or apply modifications
@ -248,7 +227,7 @@ If an interface of the requested name already exists in the container, the CNI p
A runtime should not call `ADD` twice (without an intervening DEL) for the same `(CNI_CONTAINERID, CNI_IFNAME)` tuple. This implies that a given container ID may be added to a specific network more than once only if each addition is done with a different interface name.
**Input:**
**Input:**
The runtime will provide a JSON-serialized plugin configuration object (defined below) on standard in.
@ -268,16 +247,13 @@ A CNI plugin, upon receiving a `DEL` command, should either
- delete the interface defined by `CNI_IFNAME` inside the container at `CNI_NETNS`, or
- undo any modifications applied in the plugin's `ADD` functionality
A `prevResult` must be supplied to CNI plugins as part of a `DEL` command. For the first plugin in the `DEL` command plugin chain, this `prevResult` will be the final result of the previous `ADD` command.
Plugins should still return without error if `prevResult` is empty for a `DEL` command, however.
`DEL` command invocations are always considered best-effort - plugins should always complete a `DEL` action without error to the fullest extent possible, even if some resources or state are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist.
Plugins should generally complete a `DEL` action without error even if some resources are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist.
Plugins MUST accept multiple `DEL` calls for the same (`CNI_CONTAINERID`, `CNI_IFNAME`) pair, and return success if the interface in question, or any modifications added, are missing.
**Input:**
The runtime will provide a JSON-serialized plugin configuration object (defined below) on standard in.
The runtime will provide a JSON-serialized plugin configuration object (defined below) on standard in.
Required environment parameters:
- `CNI_COMMAND`
@ -310,7 +286,7 @@ Plugin considerations:
Runtime considerations:
- A runtime must not call `CHECK` for a container that has not been `ADD`ed, or has been `DEL`eted after its last `ADD`.
- A runtime must not call `CHECK` if `disableCheck` is set to `true` in the [configuration](#configuration-format).
- A runtime must not call `CHECK` if `disableCheck` is set to `true` in the [configuration list](#network-configuration-lists).
- A runtime must include a `prevResult` field in the network configuration containing the `Result` of the immediately preceding `ADD` for the container. The runtime may wish to use libcni's support for caching `Result`s.
- A runtime may choose to stop executing `CHECK` for a chain when a plugin returns an error.
- A runtime may execute `CHECK` from immediately after a successful `ADD`, up until the container is `DEL`eted from the network.
@ -319,7 +295,7 @@ Runtime considerations:
**Input:**
The runtime will provide a json-serialized plugin configuration object (defined below) on standard in.
The runtime will provide a json-serialized plugin configuration object (defined below) on standard in.
Required environment parameters:
- `CNI_COMMAND`
@ -333,32 +309,6 @@ Optional environment parameters:
All parameters, with the exception of `CNI_PATH`, must be the same as the corresponding `ADD` for this container.
#### `STATUS`: Check plugin status
`STATUS` is a way for a runtime to determine the readiness of a network plugin.
A plugin must exit with a zero (success) return code if the plugin is ready to service ADD requests. If the plugin knows that it is not able to service ADD requests, it must exit with a non-zero return code and output an error on standard out (see below).
For example, if a plugin relies on an external service or daemon, it should return an error to `STATUS` if that service is unavailable. Likewise, if a plugin has a limited number of resources (e.g. IP addresses, hardware queues), it should return an error if those resources are exhausted and no new `ADD` requests can be serviced.
The following error codes are defined in the context of `STATUS`:
- 50: The plugin is not available (i.e. cannot service `ADD` requests)
- 51: The plugin is not available, and existing containers in the network may have limited connectivity.
Plugin considerations:
- Status is purely informational. A plugin MUST NOT rely on `STATUS` being called.
- Plugins should always expect other CNI operations (like `ADD`, `DEL`, etc) even if `STATUS` returns an error. `STATUS` does not prevent other runtime requests.
- If a plugin relies on a delegated plugin (e.g. IPAM) to service `ADD` requests, it must also execute a `STATUS` request to that plugin when it receives a `STATUS` request for itself. If the delegated plugin return an error result, the executing plugin should return an error result.
**Input:**
The runtime will provide a json-serialized plugin configuration object (defined below) on standard in.
Optional environment parameters:
- `CNI_PATH`
#### `VERSION`: probe plugin version support
The plugin should output via standard-out a json-serialized version result object (see below).
@ -370,38 +320,6 @@ A json-serialized object, with the following key:
Required environment parameters:
- `CNI_COMMAND`
#### `GC`: Clean up any stale resources
The GC command provides a way for runtimes to specify the expected set of attachments to a network.
The network plugin may then remove any resources related to attachments that do not exist in this set.
Resources may, for example, include:
- IPAM reservations
- Firewall rules
A plugin SHOULD remove as many stale resources as possible. For example, a plugin should remove any IPAM reservations associated with attachments not in the provided list. The plugin MAY assume that the isolation domain (e.g. network namespace) has been deleted, and thus any resources (e.g. network interfaces) therein have been removed.
Plugins should generally complete a `GC` action without error. If an error is encountered, a plugin should continue; removing as many resources as possible, and report the errors back to the runtime.
Plugins MUST, additionally, forward any GC calls to delegated plugins they are configured to use (see section 4).
The runtime MUST NOT use GC as a substitute for DEL. Plugins may be unable to clean up some resources from GC that they would have been able to clean up from DEL.
**Input:**
The runtime must provide a JSON-serialized plugin configuration object (defined below) on standard in. It contains an additional key;
- `cni.dev/valid-attachments` (array of objects): The list of **still valid** attachments to this network:
- `containerID` (string): the value of CNI_CONTAINERID as provided during the CNI ADD operation
- `ifname` (string): the value of CNI_IFNAME as provided during the CNI ADD operation
Required environment parameters:
- `CNI_COMMAND`
- `CNI_PATH`
**Output:**
No output on success, ["error" result structure](#Error) on error.
## Section 3: Execution of Network Configurations
@ -413,18 +331,16 @@ The operation of a network configuration on a container is called an _attachment
- The container runtime must create a new network namespace for the container before invoking any plugins.
- The container runtime must not invoke parallel operations for the same container, but is allowed to invoke parallel operations for different containers. This includes across multiple attachments.
- **Exception**: The runtime must exclusively execute either _gc_ or _add_ and _delete_. The runtime must ensure that no _add_ or _delete_ operations are in progress before executing _gc_, and must wait for _gc_ to complete before issuing new _add_ or _delete_ commands.
- Plugins must handle being executed concurrently across different containers. If necessary, they must implement locking on shared resources (e.g. IPAM databases).
- The container runtime must ensure that _add_ is eventually followed by a corresponding _delete_. The only exception is in the event of catastrophic failure, such as node loss. A _delete_ must still be executed even if the _add_ fails.
- _delete_ may be followed by additional _deletes_.
- The network configuration should not change between _add_ and _delete_.
- The network configuration should not change between _attachments_.
- The container runtime is responsible for cleanup of the container's network namespace.
### Attachment Parameters
While a network configuration should not change between _attachments_, there are certain parameters supplied by the container runtime that are per-attachment. They are:
- **Container ID:** A unique plaintext identifier for a container, allocated by the runtime. Must not be empty. Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-). During execution, always set as the `CNI_CONTAINERID` parameter.
- **Container ID:** A unique plaintext identifier for a container, allocated by the runtime. Must not be empty. Must start with a alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-). During execution, always set as the `CNI_CONTAINERID` parameter.
- **Namespace**: A reference to the container's "isolation domain". If using network namespaces, then a path to the network namespace (e.g. `/run/netns/[nsname]`). During execution, always set as the `CNI_NETNS` parameter.
- **Container interface name**: Name of the interface to create inside the container. During execution, always set as the `CNI_IFNAME` parameter.
- **Generic Arguments**: Extra arguments, in the form of key-value string pairs, that are relevant to a specific attachment. During execution, always set as the `CNI_ARGS` parameter.
@ -471,47 +387,30 @@ For every plugin defined in the `plugins` key of the network configuration,
If all plugins return success, return success to the caller.
### Garbage-collecting a network
The runtime may also ask every plugin in a network configuration to clean up any stale resources via the _GC_ command.
When garbage-collecting a configuration, there are no [Attachment Parameters](#attachment-parameters).
For every plugin defined in the `plugins` key of the network configuration,
1. Look up the executable specified in the `type` field. If this does not exist, then this is an error.
2. Derive request configuration from the plugin configuration.
3. Execute the plugin binary, with `CNI_COMMAND=GC`. Supply the derived configuration via standard in.
4. If the plugin returns an error, **continue** with execution, returning all errors to the caller.
If all plugins return success, return success to the caller.
### Deriving request configuration from plugin configuration
### Deriving execution configuration from plugin configuration
The network configuration format (which is a list of plugin configurations to execute) must be transformed to a format understood by the plugin (which is a single plugin configuration). This section describes that transformation.
The request configuration for a single plugin invocation is also JSON. It consists of the plugin configuration, primarily unchanged except for the specified additions and removals.
The execution configuration for a single plugin invocation is also JSON. It consists of the plugin configuration, primarily unchanged except for the specified additions and removals.
The following fields are always to be inserted into the request configuration by the runtime:
- `cniVersion`: the protocol version selected by the runtime - the string "1.1.0"
The following fields must be inserted into the execution configuration by the runtime:
- `cniVersion`: taken from the `cniVersion` field of the network configuration
- `name`: taken from the `name` field of the network configuration
- `runtimeConfig`: A JSON object, consisting of the union of capabilities provided by the plugin and requested by the runtime (more detail below)
- `prevResult`: A JSON object, consisting of the result type returned by the "previous" plugin. The meaning of "previous" is defined by the specific operation (_add_, _delete_, or _check_).
The following fields must be **removed** by the runtime:
- `capabilities`
For attachment-specific operations (ADD, DEL, CHECK), additional field requirements apply:
- `runtimeConfig`: the runtime must insert an object consisting of the union of capabilities provided by the plugin and requested by the runtime (more details below).
- `prevResult`: the runtime must insert consisting of the result type returned by the "previous" plugin. The meaning of "previous" is defined by the specific operation (_add_, _delete_, or _check_). This field must not be set for the first _add_ in a chain.
- `capabilities`: must not be set
For GC operations:
- `cni.dev/valid-attachments`: as specified in section 2.
All other fields not prefixed with `cni.dev/` should be passed through unaltered.
All other fields should be passed through unaltered.
#### Deriving `runtimeConfig`
Whereas CNI_ARGS are provided to all plugins, with no indication if they are going to be consumed, _Capability arguments_ need to be declared explicitly in configuration. The runtime, thus, can determine if a given network configuration supports a specific _capability_. Capabilities are not defined by the specification - rather, they are documented [conventions](CONVENTIONS.md).
As defined in section 1, the plugin configuration includes an optional key, `capabilities`. This example shows a plugin that supports the `portMapping` capability:
As defined in section 1, the plugin configuration includes an optional key, `capabilities`. This example shows a that supports the `portMapping` capability:
```json
{
{
"type": "myPlugin",
"capabilities": {
"portMappings": true
@ -558,26 +457,29 @@ When a plugin executes a delegated plugin, it should:
- Execute that plugin with the same environment and configuration that it received.
- Ensure that the delegated plugin's stderr is output to the calling plugin's stderr.
If a plugin is executed with `CNI_COMMAND=CHECK`, `DEL`, or `GC`, it must also execute any delegated plugins. If any of the delegated plugins return error, error should be returned by the upper plugin.
If a plugin is executed with `CNI_COMMAND=CHECK` or `DEL`, it must also execute any delegated plugins. If any of the delgated plugins return error, error should be returned by the upper plugin.
If, on `ADD`, a delegated plugin fails, the "upper" plugin should execute again with `DEL` before returning failure.
## Section 5: Result Types
For certain operations, plugins must output result information. The output should be serialized as JSON on standard out.
Plugins can return one of three result types:
### ADD Success
- _Success_ (or _Abbreviated Success_)
- _Error_
- _Version
### Success
Plugins provided a `prevResult` key as part of their request configuration must output it as their result, with any possible modifications made by that plugin included. If a plugin makes no changes that would be reflected in the _Success result_ type, then it must output a result equivalent to the provided `prevResult`.
Plugins must output a JSON object with the following keys upon a successful `ADD` operation:
- `cniVersion`: The same version supplied on input - the string "1.1.0"
- `cniVersion`: The same version supplied on input - the string "1.0.0"
- `interfaces`: An array of all interfaces created by the attachment, including any host-level interfaces:
- `name` (string): The name of the interface.
- `mac` (string): The hardware address of the interface (if applicable).
- `mtu`: (uint) The MTU of the interface (if applicable).
- `sandbox` (string): The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
- `socketPath` (string, optional): An absolute path to a socket file corresponding to this interface, if applicable.
- `pciID` (string, optional): The platform-specific identifier of the PCI device corresponding to this interface, if applicable.
- `name`: The name of the interface.
- `mac`: The hardware address of the interface (if applicable).
- `sandbox`: The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
- `ips`: IPs assigned by this attachment. Plugins may include IPs assigned external to the container.
- `address` (string): an IP address in CIDR notation (eg "192.168.1.3/24").
- `gateway` (string): the default gateway for this subnet, if one exists.
@ -585,26 +487,55 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
- `routes`: Routes created by this attachment:
- `dst`: The destination of the route, in CIDR notation
- `gw`: The next hop address. If unset, a value in `gateway` in the `ips` array may be used.
- `mtu` (uint): The MTU (Maximum transmission unit) along the path to the destination.
- `advmss` (uint): The MSS (Maximal Segment Size) to advertise to these destinations when establishing TCP connections.
- `priority` (uint): The priority of route, lower is higher.
- `table` (uint): The table to add the route to.
- `scope` (uint): The scope of the destinations covered by the route prefix (global (0), link (253), host (254)).
- `dns`: a dictionary consisting of DNS configuration information
- `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
- `domain` (string): the local domain used for short hostname lookups.
- `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers.
- `options` (list of strings): list of options that can be passed to the resolver.
Plugins provided a `prevResult` key as part of their request configuration must output it as their result, with any possible modifications made by that plugin included. If a plugin makes no changes that would be reflected in the _Success result_ type, then it must output a result equivalent to the provided `prevResult`.
#### Delegated plugins (IPAM)
Delegated plugins may omit irrelevant sections.
Delegated IPAM plugins must return an abbreviated _Success_ object. Specifically, it is missing the `interfaces` array, as well as the `interface` entry in `ips`.
### VERSION Success
### Error
Plugins should output a JSON object with the following keys if they encounter an error:
- `cniVersion`: The same value as provided by the configuration
- `code`: A numeric error code, see below for reserved codes.
- `msg`: A short message characterizing the error.
- `details`: A longer message describing the error.
Example:
```json
{
"cniVersion": "1.0.0",
"code": 7,
"msg": "Invalid Configuration",
"details": "Network 192.168.0.0/31 too small to allocate from."
}
```
Error codes 0-99 are reserved for well-known errors. Values of 100+ can be freely used for plugin specific errors.
Error Code|Error Description
---|---
`1`|Incompatible CNI version
`2`|Unsupported field in network configuration. The error message must contain the key and value of the unsupported field.
`3`|Container unknown or does not exist. This error implies the runtime does not need to perform any container network cleanup (for example, calling the `DEL` action on the container).
`4`|Invalid necessary environment variables, like CNI_COMMAND, CNI_CONTAINERID, etc. The error message must contain the names of invalid variables.
`5`|I/O failure. For example, failed to read network config bytes from stdin.
`6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
`7`|Invalid network config. If some validations on network configs do not pass, this error will be raised.
`11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
In addition, stderr can be used for unstructured output such as logs.
### Version
Plugins must output a JSON object with the following keys upon a `VERSION` operation:
@ -619,62 +550,9 @@ Example:
}
```
### Error
Plugins should output a JSON object with the following keys if they encounter an error:
- `cniVersion`: The protocol version in use - "1.1.0"
- `code`: A numeric error code, see below for reserved codes.
- `msg`: A short message characterizing the error.
- `details`: A longer message describing the error.
Example:
```json
{
"cniVersion": "1.1.0",
"code": 7,
"msg": "Invalid Configuration",
"details": "Network 192.168.0.0/31 too small to allocate from."
}
```
Error codes 0-99 are reserved for well-known errors. Values of 100+ can be freely used for plugin specific errors.
Error Code|Error Description
---|---
`1`|Incompatible CNI version
`2`|Unsupported field in network configuration. The error message must contain the key and value of the unsupported field.
`3`|Container unknown or does not exist. This error implies the runtime does not need to perform any container network cleanup (for example, calling the `DEL` action on the container).
`4`|Invalid necessary environment variables, like CNI_COMMAND, CNI_CONTAINERID, etc. The error message must contain the names of invalid variables.
`5`|I/O failure. For example, failed to read network config bytes from stdin.
`6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
`7`|Invalid network config. If some validations on network configs do not pass, this error will be raised.
`11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
`50`|The plugin is not available (i.e. cannot service `ADD` requests)
`51`|The plugin is not available, and existing containers in the network may have limited connectivity.
In addition, stderr can be used for unstructured output such as logs.
### Version
Plugins must output a JSON object with the following keys upon a `VERSION` operation:
- `cniVersion`: The value of `cniVersion` specified on input
- `supportedVersions`: A list of supported specification versions
Example:
```json
{
"cniVersion": "1.1.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0" ]
}
```
## Appendix: Examples
We assume the network configuration [shown above](#Example-configuration) in section 1. For this attachment, the runtime produces `portmap` and `mac` capability args, along with the generic argument "argA=foo".
The examples uses `CNI_IFNAME=eth0`.
### Add example
@ -685,7 +563,7 @@ The container runtime would perform the following steps for the `add` operation.
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
@ -724,7 +602,7 @@ The `host-local` plugin returns the following result:
}
```
The bridge plugin returns the following result, configuring the interface according to the delegated IPAM configuration:
The plugin returns the following result, configuring the interface according to the delegated IPAM configuration:
```json
{
@ -765,7 +643,7 @@ The bridge plugin returns the following result, configuring the interface accord
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
@ -850,7 +728,7 @@ The plugin returns the following result. Note that the **mac** has changed.
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
@ -898,13 +776,13 @@ The `portmap` plugin outputs the exact same result as that returned by `bridge`,
### Check example
Given the previous _Add_, the container runtime would perform the following steps for the _Check_ action:
Given the previous _Add_, the container runtime would perform the following steps for the _Check_ action.:
1) First call the `bridge` plugin with the following request configuration, including the `prevResult` field containing the final JSON response from the _Add_ operation, **including the changed mac**. `CNI_COMMAND=CHECK`
1) first call the `bridge` plugin with the following request configuration, including the `prevResult` field containing the final JSON response from the _Add_ operation, **including the changed mac**. `CNI_COMMAND=CHECK`
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
@ -956,11 +834,11 @@ The `bridge` plugin, as it delegates IPAM, calls `host-local`, `CNI_COMMAND=CHEC
Assuming the `bridge` plugin is satisfied, it produces no output on standard out and exits with a 0 return code.
2) Next call the `tuning` plugin with the following request configuration:
2) next call the `tuning` plugin with the following request configuration:
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
@ -1010,7 +888,7 @@ Likewise, the `tuning` plugin exits indicating success.
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
@ -1063,7 +941,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
@ -1111,7 +989,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
@ -1159,7 +1037,7 @@ Note that plugins are executed in reverse order from the _Add_ and _Check_ actio
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",

View File

@ -7,12 +7,8 @@ add or remove an interface in an already-created network namespace.
* `NETCONFPATH`: This environment variable needs to be set to a
directory. It defaults to `/etc/cni/net.d`. The `cnitool` searches
for CNI configuration files in this directory 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
for CNI configuration files in this directory with the extension
`*.conf` or `*.json`. It loads all the CNI configuration files in
this directory and if it finds a CNI configuration with the `network
name` given to the cnitool it returns the corresponding CNI
configuration, else it returns `nil`.

View File

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

20
go.mod
View File

@ -1,22 +1,8 @@
module github.com/containernetworking/cni
go 1.21
go 1.14
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
github.com/onsi/ginkgo v1.13.0
github.com/onsi/gomega v1.10.1
)

92
go.sum
View File

@ -1,36 +1,60 @@
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=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

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

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
@ -27,23 +28,21 @@ import (
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
type pluginInfo struct {
debugFilePath string
commandFilePath string
debug *noop_debug.Debug
config string
stdinData []byte
debugFilePath string
debug *noop_debug.Debug
config string
stdinData []byte
}
type portMapping struct {
@ -62,16 +61,11 @@ func stringInList(s string, list []string) bool {
}
func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath := debugFile.Name()
commandLog, err := os.CreateTemp("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(commandLog.Close()).To(Succeed())
commandFilePath := commandLog.Name()
debug := &noop_debug.Debug{
ReportResult: result,
}
@ -85,7 +79,6 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
}
if injectDebugFilePath {
config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath)
config += fmt.Sprintf(`, "commandLog": %q`, commandFilePath)
}
if len(capabilities) > 0 {
config += `, "capabilities": {`
@ -122,11 +115,10 @@ func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePa
Expect(err).NotTo(HaveOccurred())
return pluginInfo{
debugFilePath: debugFilePath,
commandFilePath: commandFilePath,
debug: debug,
config: config,
stdinData: stdinData,
debugFilePath: debugFilePath,
debug: debug,
config: config,
stdinData: stdinData,
}
}
@ -161,7 +153,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
var err error
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred())
})
@ -176,19 +168,19 @@ var _ = Describe("Invoking plugins", func() {
pluginConfig []byte
cniConfig *libcni.CNIConfig
runtimeConfig *libcni.RuntimeConf
netConfig *libcni.PluginConfig
netConfig *libcni.NetworkConfig
ctx context.Context
)
BeforeEach(func() {
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name()
debug = &noop_debug.Debug{
ReportResult: `{
"cniVersion": "` + version.Current() + `",
"cniVersion": "1.0.0",
"ips": [{"address": "10.1.2.3/24"}],
"dns": {}
}`,
@ -204,7 +196,7 @@ var _ = Describe("Invoking plugins", func() {
"somethingElse": true,
"noCapability": false
}
}`, version.Current()))
}`, current.ImplementedSpecVersion))
netConfig, err = libcni.ConfFromBytes(pluginConfig)
Expect(err).NotTo(HaveOccurred())
@ -246,12 +238,12 @@ var _ = Describe("Invoking plugins", func() {
// We expect runtimeConfig keys only for portMappings and somethingElse
rawRc := conf["runtimeConfig"]
rc, ok := rawRc.(map[string]interface{})
Expect(ok).To(BeTrue())
Expect(ok).To(Equal(true))
expectedKeys := []string{"portMappings", "somethingElse"}
Expect(rc).To(HaveLen(len(expectedKeys)))
Expect(len(rc)).To(Equal(len(expectedKeys)))
for _, key := range expectedKeys {
_, ok := rc[key]
Expect(ok).To(BeTrue())
Expect(ok).To(Equal(true))
}
})
@ -287,6 +279,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred())
Expect(caps).To(ConsistOf("portMappings", "somethingElse"))
})
})
Describe("Invoking a single plugin", func() {
@ -295,7 +288,7 @@ var _ = Describe("Invoking plugins", func() {
debug *noop_debug.Debug
pluginConfig string
cniConfig *libcni.CNIConfig
netConfig *libcni.PluginConfig
netConfig *libcni.NetworkConfig
runtimeConfig *libcni.RuntimeConf
ctx context.Context
@ -303,14 +296,14 @@ var _ = Describe("Invoking plugins", func() {
)
BeforeEach(func() {
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name()
debug = &noop_debug.Debug{
ReportResult: `{
"cniVersion": "` + version.Current() + `",
"cniVersion": "1.0.0",
"ips": [{"address": "10.1.2.3/24"}],
"dns": {}
}`,
@ -328,7 +321,7 @@ var _ = Describe("Invoking plugins", func() {
"some-key": "some-value",
"cniVersion": "%s",
"capabilities": { "portMappings": true }
}`, version.Current())
}`, current.ImplementedSpecVersion)
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
Expect(err).NotTo(HaveOccurred())
@ -418,27 +411,6 @@ var _ = Describe("Invoking plugins", func() {
returnedJson, err := json.Marshal(result)
Expect(err).NotTo(HaveOccurred())
Expect(cachedJson).To(MatchJSON(returnedJson))
// Ensure the cached attachments matches requested one
for _, containerID := range []string{"", runtimeConfig.ContainerID} {
expected, err := json.Marshal(libcni.NetworkAttachment{
ContainerID: runtimeConfig.ContainerID,
Network: netConfig.Network.Name,
NetNS: runtimeConfig.NetNS,
IfName: runtimeConfig.IfName,
Config: netConfig.Bytes,
CniArgs: runtimeConfig.Args,
CapabilityArgs: runtimeConfig.CapabilityArgs,
})
Expect(err).NotTo(HaveOccurred())
attachments, err := cniConfig.GetCachedAttachments(containerID)
Expect(err).NotTo(HaveOccurred())
if Expect(len(attachments)).To(Equal(1)) {
json, err := json.Marshal(attachments[0])
Expect(err).NotTo(HaveOccurred())
Expect(json).To(MatchJSON(expected))
}
}
})
Context("when finding the plugin fails", func() {
@ -469,7 +441,7 @@ var _ = Describe("Invoking plugins", func() {
// Make the results directory inaccessible by making it a
// file instead of a directory
tmpPath := filepath.Join(cacheDirPath, "results")
err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
Expect(err).NotTo(HaveOccurred())
result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
@ -482,13 +454,14 @@ var _ = Describe("Invoking plugins", func() {
Describe("CheckNetwork", func() {
It("executes the plugin with command CHECK", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
cachedJson := `{
"cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}]
"cniVersion": "1.0.0",
"ips": [{"address": "10.1.2.3/24"}],
"dns": {}
}`
err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -543,7 +516,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
@ -568,11 +541,11 @@ var _ = Describe("Invoking plugins", func() {
Context("containing only a cached result", func() {
It("only passes a prevResult to the plugin", func() {
err := os.WriteFile(cacheFile, []byte(`{
"cniVersion": "`+version.Current()+`",
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "1.0.0",
"ips": [{"address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -606,7 +579,7 @@ var _ = Describe("Invoking plugins", func() {
}`))
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
Expect(err).NotTo(HaveOccurred())
debug.ReportResult = ipResult
@ -626,13 +599,13 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
Context("is invalid JSON", func() {
It("returns an error", func() {
err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -642,11 +615,11 @@ var _ = Describe("Invoking plugins", func() {
Context("version doesn't match the config version", func() {
It("succeeds when the cached result can be converted", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -654,11 +627,11 @@ var _ = Describe("Invoking plugins", func() {
})
It("returns an error when the cached result cannot be converted", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.4567.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
@ -671,13 +644,14 @@ var _ = Describe("Invoking plugins", func() {
Describe("DelNetwork", func() {
It("executes the plugin with command DEL", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
cachedJson := `{
"cniVersion": "` + version.Current() + `",
"ips": [{"address": "10.1.2.3/24"}]
"cniVersion": "1.0.0",
"ips": [{"address": "10.1.2.3/24"}],
"dns": {}
}`
err = os.WriteFile(cacheFile, []byte(cachedJson), 0o600)
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -714,13 +688,6 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred())
Expect(cachedConfig).To(BeNil())
Expect(newRt).To(BeNil())
// Ensure the cached attachments no longer exist
for _, containerID := range []string{"", runtimeConfig.ContainerID} {
attachments, err := cniConfig.GetCachedAttachments(containerID)
Expect(err).NotTo(HaveOccurred())
Expect(attachments).To(BeEmpty())
}
})
Context("when finding the plugin fails", func() {
@ -750,21 +717,21 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
resultCacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
It("deletes the cached result and config after the first DEL", func() {
err := os.WriteFile(resultCacheFile, []byte(`{
err := ioutil.WriteFile(resultCacheFile, []byte(`{
"cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
_, err = os.ReadFile(resultCacheFile)
_, err = ioutil.ReadFile(resultCacheFile)
Expect(err).To(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -777,17 +744,17 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
Context("less than 0.4.0", func() {
It("does not pass a prevResult to the plugin", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
// Generate plugin config with older version
@ -809,11 +776,11 @@ var _ = Describe("Invoking plugins", func() {
Context("equal to 0.4.0", func() {
It("passes a prevResult to the plugin", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.4.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -832,13 +799,13 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
Context("result is invalid JSON", func() {
It("returns an error", func() {
err := os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -848,11 +815,11 @@ var _ = Describe("Invoking plugins", func() {
Context("result version doesn't match the config version", func() {
It("succeeds when the cached result can be converted", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.3.1",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -860,11 +827,11 @@ var _ = Describe("Invoking plugins", func() {
})
It("returns an error when the cached result cannot be converted", func() {
err := os.WriteFile(cacheFile, []byte(`{
err := ioutil.WriteFile(cacheFile, []byte(`{
"cniVersion": "0.4567.0",
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`), 0o600)
}`), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
@ -881,7 +848,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(versionInfo).NotTo(BeNil())
Expect(versionInfo.SupportedVersions()).To(Equal([]string{
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0",
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0",
}))
})
@ -969,8 +936,8 @@ var _ = Describe("Invoking plugins", func() {
"otherCapability": capabilityArgs["otherCapability"],
}
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "ips":[{"address": "10.1.2.3/24"}]}`, version.Current())
netConfigList, plugins = makePluginList(version.Current(), ipResult, rcMap)
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
netConfigList, plugins = makePluginList(current.ImplementedSpecVersion, ipResult, rcMap)
ctx = context.TODO()
})
@ -1196,7 +1163,7 @@ var _ = Describe("Invoking plugins", func() {
})
It("should not have written cache files", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
_, err := os.ReadFile(resultCacheFile)
_, err := ioutil.ReadFile(resultCacheFile)
Expect(err).To(HaveOccurred())
})
})
@ -1206,7 +1173,7 @@ var _ = Describe("Invoking plugins", func() {
// Make the results directory inaccessible by making it a
// file instead of a directory
tmpPath := filepath.Join(cacheDirPath, "results")
err := os.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0o600)
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
Expect(err).NotTo(HaveOccurred())
result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
@ -1219,9 +1186,9 @@ var _ = Describe("Invoking plugins", func() {
Describe("CheckNetworkList", func() {
It("executes all plugins with command CHECK", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(cacheFile, []byte(ipResult), 0o600)
err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
@ -1263,7 +1230,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
@ -1274,7 +1241,7 @@ var _ = Describe("Invoking plugins", func() {
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
@ -1313,7 +1280,6 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command"))
Expect(errors.Is(err, libcni.ErrorCheckNotSupp)).To(BeTrue())
})
})
})
@ -1343,9 +1309,9 @@ var _ = Describe("Invoking plugins", func() {
Context("when the cached result is invalid", func() {
It("returns an error", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
@ -1387,7 +1353,7 @@ var _ = Describe("Invoking plugins", func() {
BeforeEach(func() {
cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
})
@ -1398,7 +1364,7 @@ var _ = Describe("Invoking plugins", func() {
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
@ -1430,7 +1396,7 @@ var _ = Describe("Invoking plugins", func() {
"ips": [{"version": "4", "address": "10.1.2.3/24"}],
"dns": {}
}`
err := os.WriteFile(cacheFile, []byte(ipResult), 0o600)
err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
Expect(err).NotTo(HaveOccurred())
netConfigList, plugins = makePluginList("0.3.1", ipResult, rcMap)
@ -1474,15 +1440,15 @@ var _ = Describe("Invoking plugins", func() {
})
Context("when the cached result is invalid", func() {
It("tolerates the error", func() {
It("returns an error", func() {
cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(cacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
Expect(err).NotTo(HaveOccurred())
err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
})
})
})
@ -1505,128 +1471,6 @@ var _ = Describe("Invoking plugins", func() {
Expect(err).To(MatchError("[plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\"]"))
})
})
Describe("GCNetworkList", func() {
It("issues a DEL and GC as necessary", func() {
By("doing a CNI ADD")
_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
By("Issuing a GC with disableGC=true")
netConfigList.DisableGC = true
gcargs := &libcni.GCArgs{}
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
Expect(err).NotTo(HaveOccurred())
commands, err := noop_debug.ReadCommandLog(plugins[0].commandFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(commands).To(HaveLen(1)) // ADD
By("Issuing a GC with valid networks")
netConfigList.DisableGC = false
gcargs = &libcni.GCArgs{
ValidAttachments: []types.GCAttachment{{
ContainerID: runtimeConfig.ContainerID,
IfName: runtimeConfig.IfName,
}},
}
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
Expect(err).NotTo(HaveOccurred())
By("Issuing a GC with no valid networks")
gcargs.ValidAttachments = nil
err = cniConfig.GCNetworkList(ctx, netConfigList, gcargs)
Expect(err).NotTo(HaveOccurred())
commands, err = noop_debug.ReadCommandLog(plugins[0].commandFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(commands).To(HaveLen(4))
validations := []struct {
name string
fn func(entry noop_debug.CmdLogEntry)
}{
{
name: "ADD",
fn: func(entry noop_debug.CmdLogEntry) {
Expect(entry.CmdArgs.ContainerID).To(Equal(runtimeConfig.ContainerID))
Expect(entry.CmdArgs.IfName).To(Equal(runtimeConfig.IfName))
},
},
{
name: "GC",
fn: func(entry noop_debug.CmdLogEntry) {
var conf struct {
Attachments []map[string]string `json:"cni.dev/valid-attachments"`
}
err = json.Unmarshal(entry.CmdArgs.StdinData, &conf)
Expect(err).NotTo(HaveOccurred())
Expect(conf.Attachments).To(HaveLen(1))
Expect(conf.Attachments[0]).To(Equal(map[string]string{"containerID": runtimeConfig.ContainerID, "ifname": runtimeConfig.IfName}))
},
},
{
name: "DEL",
fn: func(entry noop_debug.CmdLogEntry) {
Expect(entry.CmdArgs.ContainerID).To(Equal(runtimeConfig.ContainerID))
Expect(entry.CmdArgs.IfName).To(Equal(runtimeConfig.IfName))
},
},
{
name: "GC",
fn: func(entry noop_debug.CmdLogEntry) {
var conf struct {
Attachments []map[string]string `json:"cni.dev/valid-attachments"`
}
err = json.Unmarshal(entry.CmdArgs.StdinData, &conf)
Expect(err).NotTo(HaveOccurred())
Expect(conf.Attachments).To(BeEmpty())
},
},
}
for i, c := range validations {
Expect(commands[i].Command).To(Equal(c.name))
c.fn(commands[i])
}
})
})
Describe("GetStatusNetworkList", func() {
It("issues a STATUS request", func() {
netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap)
err := cniConfig.GetStatusNetworkList(ctx, netConfigList)
Expect(err).NotTo(HaveOccurred())
debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("STATUS"))
})
It("correctly reports an error", func() {
netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap)
plugins[1].debug.ReportError = "plugin error: banana"
plugins[1].debug.ReportErrorCode = 50
Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
err := cniConfig.GetStatusNetworkList(ctx, netConfigList)
Expect(err).To(HaveOccurred())
var eerr *types.Error
Expect(errors.As(err, &eerr)).To(BeTrue())
Expect(eerr.Code).To(Equal(uint(50)))
debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("STATUS"))
debug, err = noop_debug.ReadDebug(plugins[1].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal("STATUS"))
debug, err = noop_debug.ReadDebug(plugins[2].debugFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(debug.Command).To(Equal(""))
})
})
})
Describe("Invoking a sleep plugin", func() {
@ -1636,13 +1480,13 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string
pluginConfig string
cniConfig *libcni.CNIConfig
netConfig *libcni.PluginConfig
netConfig *libcni.NetworkConfig
runtimeConfig *libcni.RuntimeConf
netConfigList *libcni.NetworkConfigList
)
BeforeEach(func() {
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name()
@ -1662,7 +1506,7 @@ var _ = Describe("Invoking plugins", func() {
"some-key": "some-value",
"cniVersion": "%s",
"capabilities": { "portMappings": true }
}`, version.Current())
}`, current.ImplementedSpecVersion)
cniBinPath = filepath.Dir(pluginPaths["sleep"])
cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
@ -1689,10 +1533,11 @@ var _ = Describe("Invoking plugins", func() {
"plugins": [
%s
]
}`, version.Current(), pluginConfig))
}`, current.ImplementedSpecVersion, pluginConfig))
netConfigList, err = libcni.ConfListFromBytes(configList)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
@ -1708,6 +1553,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1719,6 +1565,7 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1730,6 +1577,7 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1742,6 +1590,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1753,6 +1602,7 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1765,6 +1615,7 @@ var _ = Describe("Invoking plugins", func() {
Expect(result).To(BeNil())
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1776,6 +1627,7 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1787,6 +1639,7 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
@ -1798,8 +1651,10 @@ var _ = Describe("Invoking plugins", func() {
cancel()
Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
})
})
})
})
Describe("Cache operations", func() {
@ -1809,7 +1664,7 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string
pluginConfig string
cniConfig *libcni.CNIConfig
netConfig *libcni.PluginConfig
netConfig *libcni.NetworkConfig
runtimeConfig *libcni.RuntimeConf
ctx context.Context
@ -1823,7 +1678,7 @@ var _ = Describe("Invoking plugins", func() {
netNS := "/some/netns/path"
BeforeEach(func() {
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFilePath = debugFile.Name()
@ -1832,7 +1687,7 @@ var _ = Describe("Invoking plugins", func() {
ReportResult: fmt.Sprintf(`{
"cniVersion": "%s",
"ips": [{"version": "4", "address": "%s"}]
}`, version.Current(), firstIP),
}`, current.ImplementedSpecVersion, firstIP),
}
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
@ -1841,7 +1696,7 @@ var _ = Describe("Invoking plugins", func() {
"type": "noop",
"name": "%s",
"cniVersion": "%s"
}`, netName, version.Current())
}`, netName, current.ImplementedSpecVersion)
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
Expect(err).NotTo(HaveOccurred())
@ -1865,16 +1720,16 @@ var _ = Describe("Invoking plugins", func() {
debug.ReportResult = fmt.Sprintf(`{
"cniVersion": "%s",
"ips": [{"version": "4", "address": "%s"}]
}`, version.Current(), secondIP)
}`, current.ImplementedSpecVersion, secondIP)
Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
runtimeConfig.IfName = secondIfname
_, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
Expect(err).NotTo(HaveOccurred())
resultsDir := filepath.Join(cacheDirPath, "results")
files, err := os.ReadDir(resultsDir)
files, err := ioutil.ReadDir(resultsDir)
Expect(err).NotTo(HaveOccurred())
Expect(files).To(HaveLen(2))
Expect(len(files)).To(Equal(2))
var foundFirst, foundSecond bool
for _, f := range files {
type cachedConfig struct {
@ -1884,7 +1739,7 @@ var _ = Describe("Invoking plugins", func() {
NetworkName string `json:"networkName"`
}
data, err := os.ReadFile(filepath.Join(resultsDir, f.Name()))
data, err := ioutil.ReadFile(filepath.Join(resultsDir, f.Name()))
Expect(err).NotTo(HaveOccurred())
cc := &cachedConfig{}
err = json.Unmarshal(data, cc)
@ -1931,14 +1786,14 @@ var _ = Describe("Invoking plugins", func() {
Context("when the RuntimeConf is incomplete", func() {
var (
testRt *libcni.RuntimeConf
testNetConf *libcni.PluginConfig
testNetConf *libcni.NetworkConfig
testNetConfList *libcni.NetworkConfigList
)
BeforeEach(func() {
testRt = &libcni.RuntimeConf{}
testNetConf = &libcni.PluginConfig{
Network: &types.PluginConf{},
testNetConf = &libcni.NetworkConfig{
Network: &types.NetConf{},
}
testNetConfList = &libcni.NetworkConfigList{}
})
@ -1994,10 +1849,10 @@ var _ = Describe("Invoking plugins", func() {
Context("is invalid JSON", func() {
It("returns an error", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0o600)
err = ioutil.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
Expect(err).NotTo(HaveOccurred())
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
@ -2009,7 +1864,7 @@ var _ = Describe("Invoking plugins", func() {
Context("is missing", func() {
It("returns no error", func() {
resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0o700)
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
Expect(err).NotTo(HaveOccurred())
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
@ -2019,5 +1874,6 @@ var _ = Describe("Invoking plugins", func() {
})
})
})
})
})

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
#!/bin/bash
set -e
GOLANGCI_LINT_VERSION="v1.57.1"
go install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}"

View File

@ -1,14 +0,0 @@
.PHONY: lint
lint: golangci/install golangci/lint
.PHONY: golangci/install
golangci/install:
./mk/dependencies/golangci.sh
.PHONY: golangci/lint
golangci/lint:
golangci-lint run --verbose
.PHONY: golangci/fix
golangci/fix:
golangci-lint run --verbose --fix

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +0,0 @@
// 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

@ -1,50 +0,0 @@
// 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
}

View File

@ -1,21 +0,0 @@
// Copyright 2022 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ns
import "github.com/containernetworking/cni/pkg/types"
func CheckNetNS(nsPath string) (bool, *types.Error) {
return false, nil
}

View File

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

View File

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

View File

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

View File

@ -49,9 +49,6 @@ func NewResult(data []byte) (types.Result, error) {
}
for _, v := range supportedVersions {
if result.CNIVersion == v {
if result.CNIVersion == "" {
result.CNIVersion = "0.1.0"
}
return result, nil
}
}

View File

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

View File

@ -19,12 +19,11 @@ import (
"fmt"
"net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/create"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
@ -145,13 +144,4 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
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"))
})
})

View File

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

View File

@ -16,16 +16,16 @@ package types040_test
import (
"encoding/json"
"io"
"io/ioutil"
"net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func testResult() *types040.Result {
@ -99,7 +99,7 @@ var _ = Describe("040 types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
@ -169,7 +169,7 @@ var _ = Describe("040 types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
@ -348,7 +348,7 @@ var _ = Describe("040 types operations", func() {
}`))
recovered := &types040.IPConfig{}
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
Expect(recovered).To(Equal(ipc))
})
@ -363,7 +363,7 @@ var _ = Describe("040 types operations", func() {
Context("when unmarshalling json fails", func() {
It("returns an error", func() {
recovered := &types040.IPConfig{}
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
})
})

View File

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

View File

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

View File

@ -16,15 +16,15 @@ package types100_test
import (
"encoding/json"
"io"
"io/ioutil"
"net"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func testResult() *current.Result {
@ -45,17 +45,15 @@ func testResult() *current.Result {
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
Expect(routegwv6).NotTo(BeNil())
// Set every field of the struct to ensure source compatibility
return &current.Result{
CNIVersion: current.ImplementedSpecVersion,
CNIVersion: "1.0.0",
Interfaces: []*current.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Mtu: 1500,
Sandbox: "/proc/3553/ns/net",
PciID: "8086:9a01",
SocketPath: "/path/to/vhost/fd",
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
},
IPs: []*current.IPConfig{
@ -84,7 +82,7 @@ func testResult() *current.Result {
}
var _ = Describe("Current types operations", func() {
It("correctly encodes a 1.1.0 Result", func() {
It("correctly encodes a 1.0.0 Result", func() {
res := testResult()
// Redirect stdout to capture JSON result
@ -98,20 +96,17 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(MatchJSON(`{
"cniVersion": "` + current.ImplementedSpecVersion + `",
"cniVersion": "1.0.0",
"interfaces": [
{
"name": "eth0",
"mac": "00:11:22:33:44:55",
"mtu": 1500,
"sandbox": "/proc/3553/ns/net",
"pciID": "8086:9a01",
"socketPath": "/path/to/vhost/fd"
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net"
}
],
"ips": [
@ -154,22 +149,6 @@ var _ = Describe("Current types operations", func() {
}`))
})
It("correctly converts a 1.0.0 Result to 1.1.0", func() {
tr, err := testResult().GetAsVersion("1.0.0")
Expect(err).NotTo(HaveOccurred())
trv1, ok := tr.(*current.Result)
Expect(ok).To(BeTrue())
// 1.0.0 and 1.1.0 should be the same except for CNI version
Expect(trv1.CNIVersion).To(Equal("1.0.0"))
// If we convert 1.0.0 back to 1.1.0 it should be identical
trv11, err := trv1.GetAsVersion("1.1.0")
Expect(err).NotTo(HaveOccurred())
Expect(trv11).To(Equal(testResult()))
})
It("correctly encodes a 0.1.0 Result", func() {
res, err := testResult().GetAsVersion("0.1.0")
Expect(err).NotTo(HaveOccurred())
@ -185,7 +164,7 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
@ -244,7 +223,7 @@ var _ = Describe("Current types operations", func() {
Expect(err).NotTo(HaveOccurred())
// parse the result
out, err := io.ReadAll(r)
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
Expect(err).NotTo(HaveOccurred())
@ -316,14 +295,14 @@ var _ = Describe("Current types operations", func() {
}`))
recovered := &current.IPConfig{}
Expect(json.Unmarshal(jsonBytes, recovered)).To(Succeed())
Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
Expect(recovered).To(Equal(ipc))
})
Context("when unmarshalling json fails", func() {
It("returns an error", func() {
recovered := &current.IPConfig{}
err := json.Unmarshal([]byte(`{"address": 5}`), recovered)
err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
})
})

View File

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

View File

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

View File

@ -15,45 +15,12 @@
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
// 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) {
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

@ -53,10 +53,6 @@ func findConverter(fromVersion, toVersion string) *converter {
// 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

View File

@ -56,72 +56,31 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil
}
// Use PluginConf instead of NetConf, the NetConf
// backwards-compat alias will be removed in a future release.
type NetConf = PluginConf
// PluginConf describes a plugin configuration for a specific network.
type PluginConf struct {
// NetConf describes a network.
type NetConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM IPAM `json:"ipam,omitempty"`
DNS DNS `json:"dns,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.
type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
DisableGC bool `json:"disableGC,omitempty"`
Plugins []*PluginConf `json:"plugins,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"`
}
// Result is an interface that provides the result of plugin execution
@ -157,48 +116,31 @@ type DNS struct {
Options []string `json:"options,omitempty"`
}
// IsEmpty returns true if DNS structure has no value, otherwise return false
func (d *DNS) IsEmpty() bool {
if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 {
return true
}
return false
}
func (d *DNS) Copy() *DNS {
if d == nil {
return nil
}
to := &DNS{Domain: d.Domain}
to.Nameservers = append(to.Nameservers, d.Nameservers...)
to.Search = append(to.Search, d.Search...)
to.Options = append(to.Options, d.Options...)
for _, ns := range d.Nameservers {
to.Nameservers = append(to.Nameservers, ns)
}
for _, s := range d.Search {
to.Search = append(to.Search, s)
}
for _, o := range d.Options {
to.Options = append(to.Options, o)
}
return to
}
type Route struct {
Dst net.IPNet
GW net.IP
MTU int
AdvMSS int
Priority int
Table *int
Scope *int
Dst net.IPNet
GW net.IP
}
func (r *Route) String() string {
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)
return fmt.Sprintf("%+v", *r)
}
func (r *Route) Copy() *Route {
@ -206,30 +148,14 @@ func (r *Route) Copy() *Route {
return nil
}
route := &Route{
Dst: r.Dst,
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Scope: r.Scope,
return &Route{
Dst: r.Dst,
GW: r.GW,
}
if r.Table != nil {
table := *r.Table
route.Table = &table
}
if r.Scope != nil {
scope := *r.Scope
route.Scope = &scope
}
return route
}
// Well known error codes
// see https://github.com/containernetworking/cni/blob/main/SPEC.md#error
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
const (
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
@ -239,10 +165,7 @@ const (
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11
ErrPluginNotAvailable uint = 50
ErrLimitedConnectivity uint = 51
ErrInternal uint = 999
)
@ -277,13 +200,8 @@ func (e *Error) Print() error {
// JSON (un)marshallable types
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
MTU int `json:"mtu,omitempty"`
AdvMSS int `json:"advmss,omitempty"`
Priority int `json:"priority,omitempty"`
Table *int `json:"table,omitempty"`
Scope *int `json:"scope,omitempty"`
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
}
func (r *Route) UnmarshalJSON(data []byte) error {
@ -294,24 +212,13 @@ func (r *Route) UnmarshalJSON(data []byte) error {
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
r.MTU = rt.MTU
r.AdvMSS = rt.AdvMSS
r.Priority = rt.Priority
r.Table = rt.Table
r.Scope = rt.Scope
return nil
}
func (r Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
MTU: r.MTU,
AdvMSS: r.AdvMSS,
Priority: r.Priority,
Table: r.Table,
Scope: r.Scope,
Dst: IPNet(r.Dst),
GW: r.GW,
}
return json.Marshal(rt)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,12 +22,15 @@ package testhelpers
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
const packageBaseName = "github.com/containernetworking/cni"
func run(cmd *exec.Cmd) error {
out, err := cmd.CombinedOutput()
if err != nil {
@ -61,16 +64,8 @@ func modInit(path, name string) error {
return run(cmd)
}
// addLibcni will execute `go mod edit -replace` to fix libcni at a specified version
func addLibcni(path, gitRef string) error {
cmd := exec.Command("go", "mod", "edit", "-replace=github.com/containernetworking/cni=github.com/containernetworking/cni@"+gitRef)
cmd.Dir = path
return run(cmd)
}
// modTidy will execute `go mod tidy` to ensure all necessary dependencies
func modTidy(path string) error {
cmd := exec.Command("go", "mod", "tidy")
cmd := exec.Command("go", "get", "github.com/containernetworking/cni@"+gitRef)
cmd.Dir = path
return run(cmd)
}
@ -78,7 +73,7 @@ func modTidy(path string) error {
// BuildAt builds the go programSource using the version of the CNI library
// at gitRef, and saves the resulting binary file at outputFilePath
func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
tempDir, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "cni-test-")
tempDir, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "cni-test-")
if err != nil {
return err
}
@ -90,15 +85,12 @@ func BuildAt(programSource []byte, gitRef string, outputFilePath string) error {
return err
}
// go get
if err := addLibcni(tempDir, gitRef); err != nil {
return err
}
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0o600); err != nil {
return err
}
if err := modTidy(tempDir); err != nil {
if err := ioutil.WriteFile(filepath.Join(tempDir, "main.go"), programSource, 0600); err != nil {
return err
}

View File

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

View File

@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@ -14,12 +14,13 @@
package testhelpers
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
@ -42,7 +43,7 @@ func main() { skel.PluginMain(c, c) }
gitRef = "f4364185253"
var err error
outputDir, err = os.MkdirTemp("", "bin")
outputDir, err = ioutil.TempDir("", "bin")
Expect(err).NotTo(HaveOccurred())
outputFilePath = filepath.Join(outputDir, "some-binary")
if runtime.GOOS == "windows" {

View File

@ -19,12 +19,13 @@ import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/types/create"
)
// Current reports the version of the CNI spec implemented by this library
func Current() string {
return "1.1.0"
return types100.ImplementedSpecVersion
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
@ -34,26 +35,8 @@ func Current() string {
//
// Any future CNI spec versions which meet this definition should be added to
// this list.
var (
Legacy = PluginSupports("0.1.0", "0.2.0")
All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0")
)
// VersionsFrom returns a list of versions starting from min, inclusive
func VersionsStartingFrom(min string) PluginInfo {
out := []string{}
// cheat, just assume ordered
ok := false
for _, v := range All.SupportedVersions() {
if !ok && v == min {
ok = true
}
if ok {
out = append(out, v)
}
}
return PluginSupports(out...)
}
var Legacy = PluginSupports("0.1.0", "0.2.0")
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0")
// 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.
@ -63,7 +46,7 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
// ParsePrevResult parses a prevResult in a NetConf structure and sets
// the NetConf's PrevResult member to the parsed Result object.
func ParsePrevResult(conf *types.PluginConf) error {
func ParsePrevResult(conf *types.NetConf) error {
if conf.RawPrevResult == nil {
return nil
}
@ -77,13 +60,13 @@ func ParsePrevResult(conf *types.PluginConf) error {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return fmt.Errorf("could not serialize prevResult: %w", err)
return fmt.Errorf("could not serialize prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = create.Create(conf.CNIVersion, resultBytes)
conf.PrevResult, err = NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return fmt.Errorf("could not parse prevResult: %w", err)
return fmt.Errorf("could not parse prevResult: %v", err)
}
return nil

View File

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

View File

@ -19,31 +19,24 @@ import (
"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"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
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"
"name": "eth0",
"mac": "00:11:22:33:44:55",
"sandbox": "/proc/3553/ns/net"
}
],
"ips": [
@ -59,7 +52,7 @@ var _ = Describe("Version operations", func() {
err := json.Unmarshal(rawBytes, &raw)
Expect(err).NotTo(HaveOccurred())
conf := &types.PluginConf{
conf := &types.NetConf{
CNIVersion: "1.0.0",
Name: "foobar",
Type: "baz",
@ -68,20 +61,19 @@ var _ = Describe("Version operations", func() {
err = version.ParsePrevResult(conf)
Expect(err).NotTo(HaveOccurred())
expectedResult := &cniv1.Result{
CNIVersion: "1.0.0",
Interfaces: []*cniv1.Interface{
expectedResult := &current.Result{
CNIVersion: current.ImplementedSpecVersion,
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",
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
},
IPs: []*cniv1.IPConfig{
IPs: []*current.IPConfig{
{
Interface: cniv1.Int(0),
Interface: current.Int(0),
Address: net.IPNet{
IP: net.ParseIP("1.2.3.30"),
Mask: net.IPv4Mask(255, 255, 255, 0),
@ -94,8 +86,8 @@ var _ = Describe("Version operations", func() {
})
It("fails if the prevResult version is unknown", func() {
conf := &types.PluginConf{
CNIVersion: version.Current(),
conf := &types.NetConf{
CNIVersion: current.ImplementedSpecVersion,
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
@ -104,12 +96,12 @@ var _ = Describe("Version operations", func() {
}
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"`))
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.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(),
conf := &types.NetConf{
CNIVersion: current.ImplementedSpecVersion,
Name: "foobar",
Type: "baz",
RawPrevResult: map[string]interface{}{
@ -122,14 +114,14 @@ var _ = Describe("Version operations", func() {
}
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\""))
Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.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(),
conf := &types.NetConf{
CNIVersion: current.ImplementedSpecVersion,
Name: "foobar",
Type: "baz",
}
@ -139,22 +131,4 @@ var _ = Describe("Version operations", func() {
Expect(conf.PrevResult).To(BeNil())
})
})
Context("version parsing", func() {
It("parses versions correctly", func() {
v1 := "1.1.0"
v2 := "1.1.1"
check := func(a, b string, want bool) {
GinkgoHelper()
gt, err := version.GreaterThan(a, b)
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(want))
}
check(v1, v2, false)
check(v2, v1, true)
check(v2, v2, false)
})
})
})

View File

@ -1,72 +0,0 @@
# 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"}
----------------------
```

View File

@ -1,15 +0,0 @@
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 => ../..

View File

@ -1,26 +0,0 @@
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=

View File

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

View File

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

View File

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

View File

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

View File

@ -16,18 +16,18 @@ package main_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("No-op plugin", func() {
@ -46,7 +46,7 @@ var _ = Describe("No-op plugin", func() {
ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"},
}
debugFile, err := os.CreateTemp("", "cni_debug")
debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred())
Expect(debugFile.Close()).To(Succeed())
debugFileName = debugFile.Name()
@ -230,7 +230,7 @@ var _ = Describe("No-op plugin", func() {
})
Context("when the ReportResult debug field is set", func() {
expectedResultString := fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
var expectedResultString = fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage)
BeforeEach(func() {
debug.ReportResult = expectedResultString

View File

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

38
scripts/release.sh Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -xe
SRC_DIR="${SRC_DIR:-$PWD}"
BUILDFLAGS="-a --ldflags '-extldflags \"-static\"'"
TAG=$(git describe --tags --dirty)
RELEASE_DIR=release-${TAG}
OUTPUT_DIR=bin
# Always clean first
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${OUTPUT_DIR}
docker run -i -v ${SRC_DIR}:/opt/src --rm golang:1.14-alpine \
/bin/sh -xe -c "\
apk --no-cache add bash tar;
cd /opt/src; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x; do \
rm -f ${OUTPUT_DIR}/* ; \
CGO_ENABLED=0 GOARCH=\$arch ./build.sh ${BUILDFLAGS}; \
for format in tgz; do \
FILENAME=cni-\$arch-${TAG}.\$format; \
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
if [ \"\$arch\" == \"amd64\" ]; then \
cp \$FILEPATH ${RELEASE_DIR}/cni-${TAG}.\$format; \
fi; \
done; \
done;
cd ${RELEASE_DIR};
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
cd ..
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"

16
test.sh
View File

@ -20,6 +20,22 @@ else
go test ${PKGS}
fi
GO_FILES=$(find . -name '*.go' -type f -print)
echo "Checking gofmt..."
fmtRes=$(gofmt -d -e -s ${GO_FILES})
if [ -n "${fmtRes}" ]; then
echo -e "go fmt checking failed:\n${fmtRes}"
exit 255
fi
echo "Checking govet..."
vetRes=$(go vet ${PKGS})
if [ -n "${vetRes}" ]; then
echo -e "go vet checking failed:\n${vetRes}"
exit 255
fi
echo "Checking license header..."
licRes=$(
for file in $(find . -type f -iname '*.go'); do