Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cni): test #12590

Draft
wants to merge 29 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f0b8c80
refactor(cni): unhardcode file paths in install logic
bartsmykla Jan 17, 2025
6ec5cfd
refactor(cni): simplify error handling in install functions
bartsmykla Jan 17, 2025
d299627
refactor(cni): rename receiver in InstallerConfig validation
bartsmykla Jan 17, 2025
5d7a4e8
refactor(cni): improve error messages in validation logic
bartsmykla Jan 17, 2025
6723755
refactor(cni): enhance validation error messages and logging
bartsmykla Jan 17, 2025
8274bbf
refactor(cni): improve error handling and logging in main logic
bartsmykla Jan 17, 2025
9629acf
refactor(cni): add context support and improve error handling
bartsmykla Jan 17, 2025
e11459b
refactor(cni): fix tests
bartsmykla Jan 17, 2025
c2d8de1
refactor(cni): small cleanup/refactor in findCniConfFile
bartsmykla Jan 18, 2025
9738836
refactor(cni): change receiver to pointer in InstallerConfig methods
bartsmykla Jan 18, 2025
589a1c3
refactor(cni): move cni config discovery to post-processing step
bartsmykla Jan 18, 2025
317988d
test(cni): update installer config tests for post-process logic
bartsmykla Jan 18, 2025
c2c3680
refactor(cni): restructure kubeconfig generation and add tests
bartsmykla Jan 18, 2025
da81cc9
refactor(cni): simplify kubeconfig handling and config preparation
bartsmykla Jan 18, 2025
285a39c
refactor(cni): move install checks to InstallerConfig and update tests
bartsmykla Jan 18, 2025
2ff0951
refactor(cni): simplify installer configuration and preparation
bartsmykla Jan 18, 2025
6ac9cc7
Trigger CI for PR #12590 on 2025-01-18 14:12:48
github-actions[bot] Jan 18, 2025
16e1ed5
Trigger CI for PR #12590 on 2025-01-18 16:16:09
github-actions[bot] Jan 18, 2025
01ebce2
Trigger CI for PR #12590 on 2025-01-18 18:18:19
github-actions[bot] Jan 18, 2025
bd03f5c
Trigger CI for PR #12590 on 2025-01-18 20:14:37
github-actions[bot] Jan 18, 2025
9dfffed
Trigger CI for PR #12590 on 2025-01-18 22:12:44
github-actions[bot] Jan 18, 2025
ca0a454
Trigger CI for PR #12590 on 2025-01-19 01:14:16
github-actions[bot] Jan 19, 2025
9bbac08
Trigger CI for PR #12590 on 2025-01-19 02:46:57
github-actions[bot] Jan 19, 2025
39ec518
Trigger CI for PR #12590 on 2025-01-19 04:17:12
github-actions[bot] Jan 19, 2025
cb41a4a
Trigger CI for PR #12590 on 2025-01-19 06:19:17
github-actions[bot] Jan 19, 2025
66b73f5
Trigger CI for PR #12590 on 2025-01-19 08:16:57
github-actions[bot] Jan 19, 2025
d418397
Trigger CI for PR #12590 on 2025-01-19 10:15:02
github-actions[bot] Jan 19, 2025
2fc09e6
Trigger CI for PR #12590 on 2025-01-19 12:26:17
github-actions[bot] Jan 19, 2025
47f0648
Trigger CI for PR #12590 on 2025-01-19 14:13:46
github-actions[bot] Jan 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/cni/cmd/install/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package main

import (
"context"

"github.com/kumahq/kuma/app/cni/pkg/install"
)

func main() {
install.Run()
install.Run(context.Background())
}
233 changes: 140 additions & 93 deletions app/cni/pkg/install/installer_config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package install

