Compare commits

..

No commits in common. "main" and "v1.1.1" have entirely different histories.
main ... v1.1.1

82 changed files with 1004 additions and 2698 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.16"
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",
@ -66,10 +66,9 @@ But the runtime would fill in the mappings so the plugin itself would receive so
| infiniband guid | Dynamically assign Infiniband GUID to network interface. Runtime can pass this to plugins which need Infiniband GUID as input. | `infinibandGUID` | `GUID` (string entry). <pre> "c2:11:22:33:44:55:66:77" </pre> | none | CNI [`ib-sriov-cni`](https://github.com/Mellanox/ib-sriov-cni) plugin |
| device id | Provide device identifier which is associated with the network to allow the CNI plugin to perform device dependent network configurations. | `deviceID` | `deviceID` (string entry). <pre> "0000:04:00.5" </pre> | none | CNI `host-device` plugin |
| aliases | Provide a list of names that will be mapped to the IP addresses assigned to this interface. Other containers on the same network may use one of these names to access the container.| `aliases` | List of `alias` (string entry). <pre> ["my-container", "primary-db"] </pre> | none | CNI `alias` plugin |
| cgroup path | Provide the cgroup path for pod as requested by CNI plugins. | `cgroupPath` | `cgroupPath` (string entry). <pre>"/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-burstable.slice/kubelet-kubepods-burstable-pod28ce45bc_63f8_48a3_a99b_cfb9e63c856c.slice" </pre> | none | CNI `host-local` plugin |
## "args" in network config
`args` in [network config](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.

View File

@ -64,12 +64,12 @@ ensure that the configuration files specify a `cniVersion` field and that the
version there is supported by your container runtime and CNI plugins.
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.
@ -94,7 +94,7 @@ command with the following JSON data:
```
Second, for the `ADD` command, a plugin must respect the `cniVersion` field
provided in the [network configuration JSON](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 +102,11 @@ That field is a request for the plugin to return results of a particular format:
- If the plugin doesn't support the version, the plugin must error.
- Otherwise, the plugin must return a [CNI Result](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`,
@ -182,7 +182,7 @@ result, err := current.NewResultFromResult(ipamResult)
```
Other examples of spec v0.3.0-compatible plugins are the
[main plugins in this repo](https://github.com/containernetworking/plugins/)
[main plugins in this repo](https://github.com/containernetworking/plugins/tree/master/plugins)
## For Runtime Authors
@ -203,15 +203,15 @@ call both new and legacy plugins, and handle the results from either.
When calling a plugin, the runtime must request that the plugin respond in a
particular format by specifying the `cniVersion` field in the
[Network Configuration](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
{

View File

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

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)
- [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/k8snetworkplumbingwg/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
- [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins)
@ -65,19 +58,25 @@ 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)
- [NetLOX Loxilight - Loxilight CNI is based on TC eBPF. It works either as a pure eBPF mode or in a hybrid-mode with multi-vendor DPU support when DPU units are available](https://www.netlox.io/product)
- [Hybridnet - a CNI plugin designed for hybrid clouds which provides both overlay and underlay networking for containers in one or more clusters. Overlay and underlay containers can run on the same node and have cluster-wide bidirectional network connectivity.](https://github.com/alibaba/hybridnet)
- [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

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.

219
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**.
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)).
@ -107,18 +102,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 \\).
@ -149,11 +140,9 @@ Plugins that consume any of these configuration keys should respect their intend
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
#### Example configuration
The following is an example JSON representation of a network configuration `dbnet` with three plugin configurations (`bridge`, `tuning`, and `portmap`).
```jsonc
{
"cniVersion": "1.1.0",
"cniVersions": ["0.3.1", "0.4.0", "1.0.0", "1.1.0"],
"cniVersion": "1.0.0",
"name": "dbnet",
"plugins": [
{
@ -161,7 +150,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
@ -192,15 +181,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
@ -222,7 +202,7 @@ The runtime must execute the plugin in the runtime's networking domain. (For mos
Protocol parameters are passed to the plugins via OS environment variables.
- `CNI_COMMAND`: indicates the desired operation; `ADD`, `DEL`, `CHECK`, `GC`, or `VERSION`.
- `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 an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore (), dot (.) or hyphen (-).
- `CNI_NETNS`: A reference to the container's "isolation domain". If using network namespaces, then a path to the network namespace (e.g. `/run/netns/[nsname]`)
- `CNI_IFNAME`: Name of the interface to create inside the container; if the plugin is unable to use this interface name it must return an error.
@ -234,7 +214,7 @@ A plugin must exit with a return code of 0 on success, and non-zero on failure.
### 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
@ -268,10 +248,7 @@ 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.
@ -333,32 +310,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 +321,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,7 +332,6 @@ 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_.
@ -471,38 +389,21 @@ 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 details below)
- `prevResult`: A JSON object, consisting of the result type returned by the "previous" plugin. The meaning of "previous" is defined by the specific operation (_add_, _delete_, or _check_).
The following fields must be **removed** by the runtime:
- `capabilities`
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`
@ -558,26 +459,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 delegated plugins return error, error should be returned by the upper plugin.
If, on `ADD`, a delegated plugin fails, the "upper" plugin should execute again with `DEL` before returning failure.
## Section 5: Result Types
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,45 +489,23 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
- `routes`: Routes created by this attachment:
- `dst`: The destination of the route, in CIDR notation
- `gw`: The next hop address. If unset, a value in `gateway` in the `ips` array may be used.
- `mtu` (uint): The MTU (Maximum transmission unit) along the path to the destination.
- `advmss` (uint): The MSS (Maximal Segment Size) to advertise to these destinations when establishing TCP connections.
- `priority` (uint): The priority of route, lower is higher.
- `table` (uint): The table to add the route to.
- `scope` (uint): The scope of the destinations covered by the route prefix (global (0), link (253), host (254)).
- `dns`: a dictionary consisting of DNS configuration information
- `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
- `domain` (string): the local domain used for short hostname lookups.
- `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers.
- `options` (list of strings): list of options that can be passed to the resolver.
Plugins provided a `prevResult` key as part of their request configuration must output it as their result, with any possible modifications made by that plugin included. If a plugin makes no changes that would be reflected in the _Success result_ type, then it must output a result equivalent to the provided `prevResult`.
#### Delegated plugins (IPAM)
Delegated plugins may omit irrelevant sections.
Delegated IPAM plugins must return an abbreviated _Success_ object. Specifically, it is missing the `interfaces` array, as well as the `interface` entry in `ips`.
### VERSION Success
Plugins must output a JSON object with the following keys upon a `VERSION` operation:
- `cniVersion`: The value of `cniVersion` specified on input
- `supportedVersions`: A list of supported specification versions
Example:
```json
{
"cniVersion": "1.0.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
}
```
### Error
Plugins should output a JSON object with the following keys if they encounter an error:
- `cniVersion`: The protocol version in use - "1.1.0"
- `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.
@ -632,7 +514,7 @@ Example:
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"code": 7,
"msg": "Invalid Configuration",
"details": "Network 192.168.0.0/31 too small to allocate from."
@ -641,6 +523,7 @@ Example:
Error codes 0-99 are reserved for well-known errors. Values of 100+ can be freely used for plugin specific errors.
Error Code|Error Description
---|---
`1`|Incompatible CNI version
@ -651,8 +534,6 @@ Error Code|Error Description
`6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
`7`|Invalid network config. If some validations on network configs do not pass, this error will be raised.
`11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
`50`|The plugin is not available (i.e. cannot service `ADD` requests)
`51`|The plugin is not available, and existing containers in the network may have limited connectivity.
In addition, stderr can be used for unstructured output such as logs.
@ -666,8 +547,8 @@ Plugins must output a JSON object with the following keys upon a `VERSION` opera
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" ]
"cniVersion": "1.0.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
}
```
@ -685,7 +566,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",
@ -765,7 +646,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 +731,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": {
@ -904,7 +785,7 @@ Given the previous _Add_, the container runtime would perform the following step
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
@ -960,7 +841,7 @@ Assuming the `bridge` plugin is satisfied, it produces no output on standard out
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "tuning",
"sysctl": {
@ -1010,7 +891,7 @@ Likewise, the `tuning` plugin exits indicating success.
```json
{
"cniVersion": "1.1.0",
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "portmap",
"runtimeConfig": {
@ -1063,7 +944,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 +992,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 +1040,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

@ -36,11 +36,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) {
@ -68,7 +66,7 @@ func main() {
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/v2 v2.1.3
github.com/onsi/gomega v1.17.0
)

131
go.sum
View File

@ -1,36 +1,103 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/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/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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -15,7 +15,7 @@
package libcni
// Note this is the actual implementation of the CNI specification, which
// is reflected in the SPEC.md file.
// is reflected in the https://github.com/containernetworking/cni/blob/master/SPEC.md file
// it is typically bundled into runtime providers (i.e. containerd or cri-o would use this
// before calling runc or hcsshim). It is also bundled into CNI providers as well, for example,
// to add an IP to a container, to parse the configuration of the CNI and so on.
@ -23,11 +23,10 @@ package libcni
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
@ -39,8 +38,6 @@ import (
var (
CacheDir = "/var/lib/cni"
// slightly awkward wording to preserve anyone matching on error strings
ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command")
)
const (
@ -67,37 +64,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 +84,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 +122,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 +139,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 +155,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 +195,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 +229,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 +254,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 +277,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
@ -340,7 +305,7 @@ 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
@ -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
@ -409,7 +374,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 +386,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 {
@ -529,7 +432,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
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 +453,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 {
@ -571,7 +474,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 +497,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: %w", list.Name, err)
}
}
@ -606,13 +509,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,7 +528,7 @@ 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
@ -640,12 +542,12 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *Runti
}
// 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)
@ -656,7 +558,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, rt *Run
}
// 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
@ -716,7 +618,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 +666,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/v2"
. "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/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/version/legacy_examples"
)
var _ = Describe("Backwards compatibility", func() {
@ -36,7 +36,7 @@ var _ = Describe("Backwards compatibility", func() {
BeforeEach(func() {
var err error
cacheDirPath, err = os.MkdirTemp("", "cni_cachedir")
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
Expect(err).NotTo(HaveOccurred())
})

View File

@ -16,16 +16,13 @@ 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,16 +42,9 @@ 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 {
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
conf := &NetworkConfig{Bytes: bytes, Network: &types.NetConf{}}
if err := json.Unmarshal(bytes, conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %w", err)
}
if conf.Network.Type == "" {
@ -63,35 +53,17 @@ 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: %w", 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 {
if err := json.Unmarshal(bytes, &rawList); err != nil {
return nil, fmt.Errorf("error parsing configuration list: %w", err)
}
@ -113,115 +85,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)
@ -241,68 +124,24 @@ func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
}
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: %w", 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 +162,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 +185,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 +192,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 +201,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,8 +216,7 @@ 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 {
@ -413,14 +240,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/v2"
. "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/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/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
)
var _ = Describe("CNIArgs AsEnv", func() {
@ -51,21 +51,21 @@ var _ = Describe("CNIArgs AsEnv", func() {
numLatentEnvs := len(latentEnvs)
cniEnvs := args.AsEnv()
Expect(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/v2"
. "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

@ -50,12 +50,6 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
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.
@ -81,17 +75,17 @@ func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
// 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 +103,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 {

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/v2"
. "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"}))
@ -106,7 +105,7 @@ 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"))
})
@ -118,7 +117,7 @@ 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"))
})
@ -133,8 +132,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
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 +163,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 +199,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/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
)
var _ = Describe("FindInPath", func() {
@ -37,17 +37,17 @@ var _ = Describe("FindInPath", func() {
)
BeforeEach(func() {
tempDir, err := 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,17 @@ 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/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("GetVersion, integration tests", func() {
@ -35,7 +36,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 +128,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/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/containernetworking/cni/pkg/invoke"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/invoke"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
)
var _ = Describe("RawExec", func() {
@ -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,7 +190,7 @@ 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
@ -241,20 +202,21 @@ func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo,
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 +232,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 +240,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(), "")
@ -357,63 +264,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 +284,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/gomega"
"testing"
)
func TestSkel(t *testing.T) {

View File

@ -20,11 +20,12 @@ import (
"fmt"
"strings"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
type fakeCmd struct {
@ -45,14 +46,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() {
@ -78,14 +78,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 +88,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 +103,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 +115,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 +128,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 +140,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 +152,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 +164,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 +176,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 +188,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 +199,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 +223,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 +245,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 +260,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 +282,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 +292,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 +316,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 +329,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 +342,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 +355,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 +367,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 +379,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 +388,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 +402,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 +424,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())))
}`, 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 +454,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())))
}`, current.ImplementedSpecVersion)))
})
})
@ -607,14 +471,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 +488,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,7 +496,7 @@ 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))
@ -644,7 +507,7 @@ var _ = Describe("dispatching to the correct callback", func() {
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 +525,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 +551,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 +566,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

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

View File

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

View File

@ -15,10 +15,10 @@
package types040_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "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/v2"
. "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())

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/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/v2"
. "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())

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 {

View File

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

View File

@ -19,9 +19,6 @@ import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
_ "github.com/containernetworking/cni/pkg/types/020"
_ "github.com/containernetworking/cni/pkg/types/040"
_ "github.com/containernetworking/cni/pkg/types/100"
convert "github.com/containernetworking/cni/pkg/types/internal"
)

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/gomega"
"testing"
)
func TestTypes(t *testing.T) {

View File

@ -18,15 +18,14 @@ import (
"encoding/json"
"net"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
types040 "github.com/containernetworking/cni/pkg/types/040"
current "github.com/containernetworking/cni/pkg/types/100"
)
var _ = Describe("Types", func() {
Describe("ParseCIDR", func() {
DescribeTable("Parse and stringify",
func(input, expectedIP string, expectedMask int) {
@ -88,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}`))
})
})
@ -160,15 +154,14 @@ var _ = Describe("Types", func() {
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",
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
},
IPs: []*current.IPConfig{

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

View File

@ -16,6 +16,7 @@ package legacy_examples
import (
"fmt"
"io/ioutil"
"os"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
@ -107,7 +108,7 @@ 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)
}
@ -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
}

