diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecd9cf8..5c76f82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,17 @@ jobs: container: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build container + uses: docker/build-push-action@v5 + + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -25,14 +35,60 @@ jobs: uses: docker/build-push-action@v5 with: context: . + load: true + tags: quay.io/observatorium/namespace-provisioner:test + - name: Setup Kubernetes + uses: helm/kind-action@v1.8.0 + with: + cluster_name: e2e + - name: Install namespace-provisioner + run: | + SERVER="$(kubectl config view -o jsonpath='{.clusters[?(@.name == "kind-e2e")].cluster.server}')" + kind load docker-image --name e2e quay.io/observatorium/namespace-provisioner:test + kubectl apply -f manifests/namespace-provisioner.yaml + cat </dev/null; do sleep 1; done + curl localhost:8080/api/v1/namespace -X POST -H "Authorization: bearer PASSWORD" > kubeconfig + kubectl --kubeconfig kubeconfig get pods + - name: Debug failure + if: failure() + run: | + kubectl get -A all + kubectl -n namespace-provisioner logs deploy/namespace-provisioner push: if: github.event_name != 'pull_request' needs: - container + - e2e runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -50,7 +106,6 @@ jobs: id: push uses: docker/build-push-action@v5 with: - context: . push: true platforms: linux/arm64, linux/amd64 tags: quay.io/observatorium/namespace-provisioner:latest, quay.io/observatorium/namespace-provisioner:${{ steps.sha.outputs.sha }} diff --git a/main.go b/main.go index f69ced7..e660aca 100644 --- a/main.go +++ b/main.go @@ -55,7 +55,7 @@ func Main() error { listen := flag.String("listen", ":8080", "The address at which to to serve the API.") listenInternal := flag.String("listen-internal", ":9090", "The address at which to to serve the internal API.") logLevel := flag.String("log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels)) - apiServer := flag.String("server", "kubernetes", "The address of the Kubernetes API server to use in generated kubeconfigs.") + apiServer := flag.String("server", "https://kubernetes", "The address of the Kubernetes API server to use in generated kubeconfigs.") clusterRole := flag.String("cluster-role", "", "The of a Kubernetes ClusterRole to bind to ServiceAccounts in created Namespaces.") prefix := flag.String("prefix", "np", "The prefix to use for Namespace names.") selector := flag.String("selector", "controller.observatorium.io=namespace-selector", "The label selector to use to select resources.") diff --git a/manifests/namespace-provisioner.yaml b/manifests/namespace-provisioner.yaml index 8810de6..841c112 100644 --- a/manifests/namespace-provisioner.yaml +++ b/manifests/namespace-provisioner.yaml @@ -18,6 +18,17 @@ stringData: token: PASSWORD --- apiVersion: v1 +kind: ConfigMap +metadata: + name: namespace-provisioner + namespace: namespace-provisioner + labels: + app.kubernetes.io/name: namespace-provisioner + app.kubernetes.io/part-of: namespace-provisioner +data: + server: https://kubernetes +--- +apiVersion: v1 kind: ServiceAccount metadata: name: namespace-provisioner @@ -94,6 +105,22 @@ subjects: namespace: namespace-provisioner name: namespace-provisioner --- +apiVersion: v1 +kind: Service +metadata: + name: namespace-provisioner + namespace: namespace-provisioner + labels: + app.kubernetes.io/name: namespace-provisioner + app.kubernetes.io/part-of: namespace-provisioner +spec: + selector: + app.kubernetes.io/name: namespace-provisioner + app.kubernetes.io/part-of: namespace-provisioner + ports: + - port: 8080 + targetPort: http +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -126,6 +153,7 @@ spec: - --listen=:8080 - --listen-internal=:9090 - --cluster-role=namespace-provisioner-grant + - --server=$(SERVER) - --token=$(TOKEN) env: - name: TOKEN @@ -133,6 +161,11 @@ spec: secretKeyRef: name: namespace-provisioner key: token + - name: SERVER + valueFrom: + configMapKeyRef: + name: namespace-provisioner + key: server ports: - containerPort: 8080 name: http diff --git a/server.go b/server.go index d5c5565..d6675a4 100644 --- a/server.go +++ b/server.go @@ -16,6 +16,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -172,17 +173,32 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } // Create Kubeconfig - se, err = h.c.CoreV1().Secrets(namespace).Get(r.Context(), np, metav1.GetOptions{}) - if err != nil { - msg := "no secret for service account" + var caCert, token []byte + if err := wait.ExponentialBackoff(wait.Backoff{ + Duration: time.Second, + Cap: time.Minute, + Steps: 10, + Factor: 2, + }, func() (bool, error) { + se, err = h.c.CoreV1().Secrets(namespace).Get(r.Context(), np, metav1.GetOptions{}) + if err != nil { + return false, err + } + var ok bool + if caCert, ok = se.Data["ca.crt"]; !ok { + return false, nil + } + if token, ok = se.Data["token"]; !ok { + return false, nil + } + return true, nil + }); err != nil { + msg := "failed to get secret for service account" level.Error(h.logger).Log("msg", msg, "err", err) http.Error(w, msg, http.StatusInternalServerError) return } - caCert := se.Data["ca.crt"] - token := se.Data["token"] - config := api.NewConfig() config.APIVersion = apiv1.SchemeGroupVersion.Version config.Kind = "Config"