Compare commits

...

31 Commits
v1.2.1 ... main

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

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

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

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

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


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

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

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

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

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

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

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-06-24 18:32:57 +02:00
Benjamin Leggett 425425837e
libcni: Support subdirectory-based plugin loading (#928)
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 12:22:05 -04:00
Benjamin Leggett c0cc785dbc
Use type aliases to hint deprecation for old API types (#928)
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 11:45:13 -04:00
Benjamin Leggett 9f73d4da82
SPEC: #928 support non-inlined plugin loading
Signed-off-by: Benjamin Leggett <benjamin.leggett@solo.io>
2024-06-17 11:19:33 -04:00
21 changed files with 638 additions and 216 deletions

View File

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

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

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

View File

@ -10,30 +10,39 @@ env:
jobs: jobs:
lint: lint:
name: Lint name: Lint
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v4
- uses: ibiqlik/action-yamllint@v3 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1
with: with:
format: auto format: auto
- uses: golangci/golangci-lint-action@v6 config_file: .yamllint.yaml
- uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
with: with:
args: --verbose args: --verbose
version: v1.57.1 version: v1.57.1
build: build:
name: Build all linux architectures name: Build all linux architectures
needs: lint needs: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Build on all supported architectures - name: Build on all supported architectures
run: | run: |
@ -49,10 +58,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install test binaries - name: Install test binaries
run: | run: |
@ -62,9 +72,9 @@ jobs:
- name: test - name: test
run: COVERALLS=1 ./test.sh run: COVERALLS=1 ./test.sh
- name: Send coverage to coveralls - env:
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Send coverage to coveralls
run: | run: |
PATH=$PATH:$(go env GOPATH)/bin PATH=$PATH:$(go env GOPATH)/bin
gover gover
@ -76,9 +86,11 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: test - name: test
run: bash ./test.sh run: bash ./test.sh

3
.gitignore vendored
View File

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

View File

@ -3,6 +3,7 @@ extends: default
rules: rules:
document-start: disable document-start: disable
line-length: disable
truthy: truthy:
ignore: | ignore: |
.github/workflows/*.yml .github/workflows/*.yml

View File

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

View File

@ -4,6 +4,9 @@
# CNI - the Container Network Interface # CNI - the Container Network Interface
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2446/badge)](https://bestpractices.coreinfrastructure.org/projects/2446)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/containernetworking/cni/badge)](https://securityscorecards.dev/viewer/?uri=github.com/containernetworking/cni)
## What is CNI? ## What is CNI?
CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins. CNI (_Container Network Interface_), a [Cloud Native Computing Foundation](https://cncf.io) project, consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
@ -37,7 +40,6 @@ To avoid duplication, we think it is prudent to define a common interface betwee
## Who is using CNI? ## Who is using CNI?
### Container runtimes ### Container runtimes
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
- [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) - [Kubernetes - a system to simplify container operations](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/)
- [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md) - [OpenShift - Kubernetes with additional enterprise features](https://github.com/openshift/origin/blob/master/docs/openshift_networking_requirements.md)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release) - [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/cf-networking-release)
@ -48,16 +50,13 @@ To avoid duplication, we think it is prudent to define a common interface betwee
### 3rd party plugins ### 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)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin) - [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
- [SR-IOV](https://github.com/hustcat/sriov-cni) - [SR-IOV](https://github.com/hustcat/sriov-cni)
- [Cilium - BPF & XDP for containers](https://github.com/cilium/cilium) - [Cilium - eBPF & XDP for containers](https://github.com/cilium/cilium)
- [Infoblox - enterprise IP address management for containers](https://github.com/infobloxopen/cni-infoblox)
- [Multus - a Multi plugin](https://github.com/k8snetworkplumbingwg/multus-cni) - [Multus - a Multi plugin](https://github.com/k8snetworkplumbingwg/multus-cni)
- [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube) - [Romana - Layer 3 CNI plugin supporting network policy for Kubernetes](https://github.com/romana/kube)
- [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie) - [CNI-Genie - generic CNI network plugin](https://github.com/Huawei-PaaS/CNI-Genie)
- [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni) - [Nuage CNI - Nuage Networks SDN plugin for network policy kubernetes support ](https://github.com/nuagenetworks/nuage-cni)
- [Silk - a CNI plugin designed for Cloud Foundry](https://github.com/cloudfoundry-incubator/silk)
- [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni) - [Linen - a CNI plugin designed for overlay networks with Open vSwitch and fit in SDN/OpenFlow network environment](https://github.com/John-Lin/linen-cni)
- [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin) - [Vhostuser - a Dataplane network plugin - Supports OVS-DPDK & VPP](https://github.com/intel/vhost-user-net-plugin)
- [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins) - [Amazon ECS CNI Plugins - a collection of CNI Plugins to configure containers with Amazon EC2 elastic network interfaces (ENIs)](https://github.com/aws/amazon-ecs-cni-plugins)
@ -66,13 +65,11 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [Juniper Contrail](https://www.juniper.net/cloud) / [TungstenFabric](https://tungstenfabric.io) - Provides overlay SDN solution, delivering multicloud networking, hybrid cloud networking, simultaneous overlay-underlay support, network policy enforcement, network isolation, service chaining and flexible load balancing - [Juniper Contrail](https://www.juniper.net/cloud) / [TungstenFabric](https://tungstenfabric.io) - Provides overlay SDN solution, delivering multicloud networking, hybrid cloud networking, simultaneous overlay-underlay support, network policy enforcement, network isolation, service chaining and flexible load balancing
- [Knitter - a CNI plugin supporting multiple networking for Kubernetes](https://github.com/ZTE/Knitter) - [Knitter - a CNI plugin supporting multiple networking for Kubernetes](https://github.com/ZTE/Knitter)
- [DANM - a CNI-compliant networking solution for TelCo workloads running on Kubernetes](https://github.com/nokia/danm) - [DANM - a CNI-compliant networking solution for TelCo workloads running on Kubernetes](https://github.com/nokia/danm)
- [VMware NSX a CNI plugin that enables automated NSX L2/L3 networking and L4/L7 Load Balancing; network isolation at the pod, node, and cluster level; and zero-trust security policy for your Kubernetes cluster.](https://docs.vmware.com/en/VMware-NSX-T/2.2/com.vmware.nsxt.ncp_kubernetes.doc/GUID-6AFA724E-BB62-4693-B95C-321E8DDEA7E1.html)
- [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override) - [cni-route-override - a meta CNI plugin that override route information](https://github.com/redhat-nfvpe/cni-route-override)
- [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway) - [Terway - a collection of CNI Plugins based on alibaba cloud VPC/ECS network product](https://github.com/AliyunContainerService/terway)
- [Cisco ACI CNI - for on-prem and cloud container networking with consistent policy and security model.](https://github.com/noironetworks/aci-containers) - [Cisco ACI CNI - for on-prem and cloud container networking with consistent policy and security model.](https://github.com/noironetworks/aci-containers)
- [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/kubeovn/kube-ovn) - [Kube-OVN - a CNI plugin that bases on OVN/OVS and provides advanced features like subnet, static ip, ACL, QoS, etc.](https://github.com/kubeovn/kube-ovn)
- [Project Antrea - an Open vSwitch k8s CNI](https://github.com/vmware-tanzu/antrea) - [Project Antrea - an Open vSwitch k8s CNI](https://github.com/vmware-tanzu/antrea)
- [OVN4NFV-K8S-Plugin - a OVN based CNI controller plugin to provide cloud native based Service function chaining (SFC), Multiple OVN overlay networking](https://github.com/opnfv/ovn4nfv-k8s-plugin)
- [Azure CNI - a CNI plugin that natively extends Azure Virtual Networks to containers](https://github.com/Azure/azure-container-networking) - [Azure CNI - a CNI plugin that natively extends Azure Virtual Networks to containers](https://github.com/Azure/azure-container-networking)
- [Hybridnet - a CNI plugin designed for hybrid clouds which provides both overlay and underlay networking for containers in one or more clusters. Overlay and underlay containers can run on the same node and have cluster-wide bidirectional network connectivity.](https://github.com/alibaba/hybridnet) - [Hybridnet - a CNI plugin designed for hybrid clouds which provides both overlay and underlay networking for containers in one or more clusters. Overlay and underlay containers can run on the same node and have cluster-wide bidirectional network connectivity.](https://github.com/alibaba/hybridnet)
- [Spiderpool - An IP Address Management (IPAM) CNI plugin of Kubernetes for managing static ip for underlay network](https://github.com/spidernet-io/spiderpool) - [Spiderpool - An IP Address Management (IPAM) CNI plugin of Kubernetes for managing static ip for underlay network](https://github.com/spidernet-io/spiderpool)

26
SPEC.md
View File

@ -109,14 +109,16 @@ 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" - `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. - `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 (-). - `name` (string): Network name. This should be unique across all network configurations on a host (or other administrative domain). Must start with an alphanumeric character, optionally followed by any combination of one or more alphanumeric characters, underscore, dot (.) or hyphen (-). Must not contain characters disallowed in file paths.
- `disableCheck` (boolean): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors. - `disableCheck` (boolean): Either `true` or `false`. If `disableCheck` is `true`, runtimes must not call `CHECK` for this network configuration list. This allows an administrator to prevent `CHECK`ing where a combination of plugins is known to return spurious errors.
- `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). - `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).
- `plugins` (list): A list of CNI plugins and their configuration, which is a list of plugin configuration objects. - `loadOnlyInlinedPlugins` (boolean): Either `true` or `false`. If `false` (default), indicates [plugin configuration objects](#plugin-configuration-objects) can be aggregated from multiple sources. Any valid plugin configuration objects aggregated from other sources must be appended to the final list of `plugins` for that network name. If set to `true`, indicates that valid plugin configuration objects aggregated from sources other than the main network configuration will be ignored. If `plugins` is not present in the network configuration, `loadOnlyInlinedPlugins` cannot be set to `true`.
- `plugins` (list): A list of inlined [plugin configuration objects](#plugin-configuration-objects). If this key is populated with inlined plugin objects, and `loadOnlyInlinedPlugins` is true, the final set of plugins for a network must consist of all the plugin objects in this list, merged with all the plugins loaded from the sibling folder with the same name as the network.
#### Plugin configuration objects: #### Plugin configuration objects:
Plugin configuration objects may contain additional fields than the ones defined here. Runtimes may aggregate plugin configuration objects from multiple sources, and must unambiguously associate each loaded plugin configuration object with a single, valid network configuration. All aggregated plugin configuration objects must be validated, and each plugin with a valid configuration object must be invoked.
The runtime MUST pass through these fields, unchanged, to the plugin, as defined in section 3.
Plugin configuration objects may contain additional fields beyond the ones defined here. The runtime MUST pass through these fields, unchanged, to the invoked plugin, as defined in section 3.
**Required keys:** **Required keys:**
- `type` (string): Matches the name of the CNI plugin binary on disk. Must not contain characters disallowed in file paths for the system (e.g. / or \\). - `type` (string): Matches the name of the CNI plugin binary on disk. Must not contain characters disallowed in file paths for the system (e.g. / or \\).
@ -147,6 +149,7 @@ Plugins that consume any of these configuration keys should respect their intend
Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution. Plugins may define additional fields that they accept and may generate an error if called with unknown fields. Runtimes must preserve unknown fields in plugin configuration objects when transforming for execution.
#### Example configuration #### Example configuration
The following is an example JSON representation of a network configuration `dbnet` with three plugin configurations (`bridge`, `tuning`, and `portmap`).
```jsonc ```jsonc
{ {
"cniVersion": "1.1.0", "cniVersion": "1.1.0",
@ -158,7 +161,7 @@ Plugins may define additional fields that they accept and may generate an error
// plugin specific parameters // plugin specific parameters
"bridge": "cni0", "bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"], "keyA": ["some more", "plugin specific", "configuration"],
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
// ipam specific // ipam specific
@ -265,7 +268,10 @@ A CNI plugin, upon receiving a `DEL` command, should either
- delete the interface defined by `CNI_IFNAME` inside the container at `CNI_NETNS`, or - delete the interface defined by `CNI_IFNAME` inside the container at `CNI_NETNS`, or
- undo any modifications applied in the plugin's `ADD` functionality - undo any modifications applied in the plugin's `ADD` functionality
Plugins should generally complete a `DEL` action without error even if some resources are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist. A `prevResult` must be supplied to CNI plugins as part of a `DEL` command. For the first plugin in the `DEL` command plugin chain, this `prevResult` will be the final result of the previous `ADD` command.
Plugins should still return without error if `prevResult` is empty for a `DEL` command, however.
`DEL` command invocations are always considered best-effort - plugins should always complete a `DEL` action without error to the fullest extent possible, even if some resources or state are missing. For example, an IPAM plugin should generally release an IP allocation and return success even if the container network namespace no longer exists, unless that network namespace is critical for IPAM management. While DHCP may usually send a 'release' message on the container network interface, since DHCP leases have a lifetime this release action would not be considered critical and no error should be returned if this action fails. For another example, the `bridge` plugin should delegate the DEL action to the IPAM plugin and clean up its own resources even if the container network namespace and/or container network interface no longer exist.
Plugins MUST accept multiple `DEL` calls for the same (`CNI_CONTAINERID`, `CNI_IFNAME`) pair, and return success if the interface in question, or any modifications added, are missing. Plugins MUST accept multiple `DEL` calls for the same (`CNI_CONTAINERID`, `CNI_IFNAME`) pair, and return success if the interface in question, or any modifications added, are missing.
@ -375,8 +381,6 @@ Resources may, for example, include:
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. 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.
Garbage collection is a per-network operation. If a plugin manages resources shared across multiple networks, it must only remove stale resources known to belong to the network provided in the `GC `action.
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 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). Plugins MUST, additionally, forward any GC calls to delegated plugins they are configured to use (see section 4).
@ -387,7 +391,7 @@ The runtime MUST NOT use GC as a substitute for DEL. Plugins may be unable to cl
The runtime must provide a JSON-serialized plugin configuration object (defined below) on standard in. It contains an additional key; The runtime must provide a JSON-serialized plugin configuration object (defined below) on standard in. It contains an additional key;
- `cni.dev/attachments` (array of objects): The list of **still valid** attachments to this network: - `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 - `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 - `ifname` (string): the value of CNI_IFNAME as provided during the CNI ADD operation
@ -496,7 +500,7 @@ For attachment-specific operations (ADD, DEL, CHECK), additional field requireme
- `capabilities`: must not be set - `capabilities`: must not be set
For GC operations: For GC operations:
- `cni.dev/attachments`: as specified in section 2. - `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 not prefixed with `cni.dev/` should be passed through unaltered.
@ -647,6 +651,8 @@ Error Code|Error Description
`6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string. `6`|Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
`7`|Invalid network config. If some validations on network configs do not pass, this error will be raised. `7`|Invalid network config. If some validations on network configs do not pass, this error will be raised.
`11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later. `11`|Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
`50`|The plugin is not available (i.e. cannot service `ADD` requests)
`51`|The plugin is not available, and existing containers in the network may have limited connectivity.
In addition, stderr can be used for unstructured output such as logs. In addition, stderr can be used for unstructured output such as logs.

Binary file not shown.

View File

@ -68,7 +68,7 @@ func main() {
if netdir == "" { if netdir == "" {
netdir = DefaultNetDir netdir = DefaultNetDir
} }
netconf, err := libcni.LoadConfList(netdir, os.Args[2]) netconf, err := libcni.LoadNetworkConf(netdir, os.Args[2])
if err != nil { if err != nil {
exit(err) exit(err)
} }

18
go.mod
View File

@ -3,20 +3,20 @@ module github.com/containernetworking/cni
go 1.21 go 1.21
require ( require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/onsi/ginkgo/v2 v2.20.1
github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.34.1
github.com/onsi/gomega v1.33.1
github.com/vishvananda/netns v0.0.4 github.com/vishvananda/netns v0.0.4
) )
require ( require (
github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/sys v0.23.0 // indirect
golang.org/x/tools v0.21.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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

40
go.sum
View File

@ -1,35 +1,35 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 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/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 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -67,18 +67,23 @@ type RuntimeConf struct {
CacheDir string CacheDir string
} }
type NetworkConfig struct { // Use PluginConfig instead of NetworkConfig, the NetworkConfig
Network *types.NetConf // backwards-compat alias will be removed in a future release.
type NetworkConfig = PluginConfig
type PluginConfig struct {
Network *types.PluginConf
Bytes []byte Bytes []byte
} }
type NetworkConfigList struct { type NetworkConfigList struct {
Name string Name string
CNIVersion string CNIVersion string
DisableCheck bool DisableCheck bool
DisableGC bool DisableGC bool
Plugins []*NetworkConfig LoadOnlyInlinedPlugins bool
Bytes []byte Plugins []*PluginConfig
Bytes []byte
} }
type NetworkAttachment struct { type NetworkAttachment struct {
@ -102,14 +107,14 @@ type CNI interface {
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error)
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error) ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error)
GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error
GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error
@ -147,7 +152,7 @@ func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec)
} }
} }
func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) {
var err error var err error
inject := map[string]interface{}{ inject := map[string]interface{}{
@ -183,7 +188,7 @@ func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult typ
// capabilities include "portMappings", and the CapabilityArgs map includes a // capabilities include "portMappings", and the CapabilityArgs map includes a
// "portMappings" key, that key and its value are added to the "runtimeConfig" // "portMappings" key, that key and its value are added to the "runtimeConfig"
// dictionary to be passed to the plugin's stdin. // dictionary to be passed to the plugin's stdin.
func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) { func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) {
var err error var err error
rc := make(map[string]interface{}) rc := make(map[string]interface{})
@ -404,7 +409,7 @@ func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *Runt
// GetNetworkCachedResult returns the cached Result of the previous // GetNetworkCachedResult returns the cached Result of the previous
// AddNetwork() operation for a network, or an error. // AddNetwork() operation for a network, or an error.
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
} }
@ -416,7 +421,7 @@ func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *Runt
// GetNetworkCachedConfig copies the input RuntimeConf to output // GetNetworkCachedConfig copies the input RuntimeConf to output
// RuntimeConf with fields updated with info from the cached Config. // RuntimeConf with fields updated with info from the cached Config.
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
return c.getCachedConfig(net.Network.Name, rt) return c.getCachedConfig(net.Network.Name, rt)
} }
@ -482,7 +487,7 @@ func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachme
return attachments, nil return attachments, nil
} }
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
c.ensureExec() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
@ -524,7 +529,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
return result, nil return result, nil
} }
func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
c.ensureExec() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
@ -566,7 +571,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
return nil return nil
} }
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error {
c.ensureExec() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
@ -607,7 +612,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
return nil return nil
} }
func pluginDescription(net *types.NetConf) string { func pluginDescription(net *types.PluginConf) string {
if net == nil { if net == nil {
return "<missing>" return "<missing>"
} }
@ -621,7 +626,7 @@ func pluginDescription(net *types.NetConf) string {
} }
// AddNetwork executes the plugin with the ADD command // AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) {
result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt) result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt)
if err != nil { if err != nil {
return nil, err return nil, err
@ -635,7 +640,7 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
} }
// CheckNetwork executes the plugin with the CHECK command // CheckNetwork executes the plugin with the CHECK command
func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error { func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
// CHECK was added in CNI spec version 0.4.0 and higher // CHECK was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
return err return err
@ -651,7 +656,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
} }
// DelNetwork executes the plugin with the DEL command // DelNetwork executes the plugin with the DEL command
func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error { func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error {
var cachedResult types.Result var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher // Cached result on DEL was added in CNI spec version 0.4.0 and higher
@ -711,7 +716,7 @@ func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfig
// ValidateNetwork checks that a configuration is reasonably valid. // ValidateNetwork checks that a configuration is reasonably valid.
// It uses the same logic as ValidateNetworkList) // It uses the same logic as ValidateNetworkList)
// Returns a list of capabilities // Returns a list of capabilities
func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) { func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) {
caps := []string{} caps := []string{}
for c, ok := range net.Network.Capabilities { for c, ok := range net.Network.Capabilities {
if ok { if ok {
@ -817,6 +822,8 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList,
} }
if args != nil { if args != nil {
inject["cni.dev/valid-attachments"] = args.ValidAttachments inject["cni.dev/valid-attachments"] = args.ValidAttachments
// #1101: spec used incorrect variable name
inject["cni.dev/attachments"] = args.ValidAttachments
} }
for _, plugin := range list.Plugins { for _, plugin := range list.Plugins {
@ -834,7 +841,7 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList,
return errors.Join(errs...) return errors.Join(errs...)
} }
func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error { func (c *CNIConfig) gcNetwork(ctx context.Context, net *PluginConfig) error {
c.ensureExec() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {
@ -869,7 +876,7 @@ func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfi
return nil return nil
} }
func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *NetworkConfig) error { func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *PluginConfig) error {
c.ensureExec() c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil { if err != nil {

View File

@ -176,7 +176,7 @@ var _ = Describe("Invoking plugins", func() {
pluginConfig []byte pluginConfig []byte
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
ctx context.Context ctx context.Context
) )
@ -295,7 +295,7 @@ var _ = Describe("Invoking plugins", func() {
debug *noop_debug.Debug debug *noop_debug.Debug
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
ctx context.Context ctx context.Context
@ -1636,7 +1636,7 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string cniBinPath string
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
netConfigList *libcni.NetworkConfigList netConfigList *libcni.NetworkConfigList
) )
@ -1809,7 +1809,7 @@ var _ = Describe("Invoking plugins", func() {
cniBinPath string cniBinPath string
pluginConfig string pluginConfig string
cniConfig *libcni.CNIConfig cniConfig *libcni.CNIConfig
netConfig *libcni.NetworkConfig netConfig *libcni.PluginConfig
runtimeConfig *libcni.RuntimeConf runtimeConfig *libcni.RuntimeConf
ctx context.Context ctx context.Context
@ -1931,14 +1931,14 @@ var _ = Describe("Invoking plugins", func() {
Context("when the RuntimeConf is incomplete", func() { Context("when the RuntimeConf is incomplete", func() {
var ( var (
testRt *libcni.RuntimeConf testRt *libcni.RuntimeConf
testNetConf *libcni.NetworkConfig testNetConf *libcni.PluginConfig
testNetConfList *libcni.NetworkConfigList testNetConfList *libcni.NetworkConfigList
) )
BeforeEach(func() { BeforeEach(func() {
testRt = &libcni.RuntimeConf{} testRt = &libcni.RuntimeConf{}
testNetConf = &libcni.NetworkConfig{ testNetConf = &libcni.PluginConfig{
Network: &types.NetConf{}, Network: &types.PluginConf{},
} }
testNetConfList = &libcni.NetworkConfigList{} testNetConfList = &libcni.NetworkConfigList{}
}) })

View File

@ -20,11 +20,10 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"sort" "sort"
"strings" "strings"
"github.com/Masterminds/semver/v3"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
) )
@ -46,9 +45,16 @@ func (e NoConfigsFoundError) Error() string {
return fmt.Sprintf(`no net configurations found in %s`, e.Dir) return fmt.Sprintf(`no net configurations found in %s`, e.Dir)
} }
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { // This will not validate that the plugins actually belong to the netconfig by ensuring
conf := &NetworkConfig{Bytes: bytes, Network: &types.NetConf{}} // that they are loaded from a directory named after the networkName, relative to the network config.
if err := json.Unmarshal(bytes, conf.Network); err != nil { //
// Since here we are just accepting raw bytes, the caller is responsible for ensuring that the plugin
// config provided here actually "belongs" to the networkconfig in question.
func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) {
// TODO why are we creating a struct that holds both the byte representation and the deserialized
// representation, and returning that, instead of just returning the deserialized representation?
conf := &PluginConfig{Bytes: pluginConfBytes, Network: &types.PluginConf{}}
if err := json.Unmarshal(pluginConfBytes, conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %w", err) return nil, fmt.Errorf("error parsing configuration: %w", err)
} }
if conf.Network.Type == "" { if conf.Network.Type == "" {
@ -57,17 +63,35 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return conf, nil return conf, nil
} }
func ConfFromFile(filename string) (*NetworkConfig, error) { // Given a path to a directory containing a network configuration, and the name of a network,
bytes, err := os.ReadFile(filename) // loads all plugin definitions found at path `networkConfPath/networkName/*.conf`
func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) {
var pConfs []*PluginConfig
pluginConfPath := filepath.Join(networkConfPath, networkName)
pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"})
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err) return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err)
} }
return ConfFromBytes(bytes)
for _, pluginConfFile := range pluginConfFiles {
pluginConfBytes, err := os.ReadFile(pluginConfFile)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", pluginConfFile, err)
}
pluginConf, err := NetworkPluginConfFromBytes(pluginConfBytes)
if err != nil {
return nil, err
}
pConfs = append(pConfs, pluginConf)
}
return pConfs, nil
} }
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) {
rawList := make(map[string]interface{}) rawList := make(map[string]interface{})
if err := json.Unmarshal(bytes, &rawList); err != nil { if err := json.Unmarshal(confBytes, &rawList); err != nil {
return nil, fmt.Errorf("error parsing configuration list: %w", err) return nil, fmt.Errorf("error parsing configuration list: %w", err)
} }
@ -92,24 +116,20 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
rawVersions, ok := rawList["cniVersions"] rawVersions, ok := rawList["cniVersions"]
if ok { if ok {
// Parse the current package CNI version // Parse the current package CNI version
currentVersion, err := semver.NewVersion(version.Current())
if err != nil {
panic("CNI version is invalid semver!")
}
rvs, ok := rawVersions.([]interface{}) rvs, ok := rawVersions.([]interface{})
if !ok { 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 type for cniVersions: %T", rvs)
} }
vs := make([]*semver.Version, 0, len(rvs)) vs := make([]string, 0, len(rvs))
for i, rv := range rvs { for i, rv := range rvs {
v, ok := rv.(string) v, ok := rv.(string)
if !ok { if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv) return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv)
} }
if v, err := semver.NewVersion(v); err != nil { 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) return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err)
} else if !v.GreaterThan(currentVersion) { } else if !gt {
// Skip versions "greater" than this implementation of the spec // Skip versions "greater" than this implementation of the spec
vs = append(vs, v) vs = append(vs, v)
} }
@ -117,16 +137,25 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
// if cniVersion was already set, append it to the list for sorting. // if cniVersion was already set, append it to the list for sorting.
if cniVersion != "" { if cniVersion != "" {
if v, err := semver.NewVersion(cniVersion); err != nil { gt, err := version.GreaterThan(cniVersion, version.Current())
if err != nil {
return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err) return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err)
} else if !v.GreaterThan(currentVersion) { } else if !gt {
// ignore any versions higher than the current implemented spec version // ignore any versions higher than the current implemented spec version
vs = append(vs, v) vs = append(vs, cniVersion)
} }
} }
sort.Sort(semver.Collection(vs)) 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 { if len(vs) > 0 {
cniVersion = vs[len(vs)-1].String() cniVersion = vs[len(vs)-1]
} }
} }
@ -163,19 +192,36 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
return nil, err return nil, err
} }
loadOnlyInlinedPlugins, err := readBool("loadOnlyInlinedPlugins")
if err != nil {
return nil, err
}
list := &NetworkConfigList{ list := &NetworkConfigList{
Name: name, Name: name,
DisableCheck: disableCheck, DisableCheck: disableCheck,
DisableGC: disableGC, DisableGC: disableGC,
CNIVersion: cniVersion, LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins,
Bytes: bytes, CNIVersion: cniVersion,
Bytes: confBytes,
} }
var plugins []interface{} var plugins []interface{}
plug, ok := rawList["plugins"] plug, ok := rawList["plugins"]
if !ok { // We can have a `plugins` list key in the main conf,
return nil, fmt.Errorf("error parsing configuration list: no 'plugins' key") // We can also have `loadOnlyInlinedPlugins == true`
//
// If `plugins` is there, then `loadOnlyInlinedPlugins` can be true
//
// If plugins is NOT there, then `loadOnlyInlinedPlugins` cannot be true
//
// We have to have at least some plugins.
if !ok && loadOnlyInlinedPlugins {
return nil, fmt.Errorf("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key")
} else if !ok && !loadOnlyInlinedPlugins {
return list, nil
} }
plugins, ok = plug.([]interface{}) plugins, ok = plug.([]interface{})
if !ok { if !ok {
return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug) return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug)
@ -195,24 +241,68 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
} }
list.Plugins = append(list.Plugins, netConf) list.Plugins = append(list.Plugins, netConf)
} }
return list, nil return list, nil
} }
func ConfListFromFile(filename string) (*NetworkConfigList, error) { func NetworkConfFromFile(filename string) (*NetworkConfigList, error) {
bytes, err := os.ReadFile(filename) bytes, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err) return nil, fmt.Errorf("error reading %s: %w", filename, err)
} }
return ConfListFromBytes(bytes)
conf, err := NetworkConfFromBytes(bytes)
if err != nil {
return nil, err
}
if !conf.LoadOnlyInlinedPlugins {
plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name)
if err != nil {
return nil, err
}
conf.Plugins = append(conf.Plugins, plugins...)
}
if len(conf.Plugins) == 0 {
// Having 0 plugins for a given network is not necessarily a problem,
// but return as error for caller to decide, since they tried to load
return nil, fmt.Errorf("no plugin configs found")
}
return conf, nil
} }
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
return NetworkPluginConfFromBytes(bytes)
}
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", filename, err)
}
return ConfFromBytes(bytes)
}
func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
return NetworkConfFromBytes(bytes)
}
func ConfListFromFile(filename string) (*NetworkConfigList, error) {
return NetworkConfFromFile(filename)
}
// ConfFiles simply returns a slice of all files in the provided directory
// with extensions matching the provided set.
func ConfFiles(dir string, extensions []string) ([]string, error) { func ConfFiles(dir string, extensions []string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles // In part, adapted from rkt/networking/podenv.go#listFiles
files, err := os.ReadDir(dir) files, err := os.ReadDir(dir)
switch { switch {
case err == nil: // break case err == nil: // break
case os.IsNotExist(err): case os.IsNotExist(err):
// If folder not there, return no error - only return an
// error if we cannot read contents or there are no contents.
return nil, nil return nil, nil
default: default:
return nil, err return nil, err
@ -233,6 +323,7 @@ func ConfFiles(dir string, extensions []string) ([]string, error) {
return confFiles, nil return confFiles, nil
} }
// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions
func LoadConf(dir, name string) (*NetworkConfig, error) { func LoadConf(dir, name string) (*NetworkConfig, error) {
files, err := ConfFiles(dir, []string{".conf", ".json"}) files, err := ConfFiles(dir, []string{".conf", ".json"})
switch { switch {
@ -256,6 +347,15 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
} }
func LoadConfList(dir, name string) (*NetworkConfigList, error) { func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return LoadNetworkConf(dir, name)
}
// LoadNetworkConf looks at all the network configs in a given dir,
// loads and parses them all, and returns the first one with an extension of `.conf`
// that matches the provided network name predicate.
func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) {
// TODO this .conflist/.conf extension thing is confusing and inexact
// for implementors. We should pick one extension for everything and stick with it.
files, err := ConfFiles(dir, []string{".conflist"}) files, err := ConfFiles(dir, []string{".conflist"})
if err != nil { if err != nil {
return nil, err return nil, err
@ -263,7 +363,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
sort.Strings(files) sort.Strings(files)
for _, confFile := range files { for _, confFile := range files {
conf, err := ConfListFromFile(confFile) conf, err := NetworkConfFromFile(confFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -272,7 +372,7 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
} }
} }
// Try and load a network configuration file (instead of list) // Deprecated: Try and load a network configuration file (instead of list)
// from the same name, then upconvert. // from the same name, then upconvert.
singleConf, err := LoadConf(dir, name) singleConf, err := LoadConf(dir, name)
if err != nil { if err != nil {
@ -288,7 +388,8 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
return ConfListFromConf(singleConf) return ConfListFromConf(singleConf)
} }
func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*NetworkConfig, error) { // InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable.
func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) {
config := make(map[string]interface{}) config := make(map[string]interface{})
err := json.Unmarshal(original.Bytes, &config) err := json.Unmarshal(original.Bytes, &config)
if err != nil { if err != nil {
@ -312,12 +413,14 @@ func InjectConf(original *NetworkConfig, newValues map[string]interface{}) (*Net
return nil, err return nil, err
} }
return ConfFromBytes(newBytes) return NetworkPluginConfFromBytes(newBytes)
} }
// ConfListFromConf "upconverts" a network config in to a NetworkConfigList, // ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
// with the single network as the only entry in the list. // with the single network as the only entry in the list.
func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) { //
// Deprecated: Non-conflist file formats are unsupported, use NetworkConfXXX and NetworkPluginXXX functions
func ConfListFromConf(original *PluginConfig) (*NetworkConfigList, error) {
// Re-deserialize the config's json, then make a raw map configlist. // Re-deserialize the config's json, then make a raw map configlist.
// This may seem a bit strange, but it's to make the Bytes fields // This may seem a bit strange, but it's to make the Bytes fields
// actually make sense. Otherwise, the generated json is littered with // actually make sense. Otherwise, the generated json is littered with

View File

@ -50,8 +50,8 @@ var _ = Describe("Loading configuration from disk", func() {
It("finds the network config file for the plugin of the given type", func() { It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin") netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{ Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -79,8 +79,8 @@ var _ = Describe("Loading configuration from disk", func() {
It("finds the network config file for the plugin of the given type", func() { It("finds the network config file for the plugin of the given type", func() {
netConfig, err := libcni.LoadConf(configDir, "some-plugin") netConfig, err := libcni.LoadConf(configDir, "some-plugin")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfig).To(Equal(&libcni.NetworkConfig{ Expect(netConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -181,7 +181,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
}) })
Describe("ConfFromBytes", func() { Describe("NetworkPluginConfFromBytes", func() {
Context("when the config is missing 'type'", func() { Context("when the config is missing 'type'", func() {
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`)) _, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`))
@ -190,7 +190,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
}) })
Describe("LoadConfList", func() { Describe("LoadNetworkConf", func() {
var ( var (
configDir string configDir string
configList []byte configList []byte
@ -202,7 +202,7 @@ var _ = Describe("Loading configuration from disk", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
configList = []byte(`{ configList = []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.2.0", "cniVersion": "0.2.0",
"disableCheck": true, "disableCheck": true,
"plugins": [ "plugins": [
@ -228,23 +228,23 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("finds the network config file for the plugin of the given type", func() { It("finds the network config file for the plugin of the given type", func() {
netConfigList, err := libcni.LoadConfList(configDir, "some-list") netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{ Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{
Name: "some-list", Name: "some-network",
CNIVersion: "0.2.0", CNIVersion: "0.2.0",
DisableCheck: true, DisableCheck: true,
Plugins: []*libcni.NetworkConfig{ Plugins: []*libcni.PluginConfig{
{ {
Network: &types.NetConf{Type: "host-local"}, Network: &types.PluginConf{Type: "host-local"},
Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`), Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`),
}, },
{ {
Network: &types.NetConf{Type: "bridge"}, Network: &types.PluginConf{Type: "bridge"},
Bytes: []byte(`{"mtu":1400,"type":"bridge"}`), Bytes: []byte(`{"mtu":1400,"type":"bridge"}`),
}, },
{ {
Network: &types.NetConf{Type: "port-forwarding"}, Network: &types.PluginConf{Type: "port-forwarding"},
Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`), Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`),
}, },
}, },
@ -255,7 +255,7 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when there is a config file with the same name as the list", func() { Context("when there is a config file with the same name as the list", func() {
BeforeEach(func() { BeforeEach(func() {
configFile := []byte(`{ configFile := []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.2.0", "cniVersion": "0.2.0",
"type": "bridge" "type": "bridge"
}`) }`)
@ -263,7 +263,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("Loads the config list first", func() { It("Loads the config list first", func() {
netConfigList, err := libcni.LoadConfList(configDir, "some-list") netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.Plugins).To(HaveLen(3)) Expect(netConfigList.Plugins).To(HaveLen(3))
}) })
@ -271,7 +271,7 @@ var _ = Describe("Loading configuration from disk", func() {
It("falls back to the config file", func() { It("falls back to the config file", func() {
Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed()) Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed())
netConfigList, err := libcni.LoadConfList(configDir, "some-list") netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.Plugins).To(HaveLen(1)) Expect(netConfigList.Plugins).To(HaveLen(1))
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge")) Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge"))
@ -284,15 +284,15 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin") _, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir})) Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir}))
}) })
}) })
Context("when there is no config for the desired plugin list", func() { Context("when there is no config for the desired network name", func() {
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-other-plugin") _, err := libcni.LoadNetworkConf(configDir, "some-other-network")
Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"})) Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-network"}))
}) })
}) })
@ -302,7 +302,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.LoadConfList(configDir, "some-plugin") _, err := libcni.LoadNetworkConf(configDir, "some-plugin")
Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`)) Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`))
}) })
}) })
@ -326,7 +326,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
It("will not find the config", func() { It("will not find the config", func() {
_, err := libcni.LoadConfList(configDir, "deep") _, err := libcni.LoadNetworkConf(configDir, "deep")
Expect(err).To(MatchError(HavePrefix("no net configuration with name"))) Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
}) })
}) })
@ -334,7 +334,7 @@ var _ = Describe("Loading configuration from disk", func() {
Context("when disableCheck is a string not a boolean", func() { Context("when disableCheck is a string not a boolean", func() {
It("will read a 'true' value and convert to boolean", func() { It("will read a 'true' value and convert to boolean", func() {
configList = []byte(`{ configList = []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.4.0", "cniVersion": "0.4.0",
"disableCheck": "true", "disableCheck": "true",
"plugins": [ "plugins": [
@ -346,14 +346,14 @@ var _ = Describe("Loading configuration from disk", func() {
}`) }`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed()) Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadConfList(configDir, "some-list") netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.DisableCheck).To(BeTrue()) Expect(netConfigList.DisableCheck).To(BeTrue())
}) })
It("will read a 'false' value and convert to boolean", func() { It("will read a 'false' value and convert to boolean", func() {
configList = []byte(`{ configList = []byte(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.4.0", "cniVersion": "0.4.0",
"disableCheck": "false", "disableCheck": "false",
"plugins": [ "plugins": [
@ -365,7 +365,7 @@ var _ = Describe("Loading configuration from disk", func() {
}`) }`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed()) Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadConfList(configDir, "some-list") netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.DisableCheck).To(BeFalse()) Expect(netConfigList.DisableCheck).To(BeFalse())
}) })
@ -373,7 +373,7 @@ var _ = Describe("Loading configuration from disk", func() {
It("will return an error on an unrecognized value", func() { It("will return an error on an unrecognized value", func() {
const badValue string = "adsfasdfasf" const badValue string = "adsfasdfasf"
configList = []byte(fmt.Sprintf(`{ configList = []byte(fmt.Sprintf(`{
"name": "some-list", "name": "some-network",
"cniVersion": "0.4.0", "cniVersion": "0.4.0",
"disableCheck": "%s", "disableCheck": "%s",
"plugins": [ "plugins": [
@ -385,35 +385,244 @@ var _ = Describe("Loading configuration from disk", func() {
}`, badValue)) }`, badValue))
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed()) Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadConfList(configDir, "some-list") _, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(`error parsing configuration list: invalid value "adsfasdfasf" for disableCheck`)) Expect(err).To(MatchError(fmt.Sprintf("error parsing configuration list: invalid value \"%s\" for disableCheck", badValue)))
})
})
Context("for loadOnlyInlinedPlugins", func() {
It("the value will be parsed", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true,
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
})
It("the value will be false if not in config", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
})
It("will return an error on an unrecognized value", func() {
const badValue string = "sphagnum"
configList = []byte(fmt.Sprintf(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": "%s",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`, badValue))
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError(fmt.Sprintf(`error parsing configuration list: invalid value "%s" for loadOnlyInlinedPlugins`, badValue)))
})
It("will return an error if `plugins` is missing and `loadOnlyInlinedPlugins` is `true`", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key"))
})
It("will return no error if `plugins` is missing and `loadOnlyInlinedPlugins` is false", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": false
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
Expect(netConfigList.Plugins).To(HaveLen(1))
})
It("will return error if `loadOnlyInlinedPlugins` is implicitly false + no conf plugin is defined, but no plugins subfolder with network name exists", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).To(MatchError("no plugin configs found"))
})
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined, but no plugins subfolder with network name exists", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
})
It("will return NO error if `loadOnlyInlinedPlugins` is implicitly false + at least 1 conf plugin is defined and network name subfolder exists, but is empty/unreadable", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
_, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
})
It("will merge loaded and inlined plugin lists if both `plugins` is set and `loadOnlyInlinedPlugins` is false", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeFalse())
Expect(netConfigList.Plugins).To(HaveLen(2))
})
It("will ignore loaded plugins if `plugins` is set and `loadOnlyInlinedPlugins` is true", func() {
configList = []byte(`{
"name": "some-network",
"cniVersion": "0.4.0",
"loadOnlyInlinedPlugins": true,
"plugins": [
{
"type": "host-local",
"subnet": "10.0.0.1/24"
}
]
}`)
dirPluginConf := []byte(`{
"type": "bro-check-out-my-plugin",
"subnet": "10.0.0.1/24"
}`)
Expect(os.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0o600)).To(Succeed())
subDir := filepath.Join(configDir, "some-network")
Expect(os.MkdirAll(subDir, 0o700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(subDir, "funky-second-plugin.conf"), dirPluginConf, 0o600)).To(Succeed())
netConfigList, err := libcni.LoadNetworkConf(configDir, "some-network")
Expect(err).NotTo(HaveOccurred())
Expect(netConfigList.LoadOnlyInlinedPlugins).To(BeTrue())
Expect(netConfigList.Plugins).To(HaveLen(1))
Expect(netConfigList.Plugins[0].Network.Type).To(Equal("host-local"))
}) })
}) })
}) })
Describe("ConfListFromFile", func() { Describe("NetworkConfFromFile", func() {
Context("when the file cannot be opened", func() { Context("when the file cannot be opened", func() {
It("returns a useful error", func() { It("returns a useful error", func() {
_, err := libcni.ConfListFromFile("/tmp/nope/not-here") _, err := libcni.NetworkConfFromFile("/tmp/nope/not-here")
Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`))) Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`)))
}) })
}) })
}) })
Describe("InjectConf", func() { Describe("InjectConf", func() {
var testNetConfig *libcni.NetworkConfig var testNetConfig *libcni.PluginConfig
BeforeEach(func() { BeforeEach(func() {
testNetConfig = &libcni.NetworkConfig{ testNetConfig = &libcni.PluginConfig{
Network: &types.NetConf{Name: "some-plugin", Type: "foobar"}, Network: &types.PluginConf{Name: "some-plugin", Type: "foobar"},
Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`), Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`),
} }
}) })
Context("when function parameters are incorrect", func() { Context("when function parameters are incorrect", func() {
It("returns unmarshal error", func() { It("returns unmarshal error", func() {
conf := &libcni.NetworkConfig{ conf := &libcni.PluginConfig{
Network: &types.NetConf{Name: "some-plugin"}, Network: &types.PluginConf{Name: "some-plugin"},
Bytes: []byte(`{ cc cc cc}`), Bytes: []byte(`{ cc cc cc}`),
} }
@ -438,8 +647,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"}) resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -456,8 +665,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -474,8 +683,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{ Network: &types.PluginConf{
Name: "some-plugin", Name: "some-plugin",
Type: "foobar", Type: "foobar",
}, },
@ -496,8 +705,8 @@ var _ = Describe("Loading configuration from disk", func() {
resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"}) resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ Expect(resultConfig).To(Equal(&libcni.PluginConfig{
Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}}, Network: &types.PluginConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}},
Bytes: expectedPluginConfig, Bytes: expectedPluginConfig,
})) }))
}) })
@ -505,7 +714,7 @@ var _ = Describe("Loading configuration from disk", func() {
}) })
}) })
var _ = Describe("ConfListFromBytes", func() { var _ = Describe("NetworkConfFromBytes", func() {
Describe("Version selection", func() { Describe("Version selection", func() {
makeConfig := func(versions ...string) []byte { makeConfig := func(versions ...string) []byte {
// ugly fake json encoding, but whatever // ugly fake json encoding, but whatever
@ -516,36 +725,36 @@ var _ = Describe("ConfListFromBytes", func() {
return []byte(fmt.Sprintf(`{"name": "test", "cniVersions": [%s], "plugins": [{"type": "foo"}]}`, strings.Join(vs, ","))) return []byte(fmt.Sprintf(`{"name": "test", "cniVersions": [%s], "plugins": [{"type": "foo"}]}`, strings.Join(vs, ",")))
} }
It("correctly selects the maximum version", func() { It("correctly selects the maximum version", func() {
conf, err := libcni.ConfListFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.0")) conf, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.0"))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.1.0")) Expect(conf.CNIVersion).To(Equal("1.1.0"))
}) })
It("selects the highest version supported by libcni", func() { It("selects the highest version supported by libcni", func() {
conf, err := libcni.ConfListFromBytes(makeConfig("99.0.0", "1.1.0", "0.4.0", "1.0.0")) conf, err := libcni.NetworkConfFromBytes(makeConfig("99.0.0", "1.1.0", "0.4.0", "1.0.0"))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.1.0")) Expect(conf.CNIVersion).To(Equal("1.1.0"))
}) })
It("fails when invalid versions are specified", func() { It("fails when invalid versions are specified", func() {
_, err := libcni.ConfListFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.f")) _, err := libcni.NetworkConfFromBytes(makeConfig("1.1.0", "0.4.0", "1.0.f"))
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
It("falls back to cniVersion", func() { It("falls back to cniVersion", func() {
conf, err := libcni.ConfListFromBytes([]byte(`{"name": "test", "cniVersion": "1.2.3", "plugins": [{"type": "foo"}]}`)) conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersion": "1.2.3", "plugins": [{"type": "foo"}]}`))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.2.3")) Expect(conf.CNIVersion).To(Equal("1.2.3"))
}) })
It("merges cniVersions and cniVersion", func() { It("merges cniVersions and cniVersion", func() {
conf, err := libcni.ConfListFromBytes([]byte(`{"name": "test", "cniVersion": "1.0.0", "cniVersions": ["0.1.0", "0.4.0"], "plugins": [{"type": "foo"}]}`)) 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(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("1.0.0")) Expect(conf.CNIVersion).To(Equal("1.0.0"))
}) })
It("handles an empty cniVersions array", func() { It("handles an empty cniVersions array", func() {
conf, err := libcni.ConfListFromBytes([]byte(`{"name": "test", "cniVersions": [], "plugins": [{"type": "foo"}]}`)) conf, err := libcni.NetworkConfFromBytes([]byte(`{"name": "test", "cniVersions": [], "plugins": [{"type": "foo"}]}`))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(conf.CNIVersion).To(Equal("")) Expect(conf.CNIVersion).To(Equal(""))
}) })
@ -553,7 +762,7 @@ var _ = Describe("ConfListFromBytes", func() {
}) })
var _ = Describe("ConfListFromConf", func() { var _ = Describe("ConfListFromConf", func() {
var testNetConfig *libcni.NetworkConfig var testNetConfig *libcni.PluginConfig
BeforeEach(func() { BeforeEach(func() {
pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`) pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`)
@ -575,11 +784,11 @@ var _ = Describe("ConfListFromConf", func() {
Expect(ncl).To(Equal(&libcni.NetworkConfigList{ Expect(ncl).To(Equal(&libcni.NetworkConfigList{
Name: "some-plugin", Name: "some-plugin",
CNIVersion: "0.3.1", CNIVersion: "0.3.1",
Plugins: []*libcni.NetworkConfig{testNetConfig}, Plugins: []*libcni.PluginConfig{testNetConfig},
})) }))
// Test that the json unmarshals to the same data // Test that the json unmarshals to the same data
ncl2, err := libcni.ConfListFromBytes(bytes) ncl2, err := libcni.NetworkConfFromBytes(bytes)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
ncl2.Bytes = nil ncl2.Bytes = nil
ncl2.Plugins[0].Bytes = nil ncl2.Plugins[0].Bytes = nil

View File

@ -56,8 +56,12 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// NetConfType describes a network. // Use PluginConf instead of NetConf, the NetConf
type NetConfType struct { // backwards-compat alias will be removed in a future release.
type NetConf = PluginConf
// PluginConf describes a plugin configuration for a specific network.
type PluginConf struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@ -73,9 +77,6 @@ type NetConfType struct {
ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"` ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
} }
// NetConf is defined as different type as custom MarshalJSON() and issue #1096
type NetConf NetConfType
// GCAttachment is the parameters to a GC call -- namely, // GCAttachment is the parameters to a GC call -- namely,
// the container ID and ifname pair that represents a // the container ID and ifname pair that represents a
// still-valid attachment. // still-valid attachment.
@ -86,11 +87,8 @@ type GCAttachment struct {
// Note: DNS should be omit if DNS is empty but default Marshal function // Note: DNS should be omit if DNS is empty but default Marshal function
// will output empty structure hence need to write a Marshal function // will output empty structure hence need to write a Marshal function
func (n *NetConfType) MarshalJSON() ([]byte, error) { func (n *PluginConf) MarshalJSON() ([]byte, error) {
// use type alias to escape recursion for json.Marshal() to MarshalJSON() bytes, err := json.Marshal(*n)
type fixObjType = NetConf
bytes, err := json.Marshal(fixObjType(*n))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -120,10 +118,10 @@ func (i *IPAM) IsEmpty() bool {
type NetConfList struct { type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"` CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"` DisableCheck bool `json:"disableCheck,omitempty"`
DisableGC bool `json:"disableGC,omitempty"` DisableGC bool `json:"disableGC,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"` Plugins []*PluginConf `json:"plugins,omitempty"`
} }
// Result is an interface that provides the result of plugin execution // Result is an interface that provides the result of plugin execution
@ -231,7 +229,7 @@ func (r *Route) Copy() *Route {
} }
// Well known error codes // Well known error codes
// see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes // see https://github.com/containernetworking/cni/blob/main/SPEC.md#error
const ( const (
ErrUnknown uint = iota // 0 ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1 ErrIncompatibleCNIVersion // 1
@ -243,6 +241,8 @@ const (
ErrInvalidNetworkConfig // 7 ErrInvalidNetworkConfig // 7
ErrInvalidNetNS // 8 ErrInvalidNetNS // 8
ErrTryAgainLater uint = 11 ErrTryAgainLater uint = 11
ErrPluginNotAvailable uint = 50
ErrLimitedConnectivity uint = 51
ErrInternal uint = 999 ErrInternal uint = 999
) )

View File

@ -142,3 +142,27 @@ func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) {
} }
return false, nil return false, nil
} }
// GreaterThan returns true if the first version is greater than the second
func GreaterThan(version, otherVersion string) (bool, error) {
firstMajor, firstMinor, firstMicro, err := ParseVersion(version)
if err != nil {
return false, err
}
secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion)
if err != nil {
return false, err
}
if firstMajor > secondMajor {
return true, nil
} else if firstMajor == secondMajor {
if firstMinor > secondMinor {
return true, nil
} else if firstMinor == secondMinor && firstMicro > secondMicro {
return true, nil
}
}
return false, nil
}

View File

@ -63,7 +63,7 @@ func NewResult(version string, resultBytes []byte) (types.Result, error) {
// ParsePrevResult parses a prevResult in a NetConf structure and sets // ParsePrevResult parses a prevResult in a NetConf structure and sets
// the NetConf's PrevResult member to the parsed Result object. // the NetConf's PrevResult member to the parsed Result object.
func ParsePrevResult(conf *types.NetConf) error { func ParsePrevResult(conf *types.PluginConf) error {
if conf.RawPrevResult == nil { if conf.RawPrevResult == nil {
return nil return nil
} }

View File

@ -59,7 +59,7 @@ var _ = Describe("Version operations", func() {
err := json.Unmarshal(rawBytes, &raw) err := json.Unmarshal(rawBytes, &raw)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: "1.0.0", CNIVersion: "1.0.0",
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
@ -94,7 +94,7 @@ var _ = Describe("Version operations", func() {
}) })
It("fails if the prevResult version is unknown", func() { It("fails if the prevResult version is unknown", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: version.Current(), CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
@ -108,7 +108,7 @@ var _ = Describe("Version operations", func() {
}) })
It("fails if the prevResult version does not match the prevResult version", func() { It("fails if the prevResult version does not match the prevResult version", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: version.Current(), CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
@ -128,7 +128,7 @@ var _ = Describe("Version operations", func() {
Context("when a prevResult is not available", func() { Context("when a prevResult is not available", func() {
It("does not fail", func() { It("does not fail", func() {
conf := &types.NetConf{ conf := &types.PluginConf{
CNIVersion: version.Current(), CNIVersion: version.Current(),
Name: "foobar", Name: "foobar",
Type: "baz", Type: "baz",
@ -139,4 +139,22 @@ var _ = Describe("Version operations", func() {
Expect(conf.PrevResult).To(BeNil()) Expect(conf.PrevResult).To(BeNil())
}) })
}) })
Context("version parsing", func() {
It("parses versions correctly", func() {
v1 := "1.1.0"
v2 := "1.1.1"
check := func(a, b string, want bool) {
GinkgoHelper()
gt, err := version.GreaterThan(a, b)
Expect(err).NotTo(HaveOccurred())
Expect(gt).To(Equal(want))
}
check(v1, v2, false)
check(v2, v1, true)
check(v2, v2, false)
})
})
}) })

View File

@ -36,7 +36,7 @@ import (
) )
type NetConf struct { type NetConf struct {
types.NetConf types.PluginConf
DebugFile string `json:"debugFile"` DebugFile string `json:"debugFile"`
CommandLog string `json:"commandLog"` CommandLog string `json:"commandLog"`
} }
@ -46,7 +46,7 @@ func loadConf(bytes []byte) (*NetConf, error) {
if err := json.Unmarshal(bytes, n); err != nil { if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes)) return nil, fmt.Errorf("failed to load netconf: %w %q", err, string(bytes))
} }
if err := version.ParsePrevResult(&n.NetConf); err != nil { if err := version.ParsePrevResult(&n.PluginConf); err != nil {
return nil, err return nil, err
} }
return n, nil return n, nil