View File

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

View File

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

View File

@ -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/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Decoding versions reported by a plugin", func() {
@ -121,19 +120,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/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Reconcile versions of net config with versions supported by plugins", func() {

View File

@ -22,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/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,6 +14,7 @@
package testhelpers
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@ -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"
types100 "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,10 +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")
)
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")
// VersionsFrom returns a list of versions starting from min, inclusive
func VersionsStartingFrom(min string) PluginInfo {
@ -63,7 +62,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
}

View File

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

View File

@ -19,18 +19,18 @@ import (
"net"
"reflect"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types"
cniv1 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
)
var _ = Describe("Version operations", func() {
It("computes a list of versions correctly", func() {
actual := version.VersionsStartingFrom("0.3.1")
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0", "1.1.0"}))
Expect(actual.SupportedVersions()).To(Equal([]string{"0.3.1", "0.4.0", "1.0.0"}))
})
Context("when a prevResult is available", func() {
@ -39,11 +39,9 @@ var _ = Describe("Version operations", func() {
"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 +57,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 +66,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 +91,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 +101,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 +119,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 +136,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,15 +1,8 @@
module github.com/containernetworking/cni/plugins/debug
go 1.21
go 1.14
require (
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.4.0
github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v0.9.1
)
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 +1,98 @@
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=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v1.0.1 h1:9OIL/sZmMYDBe+G8svzILAlulUpaDTUjeAbtH/JNLBo=
github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8=
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -17,17 +17,15 @@ package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
type100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"io"
"os"
"os/exec"
)
type NetConf struct {
@ -103,7 +101,7 @@ func cmdAdd(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
defer fp.Close()
fmt.Fprintf(fp, "CmdAdd\n")
outputCmdArgs(fp, args)
@ -119,7 +117,7 @@ func cmdDel(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
defer fp.Close()
fmt.Fprintf(fp, "CmdDel\n")
outputCmdArgs(fp, args)
@ -135,7 +133,7 @@ func cmdCheck(args *skel.CmdArgs) error {
netConf, _ := parseConf(args.StdinData)
// Output CNI
if netConf.CNIOutput != "" {
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
fp, _ := os.OpenFile(netConf.CNIOutput, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
defer fp.Close()
fmt.Fprintf(fp, "CmdCheck\n")
outputCmdArgs(fp, args)

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,9 +37,8 @@ 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) {
@ -46,7 +46,7 @@ func loadConf(bytes []byte) (*NetConf, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes))
}
if err := version.ParsePrevResult(&n.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)
@ -167,7 +149,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
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/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/v2"
. "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

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