From 7609df41d904cb9af4cd870482b2b52141dc5215 Mon Sep 17 00:00:00 2001 From: Michi Mutsuzaki Date: Mon, 26 Feb 2024 01:07:28 +0000 Subject: [PATCH 1/2] Delete classic mode clustermesh commands Delete classic mode clustermesh commands to get ready for v0.16 release. Ref: #2327 Signed-off-by: Michi Mutsuzaki --- clustermesh/certs.go | 222 ----------- clustermesh/clustermesh.go | 687 +------------------------------- clustermesh/clustermesh_test.go | 84 ---- defaults/defaults.go | 5 - internal/certs/certs.go | 182 --------- internal/cli/cmd/clustermesh.go | 123 +----- internal/utils/utils.go | 59 --- internal/utils/utils_test.go | 127 ------ 8 files changed, 10 insertions(+), 1479 deletions(-) delete mode 100644 clustermesh/certs.go delete mode 100644 internal/certs/certs.go diff --git a/clustermesh/certs.go b/clustermesh/certs.go deleted file mode 100644 index b16f7948ae..0000000000 --- a/clustermesh/certs.go +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package clustermesh - -import ( - "context" - "fmt" - "time" - - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/csr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/cilium/cilium-cli/defaults" - "github.com/cilium/cilium-cli/k8s" -) - -func (k *K8sClusterMesh) createClusterMeshServerCertificate(ctx context.Context) error { - certReq := &csr.CertificateRequest{ - Names: []csr.Name{{C: "US", ST: "San Francisco", L: "CA"}}, - KeyRequest: csr.NewKeyRequest(), - Hosts: []string{ - "clustermesh-apiserver.cilium.io", - "*.mesh.cilium.io", - "localhost", - "127.0.0.1", - }, - CN: "ClusterMesh Server", - } - - signConf := &config.Signing{ - Default: &config.SigningProfile{Expiry: 5 * 365 * 24 * time.Hour}, - Profiles: map[string]*config.SigningProfile{ - defaults.ClusterMeshServerSecretName: { - Expiry: 5 * 365 * 24 * time.Hour, - Usage: []string{"signing", "key encipherment", "server auth", "client auth"}, - }, - }, - } - - cert, key, err := k.certManager.GenerateCertificate(defaults.ClusterMeshServerSecretName, certReq, signConf) - if err != nil { - return fmt.Errorf("unable to generate certificate %s: %w", defaults.ClusterMeshServerSecretName, err) - } - - data := map[string][]byte{ - corev1.TLSCertKey: cert, - corev1.TLSPrivateKeyKey: key, - defaults.CASecretCertName: k.certManager.CACertBytes(), - } - - _, err = k.client.CreateSecret(ctx, k.params.Namespace, k8s.NewTLSSecret(defaults.ClusterMeshServerSecretName, k.params.Namespace, data), metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("unable to create secret %s/%s: %w", k.params.Namespace, defaults.ClusterMeshServerSecretName, err) - } - - return nil -} - -func (k *K8sClusterMesh) createClusterMeshAdminCertificate(ctx context.Context) error { - certReq := &csr.CertificateRequest{ - Names: []csr.Name{{C: "US", ST: "San Francisco", L: "CA"}}, - KeyRequest: csr.NewKeyRequest(), - Hosts: []string{ - "localhost", - "127.0.0.1", - }, - CN: "root", - } - - signConf := &config.Signing{ - Default: &config.SigningProfile{Expiry: 5 * 365 * 24 * time.Hour}, - Profiles: map[string]*config.SigningProfile{ - defaults.ClusterMeshAdminSecretName: { - Expiry: 5 * 365 * 24 * time.Hour, - Usage: []string{"signing", "key encipherment", "server auth", "client auth"}, - }, - }, - } - - cert, key, err := k.certManager.GenerateCertificate(defaults.ClusterMeshAdminSecretName, certReq, signConf) - if err != nil { - return fmt.Errorf("unable to generate certificate %s: %w", defaults.ClusterMeshAdminSecretName, err) - } - - data := map[string][]byte{ - corev1.TLSCertKey: cert, - corev1.TLSPrivateKeyKey: key, - defaults.CASecretCertName: k.certManager.CACertBytes(), - } - - _, err = k.client.CreateSecret(ctx, k.params.Namespace, k8s.NewTLSSecret(defaults.ClusterMeshAdminSecretName, k.params.Namespace, data), metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("unable to create secret %s/%s: %w", k.params.Namespace, defaults.ClusterMeshAdminSecretName, err) - } - - return nil -} - -func (k *K8sClusterMesh) createClusterMeshClientCertificate(ctx context.Context) error { - certReq := &csr.CertificateRequest{ - Names: []csr.Name{{C: "US", ST: "San Francisco", L: "CA"}}, - KeyRequest: csr.NewKeyRequest(), - Hosts: []string{""}, - CN: "remote", - } - - signConf := &config.Signing{ - Default: &config.SigningProfile{Expiry: 5 * 365 * 24 * time.Hour}, - Profiles: map[string]*config.SigningProfile{ - defaults.ClusterMeshRemoteSecretName: { - Expiry: 5 * 365 * 24 * time.Hour, - Usage: []string{"signing", "key encipherment", "server auth", "client auth"}, - }, - }, - } - - cert, key, err := k.certManager.GenerateCertificate(defaults.ClusterMeshRemoteSecretName, certReq, signConf) - if err != nil { - return fmt.Errorf("unable to generate certificate %s: %w", defaults.ClusterMeshRemoteSecretName, err) - } - - data := map[string][]byte{ - corev1.TLSCertKey: cert, - corev1.TLSPrivateKeyKey: key, - defaults.CASecretCertName: k.certManager.CACertBytes(), - } - - _, err = k.client.CreateSecret(ctx, k.params.Namespace, k8s.NewTLSSecret(defaults.ClusterMeshRemoteSecretName, k.params.Namespace, data), metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("unable to create secret %s/%s: %w", k.params.Namespace, defaults.ClusterMeshRemoteSecretName, err) - } - - return nil -} - -func (k *K8sClusterMesh) createClusterMeshExternalWorkloadCertificate(ctx context.Context) error { - certName := getExternalWorkloadCertName() - certReq := &csr.CertificateRequest{ - Names: []csr.Name{{C: "US", ST: "San Francisco", L: "CA"}}, - KeyRequest: csr.NewKeyRequest(), - Hosts: []string{""}, - CN: "externalworkload", - } - - signConf := &config.Signing{ - Default: &config.SigningProfile{Expiry: 5 * 365 * 24 * time.Hour}, - Profiles: map[string]*config.SigningProfile{ - certName: { - Expiry: 5 * 365 * 24 * time.Hour, - Usage: []string{"signing", "key encipherment", "server auth", "client auth"}, - }, - }, - } - - cert, key, err := k.certManager.GenerateCertificate(certName, certReq, signConf) - if err != nil { - return fmt.Errorf("unable to generate certificate %s: %w", certName, err) - } - - data := map[string][]byte{ - corev1.TLSCertKey: cert, - corev1.TLSPrivateKeyKey: key, - defaults.CASecretCertName: k.certManager.CACertBytes(), - } - - _, err = k.client.CreateSecret(ctx, k.params.Namespace, k8s.NewTLSSecret(certName, k.params.Namespace, data), metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("unable to create secret %s/%s: %w", k.params.Namespace, certName, err) - } - - return nil -} - -func (k *K8sClusterMesh) deleteCertificates(ctx context.Context) error { - k.Log("🔥 Deleting ClusterMesh certificates...") - k.client.DeleteSecret(ctx, k.params.Namespace, defaults.ClusterMeshServerSecretName, metav1.DeleteOptions{}) - k.client.DeleteSecret(ctx, k.params.Namespace, defaults.ClusterMeshAdminSecretName, metav1.DeleteOptions{}) - k.client.DeleteSecret(ctx, k.params.Namespace, defaults.ClusterMeshRemoteSecretName, metav1.DeleteOptions{}) - k.client.DeleteSecret(ctx, k.params.Namespace, getExternalWorkloadCertName(), metav1.DeleteOptions{}) - k.client.DeleteSecret(ctx, k.params.Namespace, defaults.ClusterMeshClientSecretName, metav1.DeleteOptions{}) - return nil -} - -func (k *K8sClusterMesh) installCertificates(ctx context.Context) error { - caSecret, created, err := k.certManager.GetOrCreateCASecret(ctx, defaults.CASecretName, k.params.CreateCA) - if err != nil { - k.Log("❌ Unable to get or create the Cilium CA Secret: %s", err) - return err - } - - if caSecret != nil { - err = k.certManager.LoadCAFromK8s(caSecret) - if err != nil { - k.Log("❌ Unable to load Cilium CA: %s", err) - return err - } - if created { - k.Log("🔑 Created CA in secret %s", caSecret.Name) - } else { - k.Log("🔑 Found CA in secret %s", caSecret.Name) - } - } - - k.Log("🔑 Generating certificates for ClusterMesh...") - - if err := k.createClusterMeshServerCertificate(ctx); err != nil { - return err - } - - if err := k.createClusterMeshAdminCertificate(ctx); err != nil { - return err - } - - if err := k.createClusterMeshClientCertificate(ctx); err != nil { - return err - } - - return k.createClusterMeshExternalWorkloadCertificate(ctx) -} diff --git a/clustermesh/clustermesh.go b/clustermesh/clustermesh.go index 5d128b9756..763079b969 100644 --- a/clustermesh/clustermesh.go +++ b/clustermesh/clustermesh.go @@ -22,25 +22,18 @@ import ( "text/tabwriter" "time" + "github.com/cilium/cilium/api/v1/models" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" "golang.org/x/exp/maps" + "helm.sh/helm/v3/pkg/release" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "github.com/blang/semver/v4" - "github.com/cilium/cilium/api/v1/models" - ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" - "helm.sh/helm/v3/pkg/release" "github.com/cilium/cilium-cli/defaults" - "github.com/cilium/cilium-cli/internal/certs" "github.com/cilium/cilium-cli/internal/helm" - "github.com/cilium/cilium-cli/internal/utils" "github.com/cilium/cilium-cli/k8s" "github.com/cilium/cilium-cli/status" "github.com/cilium/cilium-cli/utils/wait" @@ -56,389 +49,20 @@ const ( configNameMaxConnectedClusters = "max-connected-clusters" configNameIdentityAllocationMode = "identity-allocation-mode" - - caSuffix = ".etcd-client-ca.crt" - keySuffix = ".etcd-client.key" - certSuffix = ".etcd-client.crt" ) var ( - replicas = int32(1) - deploymentMaxSurge = intstr.FromInt(1) - deploymentMaxUnavailable = intstr.FromInt(1) - secretDefaultMode = int32(0400) // This can be replaced with cilium/pkg/defaults.MaxConnectedClusters once // changes are merged there. maxConnectedClusters = defaults.ClustermeshMaxConnectedClusters ) -var clusterRole = &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaults.ClusterMeshClusterRoleName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"discovery.k8s.io"}, - Resources: []string{"endpointslices"}, - Verbs: []string{"get", "list", "watch"}, - }, - { - APIGroups: []string{""}, - Resources: []string{"namespaces", "services", "endpoints"}, - Verbs: []string{"get", "list", "watch"}, - }, - { - APIGroups: []string{"apiextensions.k8s.io"}, - Resources: []string{"customresourcedefinitions"}, - Verbs: []string{"list"}, - }, - { - APIGroups: []string{"cilium.io"}, - Resources: []string{ - "ciliumnodes", - "ciliumnodes/status", - "ciliumexternalworkloads", - "ciliumexternalworkloads/status", - "ciliumidentities", - "ciliumidentities/status", - "ciliumendpoints", - "ciliumendpoints/status", - }, - Verbs: []string{"*"}, - }, - }, -} - -func (k *K8sClusterMesh) generateService() (*corev1.Service, error) { - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaults.ClusterMeshServiceName, - Labels: defaults.ClusterMeshDeploymentLabels, - Annotations: map[string]string{}, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - {Port: int32(2379)}, - }, - Selector: defaults.ClusterMeshDeploymentLabels, - }, - } - - if k.params.ServiceType != "" { - if corev1.ServiceType(k.params.ServiceType) == corev1.ServiceTypeNodePort { - k.Log("⚠️ Using service type NodePort may fail when nodes are removed from the cluster!") - } - svc.Spec.Type = corev1.ServiceType(k.params.ServiceType) - - svc.ObjectMeta.Annotations = k.params.ServiceAnnotations - } else { - switch k.flavor.Kind { - case k8s.KindGKE: - k.Log("🔮 Auto-exposing service within GCP VPC (cloud.google.com/load-balancer-type=Internal)") - svc.Spec.Type = corev1.ServiceTypeLoadBalancer - svc.ObjectMeta.Annotations["cloud.google.com/load-balancer-type"] = "Internal" - // if all the clusters are in the same region the next annotation can be removed - svc.ObjectMeta.Annotations["networking.gke.io/internal-load-balancer-allow-global-access"] = "true" - case k8s.KindAKS: - k.Log("🔮 Auto-exposing service within Azure VPC (service.beta.kubernetes.io/azure-load-balancer-internal)") - svc.Spec.Type = corev1.ServiceTypeLoadBalancer - svc.ObjectMeta.Annotations["service.beta.kubernetes.io/azure-load-balancer-internal"] = "true" - case k8s.KindEKS: - k.Log("🔮 Auto-exposing service within AWS VPC (service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0") - svc.Spec.Type = corev1.ServiceTypeLoadBalancer - svc.ObjectMeta.Annotations["service.beta.kubernetes.io/aws-load-balancer-internal"] = "0.0.0.0/0" - default: - return nil, fmt.Errorf("cannot auto-detect service type, please specify using '--service-type' option") - } - } - - return svc, nil -} - -var initContainerArgs = []string{`rm -rf /var/run/etcd/*; -export ETCDCTL_API=3; -/usr/local/bin/etcd --data-dir=/var/run/etcd --name=clustermesh-apiserver --listen-client-urls=http://127.0.0.1:2379 --advertise-client-urls=http://127.0.0.1:2379 --initial-cluster-token=clustermesh-apiserver --initial-cluster-state=new --auto-compaction-retention=1 & -export rootpw="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)"; -echo $rootpw | etcdctl --interactive=false user add root; -etcdctl user grant-role root root; -export vmpw="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)"; -echo $vmpw | etcdctl --interactive=false user add externalworkload; -etcdctl role add externalworkload; -etcdctl role grant-permission externalworkload --from-key read ''; -etcdctl role grant-permission externalworkload readwrite --prefix cilium/state/noderegister/v1/; -etcdctl role grant-permission externalworkload readwrite --prefix cilium/.initlock/; -etcdctl user grant-role externalworkload externalworkload; -export remotepw="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)"; -echo $remotepw | etcdctl --interactive=false user add remote; -etcdctl role add remote; -etcdctl role grant-permission remote --from-key read ''; -etcdctl user grant-role remote remote; -etcdctl auth enable; -exit`} - -func (k *K8sClusterMesh) apiserverImage() string { - return utils.BuildImagePath(k.params.ApiserverImage, k.params.ApiserverVersion, defaults.ClusterMeshApiserverImage, k.imageVersion) -} - -func (k *K8sClusterMesh) etcdImage() string { - etcdVersion := "v3.5.4" - if k.clusterArch == "amd64" { - return "quay.io/coreos/etcd:" + etcdVersion - } - return "quay.io/coreos/etcd:" + etcdVersion + "-" + k.clusterArch -} - -func (k *K8sClusterMesh) etcdEnvs() []corev1.EnvVar { - envs := []corev1.EnvVar{ - { - Name: "ETCDCTL_API", - Value: "3", - }, - { - Name: "HOSTNAME_IP", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "status.podIP", - }, - }, - }, - } - if k.clusterArch == "arm64" { - envs = append(envs, corev1.EnvVar{ - Name: "ETCD_UNSUPPORTED_ARCH", - Value: "arm64", - }) - } - return envs -} - -func (k *K8sClusterMesh) generateDeployment(clustermeshApiserverArgs []string) *appsv1.Deployment { - - deployment := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaults.ClusterMeshDeploymentName, - Labels: defaults.ClusterMeshDeploymentLabels, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: defaults.ClusterMeshDeploymentLabels, - }, - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: &deploymentMaxUnavailable, - MaxSurge: &deploymentMaxSurge, - }, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaults.ClusterMeshDeploymentName, - Labels: defaults.ClusterMeshDeploymentLabels, - }, - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyAlways, - ServiceAccountName: defaults.ClusterMeshServiceAccountName, - Containers: []corev1.Container{ - { - Name: "etcd", - Command: []string{"/usr/local/bin/etcd"}, - Args: []string{ - "--data-dir=/var/run/etcd", - "--name=clustermesh-apiserver", - "--client-cert-auth", - "--trusted-ca-file=/var/lib/etcd-secrets/ca.crt", - "--cert-file=/var/lib/etcd-secrets/tls.crt", - "--key-file=/var/lib/etcd-secrets/tls.key", - // Surrounding the IPv4 address with brackets works in this case, since etcd - // uses net.SplitHostPort() internally and it accepts that format. - "--listen-client-urls=https://127.0.0.1:2379,https://[$(HOSTNAME_IP)]:2379", - "--advertise-client-urls=https://[$(HOSTNAME_IP)]:2379", - "--initial-cluster-token=clustermesh-apiserver", - "--auto-compaction-retention=1", - }, - Image: k.etcdImage(), - ImagePullPolicy: corev1.PullIfNotPresent, - Env: k.etcdEnvs(), - VolumeMounts: []corev1.VolumeMount{ - { - Name: "etcd-server-secrets", - MountPath: "/var/lib/etcd-secrets", - ReadOnly: true, - }, - { - Name: "etcd-data-dir", - MountPath: "/var/run/etcd", - }, - }, - }, - { - Name: "apiserver", - Command: []string{"/usr/bin/clustermesh-apiserver"}, - Args: append(clustermeshApiserverArgs, - "--cluster-name="+k.clusterName, - "--cluster-id="+k.clusterID, - "--kvstore-opt", - "etcd.config=/var/lib/cilium/etcd-config.yaml", - ), - Image: k.apiserverImage(), - ImagePullPolicy: corev1.PullIfNotPresent, - Env: []corev1.EnvVar{ - { - Name: "CILIUM_CLUSTER_NAME", - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.ConfigMapName, - }, - Key: configNameClusterName, - }, - }, - }, - { - Name: "CILIUM_CLUSTER_ID", - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.ConfigMapName, - }, - Key: configNameClusterID, - }, - }, - }, - { - Name: "CILIUM_IDENTITY_ALLOCATION_MODE", - ValueFrom: &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.ConfigMapName, - }, - Key: "identity-allocation-mode", - }, - }, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "etcd-admin-client", - MountPath: "/var/lib/cilium/etcd-secrets", - ReadOnly: true, - }, - }, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "etcd-init", - Command: []string{"/bin/sh", "-c"}, - Args: initContainerArgs, - Image: k.etcdImage(), - ImagePullPolicy: corev1.PullIfNotPresent, - Env: k.etcdEnvs(), - VolumeMounts: []corev1.VolumeMount{ - { - Name: "etcd-data-dir", - MountPath: "/var/run/etcd", - }, - }, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "etcd-data-dir", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - { - Name: "etcd-server-secrets", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - DefaultMode: &secretDefaultMode, - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.CASecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: defaults.CASecretCertName, - Path: "ca.crt", - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.ClusterMeshServerSecretName, - }, - }, - }, - }, - }, - }, - }, - { - Name: "etcd-admin-client", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - DefaultMode: &secretDefaultMode, - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.CASecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: defaults.CASecretCertName, - Path: "ca.crt", - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.ClusterMeshAdminSecretName, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - return deployment -} - type k8sClusterMeshImplementation interface { - CreateSecret(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) - PatchSecret(ctx context.Context, namespace, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*corev1.Secret, error) - DeleteSecret(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error GetSecret(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Secret, error) - CreateServiceAccount(ctx context.Context, namespace string, account *corev1.ServiceAccount, opts metav1.CreateOptions) (*corev1.ServiceAccount, error) - DeleteServiceAccount(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error - CreateClusterRole(ctx context.Context, role *rbacv1.ClusterRole, opts metav1.CreateOptions) (*rbacv1.ClusterRole, error) - DeleteClusterRole(ctx context.Context, name string, opts metav1.DeleteOptions) error - CreateClusterRoleBinding(ctx context.Context, role *rbacv1.ClusterRoleBinding, opts metav1.CreateOptions) (*rbacv1.ClusterRoleBinding, error) - DeleteClusterRoleBinding(ctx context.Context, name string, opts metav1.DeleteOptions) error GetConfigMap(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) - CreateDeployment(ctx context.Context, namespace string, deployment *appsv1.Deployment, opts metav1.CreateOptions) (*appsv1.Deployment, error) GetDeployment(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*appsv1.Deployment, error) - DeleteDeployment(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error CheckDeploymentStatus(ctx context.Context, namespace, deployment string) error - CreateService(ctx context.Context, namespace string, service *corev1.Service, opts metav1.CreateOptions) (*corev1.Service, error) - DeleteService(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error GetService(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Service, error) - PatchDaemonSet(ctx context.Context, namespace, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*appsv1.DaemonSet, error) GetDaemonSet(ctx context.Context, namespace, name string, options metav1.GetOptions) (*appsv1.DaemonSet, error) ListNodes(ctx context.Context, options metav1.ListOptions) (*corev1.NodeList, error) ListPods(ctx context.Context, namespace string, options metav1.ListOptions) (*corev1.PodList, error) @@ -450,38 +74,28 @@ type k8sClusterMeshImplementation interface { GetCiliumExternalWorkload(ctx context.Context, name string, opts metav1.GetOptions) (*ciliumv2.CiliumExternalWorkload, error) CreateCiliumExternalWorkload(ctx context.Context, cew *ciliumv2.CiliumExternalWorkload, opts metav1.CreateOptions) (*ciliumv2.CiliumExternalWorkload, error) DeleteCiliumExternalWorkload(ctx context.Context, name string, opts metav1.DeleteOptions) error - GetRunningCiliumVersion(ctx context.Context, namespace string) (string, error) ListCiliumEndpoints(ctx context.Context, namespace string, options metav1.ListOptions) (*ciliumv2.CiliumEndpointList, error) - GetPlatform(ctx context.Context) (*k8s.Platform, error) CiliumLogs(ctx context.Context, namespace, pod string, since time.Time, filter *regexp.Regexp) (string, error) - GetHelmState(ctx context.Context, namespace string, secretName string) (*helm.State, error) } type K8sClusterMesh struct { client k8sClusterMeshImplementation - certManager *certs.CertManager statusCollector *status.K8sStatusCollector flavor k8s.Flavor params Parameters clusterName string clusterID string - imageVersion string - clusterArch string externalKVStore bool } type Parameters struct { Namespace string ServiceType string - ServiceAnnotations map[string]string DestinationContext string Wait bool WaitDuration time.Duration DestinationEndpoints []string SourceEndpoints []string - ApiserverImage string - ApiserverVersion string - CreateCA bool Writer io.Writer Labels map[string]string IPv4AllocCIDR string @@ -500,15 +114,6 @@ type Parameters struct { EnableKVStoreMesh bool } -func (p Parameters) validateParams() error { - if p.ApiserverImage != defaults.ClusterMeshApiserverImage { - return nil - } else if err := utils.CheckVersion(p.ApiserverVersion); err != nil { - return err - } - return nil -} - func (p Parameters) waitTimeout() time.Duration { if p.WaitDuration != time.Duration(0) { return p.WaitDuration @@ -519,9 +124,8 @@ func (p Parameters) waitTimeout() time.Duration { func NewK8sClusterMesh(client k8sClusterMeshImplementation, p Parameters) *K8sClusterMesh { return &K8sClusterMesh{ - client: client, - params: p, - certManager: certs.NewCertManager(client, certs.Parameters{Namespace: p.Namespace}), + client: client, + params: p, } } @@ -558,108 +162,6 @@ func (k *K8sClusterMesh) GetClusterConfig(ctx context.Context) error { return nil } -func (k *K8sClusterMesh) Disable(ctx context.Context) error { - k.Log("🔥 Deleting clustermesh-apiserver...") - k.client.DeleteService(ctx, k.params.Namespace, defaults.ClusterMeshServiceName, metav1.DeleteOptions{}) - k.client.DeleteDeployment(ctx, k.params.Namespace, defaults.ClusterMeshDeploymentName, metav1.DeleteOptions{}) - k.client.DeleteClusterRoleBinding(ctx, defaults.ClusterMeshClusterRoleName, metav1.DeleteOptions{}) - k.client.DeleteClusterRole(ctx, defaults.ClusterMeshClusterRoleName, metav1.DeleteOptions{}) - k.client.DeleteServiceAccount(ctx, k.params.Namespace, defaults.ClusterMeshServiceAccountName, metav1.DeleteOptions{}) - k.client.DeleteSecret(ctx, k.params.Namespace, defaults.ClusterMeshSecretName, metav1.DeleteOptions{}) - - k.deleteCertificates(ctx) - - k.Log("✅ ClusterMesh disabled.") - - return nil -} - -func (p Parameters) validateForEnable() error { - switch corev1.ServiceType(p.ServiceType) { - case corev1.ServiceTypeClusterIP: - case corev1.ServiceTypeNodePort: - case corev1.ServiceTypeLoadBalancer: - case corev1.ServiceTypeExternalName: - case "": - default: - return fmt.Errorf("unknown service type %q", p.ServiceType) - } - - return nil -} - -func (k *K8sClusterMesh) Enable(ctx context.Context) error { - if err := k.params.validateParams(); err != nil { - return err - } - - if err := k.params.validateForEnable(); err != nil { - return err - } - - if err := k.GetClusterConfig(ctx); err != nil { - return err - } - - var err error - k.imageVersion, err = k.client.GetRunningCiliumVersion(ctx, k.params.Namespace) - if err != nil { - return err - } - - p, err := k.client.GetPlatform(ctx) - if err != nil { - return err - } - k.clusterArch = p.Arch - - svc, err := k.generateService() - if err != nil { - return err - } - - _, err = k.client.GetDeployment(ctx, k.params.Namespace, defaults.ClusterMeshDeploymentName, metav1.GetOptions{}) - if err == nil { - k.Log("✅ ClusterMesh is already enabled") - return nil - } - - if err := k.installCertificates(ctx); err != nil { - return err - } - - k.Log("✨ Deploying clustermesh-apiserver from %s...", k.apiserverImage()) - if _, err := k.client.CreateServiceAccount(ctx, k.params.Namespace, k8s.NewServiceAccount(defaults.ClusterMeshServiceAccountName), metav1.CreateOptions{}); err != nil { - return err - } - - if _, err := k.client.CreateClusterRole(ctx, clusterRole, metav1.CreateOptions{}); err != nil { - return err - } - - if _, err := k.client.CreateClusterRoleBinding(ctx, k8s.NewClusterRoleBinding(defaults.ClusterMeshClusterRoleName, k.params.Namespace, defaults.ClusterMeshServiceAccountName), metav1.CreateOptions{}); err != nil { - return err - } - - for i, opt := range k.params.ConfigOverwrites { - if !strings.HasPrefix(opt, "--") { - k.params.ConfigOverwrites[i] = "--" + opt - } - } - - if _, err := k.client.CreateDeployment(ctx, k.params.Namespace, k.generateDeployment(k.params.ConfigOverwrites), metav1.CreateOptions{}); err != nil { - return err - } - - if _, err := k.client.CreateService(ctx, k.params.Namespace, svc, metav1.CreateOptions{}); err != nil { - return err - } - - k.Log("✅ ClusterMesh enabled!") - - return nil -} - type accessInformation struct { ServiceType corev1.ServiceType `json:"service_type,omitempty"` ServiceIPs []string `json:"service_ips,omitempty"` @@ -675,16 +177,6 @@ type accessInformation struct { MaxConnectedClusters int `json:"max_connected_clusters,omitempty"` } -func (ai *accessInformation) etcdConfiguration() string { - cfg := "endpoints:\n" - cfg += "- https://" + ai.ClusterName + ".mesh.cilium.io:" + fmt.Sprintf("%d", ai.ServicePort) + "\n" - cfg += "trusted-ca-file: /var/lib/cilium/clustermesh/" + ai.ClusterName + caSuffix + "\n" - cfg += "key-file: /var/lib/cilium/clustermesh/" + ai.ClusterName + keySuffix + "\n" - cfg += "cert-file: /var/lib/cilium/clustermesh/" + ai.ClusterName + certSuffix + "\n" - - return cfg -} - func (ai *accessInformation) validate() bool { return ai.ClusterName != "" && ai.ClusterName != "default" && @@ -707,10 +199,7 @@ func getDeprecatedName(secretName string) string { } func getExternalWorkloadCertName() string { - if utils.IsInHelmMode() { - return defaults.ClusterMeshClientSecretName - } - return defaults.ClusterMeshExternalWorkloadSecretName + return defaults.ClusterMeshClientSecretName } // getDeprecatedSecret attempts to retrieve a secret using one or more deprecated names @@ -986,45 +475,6 @@ func (k *K8sClusterMesh) extractAccessInformation(ctx context.Context, client k8 return ai, nil } -func (k *K8sClusterMesh) patchConfig(ctx context.Context, client k8sClusterMeshImplementation, ai *accessInformation) error { - _, err := client.GetSecret(ctx, k.params.Namespace, defaults.ClusterMeshSecretName, metav1.GetOptions{}) - if err != nil { - k.Log("🔑 Secret %s does not exist yet, creating it...", defaults.ClusterMeshSecretName) - _, err = client.CreateSecret(ctx, k.params.Namespace, k8s.NewSecret(defaults.ClusterMeshSecretName, k.params.Namespace, map[string][]byte{}), metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("unable to create secret: %w", err) - } - } - - k.Log("🔑 Patching existing secret %s...", defaults.ClusterMeshSecretName) - - etcdBase64 := `"` + ai.ClusterName + `": "` + base64.StdEncoding.EncodeToString([]byte(ai.etcdConfiguration())) + `"` - caBase64 := `"` + ai.ClusterName + caSuffix + `": "` + base64.StdEncoding.EncodeToString(ai.CA) + `"` - keyBase64 := `"` + ai.ClusterName + keySuffix + `": "` + base64.StdEncoding.EncodeToString(ai.ClientKey) + `"` - certBase64 := `"` + ai.ClusterName + certSuffix + `": "` + base64.StdEncoding.EncodeToString(ai.ClientCert) + `"` - - patch := []byte(`{"data":{` + etcdBase64 + `,` + caBase64 + `,` + keyBase64 + `,` + certBase64 + `}}`) - _, err = client.PatchSecret(ctx, k.params.Namespace, defaults.ClusterMeshSecretName, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - return fmt.Errorf("unable to patch secret %s with patch %q: %w", defaults.ClusterMeshSecretName, patch, err) - } - - var aliases []string - for _, ip := range ai.ServiceIPs { - aliases = append(aliases, `{"ip":"`+ip+`", "hostnames":["`+ai.ClusterName+`.mesh.cilium.io"]}`) - } - - patch = []byte(`{"spec":{"template":{"spec":{"hostAliases":[` + strings.Join(aliases, ",") + `]}}}}`) - - k.Log("✨ Patching DaemonSet with IP aliases %s...", defaults.ClusterMeshSecretName) - _, err = client.PatchDaemonSet(ctx, k.params.Namespace, defaults.AgentDaemonSetName, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - return fmt.Errorf("unable to patch DaemonSet %s with patch %q: %w", defaults.AgentDaemonSetName, patch, err) - } - - return nil -} - // getClientsForConnect returns a k8s.Client for the local and remote cluster, respectively func (k *K8sClusterMesh) getClientsForConnect() (*k8s.Client, *k8s.Client, error) { remoteClient, err := k8s.NewClient(k.params.DestinationContext, "", k.params.Namespace) @@ -1115,90 +565,6 @@ func (k *K8sClusterMesh) validateInfoForConnect(aiLocal, aiRemote *accessInforma return nil } -func (k *K8sClusterMesh) Connect(ctx context.Context) error { - localClient, remoteClient, err := k.getClientsForConnect() - if err != nil { - return err - } - - aiLocal, aiRemote, err := k.getAccessInfoForConnect(ctx, localClient, remoteClient) - if err != nil { - return err - } - - err = k.validateInfoForConnect(aiLocal, aiRemote) - if err != nil { - return err - } - - k.Log("✨ Connecting cluster %s -> %s...", k.client.ClusterName(), remoteClient.ClusterName()) - if err := k.patchConfig(ctx, k.client, aiRemote); err != nil { - return err - } - - k.Log("✨ Connecting cluster %s -> %s...", remoteClient.ClusterName(), k.client.ClusterName()) - if err := k.patchConfig(ctx, remoteClient, aiLocal); err != nil { - return err - } - - k.Log("✅ Connected cluster %s and %s!", localClient.ClusterName(), remoteClient.ClusterName()) - - return nil -} - -func (k *K8sClusterMesh) disconnectCluster(ctx context.Context, src, dst k8sClusterMeshImplementation) error { - cm, err := dst.GetConfigMap(ctx, k.params.Namespace, defaults.ConfigMapName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("unable to retrieve ConfigMap %q: %w", defaults.ConfigMapName, err) - } - - if _, ok := cm.Data[configNameClusterName]; !ok { - return fmt.Errorf("%s is not set in ConfigMap %q", configNameClusterName, defaults.ConfigMapName) - } - - clusterName := cm.Data[configNameClusterName] - - k.Log("🔑 Patching existing secret %s...", defaults.ClusterMeshSecretName) - meshSecret, err := src.GetSecret(ctx, k.params.Namespace, defaults.ClusterMeshSecretName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("clustermesh configuration secret %s does not exist", defaults.ClusterMeshSecretName) - } - - for _, suffix := range []string{"", caSuffix, keySuffix, certSuffix} { - if _, ok := meshSecret.Data[clusterName+suffix]; !ok { - k.Log("⚠️ Key %q does not exist in secret. Cluster already disconnected?", clusterName+suffix) - continue - } - - patch := []byte(`[{"op": "remove", "path": "/data/` + clusterName + suffix + `"}]`) - _, err = src.PatchSecret(ctx, k.params.Namespace, defaults.ClusterMeshSecretName, types.JSONPatchType, patch, metav1.PatchOptions{}) - if err != nil { - k.Log("❌ Warning: Unable to patch secret %s with path %q: %s", defaults.ClusterMeshSecretName, patch, err) - } - } - - return nil -} - -func (k *K8sClusterMesh) Disconnect(ctx context.Context) error { - remoteCluster, err := k8s.NewClient(k.params.DestinationContext, "", k.params.Namespace) - if err != nil { - return fmt.Errorf("unable to create Kubernetes client to access remote cluster %q: %w", k.params.DestinationContext, err) - } - - if err := k.disconnectCluster(ctx, k.client, remoteCluster); err != nil { - return err - } - - if err := k.disconnectCluster(ctx, remoteCluster, k.client); err != nil { - return err - } - - k.Log("✅ Disconnected cluster %s and %s.", k.client.ClusterName(), remoteCluster.ClusterName()) - - return nil -} - type Status struct { AccessInformation *accessInformation `json:"access_information,omitempty"` Connectivity *ConnectivityStatus `json:"connectivity,omitempty"` @@ -2097,14 +1463,6 @@ func (k *K8sClusterMesh) ConnectWithHelm(ctx context.Context) error { return err } - ok, err := k.needsClassicMode(localRelease) - if err != nil { - return err - } - if ok { - return k.Connect(ctx) - } - localClient, remoteClient, err := k.getClientsForConnect() if err != nil { return err @@ -2178,45 +1536,12 @@ func (k *K8sClusterMesh) ConnectWithHelm(ctx context.Context) error { return nil } -// Helm-based clustermesh connect/disconnect is only supported on Cilium -// v1.14+ due to a lack of support in earlier versions for -// autoconfigured certificates (tls.{crt,key}) for cluster -// members when running in certgen (cronJob) PKI mode -func (k *K8sClusterMesh) needsClassicMode(r *release.Release) (bool, error) { - if r == nil { - return false, fmt.Errorf("needs a valid helm release. got nil") - } - - version := r.Chart.AppVersion() - semv, err := utils.ParseCiliumVersion(version) - if err != nil { - return false, fmt.Errorf("failed to parse Cilium version: %w", err) - } - k.Log("✅ Detected Helm release with Cilium version %s", semv) - - const ciliumHelmMinRev = "1.14.0" - cv := semver.MustParse(ciliumHelmMinRev) - if semv.Major == cv.Major && semv.Minor < cv.Minor { - k.Log("⚠️ Cilium Version is less than %s. Continuing in classic mode.", ciliumHelmMinRev) - return true, nil - } - - return false, nil -} - func (k *K8sClusterMesh) DisconnectWithHelm(ctx context.Context) error { localRelease, err := getRelease(k.client.(*k8s.Client)) if err != nil { k.Log("❌ Unable to find Helm release for the target cluster") return err } - ok, err := k.needsClassicMode(localRelease) - if err != nil { - return err - } - if ok { - return k.Disconnect(ctx) - } localClient, remoteClient, err := k.getClientsForConnect() if err != nil { diff --git a/clustermesh/clustermesh_test.go b/clustermesh/clustermesh_test.go index 487b74d07c..d82a3aba6a 100644 --- a/clustermesh/clustermesh_test.go +++ b/clustermesh/clustermesh_test.go @@ -5,94 +5,16 @@ package clustermesh import ( "context" - "errors" "testing" "github.com/cilium/cilium/api/v1/models" "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/fake" "github.com/cilium/cilium-cli/k8s" ) -func TestNeedClassicMode(t *testing.T) { - uu := map[string]struct { - r release.Release - err error - e bool - }{ - "blank": { - r: release.Release{ - Chart: &chart.Chart{}, - }, - err: errors.New(`failed to parse Cilium version: strconv.ParseUint: parsing "": invalid syntax`), - }, - "1.13.1": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "1.13.1"}, - }, - }, - e: true, - }, - "v1.13.1": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "v1.13.1"}, - }, - }, - e: true, - }, - "v1.13.1-pre.2": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "v1.13.1-pre.2"}, - }, - }, - e: true, - }, - "v1.14.0": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "v1.14.0"}, - }, - }, - }, - "v1.14.0-snapshot.2": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "v1.14.0-snapshot.2"}, - }, - }, - }, - "v1.14.10": { - r: release.Release{ - Chart: &chart.Chart{ - Metadata: &chart.Metadata{AppVersion: "v1.14.10"}, - }, - }, - }, - } - - c := K8sClusterMesh{ - params: Parameters{Writer: noOptWriter{}}, - } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - ok, err := c.needsClassicMode(&u.r) - if err != nil { - assert.Equal(t, u.err.Error(), err.Error()) - return - } - assert.Equal(t, u.e, ok) - }) - } -} - func TestRemoveFromClustermeshConfig(t *testing.T) { uu := map[string]struct { vv map[string]any @@ -321,9 +243,3 @@ func TestGetCASecret(t *testing.T) { } } - -// Helpers - -type noOptWriter struct{} - -func (noOptWriter) Write([]byte) (int, error) { return 0, nil } diff --git a/defaults/defaults.go b/defaults/defaults.go index 364240b60e..5c356bf679 100644 --- a/defaults/defaults.go +++ b/defaults/defaults.go @@ -133,11 +133,6 @@ const ( ) var ( - // ClusterMeshDeploymentLabels are the labels set on the clustermesh API server by default. - ClusterMeshDeploymentLabels = map[string]string{ - "k8s-app": "clustermesh-apiserver", - } - // CiliumScheduleAffinity is the node affinity to prevent Cilium from being schedule on // nodes labeled with CiliumNoScheduleLabel. CiliumScheduleAffinity = []string{ diff --git a/internal/certs/certs.go b/internal/certs/certs.go deleted file mode 100644 index 67e04eab77..0000000000 --- a/internal/certs/certs.go +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package certs - -import ( - "context" - "encoding/base64" - "fmt" - - "github.com/cloudflare/cfssl/cli/genkey" - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/csr" - "github.com/cloudflare/cfssl/helpers" - "github.com/cloudflare/cfssl/initca" - "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/local" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/cilium/cilium-cli/defaults" - "github.com/cilium/cilium-cli/k8s" -) - -type CertManager struct { - params Parameters - - caCert []byte - caKey []byte - - client k8sCertManagerImplementation -} - -type k8sCertManagerImplementation interface { - CreateSecret(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) - DeleteSecret(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error - GetSecret(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Secret, error) -} - -type Parameters struct { - Namespace string -} - -func NewCertManager(client k8sCertManagerImplementation, p Parameters) *CertManager { - return &CertManager{ - params: p, - client: client, - } -} - -// GetOrCreateCASecret Returns a pointer to the secret data for the Cilium CA -// and true when it was created, false otherwise. If the Cilium CA does not -// already exist this function will generate a new one when createCA is true. -func (c *CertManager) GetOrCreateCASecret(ctx context.Context, caSecretName string, createCA bool) (*corev1.Secret, bool, error) { - s, err := c.client.GetSecret(ctx, c.params.Namespace, caSecretName, metav1.GetOptions{}) - if !createCA || !errors.IsNotFound(err) { - return s, false, err - } - // from here the CA Secret doesn't exists and we are requested to create - // it. - if err = c.GenerateCA(); err != nil { - return nil, false, fmt.Errorf("unable to generate CA: %w", err) - } - - if s, err = c.StoreCAInK8s(ctx); err != nil { - return nil, false, fmt.Errorf("unable to store CA in secret: %w", err) - } - - return s, true, nil -} - -func (c *CertManager) LoadCAFromK8s(secret *corev1.Secret) error { - if key, ok := secret.Data[defaults.CASecretKeyName]; ok { - c.caKey = make([]byte, len(key)) - copy(c.caKey, key) - } else { - return fmt.Errorf("secret %q does not contain a key %q", defaults.CASecretName, defaults.CASecretKeyName) - } - - if cert, ok := secret.Data[defaults.CASecretCertName]; ok { - c.caCert = make([]byte, len(cert)) - copy(c.caCert, cert) - } else { - return fmt.Errorf("secret %q does not contain a key %q", defaults.CASecretName, defaults.CASecretCertName) - } - - return nil -} - -func (c *CertManager) StoreCAInK8s(ctx context.Context) (*corev1.Secret, error) { - if len(c.caKey) == 0 || len(c.caCert) == 0 { - return nil, fmt.Errorf("no CA available") - } - - data := map[string][]byte{ - defaults.CASecretKeyName: c.caKey, - defaults.CASecretCertName: c.caCert, - } - - secret, err := c.client.CreateSecret(ctx, c.params.Namespace, k8s.NewSecret(defaults.CASecretName, c.params.Namespace, data), metav1.CreateOptions{}) - if err != nil { - return nil, fmt.Errorf("unable to create secret %s/%s: %w", c.params.Namespace, defaults.CASecretName, err) - } - - return secret, nil -} - -func (c *CertManager) GenerateCA() error { - cert, _, key, err := initca.New(&csr.CertificateRequest{ - Names: []csr.Name{{C: "US", ST: "San Francisco", L: "CA", O: "Cilium", OU: "Cilium"}}, - KeyRequest: csr.NewKeyRequest(), - CN: "Cilium CA", - }) - if err != nil { - return err - } - - c.caKey = key - c.caCert = cert - - return nil -} - -func (c *CertManager) GenerateCertificate(profile string, certReq *csr.CertificateRequest, signingConf *config.Signing) (certBytes []byte, keyBytes []byte, err error) { - g := &csr.Generator{Validator: genkey.Validator} - csrBytes, keyBytes, err := g.ProcessRequest(certReq) - if err != nil { - return nil, nil, err - } - parsedCa, err := helpers.ParseCertificatePEM(c.caCert) - if err != nil { - return nil, nil, err - } - priv, err := helpers.ParsePrivateKeyPEM(c.caKey) - if err != nil { - return nil, nil, err - } - s, err := local.NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), signingConf) - if err != nil { - return nil, nil, err - } - signReq := signer.SignRequest{ - Request: string(csrBytes), - Profile: profile, - } - certBytes, err = s.Sign(signReq) - if err != nil { - return nil, nil, err - } - return certBytes, keyBytes, nil -} - -// CACertBytes return the CA public certificate bytes, or nil when it is not -// set. -func (c *CertManager) CACertBytes() []byte { - // NOTE: return a copy just to avoid the caller modifiying our CA - // certificate. - crt := make([]byte, len(c.caCert)) - copy(crt, c.caCert) - return crt -} - -// CAKeyBytes return the CA private certificate bytes, or nil when it is not -// set. -func (c *CertManager) CAKeyBytes() []byte { - // NOTE: return a copy just to avoid the caller modifiying our CA - // certificate. - crt := make([]byte, len(c.caKey)) - copy(crt, c.caKey) - return crt -} - -// EncodeCertBytes returns an encoded format of the certificate bytes. -func EncodeCertBytes(cert []byte) string { - return base64.StdEncoding.EncodeToString(cert) -} - -// DecodeCertBytes returns a decoded format of the certificate bytes. -func DecodeCertBytes(cert string) ([]byte, error) { - return base64.StdEncoding.DecodeString(cert) -} diff --git a/internal/cli/cmd/clustermesh.go b/internal/cli/cmd/clustermesh.go index 46ab5faa5f..b90f906c06 100644 --- a/internal/cli/cmd/clustermesh.go +++ b/internal/cli/cmd/clustermesh.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/cobra" "github.com/cilium/cilium-cli/clustermesh" - "github.com/cilium/cilium-cli/internal/utils" "github.com/cilium/cilium-cli/status" ) @@ -28,126 +27,12 @@ func newCmdClusterMesh() *cobra.Command { cmd.AddCommand( newCmdClusterMeshStatus(), newCmdClusterMeshExternalWorkload(), + newCmdClusterMeshConnectWithHelm(), + newCmdClusterMeshDisconnectWithHelm(), + newCmdClusterMeshEnableWithHelm(), + newCmdClusterMeshDisableWithHelm(), ) - if utils.IsInHelmMode() { - cmd.AddCommand( - newCmdClusterMeshConnectWithHelm(), - newCmdClusterMeshDisconnectWithHelm(), - newCmdClusterMeshEnableWithHelm(), - newCmdClusterMeshDisableWithHelm(), - ) - } else { - cmd.AddCommand( - newCmdClusterMeshConnect(), - newCmdClusterMeshDisconnect(), - newCmdClusterMeshEnable(), - newCmdClusterMeshDisable(), - ) - } - - return cmd -} - -func newCmdClusterMeshEnable() *cobra.Command { - var params = clustermesh.Parameters{ - Writer: os.Stdout, - } - - cmd := &cobra.Command{ - Use: "enable", - Short: "Enable ClusterMesh ability in a cluster", - Long: ``, - RunE: func(_ *cobra.Command, _ []string) error { - params.Namespace = namespace - - cm := clustermesh.NewK8sClusterMesh(k8sClient, params) - if err := cm.Enable(context.Background()); err != nil { - fatalf("Unable to enable ClusterMesh: %s", err) - } - return nil - }, - } - - cmd.Flags().StringVar(¶ms.ServiceType, "service-type", "", "Type of Kubernetes service to expose control plane { ClusterIP | LoadBalancer | NodePort }") - cmd.Flags().StringToStringVar(¶ms.ServiceAnnotations, "service-annotation", map[string]string{}, "Annotation to add to the Kubernetes service. Can be specified multiple times") - cmd.Flags().StringVar(¶ms.ApiserverImage, "apiserver-image", "", "Container image for clustermesh-apiserver") - cmd.Flags().StringVar(¶ms.ApiserverVersion, "apiserver-version", "", "Container image version for clustermesh-apiserver") - cmd.Flags().BoolVar(¶ms.CreateCA, "create-ca", true, "Automatically create CA if needed") - cmd.Flags().StringSliceVar(¶ms.ConfigOverwrites, "config", []string{}, "clustermesh-apiserver config entries (key=value)") - - return cmd -} - -func newCmdClusterMeshDisable() *cobra.Command { - var params = clustermesh.Parameters{ - Writer: os.Stdout, - } - - cmd := &cobra.Command{ - Use: "disable", - Short: "Disable ClusterMesh ability in a cluster", - Long: ``, - RunE: func(_ *cobra.Command, _ []string) error { - params.Namespace = namespace - - cm := clustermesh.NewK8sClusterMesh(k8sClient, params) - if err := cm.Disable(context.Background()); err != nil { - fatalf("Unable to disable ClusterMesh: %s", err) - } - return nil - }, - } - - return cmd -} - -func newCmdClusterMeshConnect() *cobra.Command { - var params = clustermesh.Parameters{ - Writer: os.Stdout, - } - - cmd := &cobra.Command{ - Use: "connect", - Short: "Connect to a remote cluster", - Long: ``, - RunE: func(_ *cobra.Command, _ []string) error { - params.Namespace = namespace - - cm := clustermesh.NewK8sClusterMesh(k8sClient, params) - if err := cm.Connect(context.Background()); err != nil { - fatalf("Unable to connect cluster: %s", err) - } - return nil - }, - } - - addCommonConnectFlags(cmd, ¶ms) - - return cmd -} - -func newCmdClusterMeshDisconnect() *cobra.Command { - var params = clustermesh.Parameters{ - Writer: os.Stdout, - } - - cmd := &cobra.Command{ - Use: "disconnect", - Short: "Disconnect from a remote cluster", - Long: ``, - RunE: func(_ *cobra.Command, _ []string) error { - params.Namespace = namespace - cm := clustermesh.NewK8sClusterMesh(k8sClient, params) - if err := cm.Disconnect(context.Background()); err != nil { - fatalf("Unable to disconnect cluster: %s", err) - } - return nil - }, - } - - cmd.Flags().StringVar(¶ms.DestinationContext, "destination-context", "", "Kubernetes configuration context of destination cluster") - return cmd } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 34de225bec..447671b425 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -6,76 +6,17 @@ package utils import ( "fmt" "os" - "regexp" "strconv" - "strings" "github.com/blang/semver/v4" ) -var versionRegexp = regexp.MustCompile(`^((v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[a-zA-Z0-9.]+)*|[a-zA-Z0-9-_.@:]*:[a-zA-Z0-9-_.@:]+)|([0-9a-fA-F]{40})|(latest))$`).MatchString - -func CheckVersion(version string) error { - if !versionRegexp(version) && version != "" { - return fmt.Errorf("invalid syntax %q for image tag", version) - } - return nil -} - func ParseCiliumVersion(version string) (semver.Version, error) { return semver.ParseTolerant(version) } const CLIModeVariableName = "CILIUM_CLI_MODE" -var imageRegexp = regexp.MustCompile(`\A(.*?)(?:(:.*?)(@sha256:[0-9a-f]{64})?)?\z`) - -// BuildImagePath builds a fully-qualified image path from the given -// user image and version and default image. -// -// NOTE: Currently 'userVersion' is never passed as an empty string as -// it is defaulted on the CLI interface to the default version. -// -// If 'userVersion' can already contains a colon (':') it is simply -// concatenated with the image string. This is useful for using the -// "latest" image in testing with "--version :latest". Without the -// colon 'userVersion' is always prepended with 'v' if it is missing. -// This is also useful for postfixing the image name with "-ci", for -// example ("--version -ci:4fac771179959ca575eb6f993d566653d3bfa167"). -func BuildImagePath(userImage, userVersion, defaultImage, defaultVersion string) string { - var image string - switch { - case userImage == "" && userVersion == "": - if strings.Contains(defaultVersion, ":") { - image = defaultImage + defaultVersion - } else { - image = defaultImage + ":" + defaultVersion - } - case userImage == "" && strings.Contains(userVersion, ":"): - // userVersion already contains the colon. Useful for ":latest", - // or for "-ci:" - image = defaultImage + userVersion - case userImage == "" && !strings.HasPrefix(userVersion, "v"): - image = defaultImage + ":v" + userVersion - case userImage == "": - image = defaultImage + ":" + userVersion - case strings.Contains(userImage, ":"): - // Fully-qualified userImage? - image = userImage - case strings.Contains(userVersion, ":"): - // ':' in userVersion? - image = userImage + userVersion - default: - image = userImage + ":" + userVersion - } - - if m := imageRegexp.FindStringSubmatch(image); m != nil { - image = m[1] + m[2] - } - - return image -} - // IsInHelmMode returns true if cilium-cli is in "helm" mode. Otherwise, it returns false. func IsInHelmMode() bool { return os.Getenv(CLIModeVariableName) != "classic" diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index de23c8ea6f..1b7fda5aac 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -4,7 +4,6 @@ package utils import ( - "fmt" "os" "reflect" "testing" @@ -13,50 +12,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCheckVersion(t *testing.T) { - tests := []struct { - version string - valid bool - }{ - {"0.0.1", true}, - {"v0.0.1", true}, - {"v1.9.6", true}, - {"v1.9.6.5", false}, - {"1.9.6", true}, - {"1.9.6.5", false}, - {"v1.10.0-rc1", true}, - {"1.10.0-rc1", true}, - {"1.14.0-snapshot.0", true}, - {"10.42.0", true}, - {"1.9", false}, - {"v1.9", false}, - {"1", false}, - {"a01..0..0", false}, - {".1.9", false}, - {"..1.9", false}, - {"1...9", false}, - {"ddd", false}, - {"v.1.9", false}, - {"v..1.9", false}, - {":latest", true}, - {"92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", true}, - {":92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", true}, - {":92ff7ffa762f6f8bc397a28e6f3147906e20e8fa@sha256:4fde4abc19a1cbedb5084f683f5d91c0ea04b964a029e6d0ba43961e1ff5b5d8", true}, - {"-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", true}, - {"-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa@sha256:4fde4abc19a1cbedb5084f683f5d91c0ea04b964a029e6d0ba43961e1ff5b5d8", true}, - } - for _, tt := range tests { - t.Run(tt.version, func(t *testing.T) { - err := CheckVersion(tt.version) - if err != nil && tt.valid { - t.Errorf("CheckVersion(%q) = %v, want no error", tt.version, err) - } else if err == nil && !tt.valid { - t.Errorf("CheckVersion(%q) returned no error, want an error", tt.version) - } - }) - } -} - func TestParseCiliumVersion(t *testing.T) { tests := []struct { name string @@ -108,88 +63,6 @@ func TestParseCiliumVersion(t *testing.T) { } } -func TestBuildImagePath(t *testing.T) { - tests := []struct { - userImage string - userVersion string - defaultImage string - defaultVersion string - want string - }{ - { - userVersion: "", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium:v1.10.4", - }, - { - userVersion: "v1.9.10", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium:v1.9.10", - }, - { - userVersion: "1.9.10", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium:v1.9.10", - }, - { - userVersion: "-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", - }, - { - userVersion: ":latest", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium:latest", - }, - { - userImage: "quay.io/cilium/cilium-ci", - userVersion: "v1.9.10", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium-ci:v1.9.10", - }, - { - userImage: "quay.io/cilium/cilium-ci", - userVersion: "latest", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium-ci:latest", - }, - { - userImage: "quay.io/cilium/cilium-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium-ci:92ff7ffa762f6f8bc397a28e6f3147906e20e8fa", - }, - { - userVersion: "v1.11.0", - defaultImage: "quay.io/cilium/cilium", - defaultVersion: "v1.10.4", - want: "quay.io/cilium/cilium:v1.11.0", - }, - { - userVersion: "-service-mesh:v1.11.0-beta.1", - defaultImage: "quay.io/cilium/hubble-relay", - defaultVersion: "v1.11.0", - want: "quay.io/cilium/hubble-relay-service-mesh:v1.11.0-beta.1", - }, - } - for _, tt := range tests { - ui, uv, di, dv := tt.userImage, tt.userVersion, tt.defaultImage, tt.defaultVersion - fn := fmt.Sprintf("BuildImagePath(%q, %q, %q, %q)", ui, uv, di, dv) - t.Run(fn, func(t *testing.T) { - if got := BuildImagePath(ui, uv, di, dv); got != tt.want { - t.Errorf("%s == %q, want %q", fn, got, tt.want) - } - }) - } -} - func TestIsInHelmMode(t *testing.T) { orig := os.Getenv(CLIModeVariableName) defer func() { From 2f924b021874e9fa70e6ba50346dccb16201fa60 Mon Sep 17 00:00:00 2001 From: Michi Mutsuzaki Date: Tue, 27 Feb 2024 13:30:12 +0000 Subject: [PATCH 2/2] utils: Remove IsInHelmMode() Finally it's over. Ref: #2327 Signed-off-by: Michi Mutsuzaki --- connectivity/check/delete.go | 6 +- go.mod | 1 - go.sum | 2 - internal/cli/cmd/version.go | 3 +- internal/utils/utils.go | 8 - internal/utils/utils_test.go | 15 - k8s/client.go | 74 +-- k8s/client_test.go | 37 -- status/k8s.go | 37 +- status/status.go | 5 +- .../distribution/reference/.gitattributes | 1 - .../distribution/reference/.gitignore | 2 - .../distribution/reference/.golangci.yml | 18 - .../distribution/reference/CODE-OF-CONDUCT.md | 5 - .../distribution/reference/CONTRIBUTING.md | 114 ----- .../distribution/reference/GOVERNANCE.md | 144 ------ .../github.com/distribution/reference/LICENSE | 202 -------- .../distribution/reference/MAINTAINERS | 26 -- .../distribution/reference/Makefile | 25 - .../distribution/reference/README.md | 30 -- .../distribution/reference/SECURITY.md | 7 - .../reference/distribution-logo.svg | 1 - .../distribution/reference/helpers.go | 42 -- .../distribution/reference/normalize.go | 224 --------- .../distribution/reference/reference.go | 436 ------------------ .../distribution/reference/regexp.go | 163 ------- .../github.com/distribution/reference/sort.go | 75 --- vendor/modules.txt | 3 - 28 files changed, 29 insertions(+), 1677 deletions(-) delete mode 100644 k8s/client_test.go delete mode 100644 vendor/github.com/distribution/reference/.gitattributes delete mode 100644 vendor/github.com/distribution/reference/.gitignore delete mode 100644 vendor/github.com/distribution/reference/.golangci.yml delete mode 100644 vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md delete mode 100644 vendor/github.com/distribution/reference/CONTRIBUTING.md delete mode 100644 vendor/github.com/distribution/reference/GOVERNANCE.md delete mode 100644 vendor/github.com/distribution/reference/LICENSE delete mode 100644 vendor/github.com/distribution/reference/MAINTAINERS delete mode 100644 vendor/github.com/distribution/reference/Makefile delete mode 100644 vendor/github.com/distribution/reference/README.md delete mode 100644 vendor/github.com/distribution/reference/SECURITY.md delete mode 100644 vendor/github.com/distribution/reference/distribution-logo.svg delete mode 100644 vendor/github.com/distribution/reference/helpers.go delete mode 100644 vendor/github.com/distribution/reference/normalize.go delete mode 100644 vendor/github.com/distribution/reference/reference.go delete mode 100644 vendor/github.com/distribution/reference/regexp.go delete mode 100644 vendor/github.com/distribution/reference/sort.go diff --git a/connectivity/check/delete.go b/connectivity/check/delete.go index 3fabd14f50..944a3493d0 100644 --- a/connectivity/check/delete.go +++ b/connectivity/check/delete.go @@ -40,7 +40,7 @@ func (ct *ConnectivityTest) deleteCiliumPods(ctx context.Context) error { // or the secret parsing failed for whatever reason, then we create a default helm state. ct.Logf("Error parsing helm cli secret: %s", err) ct.Logf("Proceeding in unknown installation state") - helmState, err = ct.generateDefaultHelmState(ctx, ct.client, ct.params.CiliumNamespace) + helmState, err = ct.generateDefaultHelmState(ct.client, ct.params.CiliumNamespace) if err != nil { return err } @@ -181,8 +181,8 @@ func (ct *ConnectivityTest) generateManifestsNodeAffinity(ctx context.Context, h return nil } -func (ct *ConnectivityTest) generateDefaultHelmState(ctx context.Context, client *k8s.Client, namespace string) (*helm.State, error) { - version, err := client.GetRunningCiliumVersion(ctx, namespace) +func (ct *ConnectivityTest) generateDefaultHelmState(client *k8s.Client, namespace string) (*helm.State, error) { + version, err := client.GetRunningCiliumVersion(namespace) if version == "" || err != nil { return nil, fmt.Errorf("unable to obtain cilium version, no Cilium pods found in namespace %q", namespace) } diff --git a/go.mod b/go.mod index 0b650e3718..cd1141e661 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/cilium/tetragon/pkg/k8s v0.0.0-20231127174521-c97da4b42413 github.com/cilium/workerpool v1.2.0 github.com/cloudflare/cfssl v1.6.4 - github.com/distribution/reference v0.5.0 github.com/go-openapi/strfmt v0.22.0 github.com/google/gops v0.3.28 github.com/mholt/archiver/v3 v3.5.1 diff --git a/go.sum b/go.sum index 97ad6a20b8..d3b139474e 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= diff --git a/internal/cli/cmd/version.go b/internal/cli/cmd/version.go index b81c6821c4..6c028c9f09 100644 --- a/internal/cli/cmd/version.go +++ b/internal/cli/cmd/version.go @@ -4,7 +4,6 @@ package cmd import ( - "context" "fmt" "io" "net/http" @@ -44,7 +43,7 @@ func newCmdVersion() *cobra.Command { if clientOnly { return nil } - version, err := k8sClient.GetRunningCiliumVersion(context.Background(), namespace) + version, err := k8sClient.GetRunningCiliumVersion(namespace) if err != nil { fmt.Printf("cilium image (running): unknown. Unable to obtain cilium version. Reason: %s\n", err.Error()) } else { diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 447671b425..0a96fa092f 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -5,7 +5,6 @@ package utils import ( "fmt" - "os" "strconv" "github.com/blang/semver/v4" @@ -15,13 +14,6 @@ func ParseCiliumVersion(version string) (semver.Version, error) { return semver.ParseTolerant(version) } -const CLIModeVariableName = "CILIUM_CLI_MODE" - -// IsInHelmMode returns true if cilium-cli is in "helm" mode. Otherwise, it returns false. -func IsInHelmMode() bool { - return os.Getenv(CLIModeVariableName) != "classic" -} - func MustParseBool(v string) bool { b, err := strconv.ParseBool(v) if err != nil { diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index 1b7fda5aac..1974e677d7 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -4,12 +4,10 @@ package utils import ( - "os" "reflect" "testing" "github.com/blang/semver/v4" - "github.com/stretchr/testify/assert" ) func TestParseCiliumVersion(t *testing.T) { @@ -62,16 +60,3 @@ func TestParseCiliumVersion(t *testing.T) { }) } } - -func TestIsInHelmMode(t *testing.T) { - orig := os.Getenv(CLIModeVariableName) - defer func() { - assert.NoError(t, os.Setenv(CLIModeVariableName, orig)) - }() - assert.NoError(t, os.Setenv(CLIModeVariableName, "helm")) - assert.True(t, IsInHelmMode()) - assert.NoError(t, os.Setenv(CLIModeVariableName, "classic")) - assert.False(t, IsInHelmMode()) - assert.NoError(t, os.Setenv(CLIModeVariableName, "random")) - assert.True(t, IsInHelmMode()) -} diff --git a/k8s/client.go b/k8s/client.go index eb53bd19d0..22c2188145 100644 --- a/k8s/client.go +++ b/k8s/client.go @@ -8,7 +8,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "net" @@ -18,7 +17,6 @@ import ( "time" "github.com/blang/semver/v4" - "github.com/distribution/reference" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli/output" @@ -957,47 +955,6 @@ func (c *Client) GetLogs(ctx context.Context, namespace, name, container string, return b.String(), nil } -func getCiliumVersionFromImage(image string) (string, error) { - // default to "latest" as k8s may not include it explicitly - version := "latest" - - ref, err := reference.Parse(image) - if err != nil { - // Image has an invalid name, skip it - return "", err - } - - tagged, isTagged := ref.(reference.Tagged) - if isTagged { - version = tagged.Tag() - } - - named, isNamed := ref.(reference.Named) - if isNamed { - path := reference.Path(named) - strs := strings.Split(path, "/") - - // Take the last element as an image name - imageName := strs[len(strs)-1] - if !strings.HasPrefix(imageName, "cilium") { - // Not likely to be Cilium - return "", fmt.Errorf("image name %s is not prefixed with cilium", imageName) - } - - // Add any part in the pod image separated by a '-` to the version, - // e.g., "quay.io/cilium/cilium-ci:1234" -> "-ci:1234" - dash := strings.Index(imageName, "-") - if dash >= 0 { - version = imageName[dash:] + ":" + version - } - } else { - // Image somehow doesn't contain name, skip it. - return "", fmt.Errorf("image does't contain name") - } - - return version, nil -} - // GetCiliumVersion returns a semver.Version representing the version of cilium // running in the cilium-agent pod func (c *Client) GetCiliumVersion(ctx context.Context, p *corev1.Pod) (*semver.Version, error) { @@ -1021,32 +978,15 @@ func (c *Client) GetCiliumVersion(ctx context.Context, p *corev1.Pod) (*semver.V return &podVersion, nil } -func (c *Client) GetRunningCiliumVersion(ctx context.Context, namespace string) (string, error) { - if utils.IsInHelmMode() { - release, err := helm.Get(c.HelmActionConfig, helm.GetParameters{ - Namespace: namespace, - Name: defaults.HelmReleaseName, - }) - if err != nil { - return "", err - } - return release.Chart.Metadata.Version, nil - } - pods, err := c.ListPods(ctx, namespace, metav1.ListOptions{LabelSelector: defaults.AgentPodSelector}) +func (c *Client) GetRunningCiliumVersion(namespace string) (string, error) { + release, err := helm.Get(c.HelmActionConfig, helm.GetParameters{ + Namespace: namespace, + Name: defaults.HelmReleaseName, + }) if err != nil { - return "", fmt.Errorf("unable to list cilium pods: %w", err) - } - if len(pods.Items) > 0 && len(pods.Items[0].Spec.Containers) > 0 { - for _, container := range pods.Items[0].Spec.Containers { - version, err := getCiliumVersionFromImage(container.Image) - if err != nil { - continue - } - return version, nil - } - return "", errors.New("unable to obtain cilium version: no cilium container found") + return "", err } - return "", errors.New("unable to obtain cilium version: no cilium pods found") + return release.Chart.Metadata.Version, nil } func (c *Client) ListCiliumLoadBalancerIPPools(ctx context.Context, opts metav1.ListOptions) (*ciliumv2alpha1.CiliumLoadBalancerIPPoolList, error) { diff --git a/k8s/client_test.go b/k8s/client_test.go deleted file mode 100644 index c43ccf271c..0000000000 --- a/k8s/client_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package k8s - -import "testing" - -func TestGetCiliumVersionFromImage(t *testing.T) { - tests := []struct { - name string - image string - expectedVersion string - }{ - {"registry name and tag", "quay.io/cilium/cilium:latest", "latest"}, - {"registry name and tag with digest", "quay.io/cilium/cilium:latest@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2", "latest"}, - {"no registry name and tag", "cilium/cilium:latest", "latest"}, - {"no registry name and tag with digest", "cilium/cilium:latest@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2", "latest"}, - {"no tag", "quay.io/cilium/cilium", "latest"}, - {"with hyphen", "quay.io/cilium/cilium-ci", "-ci:latest"}, - {"with hyphen and hash", "quay.io/cilium/cilium-ci:93f68432e8da3adf8b74972b3aa6a53fc2c36517", "-ci:93f68432e8da3adf8b74972b3aa6a53fc2c36517"}, - {"with hypen, dash and digest", "quay.io/cilium/cilium-ci:93f68432e8da3adf8b74972b3aa6a53fc2c36517@sha256:f2b7707b0b5130ccc2d3fa493a9262d5989ee2eacd712b1c5136391a8361a830", "-ci:93f68432e8da3adf8b74972b3aa6a53fc2c36517"}, - {"registry with port number", "localhost:5000/cilium/cilium:latest", "latest"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - version, err := getCiliumVersionFromImage(tt.image) - if err != nil { - t.Errorf("got an unexpected error: %s", err.Error()) - } - - if version != tt.expectedVersion { - t.Errorf("expect version %s, got %s", version, tt.expectedVersion) - } - }) - } -} diff --git a/status/k8s.go b/status/k8s.go index c77f6eceab..2c4f47ae4c 100644 --- a/status/k8s.go +++ b/status/k8s.go @@ -23,7 +23,6 @@ import ( "github.com/cilium/cilium-cli/defaults" "github.com/cilium/cilium-cli/internal/helm" - "github.com/cilium/cilium-cli/internal/utils" "github.com/cilium/cilium-cli/k8s" ) @@ -512,25 +511,23 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { }, } - if utils.IsInHelmMode() { - tasks = append(tasks, statusTask{ - name: "Helm chart version", - task: func(_ context.Context) error { - client, ok := k.client.(*k8s.Client) - if !ok { - return fmt.Errorf("failed to initialize Helm client") - } - release, err := helm.Get(client.HelmActionConfig, helm.GetParameters{ - Namespace: k.params.Namespace, - Name: defaults.HelmReleaseName, - }) - if err != nil { - return err - } - status.HelmChartVersion = release.Chart.Metadata.Version - return nil - }}) - } + tasks = append(tasks, statusTask{ + name: "Helm chart version", + task: func(_ context.Context) error { + client, ok := k.client.(*k8s.Client) + if !ok { + return fmt.Errorf("failed to initialize Helm client") + } + release, err := helm.Get(client.HelmActionConfig, helm.GetParameters{ + Namespace: k.params.Namespace, + Name: defaults.HelmReleaseName, + }) + if err != nil { + return err + } + status.HelmChartVersion = release.Chart.Metadata.Version + return nil + }}) // for the sake of sanity, don't get pod logs more than once var agentLogsOnce = sync.Once{} diff --git a/status/status.go b/status/status.go index de5f30febb..21b1b8a8f6 100644 --- a/status/status.go +++ b/status/status.go @@ -14,7 +14,6 @@ import ( "github.com/cilium/cilium/api/v1/models" "github.com/cilium/cilium-cli/defaults" - "github.com/cilium/cilium-cli/internal/utils" ) const ( @@ -380,9 +379,7 @@ func (s *Status) Format() string { fmt.Fprintf(w, "Cluster Pods:\t%s\n", formatPodsCount(s.PodsCount)) - if utils.IsInHelmMode() { - fmt.Fprintf(w, "Helm chart version:\t%s\n", s.HelmChartVersion) - } + fmt.Fprintf(w, "Helm chart version:\t%s\n", s.HelmChartVersion) if len(s.ImageCount) > 0 { header := "Image versions" for name, imageCount := range s.ImageCount { diff --git a/vendor/github.com/distribution/reference/.gitattributes b/vendor/github.com/distribution/reference/.gitattributes deleted file mode 100644 index d207b1802b..0000000000 --- a/vendor/github.com/distribution/reference/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.go text eol=lf diff --git a/vendor/github.com/distribution/reference/.gitignore b/vendor/github.com/distribution/reference/.gitignore deleted file mode 100644 index dc07e6b04a..0000000000 --- a/vendor/github.com/distribution/reference/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Cover profiles -*.out diff --git a/vendor/github.com/distribution/reference/.golangci.yml b/vendor/github.com/distribution/reference/.golangci.yml deleted file mode 100644 index 793f0bb7ec..0000000000 --- a/vendor/github.com/distribution/reference/.golangci.yml +++ /dev/null @@ -1,18 +0,0 @@ -linters: - enable: - - bodyclose - - dupword # Checks for duplicate words in the source code - - gofmt - - goimports - - ineffassign - - misspell - - revive - - staticcheck - - unconvert - - unused - - vet - disable: - - errcheck - -run: - deadline: 2m diff --git a/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md b/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md deleted file mode 100644 index 48f6704c6d..0000000000 --- a/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,5 +0,0 @@ -# Code of Conduct - -We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). - -Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct. diff --git a/vendor/github.com/distribution/reference/CONTRIBUTING.md b/vendor/github.com/distribution/reference/CONTRIBUTING.md deleted file mode 100644 index ab21946656..0000000000 --- a/vendor/github.com/distribution/reference/CONTRIBUTING.md +++ /dev/null @@ -1,114 +0,0 @@ -# Contributing to the reference library - -## Community help - -If you need help, please ask in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack. -[Click here for an invite to the CNCF community slack](https://slack.cncf.io/) - -## Reporting security issues - -The maintainers take security seriously. If you discover a security -issue, please bring it to their attention right away! - -Please **DO NOT** file a public issue, instead send your report privately to -[cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io). - -## Reporting an issue properly - -By following these simple rules you will get better and faster feedback on your issue. - - - search the bugtracker for an already reported issue - -### If you found an issue that describes your problem: - - - please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments - - please refrain from adding "same thing here" or "+1" comments - - you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button - - comment if you have some new, technical and relevant information to add to the case - - __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue. - -### If you have not found an existing issue that describes your problem: - - 1. create a new issue, with a succinct title that describes your issue: - - bad title: "It doesn't work with my docker" - - good title: "Private registry push fail: 400 error with E_INVALID_DIGEST" - 2. copy the output of (or similar for other container tools): - - `docker version` - - `docker info` - - `docker exec registry --version` - 3. copy the command line you used to launch your Registry - 4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments) - 5. reproduce your problem and get your docker daemon logs showing the error - 6. if relevant, copy your registry logs that show the error - 7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used) - 8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry - -## Contributing Code - -Contributions should be made via pull requests. Pull requests will be reviewed -by one or more maintainers or reviewers and merged when acceptable. - -You should follow the basic GitHub workflow: - - 1. Use your own [fork](https://help.github.com/en/articles/about-forks) - 2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) - 3. Test your code - 4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) - 5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) - -Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes) -for tips on creating a successful contribution. - -## Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. diff --git a/vendor/github.com/distribution/reference/GOVERNANCE.md b/vendor/github.com/distribution/reference/GOVERNANCE.md deleted file mode 100644 index 200045b050..0000000000 --- a/vendor/github.com/distribution/reference/GOVERNANCE.md +++ /dev/null @@ -1,144 +0,0 @@ -# distribution/reference Project Governance - -Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here. - -For specific guidance on practical contribution steps please -see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide. - -## Maintainership - -There are different types of maintainers, with different responsibilities, but -all maintainers have 3 things in common: - -1) They share responsibility in the project's success. -2) They have made a long-term, recurring time investment to improve the project. -3) They spend that time doing whatever needs to be done, not necessarily what -is the most interesting or fun. - -Maintainers are often under-appreciated, because their work is harder to appreciate. -It's easy to appreciate a really cool and technically advanced feature. It's harder -to appreciate the absence of bugs, the slow but steady improvement in stability, -or the reliability of a release process. But those things distinguish a good -project from a great one. - -## Reviewers - -A reviewer is a core role within the project. -They share in reviewing issues and pull requests and their LGTM counts towards the -required LGTM count to merge a code change into the project. - -Reviewers are part of the organization but do not have write access. -Becoming a reviewer is a core aspect in the journey to becoming a maintainer. - -## Adding maintainers - -Maintainers are first and foremost contributors that have shown they are -committed to the long term success of a project. Contributors wanting to become -maintainers are expected to be deeply involved in contributing code, pull -request review, and triage of issues in the project for more than three months. - -Just contributing does not make you a maintainer, it is about building trust -with the current maintainers of the project and being a person that they can -depend on and trust to make decisions in the best interest of the project. - -Periodically, the existing maintainers curate a list of contributors that have -shown regular activity on the project over the prior months. From this list, -maintainer candidates are selected and proposed in a pull request or a -maintainers communication channel. - -After a candidate has been announced to the maintainers, the existing -maintainers are given five business days to discuss the candidate, raise -objections and cast their vote. Votes may take place on the communication -channel or via pull request comment. Candidates must be approved by at least 66% -of the current maintainers by adding their vote on the mailing list. The -reviewer role has the same process but only requires 33% of current maintainers. -Only maintainers of the repository that the candidate is proposed for are -allowed to vote. - -If a candidate is approved, a maintainer will contact the candidate to invite -the candidate to open a pull request that adds the contributor to the -MAINTAINERS file. The voting process may take place inside a pull request if a -maintainer has already discussed the candidacy with the candidate and a -maintainer is willing to be a sponsor by opening the pull request. The candidate -becomes a maintainer once the pull request is merged. - -## Stepping down policy - -Life priorities, interests, and passions can change. If you're a maintainer but -feel you must remove yourself from the list, inform other maintainers that you -intend to step down, and if possible, help find someone to pick up your work. -At the very least, ensure your work can be continued where you left off. - -After you've informed other maintainers, create a pull request to remove -yourself from the MAINTAINERS file. - -## Removal of inactive maintainers - -Similar to the procedure for adding new maintainers, existing maintainers can -be removed from the list if they do not show significant activity on the -project. Periodically, the maintainers review the list of maintainers and their -activity over the last three months. - -If a maintainer has shown insufficient activity over this period, a neutral -person will contact the maintainer to ask if they want to continue being -a maintainer. If the maintainer decides to step down as a maintainer, they -open a pull request to be removed from the MAINTAINERS file. - -If the maintainer wants to remain a maintainer, but is unable to perform the -required duties they can be removed with a vote of at least 66% of the current -maintainers. In this case, maintainers should first propose the change to -maintainers via the maintainers communication channel, then open a pull request -for voting. The voting period is five business days. The voting pull request -should not come as a surpise to any maintainer and any discussion related to -performance must not be discussed on the pull request. - -## How are decisions made? - -Docker distribution is an open-source project with an open design philosophy. -This means that the repository is the source of truth for EVERY aspect of the -project, including its philosophy, design, road map, and APIs. *If it's part of -the project, it's in the repo. If it's in the repo, it's part of the project.* - -As a result, all decisions can be expressed as changes to the repository. An -implementation change is a change to the source code. An API change is a change -to the API specification. A philosophy change is a change to the philosophy -manifesto, and so on. - -All decisions affecting distribution, big and small, follow the same 3 steps: - -* Step 1: Open a pull request. Anyone can do this. - -* Step 2: Discuss the pull request. Anyone can do this. - -* Step 3: Merge or refuse the pull request. Who does this depends on the nature -of the pull request and which areas of the project it affects. - -## Helping contributors with the DCO - -The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work) -requirement is not intended as a roadblock or speed bump. - -Some contributors are not as familiar with `git`, or have used a web -based editor, and thus asking them to `git commit --amend -s` is not the best -way forward. - -In this case, maintainers can update the commits based on clause (c) of the DCO. -The most trivial way for a contributor to allow the maintainer to do this, is to -add a DCO signature in a pull requests's comment, or a maintainer can simply -note that the change is sufficiently trivial that it does not substantially -change the existing contribution - i.e., a spelling change. - -When you add someone's DCO, please also add your own to keep a log. - -## I'm a maintainer. Should I make pull requests too? - -Yes. Nobody should ever push to master directly. All changes should be -made through a pull request. - -## Conflict Resolution - -If you have a technical dispute that you feel has reached an impasse with a -subset of the community, any contributor may open an issue, specifically -calling for a resolution vote of the current core maintainers to resolve the -dispute. The same voting quorums required (2/3) for adding and removing -maintainers will apply to conflict resolution. diff --git a/vendor/github.com/distribution/reference/LICENSE b/vendor/github.com/distribution/reference/LICENSE deleted file mode 100644 index e06d208186..0000000000 --- a/vendor/github.com/distribution/reference/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - diff --git a/vendor/github.com/distribution/reference/MAINTAINERS b/vendor/github.com/distribution/reference/MAINTAINERS deleted file mode 100644 index 9e0a60c8bd..0000000000 --- a/vendor/github.com/distribution/reference/MAINTAINERS +++ /dev/null @@ -1,26 +0,0 @@ -# Distribution project maintainers & reviewers -# -# See GOVERNANCE.md for maintainer versus reviewer roles -# -# MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io) -# GitHub ID, Name, Email address -"chrispat","Chris Patterson","chrispat@github.com" -"clarkbw","Bryan Clark","clarkbw@github.com" -"corhere","Cory Snider","csnider@mirantis.com" -"deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com" -"heww","He Weiwei","hweiwei@vmware.com" -"joaodrp","João Pereira","jpereira@gitlab.com" -"justincormack","Justin Cormack","justin.cormack@docker.com" -"squizzi","Kyle Squizzato","ksquizzato@mirantis.com" -"milosgajdos","Milos Gajdos","milosthegajdos@gmail.com" -"sargun","Sargun Dhillon","sargun@sargun.me" -"wy65701436","Wang Yan","wangyan@vmware.com" -"stevelasker","Steve Lasker","steve.lasker@microsoft.com" -# -# REVIEWERS -# GitHub ID, Name, Email address -"dmcgowan","Derek McGowan","derek@mcgstyle.net" -"stevvooe","Stephen Day","stevvooe@gmail.com" -"thajeztah","Sebastiaan van Stijn","github@gone.nl" -"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com" -"Jamstah", "James Hewitt", "james.hewitt@gmail.com" diff --git a/vendor/github.com/distribution/reference/Makefile b/vendor/github.com/distribution/reference/Makefile deleted file mode 100644 index c78576b75d..0000000000 --- a/vendor/github.com/distribution/reference/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -# Project packages. -PACKAGES=$(shell go list ./...) - -# Flags passed to `go test` -BUILDFLAGS ?= -TESTFLAGS ?= - -.PHONY: all build test coverage -.DEFAULT: all - -all: build - -build: ## no binaries to build, so just check compilation suceeds - go build ${BUILDFLAGS} ./... - -test: ## run tests - go test ${TESTFLAGS} ./... - -coverage: ## generate coverprofiles from the unit tests - rm -f coverage.txt - go test ${TESTFLAGS} -cover -coverprofile=cover.out ./... - -.PHONY: help -help: - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_\/%-]+:.*?##/ { printf " \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/vendor/github.com/distribution/reference/README.md b/vendor/github.com/distribution/reference/README.md deleted file mode 100644 index e2531e49c4..0000000000 --- a/vendor/github.com/distribution/reference/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Distribution reference - -Go library to handle references to container images. - - - -[![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI) -[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference) -[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE) -[![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference) -[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield) - -This repository contains a library for handling refrences to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details. - -## Contribution - -Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute -issues, fixes, and patches to this project. - -## Communication - -For async communication and long running discussions please use issues and pull requests on the github repo. -This will be the best place to discuss design and implementation. - -For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/) -that everyone is welcome to join and chat about development. - -## Licenses - -The distribution codebase is released under the [Apache 2.0 license](LICENSE). diff --git a/vendor/github.com/distribution/reference/SECURITY.md b/vendor/github.com/distribution/reference/SECURITY.md deleted file mode 100644 index aaf983c0f0..0000000000 --- a/vendor/github.com/distribution/reference/SECURITY.md +++ /dev/null @@ -1,7 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away! - -Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io. diff --git a/vendor/github.com/distribution/reference/distribution-logo.svg b/vendor/github.com/distribution/reference/distribution-logo.svg deleted file mode 100644 index cc9f4073b9..0000000000 --- a/vendor/github.com/distribution/reference/distribution-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/vendor/github.com/distribution/reference/helpers.go b/vendor/github.com/distribution/reference/helpers.go deleted file mode 100644 index d10c7ef838..0000000000 --- a/vendor/github.com/distribution/reference/helpers.go +++ /dev/null @@ -1,42 +0,0 @@ -package reference - -import "path" - -// IsNameOnly returns true if reference only contains a repo name. -func IsNameOnly(ref Named) bool { - if _, ok := ref.(NamedTagged); ok { - return false - } - if _, ok := ref.(Canonical); ok { - return false - } - return true -} - -// FamiliarName returns the familiar name string -// for the given named, familiarizing if needed. -func FamiliarName(ref Named) string { - if nn, ok := ref.(normalizedNamed); ok { - return nn.Familiar().Name() - } - return ref.Name() -} - -// FamiliarString returns the familiar string representation -// for the given reference, familiarizing if needed. -func FamiliarString(ref Reference) string { - if nn, ok := ref.(normalizedNamed); ok { - return nn.Familiar().String() - } - return ref.String() -} - -// FamiliarMatch reports whether ref matches the specified pattern. -// See [path.Match] for supported patterns. -func FamiliarMatch(pattern string, ref Reference) (bool, error) { - matched, err := path.Match(pattern, FamiliarString(ref)) - if namedRef, isNamed := ref.(Named); isNamed && !matched { - matched, _ = path.Match(pattern, FamiliarName(namedRef)) - } - return matched, err -} diff --git a/vendor/github.com/distribution/reference/normalize.go b/vendor/github.com/distribution/reference/normalize.go deleted file mode 100644 index a30229d01b..0000000000 --- a/vendor/github.com/distribution/reference/normalize.go +++ /dev/null @@ -1,224 +0,0 @@ -package reference - -import ( - "fmt" - "strings" - - "github.com/opencontainers/go-digest" -) - -const ( - // legacyDefaultDomain is the legacy domain for Docker Hub (which was - // originally named "the Docker Index"). This domain is still used for - // authentication and image search, which were part of the "v1" Docker - // registry specification. - // - // This domain will continue to be supported, but there are plans to consolidate - // legacy domains to new "canonical" domains. Once those domains are decided - // on, we must update the normalization functions, but preserve compatibility - // with existing installs, clients, and user configuration. - legacyDefaultDomain = "index.docker.io" - - // defaultDomain is the default domain used for images on Docker Hub. - // It is used to normalize "familiar" names to canonical names, for example, - // to convert "ubuntu" to "docker.io/library/ubuntu:latest". - // - // Note that actual domain of Docker Hub's registry is registry-1.docker.io. - // This domain will continue to be supported, but there are plans to consolidate - // legacy domains to new "canonical" domains. Once those domains are decided - // on, we must update the normalization functions, but preserve compatibility - // with existing installs, clients, and user configuration. - defaultDomain = "docker.io" - - // officialRepoPrefix is the namespace used for official images on Docker Hub. - // It is used to normalize "familiar" names to canonical names, for example, - // to convert "ubuntu" to "docker.io/library/ubuntu:latest". - officialRepoPrefix = "library/" - - // defaultTag is the default tag if no tag is provided. - defaultTag = "latest" -) - -// normalizedNamed represents a name which has been -// normalized and has a familiar form. A familiar name -// is what is used in Docker UI. An example normalized -// name is "docker.io/library/ubuntu" and corresponding -// familiar name of "ubuntu". -type normalizedNamed interface { - Named - Familiar() Named -} - -// ParseNormalizedNamed parses a string into a named reference -// transforming a familiar name from Docker UI to a fully -// qualified reference. If the value may be an identifier -// use ParseAnyReference. -func ParseNormalizedNamed(s string) (Named, error) { - if ok := anchoredIdentifierRegexp.MatchString(s); ok { - return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) - } - domain, remainder := splitDockerDomain(s) - var remote string - if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { - remote = remainder[:tagSep] - } else { - remote = remainder - } - if strings.ToLower(remote) != remote { - return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote) - } - - ref, err := Parse(domain + "/" + remainder) - if err != nil { - return nil, err - } - named, isNamed := ref.(Named) - if !isNamed { - return nil, fmt.Errorf("reference %s has no name", ref.String()) - } - return named, nil -} - -// namedTaggedDigested is a reference that has both a tag and a digest. -type namedTaggedDigested interface { - NamedTagged - Digested -} - -// ParseDockerRef normalizes the image reference following the docker convention, -// which allows for references to contain both a tag and a digest. It returns a -// reference that is either tagged or digested. For references containing both -// a tag and a digest, it returns a digested reference. For example, the following -// reference: -// -// docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa -// -// Is returned as a digested reference (with the ":latest" tag removed): -// -// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa -// -// References that are already "tagged" or "digested" are returned unmodified: -// -// // Already a digested reference -// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa -// -// // Already a named reference -// docker.io/library/busybox:latest -func ParseDockerRef(ref string) (Named, error) { - named, err := ParseNormalizedNamed(ref) - if err != nil { - return nil, err - } - if canonical, ok := named.(namedTaggedDigested); ok { - // The reference is both tagged and digested; only return digested. - newNamed, err := WithName(canonical.Name()) - if err != nil { - return nil, err - } - return WithDigest(newNamed, canonical.Digest()) - } - return TagNameOnly(named), nil -} - -// splitDockerDomain splits a repository name to domain and remote-name. -// If no valid domain is found, the default domain is used. Repository name -// needs to be already validated before. -func splitDockerDomain(name string) (domain, remainder string) { - i := strings.IndexRune(name, '/') - if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) { - domain, remainder = defaultDomain, name - } else { - domain, remainder = name[:i], name[i+1:] - } - if domain == legacyDefaultDomain { - domain = defaultDomain - } - if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { - remainder = officialRepoPrefix + remainder - } - return -} - -// familiarizeName returns a shortened version of the name familiar -// to the Docker UI. Familiar names have the default domain -// "docker.io" and "library/" repository prefix removed. -// For example, "docker.io/library/redis" will have the familiar -// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". -// Returns a familiarized named only reference. -func familiarizeName(named namedRepository) repository { - repo := repository{ - domain: named.Domain(), - path: named.Path(), - } - - if repo.domain == defaultDomain { - repo.domain = "" - // Handle official repositories which have the pattern "library/" - if strings.HasPrefix(repo.path, officialRepoPrefix) { - // TODO(thaJeztah): this check may be too strict, as it assumes the - // "library/" namespace does not have nested namespaces. While this - // is true (currently), technically it would be possible for Docker - // Hub to use those (e.g. "library/distros/ubuntu:latest"). - // See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785. - if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') { - repo.path = remainder - } - } - } - return repo -} - -func (r reference) Familiar() Named { - return reference{ - namedRepository: familiarizeName(r.namedRepository), - tag: r.tag, - digest: r.digest, - } -} - -func (r repository) Familiar() Named { - return familiarizeName(r) -} - -func (t taggedReference) Familiar() Named { - return taggedReference{ - namedRepository: familiarizeName(t.namedRepository), - tag: t.tag, - } -} - -func (c canonicalReference) Familiar() Named { - return canonicalReference{ - namedRepository: familiarizeName(c.namedRepository), - digest: c.digest, - } -} - -// TagNameOnly adds the default tag "latest" to a reference if it only has -// a repo name. -func TagNameOnly(ref Named) Named { - if IsNameOnly(ref) { - namedTagged, err := WithTag(ref, defaultTag) - if err != nil { - // Default tag must be valid, to create a NamedTagged - // type with non-validated input the WithTag function - // should be used instead - panic(err) - } - return namedTagged - } - return ref -} - -// ParseAnyReference parses a reference string as a possible identifier, -// full digest, or familiar name. -func ParseAnyReference(ref string) (Reference, error) { - if ok := anchoredIdentifierRegexp.MatchString(ref); ok { - return digestReference("sha256:" + ref), nil - } - if dgst, err := digest.Parse(ref); err == nil { - return digestReference(dgst), nil - } - - return ParseNormalizedNamed(ref) -} diff --git a/vendor/github.com/distribution/reference/reference.go b/vendor/github.com/distribution/reference/reference.go deleted file mode 100644 index e98c44daa2..0000000000 --- a/vendor/github.com/distribution/reference/reference.go +++ /dev/null @@ -1,436 +0,0 @@ -// Package reference provides a general type to represent any way of referencing images within the registry. -// Its main purpose is to abstract tags and digests (content-addressable hash). -// -// Grammar -// -// reference := name [ ":" tag ] [ "@" digest ] -// name := [domain '/'] remote-name -// domain := host [':' port-number] -// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A -// domain-name := domain-component ['.' domain-component]* -// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ -// port-number := /[0-9]+/ -// path-component := alpha-numeric [separator alpha-numeric]* -// path (or "remote-name") := path-component ['/' path-component]* -// alpha-numeric := /[a-z0-9]+/ -// separator := /[_.]|__|[-]*/ -// -// tag := /[\w][\w.-]{0,127}/ -// -// digest := digest-algorithm ":" digest-hex -// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* -// digest-algorithm-separator := /[+.-_]/ -// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ -// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value -// -// identifier := /[a-f0-9]{64}/ -package reference - -import ( - "errors" - "fmt" - "strings" - - "github.com/opencontainers/go-digest" -) - -const ( - // NameTotalLengthMax is the maximum total number of characters in a repository name. - NameTotalLengthMax = 255 -) - -var ( - // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. - ErrReferenceInvalidFormat = errors.New("invalid reference format") - - // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. - ErrTagInvalidFormat = errors.New("invalid tag format") - - // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. - ErrDigestInvalidFormat = errors.New("invalid digest format") - - // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. - ErrNameContainsUppercase = errors.New("repository name must be lowercase") - - // ErrNameEmpty is returned for empty, invalid repository names. - ErrNameEmpty = errors.New("repository name must have at least one component") - - // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. - ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) - - // ErrNameNotCanonical is returned when a name is not canonical. - ErrNameNotCanonical = errors.New("repository name must be canonical") -) - -// Reference is an opaque object reference identifier that may include -// modifiers such as a hostname, name, tag, and digest. -type Reference interface { - // String returns the full reference - String() string -} - -// Field provides a wrapper type for resolving correct reference types when -// working with encoding. -type Field struct { - reference Reference -} - -// AsField wraps a reference in a Field for encoding. -func AsField(reference Reference) Field { - return Field{reference} -} - -// Reference unwraps the reference type from the field to -// return the Reference object. This object should be -// of the appropriate type to further check for different -// reference types. -func (f Field) Reference() Reference { - return f.reference -} - -// MarshalText serializes the field to byte text which -// is the string of the reference. -func (f Field) MarshalText() (p []byte, err error) { - return []byte(f.reference.String()), nil -} - -// UnmarshalText parses text bytes by invoking the -// reference parser to ensure the appropriately -// typed reference object is wrapped by field. -func (f *Field) UnmarshalText(p []byte) error { - r, err := Parse(string(p)) - if err != nil { - return err - } - - f.reference = r - return nil -} - -// Named is an object with a full name -type Named interface { - Reference - Name() string -} - -// Tagged is an object which has a tag -type Tagged interface { - Reference - Tag() string -} - -// NamedTagged is an object including a name and tag. -type NamedTagged interface { - Named - Tag() string -} - -// Digested is an object which has a digest -// in which it can be referenced by -type Digested interface { - Reference - Digest() digest.Digest -} - -// Canonical reference is an object with a fully unique -// name including a name with domain and digest -type Canonical interface { - Named - Digest() digest.Digest -} - -// namedRepository is a reference to a repository with a name. -// A namedRepository has both domain and path components. -type namedRepository interface { - Named - Domain() string - Path() string -} - -// Domain returns the domain part of the [Named] reference. -func Domain(named Named) string { - if r, ok := named.(namedRepository); ok { - return r.Domain() - } - domain, _ := splitDomain(named.Name()) - return domain -} - -// Path returns the name without the domain part of the [Named] reference. -func Path(named Named) (name string) { - if r, ok := named.(namedRepository); ok { - return r.Path() - } - _, path := splitDomain(named.Name()) - return path -} - -func splitDomain(name string) (string, string) { - match := anchoredNameRegexp.FindStringSubmatch(name) - if len(match) != 3 { - return "", name - } - return match[1], match[2] -} - -// SplitHostname splits a named reference into a -// hostname and name string. If no valid hostname is -// found, the hostname is empty and the full value -// is returned as name -// -// Deprecated: Use [Domain] or [Path]. -func SplitHostname(named Named) (string, string) { - if r, ok := named.(namedRepository); ok { - return r.Domain(), r.Path() - } - return splitDomain(named.Name()) -} - -// Parse parses s and returns a syntactically valid Reference. -// If an error was encountered it is returned, along with a nil Reference. -func Parse(s string) (Reference, error) { - matches := ReferenceRegexp.FindStringSubmatch(s) - if matches == nil { - if s == "" { - return nil, ErrNameEmpty - } - if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { - return nil, ErrNameContainsUppercase - } - return nil, ErrReferenceInvalidFormat - } - - if len(matches[1]) > NameTotalLengthMax { - return nil, ErrNameTooLong - } - - var repo repository - - nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) - if len(nameMatch) == 3 { - repo.domain = nameMatch[1] - repo.path = nameMatch[2] - } else { - repo.domain = "" - repo.path = matches[1] - } - - ref := reference{ - namedRepository: repo, - tag: matches[2], - } - if matches[3] != "" { - var err error - ref.digest, err = digest.Parse(matches[3]) - if err != nil { - return nil, err - } - } - - r := getBestReferenceType(ref) - if r == nil { - return nil, ErrNameEmpty - } - - return r, nil -} - -// ParseNamed parses s and returns a syntactically valid reference implementing -// the Named interface. The reference must have a name and be in the canonical -// form, otherwise an error is returned. -// If an error was encountered it is returned, along with a nil Reference. -func ParseNamed(s string) (Named, error) { - named, err := ParseNormalizedNamed(s) - if err != nil { - return nil, err - } - if named.String() != s { - return nil, ErrNameNotCanonical - } - return named, nil -} - -// WithName returns a named object representing the given string. If the input -// is invalid ErrReferenceInvalidFormat will be returned. -func WithName(name string) (Named, error) { - if len(name) > NameTotalLengthMax { - return nil, ErrNameTooLong - } - - match := anchoredNameRegexp.FindStringSubmatch(name) - if match == nil || len(match) != 3 { - return nil, ErrReferenceInvalidFormat - } - return repository{ - domain: match[1], - path: match[2], - }, nil -} - -// WithTag combines the name from "name" and the tag from "tag" to form a -// reference incorporating both the name and the tag. -func WithTag(name Named, tag string) (NamedTagged, error) { - if !anchoredTagRegexp.MatchString(tag) { - return nil, ErrTagInvalidFormat - } - var repo repository - if r, ok := name.(namedRepository); ok { - repo.domain = r.Domain() - repo.path = r.Path() - } else { - repo.path = name.Name() - } - if canonical, ok := name.(Canonical); ok { - return reference{ - namedRepository: repo, - tag: tag, - digest: canonical.Digest(), - }, nil - } - return taggedReference{ - namedRepository: repo, - tag: tag, - }, nil -} - -// WithDigest combines the name from "name" and the digest from "digest" to form -// a reference incorporating both the name and the digest. -func WithDigest(name Named, digest digest.Digest) (Canonical, error) { - if !anchoredDigestRegexp.MatchString(digest.String()) { - return nil, ErrDigestInvalidFormat - } - var repo repository - if r, ok := name.(namedRepository); ok { - repo.domain = r.Domain() - repo.path = r.Path() - } else { - repo.path = name.Name() - } - if tagged, ok := name.(Tagged); ok { - return reference{ - namedRepository: repo, - tag: tagged.Tag(), - digest: digest, - }, nil - } - return canonicalReference{ - namedRepository: repo, - digest: digest, - }, nil -} - -// TrimNamed removes any tag or digest from the named reference. -func TrimNamed(ref Named) Named { - repo := repository{} - if r, ok := ref.(namedRepository); ok { - repo.domain, repo.path = r.Domain(), r.Path() - } else { - repo.domain, repo.path = splitDomain(ref.Name()) - } - return repo -} - -func getBestReferenceType(ref reference) Reference { - if ref.Name() == "" { - // Allow digest only references - if ref.digest != "" { - return digestReference(ref.digest) - } - return nil - } - if ref.tag == "" { - if ref.digest != "" { - return canonicalReference{ - namedRepository: ref.namedRepository, - digest: ref.digest, - } - } - return ref.namedRepository - } - if ref.digest == "" { - return taggedReference{ - namedRepository: ref.namedRepository, - tag: ref.tag, - } - } - - return ref -} - -type reference struct { - namedRepository - tag string - digest digest.Digest -} - -func (r reference) String() string { - return r.Name() + ":" + r.tag + "@" + r.digest.String() -} - -func (r reference) Tag() string { - return r.tag -} - -func (r reference) Digest() digest.Digest { - return r.digest -} - -type repository struct { - domain string - path string -} - -func (r repository) String() string { - return r.Name() -} - -func (r repository) Name() string { - if r.domain == "" { - return r.path - } - return r.domain + "/" + r.path -} - -func (r repository) Domain() string { - return r.domain -} - -func (r repository) Path() string { - return r.path -} - -type digestReference digest.Digest - -func (d digestReference) String() string { - return digest.Digest(d).String() -} - -func (d digestReference) Digest() digest.Digest { - return digest.Digest(d) -} - -type taggedReference struct { - namedRepository - tag string -} - -func (t taggedReference) String() string { - return t.Name() + ":" + t.tag -} - -func (t taggedReference) Tag() string { - return t.tag -} - -type canonicalReference struct { - namedRepository - digest digest.Digest -} - -func (c canonicalReference) String() string { - return c.Name() + "@" + c.digest.String() -} - -func (c canonicalReference) Digest() digest.Digest { - return c.digest -} diff --git a/vendor/github.com/distribution/reference/regexp.go b/vendor/github.com/distribution/reference/regexp.go deleted file mode 100644 index 65bc49d79b..0000000000 --- a/vendor/github.com/distribution/reference/regexp.go +++ /dev/null @@ -1,163 +0,0 @@ -package reference - -import ( - "regexp" - "strings" -) - -// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:"). -var DigestRegexp = regexp.MustCompile(digestPat) - -// DomainRegexp matches hostname or IP-addresses, optionally including a port -// number. It defines the structure of potential domain components that may be -// part of image names. This is purposely a subset of what is allowed by DNS to -// ensure backwards compatibility with Docker image names. It may be a subset of -// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between -// square brackets (excluding zone identifiers as defined by [RFC 6874] or special -// addresses such as IPv4-Mapped). -// -// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874. -var DomainRegexp = regexp.MustCompile(domainAndPort) - -// IdentifierRegexp is the format for string identifier used as a -// content addressable identifier using sha256. These identifiers -// are like digests without the algorithm, since sha256 is used. -var IdentifierRegexp = regexp.MustCompile(identifier) - -// NameRegexp is the format for the name component of references, including -// an optional domain and port, but without tag or digest suffix. -var NameRegexp = regexp.MustCompile(namePat) - -// ReferenceRegexp is the full supported format of a reference. The regexp -// is anchored and has capturing groups for name, tag, and digest -// components. -var ReferenceRegexp = regexp.MustCompile(referencePat) - -// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go]. -// -// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28 -var TagRegexp = regexp.MustCompile(tag) - -const ( - // alphanumeric defines the alphanumeric atom, typically a - // component of names. This only allows lower case characters and digits. - alphanumeric = `[a-z0-9]+` - - // separator defines the separators allowed to be embedded in name - // components. This allows one period, one or two underscore and multiple - // dashes. Repeated dashes and underscores are intentionally treated - // differently. In order to support valid hostnames as name components, - // supporting repeated dash was added. Additionally double underscore is - // now allowed as a separator to loosen the restriction for previously - // supported names. - separator = `(?:[._]|__|[-]+)` - - // localhost is treated as a special value for domain-name. Any other - // domain-name without a "." or a ":port" are considered a path component. - localhost = `localhost` - - // domainNameComponent restricts the registry domain component of a - // repository name to start with a component as defined by DomainRegexp. - domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])` - - // optionalPort matches an optional port-number including the port separator - // (e.g. ":80"). - optionalPort = `(?::[0-9]+)?` - - // tag matches valid tag names. From docker/docker:graph/tags.go. - tag = `[\w][\w.-]{0,127}` - - // digestPat matches well-formed digests, including algorithm (e.g. "sha256:"). - // - // TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp - // so that go-digest defines the canonical format. Note that the go-digest is - // more relaxed: - // - it allows multiple algorithms (e.g. "sha256+b64:") to allow - // future expansion of supported algorithms. - // - it allows the "" value to use urlsafe base64 encoding as defined - // in [rfc4648, section 5]. - // - // [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5. - digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}` - - // identifier is the format for a content addressable identifier using sha256. - // These identifiers are like digests without the algorithm, since sha256 is used. - identifier = `([a-f0-9]{64})` - - // ipv6address are enclosed between square brackets and may be represented - // in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format - // are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as - // IPv4-Mapped are deliberately excluded. - ipv6address = `\[(?:[a-fA-F0-9:]+)\]` -) - -var ( - // domainName defines the structure of potential domain components - // that may be part of image names. This is purposely a subset of what is - // allowed by DNS to ensure backwards compatibility with Docker image - // names. This includes IPv4 addresses on decimal format. - domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent) - - // host defines the structure of potential domains based on the URI - // Host subcomponent on rfc3986. It may be a subset of DNS domain name, - // or an IPv4 address in decimal format, or an IPv6 address between square - // brackets (excluding zone identifiers as defined by rfc6874 or special - // addresses such as IPv4-Mapped). - host = `(?:` + domainName + `|` + ipv6address + `)` - - // allowed by the URI Host subcomponent on rfc3986 to ensure backwards - // compatibility with Docker image names. - domainAndPort = host + optionalPort - - // anchoredTagRegexp matches valid tag names, anchored at the start and - // end of the matched string. - anchoredTagRegexp = regexp.MustCompile(anchored(tag)) - - // anchoredDigestRegexp matches valid digests, anchored at the start and - // end of the matched string. - anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat)) - - // pathComponent restricts path-components to start with an alphanumeric - // character, with following parts able to be separated by a separator - // (one period, one or two underscore and multiple dashes). - pathComponent = alphanumeric + anyTimes(separator+alphanumeric) - - // remoteName matches the remote-name of a repository. It consists of one - // or more forward slash (/) delimited path-components: - // - // pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu" - remoteName = pathComponent + anyTimes(`/`+pathComponent) - namePat = optional(domainAndPort+`/`) + remoteName - - // anchoredNameRegexp is used to parse a name value, capturing the - // domain and trailing components. - anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName))) - - referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat))) - - // anchoredIdentifierRegexp is used to check or match an - // identifier value, anchored at start and end of string. - anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier)) -) - -// optional wraps the expression in a non-capturing group and makes the -// production optional. -func optional(res ...string) string { - return `(?:` + strings.Join(res, "") + `)?` -} - -// anyTimes wraps the expression in a non-capturing group that can occur -// any number of times. -func anyTimes(res ...string) string { - return `(?:` + strings.Join(res, "") + `)*` -} - -// capture wraps the expression in a capturing group. -func capture(res ...string) string { - return `(` + strings.Join(res, "") + `)` -} - -// anchored anchors the regular expression by adding start and end delimiters. -func anchored(res ...string) string { - return `^` + strings.Join(res, "") + `$` -} diff --git a/vendor/github.com/distribution/reference/sort.go b/vendor/github.com/distribution/reference/sort.go deleted file mode 100644 index 416c37b076..0000000000 --- a/vendor/github.com/distribution/reference/sort.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright The containerd 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 reference - -import ( - "sort" -) - -// Sort sorts string references preferring higher information references. -// -// The precedence is as follows: -// -// 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:") -// 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest") -// 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:") -// 4. [Named] (e.g., "docker.io/library/busybox") -// 5. [Digested] (e.g., "docker.io@sha256:") -// 6. Parse error -func Sort(references []string) []string { - var prefs []Reference - var bad []string - - for _, ref := range references { - pref, err := ParseAnyReference(ref) - if err != nil { - bad = append(bad, ref) - } else { - prefs = append(prefs, pref) - } - } - sort.Slice(prefs, func(a, b int) bool { - ar := refRank(prefs[a]) - br := refRank(prefs[b]) - if ar == br { - return prefs[a].String() < prefs[b].String() - } - return ar < br - }) - sort.Strings(bad) - var refs []string - for _, pref := range prefs { - refs = append(refs, pref.String()) - } - return append(refs, bad...) -} - -func refRank(ref Reference) uint8 { - if _, ok := ref.(Named); ok { - if _, ok = ref.(Tagged); ok { - if _, ok = ref.(Digested); ok { - return 1 - } - return 2 - } - if _, ok = ref.(Digested); ok { - return 3 - } - return 4 - } - return 5 -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7f95387d05..f285a1de2a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -570,9 +570,6 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/distribution/reference v0.5.0 -## explicit; go 1.20 -github.com/distribution/reference # github.com/docker/cli v24.0.6+incompatible ## explicit github.com/docker/cli/cli/config