diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go index 4987b7c3859..a3e52b9d6bc 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go @@ -932,10 +932,6 @@ func TestChartWithRelativeURL(t *testing.T) { func newChart(name, namespace string, spec *sourcev1.HelmChartSpec, status *sourcev1.HelmChartStatus) sourcev1.HelmChart { helmChart := sourcev1.HelmChart{ - TypeMeta: metav1.TypeMeta{ - Kind: sourcev1.HelmChartKind, - APIVersion: sourcev1.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: name, Generation: int64(1), diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/integration_utils_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/integration_utils_test.go index e37a6f26448..e95f450330f 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/integration_utils_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/integration_utils_test.go @@ -189,10 +189,6 @@ func kubeAddHelmRepository(t *testing.T, name, url, namespace, secretName string interval = time.Duration(10 * time.Minute) } repo := sourcev1.HelmRepository{ - TypeMeta: metav1.TypeMeta{ - Kind: sourcev1.HelmRepositoryKind, - APIVersion: sourcev1.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, @@ -435,112 +431,127 @@ func kubeDeleteRole(t *testing.T, name, namespace string) error { return typedClient.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{}) } -func kubeCreateServiceAccountAndSecret(t *testing.T, name, namespace string) (string, error) { +func kubeCreateClusterRoleBinding(t *testing.T, name, namespace, role string) error { + t.Logf("+kubeCreateClusterRoleBinding(%s,%s,%s)", name, namespace, role) typedClient, err := kubeGetTypedClient() if err != nil { - return "", err + return err } + ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) defer cancel() - _, err = typedClient.CoreV1().ServiceAccounts(namespace).Create( + + _, err = typedClient.RbacV1().ClusterRoleBindings().Create( ctx, - &apiv1.ServiceAccount{ + &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: name + "-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: name, + Namespace: namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: role, }, }, metav1.CreateOptions{}) - if err != nil { - return "", err - } + return err +} - secretName := "" - for i := 0; i < 10; i++ { - ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) - defer cancel() - svcAccount, err := typedClient.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return "", err - } - if len(svcAccount.Secrets) >= 1 && svcAccount.Secrets[0].Name != "" { - secretName = svcAccount.Secrets[0].Name - break - } - t.Logf("Waiting 1s for service account [%s] secret to be set up... [%d/%d]", name, i+1, 10) - time.Sleep(1 * time.Second) - } - if secretName == "" { - return "", fmt.Errorf("Service account [%s] has no secrets", name) +func kubeCreateServiceAccount(t *testing.T, name, namespace string) error { + t.Logf("+kubeCreateServiceAccount(%s,%s)", name, namespace) + typedClient, err := kubeGetTypedClient() + if err != nil { + return err } - ctx, cancel = context.WithTimeout(context.Background(), defaultContextTimeout) + ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) defer cancel() - secret, err := typedClient.CoreV1().Secrets(namespace).Get( + + _, err = typedClient.CoreV1().ServiceAccounts(namespace).Create( ctx, - secretName, - metav1.GetOptions{}) - if err != nil { - return "", err - } - token := secret.Data["token"] - if token == nil { - return "", err - } else { - return string(token), nil - } + &apiv1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + metav1.CreateOptions{}) + return err } func kubeCreateServiceAccountWithClusterRole(t *testing.T, name, namespace, role string) (string, error) { t.Logf("+kubeCreateServiceAccountWithClusterRole(%s,%s,%s)", name, namespace, role) - token, err := kubeCreateServiceAccountAndSecret(t, name, namespace) + + // https://itnext.io/big-change-in-k8s-1-24-about-serviceaccounts-and-their-secrets-4b909a4af4e0 + // and + // https://github.com/vmware-tanzu/kubeapps/pull/4772 + // it used to be the case that creating service account would automatically create an + // associated secret service account token + // (per https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) + // but starting with 1.24 it doesn't. So now I do it manually + err := kubeCreateServiceAccount(t, name, namespace) if err != nil { return "", err } - typedClient, err := kubeGetTypedClient() + + err = kubeCreateClusterRoleBinding(t, name, namespace, role) if err != nil { return "", err } - ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) - defer cancel() - _, err = typedClient.RbacV1().ClusterRoleBindings().Create( - ctx, - &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: name + "-binding", - }, - Subjects: []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: name, - Namespace: namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "ClusterRole", - Name: role, + + secretName := name + "-token" + err = kubeCreateSecret(t, &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + Annotations: map[string]string{ + apiv1.ServiceAccountNameKey: name, }, }, - metav1.CreateOptions{}) + Type: apiv1.SecretTypeServiceAccountToken, + }) if err != nil { return "", err } - return string(token), nil + + var token string + for i := 0; i < 10; i++ { + token, err = kubeGetSecretToken(t, namespace, secretName, "token") + if token != "" && err == nil { + break + } + t.Logf("Waiting 1s for service account token in secret [%s] to be set up... [%d/%d]", secretName, i+1, 10) + time.Sleep(1 * time.Second) + } + + if token == "" { + return "", fmt.Errorf("Failed to get token from secret: [%s]", secretName) + } + return token, nil } func kubeCreateServiceAccountWithRoles(t *testing.T, name, namespace string, namespacesToRoles map[string]string) (string, error) { t.Logf("+kubeCreateServiceAccountWithRoles(%s,%s,%s)", name, namespace, namespacesToRoles) - token, err := kubeCreateServiceAccountAndSecret(t, name, namespace) + err := kubeCreateServiceAccount(t, name, namespace) if err != nil { return "", err } + typedClient, err := kubeGetTypedClient() if err != nil { return "", err } - ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) - defer cancel() + for ns, role := range namespacesToRoles { + ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) + defer cancel() + _, err = typedClient.RbacV1().RoleBindings(ns).Create( ctx, &rbacv1.RoleBinding{ @@ -565,7 +576,36 @@ func kubeCreateServiceAccountWithRoles(t *testing.T, name, namespace string, nam return "", err } } - return string(token), nil + + secretName := name + "-token" + err = kubeCreateSecret(t, &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + Annotations: map[string]string{ + apiv1.ServiceAccountNameKey: name, + }, + }, + Type: apiv1.SecretTypeServiceAccountToken, + }) + if err != nil { + return "", err + } + + var token string + for i := 0; i < 10; i++ { + token, err = kubeGetSecretToken(t, namespace, secretName, "token") + if token != "" && err == nil { + break + } + t.Logf("Waiting 1s for service account token in secret [%s] to be set up... [%d/%d]", secretName, i+1, 10) + time.Sleep(1 * time.Second) + } + + if token == "" { + return "", fmt.Errorf("Failed to get token from secret: [%s]", secretName) + } + return token, nil } // ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles @@ -1106,6 +1146,10 @@ func redisReceiveNotificationsLoop(t *testing.T, ch <-chan *redis.Message, sem * } } +// TODO (gfichtenholt): download bitnami index.yaml once, push it to the flux2testdata pod and use +// that URL to avoid intermittent +// "Failed: failed to fetch Helm repository index: failed to cache index to temporary file: unexpected EOF" + func initNumberOfChartsInBitnamiCatalog(t *testing.T) error { t.Logf("+initNumberOfChartsInBitnamiCatalog") diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release.go index a534feaa808..0853a8cc4e9 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release.go @@ -515,10 +515,6 @@ func (s *Server) deleteRelease(ctx context.Context, packageRef *corev1.Installed // 3. spec.targetNamespace, where flux will install any artifacts from the release func (s *Server) newFluxHelmRelease(chart *models.Chart, targetName types.NamespacedName, versionExpr string, reconcile *corev1.ReconciliationOptions, values map[string]interface{}) (*helmv2.HelmRelease, error) { fluxRelease := &helmv2.HelmRelease{ - TypeMeta: metav1.TypeMeta{ - Kind: helmv2.HelmReleaseKind, - APIVersion: helmv2.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: targetName.Name, Namespace: targetName.Namespace, diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go index fb91fd1d3ee..7a960931a7c 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go @@ -1236,10 +1236,6 @@ func compareActualVsExpectedGetInstalledPackageDetailResponse(t *testing.T, actu func newRelease(name string, namespace string, spec *helmv2.HelmReleaseSpec, status *helmv2.HelmReleaseStatus) helmv2.HelmRelease { helmRelease := helmv2.HelmRelease{ - TypeMeta: metav1.TypeMeta{ - Kind: helmv2.HelmReleaseKind, - APIVersion: helmv2.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: name, Generation: int64(1), diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go index d235add6a84..0f8d5df5d36 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go @@ -999,10 +999,6 @@ func newFluxHelmRepo( pollInterval = metav1.Duration{Duration: time.Duration(interval) * time.Second} } fluxRepo := &sourcev1.HelmRepository{ - TypeMeta: metav1.TypeMeta{ - Kind: sourcev1.HelmRepositoryKind, - APIVersion: sourcev1.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: targetName.Name, Namespace: targetName.Namespace, diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go index 1b613cd347d..6424e39d90b 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go @@ -285,6 +285,14 @@ func TestKindClusterAddPackageRepository(t *testing.T) { } } +func TestKindClusterAddOciPackageRepository(t *testing.T) { + _, _, err := checkEnv(t) + if err != nil { + t.Fatal(err) + } + // TODO +} + func TestKindClusterGetPackageRepositoryDetail(t *testing.T) { _, fluxPluginReposClient, err := checkEnv(t) if err != nil { @@ -412,10 +420,15 @@ func TestKindClusterGetPackageRepositoryDetail(t *testing.T) { var resp *corev1.GetPackageRepositoryDetailResponse for { + grpcCtx, cancel = context.WithTimeout(grpcCtx, defaultContextTimeout) + defer cancel() + resp, err = fluxPluginReposClient.GetPackageRepositoryDetail(grpcCtx, tc.request) + if got, want := status.Code(err), tc.expectedStatusCode; got != want { - t.Fatalf("got: %v, want: %v", err, want) + t.Fatalf("got: %v, want: %v, last repo detail: %v", err, want, resp) } + if tc.expectedStatusCode != codes.OK { // we are done return diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go index 343d2f7a29e..7b95724d7ca 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go @@ -2146,10 +2146,6 @@ func TestDeletePackageRepository(t *testing.T) { func newRepo(name string, namespace string, spec *sourcev1.HelmRepositorySpec, status *sourcev1.HelmRepositoryStatus) sourcev1.HelmRepository { helmRepository := sourcev1.HelmRepository{ - TypeMeta: metav1.TypeMeta{ - Kind: sourcev1.HelmRepositoryKind, - APIVersion: sourcev1.GroupVersion.String(), - }, ObjectMeta: metav1.ObjectMeta{ Name: name, Generation: 1, diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server.go index c497a65f047..96c26711093 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server.go @@ -521,7 +521,7 @@ func (s *Server) AddPackageRepository(ctx context.Context, request *corev1.AddPa name := types.NamespacedName{Name: request.Name, Namespace: request.Context.Namespace} if request.GetNamespaceScoped() { - return nil, status.Errorf(codes.Unimplemented, "Namespaced-scoped repositories are not supported") + return nil, status.Errorf(codes.Unimplemented, "namespaced-scoped repositories are not supported") } else if request.GetType() != "helm" { return nil, status.Errorf(codes.Unimplemented, "repository type [%s] not supported", request.GetType()) } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/Dockerfile b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/Dockerfile index a12adb1d4ee..f5eedbf4e92 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/Dockerfile +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/Dockerfile @@ -7,7 +7,7 @@ FROM nginx:1.21.3 COPY ./nginx.conf /etc/nginx/nginx.conf # only has a single user: foo, password: bar -COPY ./.htpasswd /etc/apache2/.htpasswd +COPY ./md5.htpasswd /etc/apache2/.htpasswd COPY ./charts/podinfo-index.yaml /usr/share/nginx/html/podinfo/index.yaml COPY ./charts/podinfo-6.0.0.tgz /usr/share/nginx/html/podinfo/ diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/bcrypt.htpasswd b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/bcrypt.htpasswd new file mode 100644 index 00000000000..34dc8e41c4c --- /dev/null +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/bcrypt.htpasswd @@ -0,0 +1 @@ +foo:$2y$05$I3zrLMxbfQryP9pUw7EauuXmNJVgBO5Oa4JqA8OPR7QqZ8HHuD5m6 diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/kind-cluster-setup.sh b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/kind-cluster-setup.sh index 7a0e3e4fb4a..9b98a1387d0 100755 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/kind-cluster-setup.sh +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/kind-cluster-setup.sh @@ -20,9 +20,22 @@ function deploy { kubectl set env deployment/fluxv2plugin-testdata-app DOMAIN=cluster --context kind-kubeapps kubectl expose deployment fluxv2plugin-testdata-app --port=80 --target-port=80 --name=fluxv2plugin-testdata-svc --context kind-kubeapps kubectl expose deployment fluxv2plugin-testdata-app --port=443 --target-port=443 --name=fluxv2plugin-testdata-ssl-svc --context kind-kubeapps + # set up OCI registry + # ref https://helm.sh/docs/topics/registries/ + # only has a single user: foo, password: bar + docker rm -f registry + docker run -dp 5000:5000 --restart=always --name registry \ + -v $(pwd)/bcrypt.htpasswd:/etc/docker/registry/auth.htpasswd \ + -e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" \ + registry + helm registry login -u foo localhost:5000 -p bar + helm push charts/podinfo-6.0.3.tgz oci://localhost:5000/helm-charts + helm show all oci://localhost:5000/helm-charts/podinfo | head -9 } function undeploy { + helm registry logout localhost:5000 + docker rm -f registry kubectl delete svc/fluxv2plugin-testdata-svc kubectl delete svc/fluxv2plugin-testdata-ssl-svc kubectl delete deployment fluxv2plugin-testdata-app --context kind-kubeapps diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/.htpasswd b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/md5.htpasswd similarity index 100% rename from cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/.htpasswd rename to cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/testdata/md5.htpasswd diff --git a/script/makefiles/deploy-dev.mk b/script/makefiles/deploy-dev.mk index 473a8d2f281..11b80b4561b 100644 --- a/script/makefiles/deploy-dev.mk +++ b/script/makefiles/deploy-dev.mk @@ -62,7 +62,7 @@ deploy-kapp-controller: # Add the flux controllers used for testing the kubeapps-apis integration. deploy-flux-controllers: - kubectl --kubeconfig=${CLUSTER_CONFIG} apply -f https://github.com/fluxcd/flux2/releases/download/v0.29.4/install.yaml + kubectl --kubeconfig=${CLUSTER_CONFIG} apply -f https://github.com/fluxcd/flux2/releases/download/v0.30.2/install.yaml reset-dev: helm --kubeconfig=${CLUSTER_CONFIG} -n kubeapps delete kubeapps || true