pkg/networking/cilium/templater.go (221 lines of code) (raw):

package cilium import ( "context" _ "embed" "fmt" "strings" "time" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/helm" "github.com/aws/eks-anywhere/pkg/retrier" "github.com/aws/eks-anywhere/pkg/semver" "github.com/aws/eks-anywhere/pkg/templater" ) //go:embed network_policy.yaml var networkPolicyAllowAll string const ( maxRetries = 10 defaultBackOffPeriod = 5 * time.Second namespace = constants.KubeSystemNamespace ) // HelmClientFactory provides a helm client for a cluster. type HelmClientFactory interface { Get(ctx context.Context, clus *anywherev1.Cluster) (helm.Client, error) } type Templater struct { helmFactory HelmClientFactory } // NewTemplater returns a new Templater. func NewTemplater(helmFactory HelmClientFactory) *Templater { return &Templater{ helmFactory: helmFactory, } } func (t *Templater) GenerateUpgradePreflightManifest(ctx context.Context, spec *cluster.Spec) ([]byte, error) { versionsBundle := spec.RootVersionsBundle() v := templateValues(spec, versionsBundle) v.set(true, "preflight", "enabled") v.set(versionsBundle.Cilium.Cilium.Image(), "preflight", "image", "repository") v.set(versionsBundle.Cilium.Cilium.Tag(), "preflight", "image", "tag") v.set(false, "agent") v.set(false, "operator", "enabled") tolerationsList := []map[string]string{ { "operator": "Exists", }, } v.set(tolerationsList, "preflight", "tolerations") uri, version := getChartURIAndVersion(versionsBundle) kubeVersion, err := getKubeVersionString(spec, versionsBundle) if err != nil { return nil, err } helm, err := t.helmFactory.Get(ctx, spec.Cluster) if err != nil { return nil, fmt.Errorf("failed to get helm client for cluster %s: %v", spec.Cluster.Name, err) } manifest, err := helm.Template(ctx, uri, version, namespace, v, kubeVersion) if err != nil { return nil, fmt.Errorf("failed generating cilium upgrade preflight manifest: %v", err) } return manifest, nil } // ManifestOpt allows to modify options for a cilium manifest. type ManifestOpt func(*ManifestConfig) type ManifestConfig struct { values values retrier *retrier.Retrier kubeVersion string namespaces []string } // WithKubeVersion allows to generate the Cilium manifest for a different kubernetes version // than the one specified in the cluster spec. Useful for upgrades scenarios where Cilium is upgraded before // the kubernetes components. func WithKubeVersion(kubeVersion string) ManifestOpt { return func(c *ManifestConfig) { c.kubeVersion = kubeVersion } } // WithRetrier introduced for optimizing unit tests. func WithRetrier(retrier *retrier.Retrier) ManifestOpt { return func(c *ManifestConfig) { c.retrier = retrier } } // WithUpgradeFromVersion allows to specify the compatibility Cilium version to use in the manifest. // This is necessary for Cilium upgrades. func WithUpgradeFromVersion(version semver.Version) ManifestOpt { return func(c *ManifestConfig) { c.values.set(fmt.Sprintf("%d.%d", version.Major, version.Minor), "upgradeCompatibility") } } // WithPolicyAllowedNamespaces allows to specify which namespaces traffic should be allowed when using // and "Always" policy enforcement mode. func WithPolicyAllowedNamespaces(namespaces []string) ManifestOpt { return func(c *ManifestConfig) { c.namespaces = namespaces } } func (t *Templater) GenerateManifest(ctx context.Context, spec *cluster.Spec, opts ...ManifestOpt) ([]byte, error) { versionsBundle := spec.RootVersionsBundle() kubeVersion, err := getKubeVersionString(spec, versionsBundle) if err != nil { return nil, err } c := &ManifestConfig{ values: templateValues(spec, versionsBundle), kubeVersion: kubeVersion, retrier: retrier.NewWithMaxRetries(maxRetries, defaultBackOffPeriod), } for _, o := range opts { o(c) } uri, version := getChartURIAndVersion(versionsBundle) var manifest []byte helm, err := t.helmFactory.Get(ctx, spec.Cluster) if err != nil { return nil, fmt.Errorf("failed to get helm client for cluster %s: %v", spec.Cluster.Name, err) } err = c.retrier.Retry(func() error { manifest, err = helm.Template(ctx, uri, version, namespace, c.values, c.kubeVersion) return err }) if err != nil { return nil, fmt.Errorf("failed generating cilium manifest: %v", err) } if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.PolicyEnforcementMode == anywherev1.CiliumPolicyModeAlways { networkPolicyManifest, err := t.GenerateNetworkPolicyManifest(spec, c.namespaces) if err != nil { return nil, err } manifest = templater.AppendYamlResources(manifest, networkPolicyManifest) } return manifest, nil } func (t *Templater) GenerateNetworkPolicyManifest(spec *cluster.Spec, namespaces []string) ([]byte, error) { values := map[string]interface{}{ "managementCluster": spec.Cluster.IsSelfManaged(), "providerNamespaces": namespaces, } if spec.Cluster.Spec.GitOpsRef != nil { values["gitopsEnabled"] = true if spec.GitOpsConfig != nil { values["fluxNamespace"] = spec.GitOpsConfig.Spec.Flux.Github.FluxSystemNamespace } } return templater.Execute(networkPolicyAllowAll, values) } type values map[string]interface{} func (c values) set(value interface{}, path ...string) { element := c for _, p := range path[:len(path)-1] { e, ok := element[p] if !ok { e = values{} element[p] = e } element = e.(values) } element[path[len(path)-1]] = value } func templateValues(spec *cluster.Spec, versionsBundle *cluster.VersionsBundle) values { val := values{ "cni": values{ "chainingMode": "portmap", }, "ipam": values{ "mode": "kubernetes", }, "identityAllocationMode": "crd", "prometheus": values{ "enabled": true, }, "rollOutCiliumPods": true, "routingMode": "tunnel", "tunnelProtocol": "geneve", "image": values{ "repository": versionsBundle.Cilium.Cilium.Image(), "tag": versionsBundle.Cilium.Cilium.Tag(), }, "operator": values{ "image": values{ // The chart expects an "incomplete" repository // and will add the necessary suffix ("-generic" in our case) "repository": strings.TrimSuffix(versionsBundle.Cilium.Operator.Image(), "-generic"), "tag": versionsBundle.Cilium.Operator.Tag(), }, "prometheus": values{ "enabled": true, }, }, } if len(spec.Cluster.Spec.WorkerNodeGroupConfigurations) == 0 && spec.Cluster.Spec.ControlPlaneConfiguration.Count == 1 { val["operator"].(values)["replicas"] = 1 } if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.PolicyEnforcementMode != "" { val["policyEnforcementMode"] = spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.PolicyEnforcementMode } if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.EgressMasqueradeInterfaces != "" { val["egressMasqueradeInterfaces"] = spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.EgressMasqueradeInterfaces } if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.RoutingMode == anywherev1.CiliumRoutingModeDirect { val["routingMode"] = "native" val["autoDirectNodeRoutes"] = "true" delete(val, "tunnelProtocol") if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.IPv4NativeRoutingCIDR != "" { val["ipv4NativeRoutingCIDR"] = spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.IPv4NativeRoutingCIDR } if spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.IPv6NativeRoutingCIDR != "" { val["ipv6NativeRoutingCIDR"] = spec.Cluster.Spec.ClusterNetwork.CNIConfig.Cilium.IPv6NativeRoutingCIDR } } return val } func getChartURIAndVersion(versionsBundle *cluster.VersionsBundle) (uri, version string) { chart := versionsBundle.Cilium.HelmChart uri = fmt.Sprintf("oci://%s", chart.Image()) version = chart.Tag() return uri, version } func getKubeVersion(versionsBundle *cluster.VersionsBundle) (*semver.Version, error) { k8sVersion, err := semver.New(versionsBundle.KubeDistro.Kubernetes.Tag) if err != nil { return nil, fmt.Errorf("parsing kubernetes version %v: %v", versionsBundle.KubeDistro.Kubernetes.Tag, err) } return k8sVersion, nil } func getKubeVersionString(spec *cluster.Spec, versionsBundle *cluster.VersionsBundle) (string, error) { k8sVersion, err := getKubeVersion(versionsBundle) if err != nil { return "", err } return fmt.Sprintf("%d.%d", k8sVersion.Major, k8sVersion.Minor), nil }