diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cfcb83af..9b7dc63a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,9 +11,10 @@ on: jobs: lint: runs-on: ubuntu-20.04 + container: ghcr.io/linkerd/dev:v38-go steps: - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - - uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 + - run: just go-lint --verbose --timeout=10m fmt: runs-on: ubuntu-20.04 diff --git a/Dockerfile b/Dockerfile index 3e49bde2..c28a4cf0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ FROM --platform=$BUILDPLATFORM ghcr.io/linkerd/dev:v38-go as go WORKDIR /build COPY --link go.mod go.sum . COPY --link ./proxy-init ./proxy-init +COPY --link ./internal ./internal RUN go mod download ARG TARGETARCH RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH GO111MODULE=on \ diff --git a/cni-plugin/deployment/linkerd-cni.conf.default b/cni-plugin/deployment/linkerd-cni.conf.default new file mode 100644 index 00000000..a7e699fe --- /dev/null +++ b/cni-plugin/deployment/linkerd-cni.conf.default @@ -0,0 +1,23 @@ +{ + "name": "linkerd-cni", + "type": "linkerd-cni", + "log_level": "info", + "policy": { + "type": "k8s", + "k8s_api_root": "https://__KUBERNETES_SERVICE_HOST__:__KUBERNETES_SERVICE_PORT__", + "k8s_auth_token": "__SERVICEACCOUNT_TOKEN__" + }, + "kubernetes": { + "kubeconfig": "__KUBECONFIG_FILEPATH__" + }, + "linkerd": { + "incoming-proxy-port": 4143, + "outgoing-proxy-port": 4140, + "proxy-uid": 2102, + "ports-to-redirect": [], + "inbound-ports-to-ignore": [], + "outbound-ports-to-ignore": [], + "simulate": false, + "use-wait-flag": false + } +} diff --git a/cni-plugin/deployment/scripts/filter.jq b/cni-plugin/deployment/scripts/filter.jq new file mode 100644 index 00000000..3737cbf9 --- /dev/null +++ b/cni-plugin/deployment/scripts/filter.jq @@ -0,0 +1,13 @@ +if has("type") then + .plugins = [.] + | del(.plugins[0].cniVersion) + | to_entries + | map(select(.key=="plugins")) + | from_entries + | .plugins += [$CNI_TMP_CONF_DATA] + | .name = "k8s-pod-network" + | .cniVersion = "0.3.0" +else + del(.plugins[]? | select(.type == "linkerd-cni")) + | .plugins += [$CNI_TMP_CONF_DATA] +end \ No newline at end of file diff --git a/cni-plugin/deployment/scripts/install-cni.sh b/cni-plugin/deployment/scripts/install-cni.sh new file mode 100755 index 00000000..f029e763 --- /dev/null +++ b/cni-plugin/deployment/scripts/install-cni.sh @@ -0,0 +1,363 @@ +#!/usr/bin/env bash +# Copyright (c) 2018 Tigera, Inc. All rights reserved. +# Copyright 2018 Istio Authors +# Modifications copyright (c) Linkerd authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file was inspired by: +# 1) https://github.com/projectcalico/cni-plugin/blob/c1175467c227c1656577c80bfc0ee7795da2e2bc/k8s-install/scripts/install-cni.sh +# 2) https://github.com/istio/cni/blob/c63a509539b5ed165a6617548c31b686f13c2133/deployments/kubernetes/install/scripts/install-cni.sh + +# Script to install Linkerd CNI on a Kubernetes host. +# - Expects the host CNI binary path to be mounted at /host/opt/cni/bin. +# - Expects the host CNI network config path to be mounted at /host/etc/cni/net.d. +# - Expects the desired CNI config in the CNI_NETWORK_CONFIG env variable. + +# Ensure all variables are defined, and that the script fails when an error is hit. +set -u -e + +# Helper function for raising errors +# Usage: +# some_command || exit_with_error "some_command_failed: maybe try..." +exit_with_error() { + echo "${1}" + exit 1 +} + +# The directory on the host where existing CNI plugin configs are installed +# and where this script will write out its configuration through the container +# mount point. Defaults to /etc/cni/net.d, but can be overridden by setting +# DEST_CNI_NET_DIR. +DEST_CNI_NET_DIR=${DEST_CNI_NET_DIR:-/etc/cni/net.d} +# The directory on the host where existing CNI binaries are installed. Defaults to +# /opt/cni/bin, but can be overridden by setting DEST_CNI_BIN_DIR. The linkerd-cni +# binary will end up in this directory from the host's point of view. +DEST_CNI_BIN_DIR=${DEST_CNI_BIN_DIR:-/opt/cni/bin} +# The mount prefix of the host machine from the container's point of view. +# Defaults to /host, but can be overridden by setting CONTAINER_MOUNT_PREFIX. +CONTAINER_MOUNT_PREFIX=${CONTAINER_MOUNT_PREFIX:-/host} +# The location in the container where the linkerd-cni binary resides. Can be +# overridden by setting CONTAINER_CNI_BIN_DIR. The binary in this directory +# will be copied over to the host DEST_CNI_BIN_DIR through the mount point. +CONTAINER_CNI_BIN_DIR=${CONTAINER_CNI_BIN_DIR:-/opt/cni/bin} +# Directory path where CNI configuration should live on the host +HOST_CNI_NET="${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}" +# Default path for when linkerd runs as a standalone CNI plugin +DEFAULT_CNI_CONF_PATH="${HOST_CNI_NET}/01-linkerd-cni.conf" +KUBECONFIG_FILE_NAME=${KUBECONFIG_FILE_NAME:-ZZZ-linkerd-cni-kubeconfig} + +############################ +### Function definitions ### +############################ + +# Cleanup will remove any installed configuration from the host If there are any +# *conflist files, then linkerd-cni configuration parameters will be removed +# from them; otherwise, if linkerd-cni is the only plugin, the configuration +# file will be removed. +cleanup() { + # First, kill 'inotifywait' so we don't process any DELETE/CREATE events + if [ "$(pgrep inotifywait)" ]; then + echo 'Sending SIGKILL to inotifywait' + kill -s KILL "$(pgrep inotifywait)" + fi + + echo 'Removing linkerd-cni artifacts.' + + # Find all conflist files and print them out using a NULL separator instead of + # writing each file in a new line. We will subsequently read each string and + # attempt to rm linkerd config from it using jq helper. + local cni_data='' + find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' \) -print0 | + while read -r -d $'\0' file; do + echo "Removing linkerd-cni config from $file" + cni_data=$(jq 'del( .plugins[]? | select( .type == "linkerd-cni" ))' "$file") + # TODO (matei): we should write this out to a temp file and then do a `mv` + # to be atomic. + echo "$cni_data" > "$file" + done + + # Check whether configuration file has been created by our own cni plugin + # and if so, rm it. + if [ -e "${DEFAULT_CNI_CONF_PATH}" ]; then + echo "Cleaning up ${DEFAULT_CNI_CONF_PATH}" + rm -f "${DEFAULT_CNI_CONF_PATH}" + fi + + # Remove binary and kubeconfig file + if [ -e "${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}" ]; then + echo "Removing linkerd-cni kubeconfig: ${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}" + rm -f "${HOST_CNI_NET}/${KUBECONFIG_FILE_NAME}" + fi + if [ -e "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}"/linkerd-cni ]; then + echo "Removing linkerd-cni binary: ${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}/linkerd-cni" + rm -f "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}/linkerd-cni" + fi + + echo 'Exiting.' + exit 0 +} + +# Capture the usual signals and exit from the script +trap 'echo "SIGINT received, simply exiting..."; cleanup' INT +trap 'echo "SIGTERM received, simply exiting..."; cleanup' TERM +trap 'echo "SIGHUP received, simply exiting..."; cleanup' HUP + +# Install CNI bin will copy the linkerd-cni binary on the host's filesystem +install_cni_bin() { + # Place the new binaries if the mounted directory is writeable. + dir="${CONTAINER_MOUNT_PREFIX}${DEST_CNI_BIN_DIR}" + if [ ! -w "${dir}" ]; then + exit_with_error "${dir} is non-writeable, failure" + fi + for path in "${CONTAINER_CNI_BIN_DIR}"/*; do + cp "${path}" "${dir}"/ || exit_with_error "Failed to copy ${path} to ${dir}." + done + + echo "Wrote linkerd CNI binaries to ${dir}" +} + +create_cni_conf() { + # Create temp configuration and kubeconfig files + # + TMP_CONF='/tmp/linkerd-cni.conf.default' + # If specified, overwrite the network configuration file. + CNI_NETWORK_CONFIG_FILE="${CNI_NETWORK_CONFIG_FILE:-}" + CNI_NETWORK_CONFIG="${CNI_NETWORK_CONFIG:-}" + + # If the CNI Network Config has been overwritten, then use template from file + if [ -e "${CNI_NETWORK_CONFIG_FILE}" ]; then + echo "Using CNI config template from ${CNI_NETWORK_CONFIG_FILE}." + cp "${CNI_NETWORK_CONFIG_FILE}" "${TMP_CONF}" + elif [ "${CNI_NETWORK_CONFIG}" ]; then + echo 'Using CNI config template from CNI_NETWORK_CONFIG environment variable.' + cat >"${TMP_CONF}" < "${CONTAINER_MOUNT_PREFIX}${DEST_CNI_NET_DIR}/${KUBECONFIG_FILE_NAME}" < ${TMP_CONF} + fi + + # If the old config filename ends with .conf, rename it to .conflist, because it has changed to be a list + filename=${cni_conf_path##*/} + extension=${filename##*.} + # When this variable has a file, we must delete it later. + old_file_path= + if [ "${filename}" != '01-linkerd-cni.conf' ] && [ "${extension}" = 'conf' ]; then + old_file_path=${cni_conf_path} + echo "Renaming ${cni_conf_path} extension to .conflist" + cni_conf_path="${cni_conf_path}list" + fi + + if [ -e "${DEFAULT_CNI_CONF_PATH}" ] && [ "$cni_conf_path" != "${DEFAULT_CNI_CONF_PATH}" ]; then + echo "Removing Linkerd's configuration file: ${DEFAULT_CNI_CONF_PATH}" + rm -f "${DEFAULT_CNI_CONF_PATH}" + fi + + # Move the temporary CNI config into place. + mv "${TMP_CONF}" "${cni_conf_path}" || exit_with_error 'Failed to mv files.' + [ -n "$old_file_path" ] && rm -f "${old_file_path}" && echo "Removing unwanted .conf file" + + echo "Created CNI config ${cni_conf_path}" +} + +# Sync() is responsible for reacting to file system changes. It is used in +# conjunction with inotify events; sync() is called with the name of the file that +# has changed, the event type (which can be either 'CREATE' or 'DELETE'), and +# the previously observed SHA of the configuration file. +# +# Based on the changed file and event type, sync() might re-install the CNI +# plugin's configuration file. +sync() { + local filename=$1 + local ev=$2 + local filepath="${HOST_CNI_NET}/$filename" + + local prev_sha=$3 + + local config_file_count + local new_sha + if [ "$ev" = 'DELETE' ]; then + # When the event type is 'DELETE', we check to see if there are any `*conf` or `*conflist` + # files on the host's filesystem. If none are present, we install in + # 'interface' mode, using our own CNI config file. + config_file_count=$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | sort | wc -l) + if [ "$config_file_count" -eq 0 ]; then + echo "No active CNI configuration file found after $ev event; re-installing in \"interface\" mode" + install_cni_conf "${DEFAULT_CNI_CONF_PATH}" + fi + elif [ "$ev" = 'CREATE' ]; then + # When the event type is 'CREATE', we check the previously observed SHA (updated + # with each file watch) and compare it against the new file's SHA. If they + # differ, it means something has changed. + new_sha=$(sha256sum "${filepath}" | while read -r s _; do echo "$s"; done) + if [ "$new_sha" != "$prev_sha" ]; then + # Create but don't rm old one since we don't know if this will be configured + # to run as _the_ cni plugin. + echo "New file [$filename] detected; re-installing in \"chained\" mode" + install_cni_conf "$filepath" + else + # If the SHA hasn't changed or we get an unrecognised event, ignore it. + # When the SHA is the same, we can get into infinite loops whereby a file has + # been created and after re-install the watch keeps triggering CREATE events + # that never end. + echo "Ignoring event: $ev $filepath; no real changes detected" + fi + fi +} + +# Monitor will start a watch on host's CNI config directory. Although files are +# mostly `mv'd`, because they are moved from the container's filesystem, the +# events logged will typically be a DELETED followed by a CREATE. When we are on +# the same system partition, `mv` simply renames, however, that won't be the +# case so we don't watch any "moved_to" or "moved_from" events. +monitor() { + inotifywait -m "${HOST_CNI_NET}" -e create,delete | + while read -r directory action filename; do + if [[ "$filename" =~ .*.(conflist|conf)$ ]]; then + echo "Detected change in $directory: $action $filename" + sync "$filename" "$action" "$cni_conf_sha" + # When file exists (i.e we didn't deal with a DELETE ev) + # then calculate its sha to be used the next turn. + if [[ -e "$directory/$filename" && "$action" != 'DELETE' ]]; then + cni_conf_sha="$(sha256sum "$directory/$filename" | while read -r s _; do echo "$s"; done)" + fi + fi + done +} + +################################ +### CNI Plugin Install Logic ### +################################ + +install_cni_bin + +# Install CNI configuration. If we have an existing CNI configuration file (*.conflist or *.conf) that is not linkerd's, +# then append our configuration to that file. Otherwise, if no CNI config files +# are present, install our stand-alone config file. +config_file_count=$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | grep -v linkerd | sort | wc -l) +if [ "$config_file_count" -eq 0 ]; then + echo "No active CNI configuration files found; installing in \"interface\" mode in ${DEFAULT_CNI_CONF_PATH}" + install_cni_conf "${DEFAULT_CNI_CONF_PATH}" +else + find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) -print0 | + while read -r -d $'\0' file; do + echo "Installing CNI configuration in \"chained\" mode for $file" + install_cni_conf "$file" + done +fi + +# Compute SHA for first config file found; this will be updated after every iteration. +# First config file is likely to be chosen as the de facto CNI config by the +# host. +cni_conf_sha="$(sha256sum "$(find "${HOST_CNI_NET}" -maxdepth 1 -type f \( -iname '*conflist' -o -iname '*conf' \) | sort | head -n 1)" | while read -r s _; do echo "$s"; done)" + +# Watch in bg so we can receive interrupt signals through 'trap'. From 'man +# bash': +# "If bash is waiting for a command to complete and receives a signal +# for which a trap has been set, the trap will not be executed until the command +# completes. When bash is waiting for an asynchronous command via the wait +# builtin, the reception of a signal for which a trap has been set will cause +# the wait builtin to return immediately with an exit status greater than 128, +# immediately after which the trap is executed." +monitor & +while true; do + # sleep so script never finishes + # we start sleep in bg so we can trap signals + sleep infinity & + # block + wait $! +done diff --git a/cni-plugin/main.go b/cni-plugin/main.go new file mode 100644 index 00000000..77d5d6c3 --- /dev/null +++ b/cni-plugin/main.go @@ -0,0 +1,329 @@ +// Copyright 2017 CNI authors +// Modifications copyright (c) Linkerd authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file was inspired by: +// 1) https://github.com/istio/cni/blob/c63a509539b5ed165a6617548c31b686f13c2133/cmd/istio-cni/main.go + +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + cniv1 "github.com/containernetworking/cni/pkg/types/100" + "github.com/containernetworking/cni/pkg/version" + "github.com/linkerd/linkerd2-proxy-init/internal/iptables" + "github.com/linkerd/linkerd2-proxy-init/proxy-init/cmd" + + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +// ProxyInit is the configuration for the proxy-init binary +type ProxyInit struct { + IncomingProxyPort int `json:"incoming-proxy-port"` + OutgoingProxyPort int `json:"outgoing-proxy-port"` + ProxyUID int `json:"proxy-uid"` + PortsToRedirect []int `json:"ports-to-redirect"` + InboundPortsToIgnore []string `json:"inbound-ports-to-ignore"` + OutboundPortsToIgnore []string `json:"outbound-ports-to-ignore"` + Simulate bool `json:"simulate"` + UseWaitFlag bool `json:"use-wait-flag"` +} + +// Kubernetes a K8s specific struct to hold config +type Kubernetes struct { + K8sAPIRoot string `json:"k8s_api_root"` + Kubeconfig string `json:"kubeconfig"` +} + +// K8sArgs is the valid CNI_ARGS used for Kubernetes +// The field names need to match exact keys in kubelet args for unmarshalling +type K8sArgs struct { + types.CommonArgs + K8sPodName types.UnmarshallableString + K8sPodNamespace types.UnmarshallableString +} + +// PluginConf is whatever JSON is passed via stdin. +type PluginConf struct { + types.NetConf + + // This is the previous result, when called in the context of a chained + // plugin. We will just pass any prevResult through. + RawPrevResult *map[string]interface{} `json:"prevResult"` + PrevResult *cniv1.Result `json:"-"` + + LogLevel string `json:"log_level"` + ProxyInit ProxyInit `json:"linkerd"` + Kubernetes Kubernetes `json:"kubernetes"` +} + +func main() { + skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "") +} + +func configureLogging(logLevel string) { + switch strings.ToLower(logLevel) { + case "debug": + logrus.SetLevel(logrus.DebugLevel) + case "info": + logrus.SetLevel(logrus.InfoLevel) + default: + logrus.SetLevel(logrus.WarnLevel) + } + + // Must log to Stderr because the CNI runtime uses Stdout as its state + logrus.SetOutput(os.Stderr) +} + +// parseConfig parses the supplied configuration (and prevResult) from stdin. +func parseConfig(stdin []byte) (*PluginConf, error) { + conf := PluginConf{} + + logrus.Debugf("linkerd-cni: stdin to plugin: %v", string(stdin)) + if err := json.Unmarshal(stdin, &conf); err != nil { + return nil, fmt.Errorf("linkerd-cni: failed to parse network configuration: %w", err) + } + + if conf.RawPrevResult != nil { + resultBytes, err := json.Marshal(conf.RawPrevResult) + if err != nil { + return nil, fmt.Errorf("linkerd-cni: could not serialize prevResult: %w", err) + } + + res, err := version.NewResult(conf.CNIVersion, resultBytes) + if err != nil { + return nil, fmt.Errorf("linkerd-cni: could not parse prevResult: %w", err) + } + conf.RawPrevResult = nil + conf.PrevResult, err = cniv1.NewResultFromResult(res) + if err != nil { + return nil, fmt.Errorf("linkerd-cni: could not convert result to version 1.0: %w", err) + } + logrus.Debugf("linkerd-cni: prevResult: %v", conf.PrevResult) + } + + return &conf, nil +} + +// cmdAdd is called by the CNI runtime for ADD requests +func cmdAdd(args *skel.CmdArgs) error { + logrus.Debug("linkerd-cni: cmdAdd, parsing config") + conf, err := parseConfig(args.StdinData) + if err != nil { + return err + } + configureLogging(conf.LogLevel) + + if conf.PrevResult != nil { + logrus.WithFields(logrus.Fields{ + "version": conf.CNIVersion, + "prevResult": conf.PrevResult, + }).Debug("linkerd-cni: cmdAdd, config parsed") + } else { + logrus.WithFields(logrus.Fields{ + "version": conf.CNIVersion, + }).Debug("linkerd-cni: cmdAdd, config parsed") + } + + // Determine if running under k8s by checking the CNI args + k8sArgs := K8sArgs{} + args.Args = strings.Replace(args.Args, "K8S_POD_NAMESPACE", "K8sPodNamespace", 1) + args.Args = strings.Replace(args.Args, "K8S_POD_NAME", "K8sPodName", 1) + if err := types.LoadArgs(args.Args, &k8sArgs); err != nil { + return err + } + + namespace := string(k8sArgs.K8sPodNamespace) + podName := string(k8sArgs.K8sPodName) + logEntry := logrus.WithFields(logrus.Fields{ + "ContainerID": args.ContainerID, + "Pod": podName, + "Namespace": namespace, + }) + + if namespace != "" && podName != "" { + ctx := context.Background() + + configLoadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: conf.Kubernetes.Kubeconfig} + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: "linkerd-cni-context"} + + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configLoadingRules, configOverrides).ClientConfig() + if err != nil { + return err + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return err + } + + pod, err := client.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{}) + if err != nil { + return err + } + + containsLinkerdProxy := false + for _, container := range pod.Spec.Containers { + if container.Name == "linkerd-proxy" { + containsLinkerdProxy = true + break + } + } + + containsInitContainer := false + for _, container := range pod.Spec.InitContainers { + if container.Name == "linkerd-init" { + containsInitContainer = true + break + } + } + + if containsLinkerdProxy && !containsInitContainer { + logEntry.Debug("linkerd-cni: setting up iptables firewall") + options := cmd.RootOptions{ + IncomingProxyPort: conf.ProxyInit.IncomingProxyPort, + OutgoingProxyPort: conf.ProxyInit.OutgoingProxyPort, + ProxyUserID: conf.ProxyInit.ProxyUID, + PortsToRedirect: conf.ProxyInit.PortsToRedirect, + InboundPortsToIgnore: conf.ProxyInit.InboundPortsToIgnore, + OutboundPortsToIgnore: conf.ProxyInit.OutboundPortsToIgnore, + SimulateOnly: conf.ProxyInit.Simulate, + NetNs: args.Netns, + UseWaitFlag: conf.ProxyInit.UseWaitFlag, + FirewallBinPath: "iptables", + FirewallSaveBinPath: "iptables-save", + } + + // Check if there are any overridden ports to be skipped + outboundSkipOverride, err := getAnnotationOverride(ctx, client, pod, "config.linkerd.io/skip-outbound-ports") + if err != nil { + logEntry.Errorf("linkerd-cni: could not retrieve overridden annotations: %s", err) + return err + } + + if outboundSkipOverride != "" { + logEntry.Debugf("linkerd-cni: overriding OutboundPortsToIgnore to %s", outboundSkipOverride) + options.OutboundPortsToIgnore = strings.Split(outboundSkipOverride, ",") + } + + inboundSkipOverride, err := getAnnotationOverride(ctx, client, pod, "config.linkerd.io/skip-inbound-ports") + if err != nil { + logEntry.Errorf("linkerd-cni: could not retrieve overridden annotations: %s", err) + return err + } + + if inboundSkipOverride != "" { + logEntry.Debugf("linkerd-cni: overriding InboundPortsToIgnore to %s", inboundSkipOverride) + options.InboundPortsToIgnore = strings.Split(inboundSkipOverride, ",") + } + + // Override ProxyUID from annotations. + proxyUIDOverride, err := getAnnotationOverride(ctx, client, pod, "config.linkerd.io/proxy-uid") + if err != nil { + logEntry.Errorf("linkerd-cni: could not retrieve overridden annotations: %s", err) + return err + } + + if proxyUIDOverride != "" { + logEntry.Debugf("linkerd-cni: overriding ProxyUID to %s", proxyUIDOverride) + + parsed, err := strconv.Atoi(proxyUIDOverride) + if err != nil { + logEntry.Errorf("linkerd-cni: could not parse ProxyUID to integer: %s", err) + return err + } + + options.ProxyUserID = parsed + } + + if pod.GetLabels()["controller-component"] != "" { + // Skip 443 outbound port if its a control plane component + logEntry.Debug("linkerd-cni: adding 443 to OutboundPortsToIgnore as its a control plane component") + options.OutboundPortsToIgnore = append(options.OutboundPortsToIgnore, "443") + } + + firewallConfiguration, err := cmd.BuildFirewallConfiguration(&options) + if err != nil { + logEntry.Errorf("linkerd-cni: could not create a Firewall Configuration from the options: %v", options) + return err + } + + err = iptables.ConfigureFirewall(*firewallConfiguration) + if err != nil { + logEntry.Errorf("linkerd-cni: could not configure firewall: %s", err) + return err + } + } else { + if containsInitContainer { + logEntry.Debug("linkerd-cni: linkerd-init initContainer is present, skipping.") + } else { + logEntry.Debug("linkerd-cni: linkerd-proxy is not present, skipping.") + } + } + } else { + logEntry.Debug("linkerd-cni: no Kubernetes namespace or pod name found, skipping.") + } + + logrus.Debug("linkerd-cni: plugin is finished") + if conf.PrevResult != nil { + // Pass through the prevResult for the next plugin + return types.PrintResult(conf.PrevResult, conf.CNIVersion) + } + + logrus.Debug("linkerd-cni: no previous result to pass through, assume stand-alone run, send ok") + + return types.PrintResult(&cniv1.Result{CNIVersion: cniv1.ImplementedSpecVersion}, conf.CNIVersion) +} + +func cmdCheck(args *skel.CmdArgs) error { + logrus.Debug("linkerd-cni: cmdCheck not implemented") + return nil +} + +// cmdDel is called for DELETE requests +func cmdDel(args *skel.CmdArgs) error { + logrus.Debug("linkerd-cni: cmdDel not implemented") + return nil +} + +func getAnnotationOverride(ctx context.Context, api *kubernetes.Clientset, pod *v1.Pod, key string) (string, error) { + // Check if the annotation is present on the pod + if override := pod.GetObjectMeta().GetAnnotations()[key]; override != "" { + return override, nil + } + + // Check if the annotation is present on the namespace + ns, err := api.CoreV1().Namespaces().Get(ctx, pod.GetObjectMeta().GetNamespace(), metav1.GetOptions{}) + if err != nil { + return "", err + } + + if override := ns.GetObjectMeta().GetAnnotations()[key]; override != "" { + return override, nil + } + + return "", nil +} diff --git a/cni-plugin/test/data/env_vars.list b/cni-plugin/test/data/env_vars.list new file mode 100644 index 00000000..8578bc7a --- /dev/null +++ b/cni-plugin/test/data/env_vars.list @@ -0,0 +1,21 @@ +KUBE_DNS_SERVICE_PORT=53 +KUBE_DNS_PORT_53_TCP_PROTO=tcp +KUBE_DNS_PORT_53_UDP=udp://10.110.0.10:53 +KUBE_DNS_PORT_53_UDP_PROTO=udp +KUBERNETES_PORT_443_TCP_PROTO=tcp +KUBERNETES_PORT_443_TCP_ADDR=10.110.0.1 +KUBE_DNS_PORT_53_UDP_ADDR=10.110.0.10 +KUBERNETES_PORT=tcp://10.110.0.1:443 +KUBE_DNS_PORT_53_TCP_ADDR=10.110.0.10 +KUBE_DNS_PORT=udp://10.110.0.10:53 +KUBERNETES_SERVICE_PORT_HTTPS=443 +KUBERNETES_PORT_443_TCP_PORT=443 +KUBERNETES_PORT_443_TCP=tcp://10.110.0.1:443 +KUBE_DNS_PORT_53_TCP_PORT=53 +KUBE_DNS_PORT_53_TCP=tcp://10.110.0.10:53 +KUBERNETES_SERVICE_PORT=443 +KUBE_DNS_SERVICE_PORT_DNS=53 +KUBE_DNS_SERVICE_PORT_DNS_TCP=53 +KUBERNETES_SERVICE_HOST=10.110.0.1 +KUBE_DNS_PORT_53_UDP_PORT=53 +KUBE_DNS_SERVICE_HOST=10.110.0.10 diff --git a/cni-plugin/test/data/expected/01-linkerd-cni.conf-1 b/cni-plugin/test/data/expected/01-linkerd-cni.conf-1 new file mode 100644 index 00000000..a527424e --- /dev/null +++ b/cni-plugin/test/data/expected/01-linkerd-cni.conf-1 @@ -0,0 +1,24 @@ +{ + "name": "linkerd-cni", + "type": "linkerd-cni", + "log_level": "info", + "policy": { + "type": "k8s", + "k8s_api_root": "https://10.110.0.1:443", + "k8s_auth_token": "MyAwesomeToken" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/ZZZ-linkerd-cni-kubeconfig" + }, + "linkerd": { + "incoming-proxy-port": 4143, + "outgoing-proxy-port": 4140, + "proxy-uid": 2102, + "ports-to-redirect": [], + "inbound-ports-to-ignore": [], + "outbound-ports-to-ignore": [], + "simulate": false, + "use-wait-flag": false + } +} + diff --git a/cni-plugin/test/data/expected/10-calico.conflist-1 b/cni-plugin/test/data/expected/10-calico.conflist-1 new file mode 100644 index 00000000..d38901d2 --- /dev/null +++ b/cni-plugin/test/data/expected/10-calico.conflist-1 @@ -0,0 +1,51 @@ +{ + "name": "k8s-pod-network", + "cniVersion": "0.3.0", + "plugins": [ + { + "type": "calico", + "etcd_endpoints": "http://10.110.0.136:6666", + "log_level": "info", + "mtu": 1500, + "ipam": { + "type": "calico-ipam" + }, + "policy": { + "type": "k8s" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/calico-kubeconfig" + } + }, + { + "type": "portmap", + "snat": true, + "capabilities": { + "portMappings": true + } + }, + { + "name": "linkerd-cni", + "type": "linkerd-cni", + "log_level": "info", + "policy": { + "type": "k8s", + "k8s_api_root": "https://10.110.0.1:443", + "k8s_auth_token": "MyAwesomeToken" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/ZZZ-linkerd-cni-kubeconfig" + }, + "linkerd": { + "incoming-proxy-port": 4143, + "outgoing-proxy-port": 4140, + "proxy-uid": 2102, + "ports-to-redirect": [], + "inbound-ports-to-ignore": [], + "outbound-ports-to-ignore": [], + "simulate": false, + "use-wait-flag": false + } + } + ] +} diff --git a/cni-plugin/test/data/expected/10-calico.conflist-1.clean b/cni-plugin/test/data/expected/10-calico.conflist-1.clean new file mode 100644 index 00000000..61d0b452 --- /dev/null +++ b/cni-plugin/test/data/expected/10-calico.conflist-1.clean @@ -0,0 +1,28 @@ +{ + "name": "k8s-pod-network", + "cniVersion": "0.3.0", + "plugins": [ + { + "type": "calico", + "etcd_endpoints": "http://10.110.0.136:6666", + "log_level": "info", + "mtu": 1500, + "ipam": { + "type": "calico-ipam" + }, + "policy": { + "type": "k8s" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/calico-kubeconfig" + } + }, + { + "type": "portmap", + "snat": true, + "capabilities": { + "portMappings": true + } + } + ] +} diff --git a/cni-plugin/test/data/expected/10-host-local.conf-1.clean b/cni-plugin/test/data/expected/10-host-local.conf-1.clean new file mode 100644 index 00000000..b8193ff5 --- /dev/null +++ b/cni-plugin/test/data/expected/10-host-local.conf-1.clean @@ -0,0 +1,21 @@ +{ + "plugins": [ + { + "name": "dbnet", + "type": "bridge", + "bridge": "cni0", + "ipam": { + "type": "host-local", + "subnet": "10.1.0.0/16", + "gateway": "10.1.0.1" + }, + "dns": { + "nameservers": [ + "10.1.0.1" + ] + } + } + ], + "name": "k8s-pod-network", + "cniVersion": "0.3.0" +} diff --git a/cni-plugin/test/data/expected/10-host-local.conflist-1 b/cni-plugin/test/data/expected/10-host-local.conflist-1 new file mode 100644 index 00000000..e271484b --- /dev/null +++ b/cni-plugin/test/data/expected/10-host-local.conflist-1 @@ -0,0 +1,44 @@ +{ + "plugins": [ + { + "name": "dbnet", + "type": "bridge", + "bridge": "cni0", + "ipam": { + "type": "host-local", + "subnet": "10.1.0.0/16", + "gateway": "10.1.0.1" + }, + "dns": { + "nameservers": [ + "10.1.0.1" + ] + } + }, + { + "name": "linkerd-cni", + "type": "linkerd-cni", + "log_level": "info", + "policy": { + "type": "k8s", + "k8s_api_root": "https://10.110.0.1:443", + "k8s_auth_token": "MyAwesomeToken" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/ZZZ-linkerd-cni-kubeconfig" + }, + "linkerd": { + "incoming-proxy-port": 4143, + "outgoing-proxy-port": 4140, + "proxy-uid": 2102, + "ports-to-redirect": [], + "inbound-ports-to-ignore": [], + "outbound-ports-to-ignore": [], + "simulate": false, + "use-wait-flag": false + } + } + ], + "name": "k8s-pod-network", + "cniVersion": "0.3.0" +} diff --git a/cni-plugin/test/data/k8s_svcacct/ca.crt b/cni-plugin/test/data/k8s_svcacct/ca.crt new file mode 100644 index 00000000..efd11277 --- /dev/null +++ b/cni-plugin/test/data/k8s_svcacct/ca.crt @@ -0,0 +1,4 @@ + +-----BEGIN CERTIFICATE----- +MyBestCertificate +-----END CERTIFICATE----- \ No newline at end of file diff --git a/cni-plugin/test/data/k8s_svcacct/namespace b/cni-plugin/test/data/k8s_svcacct/namespace new file mode 100644 index 00000000..d2826d00 --- /dev/null +++ b/cni-plugin/test/data/k8s_svcacct/namespace @@ -0,0 +1 @@ +test-namespace \ No newline at end of file diff --git a/cni-plugin/test/data/k8s_svcacct/token b/cni-plugin/test/data/k8s_svcacct/token new file mode 100644 index 00000000..0c6f7ca5 --- /dev/null +++ b/cni-plugin/test/data/k8s_svcacct/token @@ -0,0 +1 @@ +MyAwesomeToken \ No newline at end of file diff --git a/cni-plugin/test/data/pre/10-calico.conflist b/cni-plugin/test/data/pre/10-calico.conflist new file mode 100644 index 00000000..eccb89b9 --- /dev/null +++ b/cni-plugin/test/data/pre/10-calico.conflist @@ -0,0 +1,28 @@ +{ + "name": "k8s-pod-network", + "cniVersion": "0.3.0", + "plugins": [ + { + "type": "calico", + "etcd_endpoints": "http://10.110.0.136:6666", + "log_level": "info", + "mtu": 1500, + "ipam": { + "type": "calico-ipam" + }, + "policy": { + "type": "k8s" + }, + "kubernetes": { + "kubeconfig": "/etc/cni/net.d/calico-kubeconfig" + } + }, + { + "type": "portmap", + "snat": true, + "capabilities": { + "portMappings": true + } + } + ] +} \ No newline at end of file diff --git a/cni-plugin/test/data/pre/10-host-local.conf b/cni-plugin/test/data/pre/10-host-local.conf new file mode 100644 index 00000000..19d5365a --- /dev/null +++ b/cni-plugin/test/data/pre/10-host-local.conf @@ -0,0 +1,14 @@ +{ + "cniVersion": "0.3.0", + "name": "dbnet", + "type": "bridge", + "bridge": "cni0", + "ipam": { + "type": "host-local", + "subnet": "10.1.0.0/16", + "gateway": "10.1.0.1" + }, + "dns": { + "nameservers": [ "10.1.0.1" ] + } +} \ No newline at end of file diff --git a/cni-plugin/test/install-cni_test.go b/cni-plugin/test/install-cni_test.go new file mode 100644 index 00000000..92cd4297 --- /dev/null +++ b/cni-plugin/test/install-cni_test.go @@ -0,0 +1,379 @@ +// Copyright 2018 Istio Authors +// Modifications copyright (c) Linkerd authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "bytes" + "flag" + "fmt" + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "testing" + "time" +) + +const ( + hostCniNetDir = "/host/etc/cni/net.d" + cniNetSubDir = "/data/pre/" + k8sSvcAcctSubDir = "/data/k8s_svcacct/" + + cniConfName = "CNI_CONF_NAME" + cniNetworkConfigName = "CNI_NETWORK_CONFIG" + + testWd = "/tmp" +) + +func env(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +} + +func setEnv(key, value string, t *testing.T) { + err := os.Setenv(key, value) + if err != nil { + t.Fatalf("couldn't set environment variable: %v", err) + } +} + +func mktemp(dir, prefix string, t *testing.T) string { + tempDir, err := os.MkdirTemp(dir, prefix) + if err != nil { + t.Fatalf("couldn't get current working directory: %v", err) + } + t.Logf("Created temporary dir: %v", tempDir) + return tempDir +} + +func pwd(t *testing.T) string { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("couldn't get current working directory: %v", err) + } + return wd + "/" +} + +func ls(dir string, t *testing.T) []string { + files, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("failed to list files: %v", err) + } + fileNames := make([]string, len(files)) + for i, f := range files { + fileNames[i] = f.Name() + } + return fileNames +} + +func cp(src, dest string, t *testing.T) { + data, err := os.ReadFile(src) //nolint:gosec + if err != nil { + t.Fatalf("failed to read file %v: %v", src, err) + } + if err = os.WriteFile(dest, data, 0600); err != nil { + t.Fatalf("failed to write file %v: %v", dest, err) + } +} + +func rm(dir string, t *testing.T) { + err := os.RemoveAll(dir) + if err != nil { + t.Fatalf("failed to remove dir %v: %v", dir, err) + } +} + +// Checks that only a single configuration file that CNI will look for exists. CNI will look +// for any filename ending in `.conf` or `.conflist` and pick the first in lexicographic order. +func checkOnlyOneConfFileExists(t *testing.T, directory string) { + filenames := ls(directory, t) + possibleConfigFiles := []string{} + + for _, filename := range filenames { + if strings.HasSuffix(filename, ".conf") || strings.HasSuffix(filename, ".conflist") { + possibleConfigFiles = append(possibleConfigFiles, filename) + } + } + + if len(possibleConfigFiles) == 0 { + t.Log("FAIL: no files found ending with .conf or .conflist in the CNI configuration directory") + t.Fail() + } else if len(possibleConfigFiles) > 1 { + t.Logf("FAIL: CNI configuration conflict: multiple files found ending with .conf or .conflist %v", possibleConfigFiles) + t.Fail() + } +} + +// populateTempDirs populates temporary test directories with golden files +func populateTempDirs(wd string, tempCNINetDir string, preConfFile string, t *testing.T) { + t.Logf("Pre-populating working dirs") + t.Logf("Copying %v into temp config dir %v", preConfFile, tempCNINetDir) + cp(wd+cniNetSubDir+preConfFile, tempCNINetDir+"/"+preConfFile, t) +} + +// populateK8sCreds populates temporary k8s directories with k8s credentials like service account token +func populateK8sCreds(wd string, tempK8sSvcAcctDir string, t *testing.T) { + for _, f := range ls(wd+k8sSvcAcctSubDir, t) { + t.Logf("Copying %v into temp k8s serviceaccount dir %v", f, tempK8sSvcAcctDir) + cp(wd+k8sSvcAcctSubDir+f, tempK8sSvcAcctDir+"/"+f, t) + } + t.Logf("Finished pre-populating working dirs") +} + +// startDocker starts a test Docker container and runs the install-cni.sh script. +func startDocker(testNum int, wd string, testWorkRootDir string, tempCNINetDir string, tempCNIBinDir string, tempK8sSvcAcctDir string, t *testing.T) string { + // The following is in place to default to a sane development environment that mirrors how bin/fast-build + // does it. To change to a different docker image, set the HUB and TAG environment variables before running the tests. + gitShaHead, _ := exec.Command("git", "rev-parse", "--short=8", "HEAD").Output() + user, _ := user.Current() + tag := "dev-" + strings.Trim(string(gitShaHead), "\n") + "-" + user.Username + dockerImage := env("HUB", "cr.l5d.io/linkerd") + "/cni-plugin:" + env("TAG", tag) + errFileName := testWorkRootDir + "/docker_run_stderr" + + // Build arguments list by picking whatever is necessary from the environment. + args := []string{"run", "-d", + "--name", "test-linkerd-cni-install-" + strconv.Itoa(testNum), + "-v", tempCNINetDir + ":" + hostCniNetDir, + "-v", tempCNIBinDir + ":/host/opt/cni/bin", + "-v", tempK8sSvcAcctDir + ":/var/run/secrets/kubernetes.io/serviceaccount", + "--env-file", wd + "/data/env_vars.list", + "-e", cniNetworkConfigName, + "-e", "SLEEP=true", + } + if _, ok := os.LookupEnv(cniConfName); ok { + args = append(args, "-e", cniConfName) + } + args = append(args, dockerImage, "install-cni.sh") + + // Create a temporary log file to write docker command error log. + errFile, err := os.Create(errFileName) //nolint:gosec + if err != nil { + t.Fatalf("couldn't create docker stderr file: %v", err) + } + defer func() { + errClose := errFile.Close() + if errClose != nil { + t.Fatalf("couldn't close docker stderr file: %v", errClose) + } + }() + + // Run the docker command and write errors to a temporary file. + cmd := exec.Command("docker", args...) + cmd.Stderr = errFile + + containerID, err := cmd.Output() + if err != nil { + errFileContents, _ := os.ReadFile(errFileName) //nolint:gosec + t.Logf("%v contents:\n\n%v\n\n", errFileName, string(errFileContents)) + t.Fatalf("test %v ERROR: failed to start docker container '%v', see %v", + testNum, dockerImage, errFileName) + } + t.Logf("Container ID: %s", containerID) + return strings.Trim(string(containerID), "\n") +} + +// docker runs the given docker command on the given container ID. +func docker(cmd, containerID string, t *testing.T) { + out, err := exec.Command("docker", cmd, containerID).CombinedOutput() + if err != nil { + t.Fatalf("failed to execute 'docker %s %s': %v", cmd, containerID, err) + } + t.Logf("docker %s %s - out: %s", cmd, containerID, out) +} + +// compareConfResult does a string compare of 2 test files. +func compareConfResult(testWorkRootDir string, tempCNINetDir string, result string, expected string, t *testing.T) { + tempResult := tempCNINetDir + "/" + result + resultFile, err := os.ReadFile(tempResult) //nolint:gosec + if err != nil { + t.Fatalf("failed to read file %v: %v", tempResult, err) + } + + expectedFile, err := os.ReadFile(expected) //nolint:gosec + if err != nil { + t.Fatalf("failed to read file %v, err: %v", expected, err) + } + + if bytes.Equal(resultFile, expectedFile) { + t.Logf("PASS: result matches expected: %v v. %v", tempResult, expected) + } else { + tempFail := mktemp(testWorkRootDir, result+".fail.XXXX", t) //nolint:gosec + cp(tempResult, tempFail+"/"+result, t) + t.Errorf("FAIL: result doesn't match expected: %v v. %v\nCheck %v for diff contents", tempResult, expected, tempFail) + } +} + +// checkBinDir verifies the presence/absence of test files. +func checkBinDir(t *testing.T, tempCNIBinDir string, op string, files ...string) { + for _, f := range files { + if _, err := os.Stat(tempCNIBinDir + "/" + f); !os.IsNotExist(err) { + if op == "add" { + t.Logf("PASS: File %v was added to %v", f, tempCNIBinDir) + } else if op == "del" { + t.Fatalf("FAIL: File %v was not removed from %v", f, tempCNIBinDir) + } + } else { + if op == "add" { + t.Fatalf("FAIL: File %v was not added to %v", f, tempCNIBinDir) + } else if op == "del" { + t.Logf("PASS: File %v was removed from %v", f, tempCNIBinDir) + } + } + } +} + +// doTest sets up necessary environment variables, runs the Docker installation +// container and verifies output file correctness. +func doTest(testNum int, wd string, initialNetConfFile string, finalNetConfFile string, expectNetConfFile string, expectedPostCleanNetConfFile string, tempCNINetDir string, tempCNIBinDir string, tempK8sSvcAcctDir string, testWorkRootDir string, t *testing.T) { + t.Logf("Test %v: prior cni-conf='%v', expected result='%v'", testNum, initialNetConfFile, finalNetConfFile) + + if initialNetConfFile != "NONE" { + setEnv(cniConfName, initialNetConfFile, t) + } + defaultData, err := os.ReadFile(wd + "../deployment/linkerd-cni.conf.default") //nolint:gosec + if err != nil { + t.Fatalf("failed to read file %v, err: %v", wd+"../deployment/linkerd-cni.conf.default", err) + } + setEnv(cniNetworkConfigName, string(defaultData), t) + + containerID := startDocker(testNum, wd, testWorkRootDir, tempCNINetDir, tempCNIBinDir, tempK8sSvcAcctDir, t) + time.Sleep(5 * time.Second) + + compareConfResult(testWorkRootDir, tempCNINetDir, finalNetConfFile, expectNetConfFile, t) + checkBinDir(t, tempCNIBinDir, "add", "linkerd-cni") + checkOnlyOneConfFileExists(t, tempCNINetDir) + + docker("stop", containerID, t) + time.Sleep(5 * time.Second) + + t.Logf("Test %v: Check the cleanup worked", testNum) + checkBinDir(t, tempCNIBinDir, "del", "linkerd-cni") + if len(expectedPostCleanNetConfFile) > 0 { + compareConfResult(testWorkRootDir, tempCNINetDir, finalNetConfFile, expectedPostCleanNetConfFile, t) + } else { + files := ls(tempCNINetDir, t) + if len(files) > 0 { + t.Fatalf("FAIL: CNI_CONF_DIR is not empty: %v", files) + } else { + t.Log("PASS: CNI_CONF_DIR is empty") + } + } + + docker("logs", containerID, t) + docker("rm", containerID, t) +} + +func TestMain(m *testing.M) { + runTests := flag.Bool("integration-tests", false, "must be provided to run the integration tests") + flag.Parse() + + if !*runTests { + fmt.Fprintln(os.Stderr, "integration tests not enabled: enable with -integration-tests") + os.Exit(0) + } + + os.Exit(m.Run()) +} + +func TestInstallCNI_Scenario1(t *testing.T) { + t.Log("If the test fails, you will want to check the docker logs of the container and then be sure to stop && remove it before running the tests again.") + + t.Log("Scenario 1: There isn't an existing plugin configuration in the CNI_NET_DIR.") + t.Log("GIVEN the CNI_NET_DIR=/etc/cni/net.d/ is empty") + t.Log("WHEN the install-cni.sh script is executed") + t.Log("THEN it should write the 01-linkerd-cni.conf file appropriately") + t.Log("AND WHEN the container is stopped") + t.Log("THEN it should delete the linkerd-cni artifacts") + + wd := pwd(t) + t.Logf("..setting the working directory: %v", wd) + t.Logf("..setting the test working directory: %v", testWd) + testCNINetDir := mktemp(testWd, "linkerd-cni-confXXXXX", t) + t.Logf("..creating the test CNI_NET_DIR: %v", testCNINetDir) + defer rm(testCNINetDir, t) + testCNIBinDir := mktemp(testWd, "linkerd-cni-binXXXXX", t) + t.Logf("..creating the test CNI_BIN_DIR: %v", testCNIBinDir) + defer rm(testCNIBinDir, t) + testK8sSvcAcctDir := mktemp(testWd, "kube-svcacctXXXXX", t) + t.Logf("..creating the k8s service account directory: %v", testK8sSvcAcctDir) + defer rm(testK8sSvcAcctDir, t) + + populateK8sCreds(wd, testK8sSvcAcctDir, t) + doTest(1, wd, "NONE", "01-linkerd-cni.conf", wd+"data/expected/01-linkerd-cni.conf-1", "", testCNINetDir, testCNIBinDir, testK8sSvcAcctDir, testWd, t) +} + +func TestInstallCNI_Scenario2(t *testing.T) { + t.Log("If the test fails, you will want to check the docker logs of the container and then be sure to stop && remove it before running the tests again.") + + t.Log("Scenario 2: There is an existing plugin configuration (.conf) in the CNI_NET_DIR.") + t.Log("GIVEN the CNI_NET_DIR=/etc/cni/net.d/ is NOT empty") + t.Log("WHEN the install-cni.sh script is executed") + t.Log("THEN it should update the existing file contents appropriately") + t.Log("THEN it should rename the existing file appropriately") + t.Log("AND WHEN the container is stopped") + t.Log("THEN it should delete the linkerd-cni artifacts") + t.Log("THEN it should revert back to the previous plugin configuration and filename") + + wd := pwd(t) + t.Logf("..setting the working directory: %v", wd) + t.Logf("..setting the test working directory: %v", testWd) + testCNINetDir := mktemp(testWd, "linkerd-cni-confXXXXX", t) + t.Logf("..creating the test CNI_NET_DIR: %v", testCNINetDir) + defer rm(testCNINetDir, t) + testCNIBinDir := mktemp(testWd, "linkerd-cni-binXXXXX", t) + t.Logf("..creating the test CNI_BIN_DIR: %v", testCNIBinDir) + defer rm(testCNIBinDir, t) + testK8sSvcAcctDir := mktemp(testWd, "kube-svcacctXXXXX", t) + t.Logf("..creating the k8s service account directory: %v", testK8sSvcAcctDir) + defer rm(testK8sSvcAcctDir, t) + + populateTempDirs(wd, testCNINetDir, "10-host-local.conf", t) + populateK8sCreds(wd, testK8sSvcAcctDir, t) + doTest(2, wd, hostCniNetDir+"/10-host-local.conf", "10-host-local.conflist", wd+"data/expected/10-host-local.conflist-1", wd+"data/expected/10-host-local.conf-1.clean", testCNINetDir, testCNIBinDir, testK8sSvcAcctDir, testWd, t) +} + +func TestInstallCNI_Scenario3(t *testing.T) { + t.Log("If the test fails, you will want to check the docker logs of the container and then be sure to stop && remove it before running the tests again.") + + t.Log("Scenario 3: There is an existing plugin configuration (.conflist) in the CNI_NET_DIR.") + t.Log("GIVEN the CNI_NET_DIR=/etc/cni/net.d/ is NOT empty") + t.Log("WHEN the install-cni.sh script is executed") + t.Log("THEN it should update the existing file contents appropriately") + t.Log("THEN it should rename the existing file appropriately") + t.Log("AND WHEN the container is stopped") + t.Log("THEN it should delete the linkerd-cni artifacts") + t.Log("THEN it should revert back to the previous plugin configuration and filename") + + wd := pwd(t) + t.Logf("..setting the working directory: %v", wd) + t.Logf("..setting the test working directory: %v", testWd) + testCNINetDir := mktemp(testWd, "linkerd-cni-confXXXXX", t) + t.Logf("..creating the test CNI_NET_DIR: %v", testCNINetDir) + defer rm(testCNINetDir, t) + testCNIBinDir := mktemp(testWd, "linkerd-cni-binXXXXX", t) + t.Logf("..creating the test CNI_BIN_DIR: %v", testCNIBinDir) + defer rm(testCNIBinDir, t) + testK8sSvcAcctDir := mktemp(testWd, "kube-svcacctXXXXX", t) + t.Logf("..creating the k8s service account directory: %v", testK8sSvcAcctDir) + defer rm(testK8sSvcAcctDir, t) + + populateTempDirs(wd, testCNINetDir, "10-calico.conflist", t) + populateK8sCreds(wd, testK8sSvcAcctDir, t) + doTest(3, wd, hostCniNetDir+"/10-calico.conflist", "10-calico.conflist", wd+"data/expected/10-calico.conflist-1", wd+"data/expected/10-calico.conflist-1.clean", testCNINetDir, testCNIBinDir, testK8sSvcAcctDir, testWd, t) +} diff --git a/go.mod b/go.mod index 24abd864..3ac99e64 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,50 @@ module github.com/linkerd/linkerd2-proxy-init go 1.18 require ( + github.com/containernetworking/cni v1.1.2 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 + k8s.io/api v0.25.4 + k8s.io/apimachinery v0.25.4 + k8s.io/client-go v0.25.4 ) require ( - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/net v0.2.0 // indirect + golang.org/x/oauth2 v0.2.0 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/term v0.2.0 // indirect + golang.org/x/text v0.4.0 // indirect + golang.org/x/time v0.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a // indirect + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 6f2b7c97..69dcdea9 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,297 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= +github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= +golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= +golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= +k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= +k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= +k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= +k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= +k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a h1:UR2YSPKAb8j3uL2yK8V+t2ElG4RoBxhJTxa5gg0ZtSo= +k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/proxy-init/internal/iptables/iptables.go b/internal/iptables/iptables.go similarity index 99% rename from proxy-init/internal/iptables/iptables.go rename to internal/iptables/iptables.go index 09bcec99..21b6b6f4 100644 --- a/proxy-init/internal/iptables/iptables.go +++ b/internal/iptables/iptables.go @@ -12,7 +12,7 @@ import ( log "github.com/sirupsen/logrus" - "github.com/linkerd/linkerd2-proxy-init/proxy-init/internal/util" + util "github.com/linkerd/linkerd2-proxy-init/internal/util" ) const ( diff --git a/proxy-init/internal/iptables/iptables_test.go b/internal/iptables/iptables_test.go similarity index 100% rename from proxy-init/internal/iptables/iptables_test.go rename to internal/iptables/iptables_test.go diff --git a/proxy-init/internal/util/portrange.go b/internal/util/portrange.go similarity index 100% rename from proxy-init/internal/util/portrange.go rename to internal/util/portrange.go diff --git a/proxy-init/internal/util/portrange_test.go b/internal/util/portrange_test.go similarity index 100% rename from proxy-init/internal/util/portrange_test.go rename to internal/util/portrange_test.go diff --git a/justfile b/justfile index 3187a5b7..4d979e74 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,9 @@ _test-image := "test.l5d.io/linkerd/iptables-tester:test" default: lint test -lint: sh-lint md-lint rs-clippy proxy-init-lint action-lint action-dev-check +lint: sh-lint md-lint rs-clippy action-lint action-dev-check + +go-lint *flags: (proxy-init-lint flags) (cni-plugin-lint flags) test: rs-test proxy-init-test-unit proxy-init-test-integration @@ -69,6 +71,13 @@ rs-check-dir dir *flags: validator *args: {{ just_executable() }} --justfile=validator/.justfile {{ args }} +## +## cni-plugin +## + +cni-plugin-lint *flags: + golangci-lint run ./cni-plugin/... {{ flags }} + ## ## proxy-init ## @@ -76,12 +85,13 @@ validator *args: proxy-init-build: go build -o target/linkerd2-proxy-init ./proxy-init -proxy-init-lint: - golangci-lint run ./proxy-init/... +proxy-init-lint *flags: + golangci-lint run ./proxy-init/... {{ flags }} # Run proxy-init unit tests proxy-init-test-unit: go test -v ./proxy-init/... + go test -v ./internal/... # Run proxy-init integration tests after preparing dependencies proxy-init-test-integration: proxy-init-test-integration-deps proxy-init-test-integration-run diff --git a/proxy-init/cmd/root.go b/proxy-init/cmd/root.go index a20d4020..56876972 100644 --- a/proxy-init/cmd/root.go +++ b/proxy-init/cmd/root.go @@ -6,10 +6,10 @@ import ( "os/exec" log "github.com/sirupsen/logrus" - - "github.com/linkerd/linkerd2-proxy-init/proxy-init/internal/iptables" - "github.com/linkerd/linkerd2-proxy-init/proxy-init/internal/util" "github.com/spf13/cobra" + + "github.com/linkerd/linkerd2-proxy-init/internal/iptables" + "github.com/linkerd/linkerd2-proxy-init/internal/util" ) // RootOptions provides the information that will be used to build a firewall configuration. diff --git a/proxy-init/cmd/root_test.go b/proxy-init/cmd/root_test.go index 411ab413..222e9222 100644 --- a/proxy-init/cmd/root_test.go +++ b/proxy-init/cmd/root_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/linkerd/linkerd2-proxy-init/proxy-init/internal/iptables" + "github.com/linkerd/linkerd2-proxy-init/internal/iptables" ) func TestBuildFirewallConfiguration(t *testing.T) { diff --git a/proxy-init/integration/iptables/http_test.go b/proxy-init/integration/iptables/http_test.go index aa7c0f2f..d955dc96 100644 --- a/proxy-init/integration/iptables/http_test.go +++ b/proxy-init/integration/iptables/http_test.go @@ -3,7 +3,7 @@ package iptablestest import ( "flag" "fmt" - "io/ioutil" + "io" "net" "net/http" "net/url" @@ -250,7 +250,7 @@ func expectSuccessfulGetRequest(t *testing.T, url string) string { if err != nil { t.Fatalf("failed to send HTTP GET to %s:\n%v", url, err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("failed reading GET response from %s:\n%v", url, err) } diff --git a/proxy-init/integration/iptables/test_service/test_service.go b/proxy-init/integration/iptables/test_service/test_service.go index cd32c69d..f4abe6bc 100644 --- a/proxy-init/integration/iptables/test_service/test_service.go +++ b/proxy-init/integration/iptables/test_service/test_service.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net/http" "os" @@ -30,7 +30,7 @@ func callOtherServiceHandler(w http.ResponseWriter, r *http.Request) { if err != nil { http.Error(w, err.Error(), 500) } else { - body, err := ioutil.ReadAll(downstreamResp.Body) + body, err := io.ReadAll(downstreamResp.Body) if err != nil { http.Error(w, err.Error(), 500) } else {