import (
"context"
"encoding/base64"
"fmt"
"net"
"net/url"
"os"
Expand All @@ -12,34 +14,36 @@ import (
"github.com/pkg/errors"

"github.com/kumahq/kuma/pkg/config"
"github.com/kumahq/kuma/pkg/util/files"
)

const (
defaultKumaCniConfName = "YYY-kuma-cni.conflist"
)

var _ config.Config = InstallerConfig{}
var _ config.Config = &InstallerConfig{}

type InstallerConfig struct {
config.BaseConfig

CfgCheckInterval int `envconfig:"cfgcheck_interval" default:"1"`
ChainedCniPlugin bool `envconfig:"chained_cni_plugin" default:"true"`
CniConfName string `envconfig:"cni_conf_name" default:""`
CniLogLevel string `envconfig:"cni_log_level" default:"info"`
CniNetworkConfig string `envconfig:"cni_network_config" default:""`
HostCniNetDir string `envconfig:"cni_net_dir" default:"/etc/cni/net.d"`
KubeconfigName string `envconfig:"kubecfg_file_name" default:"ZZZ-kuma-cni-kubeconfig"`
KubernetesCaFile string `envconfig:"kube_ca_file"`
KubernetesServiceHost string `envconfig:"kubernetes_service_host"`
KubernetesServicePort string `envconfig:"kubernetes_service_port"`
KubernetesServiceProtocol string `envconfig:"kubernetes_service_protocol" default:"https"`
MountedCniNetDir string `envconfig:"mounted_cni_net_dir" default:"/host/etc/cni/net.d"`
ShouldSleep bool `envconfig:"sleep" default:"true"`
CfgCheckInterval int `envconfig:"cfgcheck_interval" default:"1"`
ChainedCniPlugin bool `envconfig:"chained_cni_plugin" default:"true"`
CniConfName string `envconfig:"cni_conf_name" default:""`
CniLogLevel string `envconfig:"cni_log_level" default:"info"`
CniNetworkConfig string `envconfig:"cni_network_config" default:""`
HostCniNetDir string `envconfig:"cni_net_dir" default:"/etc/cni/net.d"`
KubeconfigName string `envconfig:"kubecfg_file_name" default:"ZZZ-kuma-cni-kubeconfig"`
KubernetesCaFile string `envconfig:"kube_ca_file" default:"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"`
KubernetesServiceAccountTokenPath string `envconfig:"kube_service_account_token_path" default:"/var/run/secrets/kubernetes.io/serviceaccount/token"`
KubernetesServiceHost string `envconfig:"kubernetes_service_host"`
KubernetesServicePort string `envconfig:"kubernetes_service_port"`
KubernetesServiceProtocol string `envconfig:"kubernetes_service_protocol" default:"https"`
MountedCniNetDir string `envconfig:"mounted_cni_net_dir" default:"/host/etc/cni/net.d"`
ShouldSleep bool `envconfig:"sleep" default:"true"`
}

func (i InstallerConfig) Validate() error {
if i.CfgCheckInterval <= 0 {
func (c *InstallerConfig) Validate() error {
if c.CfgCheckInterval <= 0 {
return errors.New("CFGCHECK_INTERVAL env variable needs to be greater than 0")
}

Expand All @@ -48,136 +52,179 @@ func (i InstallerConfig) Validate() error {
return nil
}

func findCniConfFile(mountedCNINetDir string) (string, error) {
matches, err := filepath.Glob(mountedCNINetDir + "/*.conf")
if err != nil {
return "", err
func (c *InstallerConfig) PostProcess() error {
if c.CniConfName != "" {
return nil
}

file, found := lookForValidConfig(matches, isValidConfFile)
if found {
return filepath.Base(file), nil
}
for _, ext := range []string{"*.conf", "*.conflist"} {
matches, err := filepath.Glob(filepath.Join(c.MountedCniNetDir, ext))
if err != nil {
log.Info("failed to search for CNI config files", "error", err)
continue
}

matches, err = filepath.Glob(mountedCNINetDir + "/*.conflist")
if err != nil {
return "", err
}
file, found = lookForValidConfig(matches, isValidConflistFile)
if found {
return filepath.Base(file), nil
if file, ok := lookForValidConfig(matches, isValidConfFile); ok {
log.Info("found CNI config file", "file", file)
c.CniConfName = filepath.Base(file)
return nil
}
}

// use default
return "", errors.New("cni conf file not found - use default")
log.Info("could not find CNI config file, using default")
c.CniConfName = defaultKumaCniConfName

return nil
}

func prepareKubeconfig(ic *InstallerConfig, serviceAccountPath string) error {
kubeconfigPath := ic.MountedCniNetDir + "/" + ic.KubeconfigName
serviceAccountTokenPath := serviceAccountPath + "/token"
serviceAccountToken, err := os.ReadFile(serviceAccountTokenPath)
func (c *InstallerConfig) PrepareKubeconfig() error {
token, err := os.ReadFile(c.KubernetesServiceAccountTokenPath)
if err != nil {
return err
}

if ic.KubernetesServiceHost == "" {
return errors.New("KUBERNETES_SERVICE_HOST env variable not set")
return errors.Wrap(err, "failed to read service account token")
}

if ic.KubernetesServicePort == "" {
return errors.New("KUBERNETES_SERVICE_PORT env variable not set")
if c.KubernetesServiceHost == "" {
return errors.New("kubernetes service host is not set")
}

if ic.KubernetesCaFile == "" {
ic.KubernetesCaFile = serviceAccountPath + "/ca.crt"
if c.KubernetesServicePort == "" {
return errors.New("kubernetes service port is not set")
}

kubeCa, err := os.ReadFile(ic.KubernetesCaFile)
kubeCa, err := os.ReadFile(c.KubernetesCaFile)
if err != nil {
return err
return errors.Wrap(err, "failed to read Kubernetes CA file")
}
caData := base64.StdEncoding.EncodeToString(kubeCa)

kubeconfig := kubeconfigTemplate(ic.KubernetesServiceProtocol, ic.KubernetesServiceHost, ic.KubernetesServicePort, string(serviceAccountToken), caData)
log.Info("writing kubernetes config", "path", kubeconfigPath)
err = atomic.WriteFile(kubeconfigPath, strings.NewReader(kubeconfig))
if err != nil {
return err
return c.writeKubeconfig(c.GenerateKubeconfigTemplate(token, kubeCa))
}

func (c *InstallerConfig) writeKubeconfig(kubeconfig string) error {
path := filepath.Join(c.MountedCniNetDir, c.KubeconfigName)

log.Info("writing kubernetes config", "path", path)

if err := atomic.WriteFile(path, strings.NewReader(kubeconfig)); err != nil {
return errors.Wrap(err, "failed to write kubeconfig")
}

return nil
}

func kubeconfigTemplate(protocol, host, port, token, caData string) string {
serverUrl := url.URL{
Scheme: protocol,
Host: net.JoinHostPort(host, port),
}

return `# Kubeconfig file for kuma CNI plugin.
func (c *InstallerConfig) GenerateKubeconfigTemplate(token, caData []byte) string {
return fmt.Sprintf(
`# Kubeconfig file for kuma CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: ` + serverUrl.String() + `
certificate-authority-data: ` + caData + `
server: %s
certificate-authority-data: %s
users:
- name: kuma-cni
user:
token: ` + token + `
token: %s
contexts:
- name: kuma-cni-context
context:
cluster: local
user: kuma-cni
current-context: kuma-cni-context`
current-context: kuma-cni-context`,
c.kubernetesServiceURL(),
base64.StdEncoding.EncodeToString(caData),
token,
)
}

func (c *InstallerConfig) kubernetesServiceURL() *url.URL {
return &url.URL{
Scheme: c.KubernetesServiceProtocol,
Host: net.JoinHostPort(c.KubernetesServiceHost, c.KubernetesServicePort),
}
}

func (c *InstallerConfig) PrepareKumaCniConfig(ctx context.Context) error {
token, err := os.ReadFile(c.KubernetesServiceAccountTokenPath)
if err != nil {
return errors.Wrap(err, "failed to read service account token")
}

// Replace placeholders in the CNI network configuration
cniConfig := strings.NewReplacer(
"__KUBECONFIG_FILEPATH__", filepath.Join(c.HostCniNetDir, c.KubeconfigName),
"__SERVICEACCOUNT_TOKEN__", string(token),
).Replace(c.CniNetworkConfig)

log.V(1).Info("CNI config after replacement", "CNI config", cniConfig)

if c.ChainedCniPlugin {
if err := setupChainedPlugin(ctx, c.MountedCniNetDir, c.CniConfName, cniConfig); err != nil {
return errors.Wrap(err, "unable to setup kuma CNI as chained plugin")
}

return nil
}

configPath := filepath.Join(c.MountedCniNetDir, c.CniConfName)
log.Info("writing standalone CNI config", "path", configPath)

if err := atomic.WriteFile(configPath, strings.NewReader(cniConfig)); err != nil {
return errors.Wrap(err, "failed to write standalone CNI config")
}

return nil
}

func prepareKumaCniConfig(ic *InstallerConfig, serviceAccountPath string) error {
rawConfig := ic.CniNetworkConfig
kubeconfigFilePath := ic.HostCniNetDir + "/" + ic.KubeconfigName
func (c *InstallerConfig) CheckInstall() error {
confPath := filepath.Join(c.MountedCniNetDir, c.CniConfName)

cniConfig := strings.Replace(rawConfig, "__KUBECONFIG_FILEPATH__", kubeconfigFilePath, 1)
log.V(1).Info("cni config after replace", "cni config", cniConfig)
if !files.FileExists(confPath) {
return errors.Errorf("cni config file does not exist at the specified path: %s", confPath)
}

serviceAccountToken, err := os.ReadFile(serviceAccountPath + "/token")
parsed, err := parseFileToHashMap(confPath)
if err != nil {
return err
return errors.Wrap(err, "failed to parse cni config file")
}
cniConfig = strings.Replace(cniConfig, "__SERVICEACCOUNT_TOKEN__", string(serviceAccountToken), 1)

if ic.ChainedCniPlugin {
err := setupChainedPlugin(ic.MountedCniNetDir, ic.CniConfName, cniConfig)
if err != nil {
return errors.Wrap(err, "unable to setup kuma cni as chained plugin")
if c.ChainedCniPlugin {
if err := isValidConflistFile(confPath); err != nil {
return errors.Wrap(err, "chained plugin requires a valid conflist file format")
}
} else {
err := atomic.WriteFile(ic.MountedCniNetDir+"/"+ic.CniConfName, strings.NewReader(cniConfig))

plugins, err := getPluginsArray(parsed)
if err != nil {
return err
return errors.Wrap(err, "failed to retrieve plugins array from cni config")
}

if index, err := findKumaCniConfigIndex(plugins); err != nil {
return errors.Wrap(err, "failed to find kuma-cni plugin in chained config file")
} else if index < 0 {
return errors.New("kuma-cni plugin is missing in the chained config file")
}

return nil
}

if err := isValidConfFile(confPath); err != nil {
return errors.Wrap(err, "standalone plugin requires a valid conf file format")
}

if pluginType, ok := parsed["type"]; !ok {
return errors.New("cni config is missing the required 'type' field")
} else if pluginType != "kuma-cni" {
return errors.New("cni config 'type' field is not set to 'kuma-cni'")
}

return nil
}

func loadInstallerConfig() (*InstallerConfig, error) {
var installerConfig InstallerConfig
err := config.Load("", &installerConfig)
if err != nil {
return nil, err
}

if installerConfig.CniConfName == "" {
cniConfFile, err := findCniConfFile(installerConfig.MountedCniNetDir)
if err != nil {
log.Info("could not find cni conf file using default")
installerConfig.CniConfName = defaultKumaCniConfName
} else {
log.Info("found CNI config file", "file", cniConfFile)
installerConfig.CniConfName = cniConfFile
}
if err := config.Load("", &installerConfig); err != nil {
return nil, err
}

return &installerConfig, nil
Expand Down
Loading
Loading