diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4b186b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +*.md +bin +.idea \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcb2bb5..f8d165d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: Build on: push: - branches: [ "main" ] + branches: main pull_request: - branches: [ "main" ] + branches: main jobs: build: @@ -40,4 +40,31 @@ jobs: # skip-go-installation: true skip-pkg-cache: true skip-build-cache: true - # args: --timeout=15m \ No newline at end of file + # args: --timeout=15m + + image: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + outputs: local + build-args: VER=0.0.0-dev + + helm_check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Helm Template Check For Sanity + uses: igabaydulin/helm-check-action@0.1.4 + env: + CHART_LOCATION: ./charts/helm-dashboard + CHART_VALUES: ./charts/helm-dashboard/values.yaml diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml new file mode 100644 index 0000000..abbd349 --- /dev/null +++ b/.github/workflows/publish-chart.yaml @@ -0,0 +1,43 @@ +name: publish helm chart + +# for manual running in case we need to update the chart without releasing the dashboard app +on: + workflow_dispatch: + +env: + HELM_REP: helm-charts + GH_OWNER: komodorio + CHART_DIR: charts/helm-dashboard + +jobs: + publish_chart: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Bump versions + run: | + git config user.email komi@komodor.io + git config user.name komodor-bot + git fetch --tags + git checkout main + sh ./ci/bump-versions.sh + git add charts/helm-dashboard/Chart.yaml + git commit -m "Increment chart versions [skip ci]" || echo "Already up-to-date" + git push -f || echo "Nothing to push!" + env: + APP_VERSION: ${{ needs.pre_release.outputs.release_tag }} + - name: Push folder to helm-charts repository + uses: crykn/copy_folder_to_another_repo_action@v1.0.6 + env: + API_TOKEN_GITHUB: ${{ secrets.KOMI_WORKFLOW_TOKEN }} + with: + source_folder: "charts/helm-dashboard" + destination_repo: "komodorio/helm-charts" + destination_folder: "charts/helm-dashboard" + user_email: "komi@komodor.io" + user_name: "komodor-bot" + destination_branch: "master" + commit_msg: "feat(helm-dashboard): update chart" #important!! don't change this commit message unless you change the condition in pipeline.yml on helm-charts repo diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 883b032..b28d9e9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,7 +3,12 @@ name: release on: push: tags: - - "*" + - "v*" + +env: + HELM_REP: helm-charts + GH_OWNER: komodorio + CHART_DIR: charts/helm-dashboard jobs: pre_release: @@ -52,3 +57,68 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Test Binary Versions run: "dist/helm-dashboard_linux_amd64_v1/helm-dashboard --help" + + image: + runs-on: ubuntu-latest + needs: release + timeout-minutes: 60 + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Docker meta + uses: docker/metadata-action@v3 + id: meta + with: + images: komodorio/helm-dashboard + + - name: Login to DockerHub + uses: docker/login-action@v1 + if: github.event_name != 'pull_request' + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PASS }} + + - name: Build and push + uses: docker/build-push-action@v2 + if: github.event_name != 'pull_request' + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ needs.pre_release.outputs.release_tag }} + labels: ${{ steps.meta.outputs.labels }} + build-args: VER=${{ needs.pre_release.outputs.release_tag }} + + publish_chart: + runs-on: ubuntu-latest + need: image + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Bump versions + run: | + git config user.email komi@komodor.io + git config user.name komodor-bot + git fetch --tags + git checkout main + sh ./ci/bump-versions.sh + git add charts/helm-dashboard/Chart.yaml + git commit -m "Increment chart versions [skip ci]" || echo "Already up-to-date" + git push -f || echo "Nothing to push!" + env: + APP_VERSION: ${{ needs.pre_release.outputs.release_tag }} + - name: Push folder to helm-charts repository + uses: crykn/copy_folder_to_another_repo_action@v1.0.6 + env: + API_TOKEN_GITHUB: ${{ secrets.KOMI_WORKFLOW_TOKEN }} + with: + source_folder: "charts/helm-dashboard" + destination_repo: "komodorio/helm-charts" + destination_folder: "charts/helm-dashboard" + user_email: "komi@komodor.io" + user_name: "komodor-bot" + destination_branch: "master" + commit_msg: "feat(helm-dashboard): ${{ github.event.head_commit.message }}" #important!! don't change this commit message unless you change the condition in pipeline.yml on helm-charts repo diff --git a/.gitignore b/.gitignore index d02d94c..122c627 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ go.work /bin /.idea/ + +.DS_Store +.vscode/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..869608a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# Stage - builder +FROM golang as builder + +ARG VER + +ENV GOOS=linux +ENV GOARCH=amd64 +ENV CGO_ENABLED=0 +ENV VERSION=0.0.0 + +WORKDIR /build + +COPY go.mod ./ +COPY go.sum ./ +COPY main.go ./ +RUN go mod download + +ADD . src + +WORKDIR /build/src + +RUN make build + +# Stage - runner +FROM alpine/helm + +RUN curl -o /bin/kubectl -vf -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x /bin/kubectl && kubectl --help + +# Checkov scanner +RUN apk add --update --no-cache python3 +RUN python3 -m ensurepip +RUN pip3 install checkov + +# Trivy +RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.18.3 + +COPY --from=builder /build/src/bin/dashboard /bin/helm-dashboard + +ENTRYPOINT ["/bin/helm-dashboard", "--no-browser", "--bind=0.0.0.0"] + +# docker build . -t komodorio/helm-dashboard:0.0.0 && kind load docker-image komodorio/helm-dashboard:0.0.0 \ No newline at end of file diff --git a/README.md b/README.md index a02ab0f..33347d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A simplified way of working with Helm. ## What it Does? -The _Helm Dashboard_ plugin offers a UI-driven way to view the installed Helm charts, see their revision history and +_Helm Dashboard_ offers a UI-driven way to view the installed Helm charts, see their revision history and corresponding k8s resources. Also, you can perform simple actions like roll back to a revision or upgrade to newer version. @@ -22,9 +22,11 @@ Some of the key capabilities of the tool: - Integration with popular problem scanners - Easy switch between multiple clusters -## Installing +## Setup -To install it, simply run Helm command: +### Using Helm plugin manager + +To install the plugin, simply run Helm command: ```shell helm plugin install https://github.com/komodorio/helm-dashboard.git @@ -42,10 +44,6 @@ To uninstall, run: helm plugin uninstall dashboard ``` -> In case standard Helm plugin way did not work for you, you can just download the appropriate [release package](https://github.com/komodorio/helm-dashboard/releases) for your platform, unpack it and just run `dashboard` binary from it. - -## Running - To use the plugin, your machine needs to have working `helm` and also `kubectl` commands. Helm version 3.4.0+ is required. After installing, start the UI by running: @@ -62,11 +60,12 @@ You can see the list of available command-line flags by running `helm dashboard By default, the web server is only available locally. You can change that by specifying `HD_BIND` environment variable to the desired value. For example, `0.0.0.0` would bind to all IPv4 addresses or `[::0]` would be all IPv6 addresses. This can also be specified using flag `--bind `, for example `--bind=0.0.0.0` or `--bind 0.0.0.0`. + > Precedence order: flag `--bind=` > env `HD_BIND=` > default value `localhost` If your port 8080 is busy, you can specify a different port to use via `--port ` command-line flag. -If you need to limit the operations to a specific namespace, please use `--namespace=...` in your command-line. +If you need to limit the operations to a specific namespace, please use `--namespace=...` in your command-line. If you don't want browser tab to automatically open, add `--no-browser` flag in your command line. @@ -74,6 +73,14 @@ If you want to increase the logging verbosity and see all the debug info, use th > Disclaimer: For the sake of improving the project quality, there is user analytics collected by the tool. You can disable this collecting with `--no-analytics` option. The collection is done via DataDog RUM and Heap Analytics. Only the anonymous data is collected, no sensitive information is used. +### Deploying Helm Dashboard on Kubernetes + +The official helm chart is [available here](https://github.com/komodorio/helm-charts/blob/master/charts/helm-dashboard) + +### Manual Installation + +Download the appropriate [release package](https://github.com/komodorio/helm-dashboard/releases) for your platform, unpack it and just run `dashboard` binary from it. + ## Scanner Integrations Upon startup, Helm Dashboard detects the presence of [Trivy](https://github.com/aquasecurity/trivy) @@ -93,7 +100,6 @@ We have two main channels for supporting the Helm Dashboard users: [Slack community](https://join.slack.com/t/komodorkommunity/shared_invite/zt-1dm3cnkue-ov1Yh~_95teA35QNx5yuMg) for general conversations and [GitHub issues](https://github.com/komodorio/helm-dashboard/issues) for real bugs. - ## Local Dev Testing Prerequisites: `helm` and `kubectl` binaries installed and operational. diff --git a/charts/helm-dashboard/.helmignore b/charts/helm-dashboard/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/helm-dashboard/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/helm-dashboard/Chart.yaml b/charts/helm-dashboard/Chart.yaml new file mode 100644 index 0000000..9c2b47e --- /dev/null +++ b/charts/helm-dashboard/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +type: application + +name: helm-dashboard +description: A GUI Dashboard for Helm by Komodor +icon: "https://github.com/komodorio/helm-dashboard/blob/main/pkg/dashboard/static/logo.png" + +version: 0.1.0 +appVersion: "0.0.0" diff --git a/charts/helm-dashboard/README.md b/charts/helm-dashboard/README.md new file mode 100644 index 0000000..3d64d45 --- /dev/null +++ b/charts/helm-dashboard/README.md @@ -0,0 +1,76 @@ +# Helm Dashboard + +## TL;DR; + +```bash +helm repo add komodorio https://helm-charts.komodor.io +helm repo update +helm upgrade --install my-release komodorio/helm-dashboard +``` + +## Introduction + +This chart bootstraps a Helm Dashboard deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Kubernetes 1.16+ +- Helm 3+ + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +helm install my-release . +``` + +The command deploys Helm Dashboard on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +The following table lists the configurable parameters of the chart and their default values. + +| Parameter | Description | Default | +| ------------------------------------ | ---------------------------------------------------------------------------------------------- | ------------------------------------ | +| `image.repository` | Image registry/name | `docker.io/komodorio/helm-dashboard` | +| `image.tag` | Image tag | | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `replicaCount` | Number of dashboard Pods to run | `1` | +| `dashboard.allowWriteActions` | Enables write actions. Allow modifying, deleting and creating charts and kubernetes resources. | `false` | +| `resources.requests.cpu` | CPU resource requests | `200m` | +| `resources.limits.cpu` | CPU resource limits | `1` | +| `resources.requests.memory` | Memory resource requests | `256Mi` | +| `resources.limits.memory` | Memory resource limits | `1Gi` | +| `service.type ` | Kubernetes service type | `ClusterIP` | +| `service.port ` | Kubernetes service port | `8080` | +| `serviceAccount.create` | Creates a service account | `true` | +| `serviceAccount.name` | Optional name for the service account | `{RELEASE_FULLNAME}` | +| `nodeSelector` | Node labels for pod assignment | | +| `affinity` | Affinity settings for pod assignment | | +| `tolerations` | Tolerations for pod assignment | | +| `dashboard.persistence.enabled` | Enable helm data persistene using PVC | `true` | +| `dashboard.persistence.accessModes` | Persistent Volume access modes | `["ReadWriteOnce"]` | +| `dashboard.persistence.storageClass` | Persistent Volume storage class | `""` | +| `dashboard.persistence.size` | Persistent Volume size | `100M` | +| `dashboard.persistence.hostPath` | Set path in case you want to use local host path volumes (not recommended in production) | `""` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. + +```bash +helm upgrade --install my-release komodorio/helm-dashboard --set dashboard.allowWriteActions=true --set service.port=9090 +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) diff --git a/charts/helm-dashboard/templates/NOTES.txt b/charts/helm-dashboard/templates/NOTES.txt new file mode 100644 index 0000000..1b47ebc --- /dev/null +++ b/charts/helm-dashboard/templates/NOTES.txt @@ -0,0 +1,16 @@ +Thank you for installing Helm Dashboard. +Helm Dashboard can be accessed: + * Within your cluster, at the following DNS name at port {{ .Values.service.port }}: + + {{ template "helm-dashboard.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local + + * From outside the cluster, run these commands in the same shell: + + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "helm-dashboard.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT + +Visit our repo at: +https://github.com/komodorio/helm-dashboard + diff --git a/charts/helm-dashboard/templates/_helpers.tpl b/charts/helm-dashboard/templates/_helpers.tpl new file mode 100644 index 0000000..d7188c3 --- /dev/null +++ b/charts/helm-dashboard/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "helm-dashboard.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "helm-dashboard.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "helm-dashboard.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "helm-dashboard.labels" -}} +helm.sh/chart: {{ include "helm-dashboard.chart" . }} +{{ include "helm-dashboard.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "helm-dashboard.selectorLabels" -}} +app.kubernetes.io/name: {{ include "helm-dashboard.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "helm-dashboard.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "helm-dashboard.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/helm-dashboard/templates/deployment.yaml b/charts/helm-dashboard/templates/deployment.yaml new file mode 100644 index 0000000..8a86623 --- /dev/null +++ b/charts/helm-dashboard/templates/deployment.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "helm-dashboard.fullname" . }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "helm-dashboard.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "helm-dashboard.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "helm-dashboard.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: HELM_CACHE_HOME + value: /opt/dashboard/helm/cache + - name: HELM_CONFIG_HOME + value: /opt/dashboard/helm/config + - name: HELM_DATA_HOME + value: /opt/dashboard/helm/data + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /status + port: http + readinessProbe: + httpGet: + path: /status + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /opt/dashboard/helm + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: data + {{- if .Values.dashboard.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "helm-dashboard.fullname" . }} + {{- else }} + emptyDir: {} + {{- end }} diff --git a/charts/helm-dashboard/templates/ingress.yaml b/charts/helm-dashboard/templates/ingress.yaml new file mode 100644 index 0000000..4ff1d98 --- /dev/null +++ b/charts/helm-dashboard/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "helm-dashboard.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/helm-dashboard/templates/pvc.yaml b/charts/helm-dashboard/templates/pvc.yaml new file mode 100644 index 0000000..7ab8b98 --- /dev/null +++ b/charts/helm-dashboard/templates/pvc.yaml @@ -0,0 +1,48 @@ +{{- if .Values.dashboard.persistence.enabled -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "helm-dashboard.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} + {{- with .Values.dashboard.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.dashboard.persistence.hostPath }} + storageClassName: "" + {{- end }} + accessModes: + {{- if not (empty .Values.dashboard.persistence.accessModes) }} + {{- range .Values.dashboard.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.dashboard.persistence.size | quote }} +{{- end }} + +--- +{{- if and .Values.dashboard.persistence.enabled .Values.dashboard.persistence.hostPath -}} +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ include "helm-dashboard.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} + {{- with .Values.dashboard.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.dashboard.persistence.accessMode | quote }} + capacity: + storage: {{ .Values.dashboard.persistence.size | quote }} + hostPath: + path: {{ .Values.dashboard.persistence.hostPath | quote }} +{{- end -}} \ No newline at end of file diff --git a/charts/helm-dashboard/templates/service.yaml b/charts/helm-dashboard/templates/service.yaml new file mode 100644 index 0000000..59b039f --- /dev/null +++ b/charts/helm-dashboard/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "helm-dashboard.fullname" . }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "helm-dashboard.selectorLabels" . | nindent 4 }} diff --git a/charts/helm-dashboard/templates/serviceaccount.yaml b/charts/helm-dashboard/templates/serviceaccount.yaml new file mode 100644 index 0000000..35cd4c6 --- /dev/null +++ b/charts/helm-dashboard/templates/serviceaccount.yaml @@ -0,0 +1,40 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "helm-dashboard.serviceAccountName" . }} + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "helm-dashboard.serviceAccountName" . }} +rules: + - apiGroups: ["", "apps", "rbac.authorization.k8s.io", "rbac", "batch", "extensions", "networking.k8s.io", "storage.k8s.io"] + resources: ["*"] + verbs: ["get", "list", "watch"] + {{- if .Values.dashboard.allowWriteActions }} + - apiGroups: ["", "apps", "rbac.authorization.k8s.io", "rbac", "batch", "extensions", "networking.k8s.io", "storage.k8s.io"] + resources: ["*"] + verbs: ["get", "list", "watch", "create", "delete", "patch", "update"] + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "helm-dashboard.serviceAccountName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "helm-dashboard.serviceAccountName" . }} +subjects: + - kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: {{ include "helm-dashboard.serviceAccountName" . }} \ No newline at end of file diff --git a/charts/helm-dashboard/templates/tests/test-connection.yaml b/charts/helm-dashboard/templates/tests/test-connection.yaml new file mode 100644 index 0000000..08efbf5 --- /dev/null +++ b/charts/helm-dashboard/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "helm-dashboard.fullname" . }}-test-connection" + labels: + {{- include "helm-dashboard.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['--timeout=5', '{{ include "helm-dashboard.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/helm-dashboard/values.yaml b/charts/helm-dashboard/values.yaml new file mode 100644 index 0000000..df768b3 --- /dev/null +++ b/charts/helm-dashboard/values.yaml @@ -0,0 +1,95 @@ +replicaCount: 1 + +image: + repository: komodorio/helm-dashboard + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 1 + memory: 1Gi + +dashboard: + allowWriteActions: false + persistence: + enabled: true + + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "" + + ## Helm Dashboard Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## Helm Dashboard Persistent Volume labels + ## + labels: {} + + ## Helm Dashboard Persistent Volume annotations + ## + annotations: {} + + ## Set path in case you want to use local host path volumes (not recommended in production) + ## + hostPath: "" + + ## Helm Dashboard data Persistent Volume size + ## + size: 100M + +podAnnotations: {} + +podSecurityContext: {} + +securityContext: {} + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/ci/bump-versions.sh b/ci/bump-versions.sh new file mode 100755 index 0000000..f41423f --- /dev/null +++ b/ci/bump-versions.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +WORKING_DIRECTORY="$PWD" + +[ -z "$HELM_CHARTS_SOURCE" ] && HELM_CHARTS_SOURCE="$WORKING_DIRECTORY/charts/helm-dashboard" + +[ -z "$APP_VERSION" ] && { + APP_VERSION=$(cat ${HELM_CHARTS_SOURCE}/Chart.yaml | grep 'appVersion:' | awk -F'"' '{print $2}') +} + +sed -i -e "s/appVersion.*/appVersion: \"${APP_VERSION}\" /g" ${HELM_CHARTS_SOURCE}/Chart.yaml +CURRENT_VERSION=$(cat ${HELM_CHARTS_SOURCE}/Chart.yaml | grep 'version:' | awk '{print $2}') +NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') +sed -i -e "s/${CURRENT_VERSION}/${NEW_VERSION}/g" ${HELM_CHARTS_SOURCE}/Chart.yaml diff --git a/main.go b/main.go index 8904219..f26d483 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ func main() { opts.BindHost = host } + opts.Verbose = opts.Verbose || os.Getenv("DEBUG") != "" setupLogging(opts.Verbose) server := dashboard.Server{ diff --git a/pkg/dashboard/server.go b/pkg/dashboard/server.go index 8319583..4ba68ab 100644 --- a/pkg/dashboard/server.go +++ b/pkg/dashboard/server.go @@ -29,6 +29,11 @@ func (s Server) StartServer() (string, utils.ControlChan) { data := subproc.DataLayer{ Namespace: s.Namespace, Cache: subproc.NewCache(), + StatusInfo: &subproc.StatusInfo{ + CurVer: s.Version, + Analytics: false, + LimitedToNamespace: s.Namespace, + }, } err := data.CheckConnectivity() if err != nil { @@ -36,12 +41,7 @@ func (s Server) StartServer() (string, utils.ControlChan) { os.Exit(1) // TODO: propagate error instead? } isDevModeWithAnalytics := os.Getenv("HD_DEV_ANALYTICS") == "true" - enableAnalytics := (!s.NoTracking && s.Version != "0.0.0") || isDevModeWithAnalytics - data.StatusInfo = &subproc.StatusInfo{ - CurVer: s.Version, - Analytics: enableAnalytics, - LimitedToNamespace: s.Namespace, - } + data.StatusInfo.Analytics = (!s.NoTracking && s.Version != "0.0.0") || isDevModeWithAnalytics go checkUpgrade(data.StatusInfo) discoverScanners(&data) @@ -113,7 +113,7 @@ func discoverScanners(data *subproc.DataLayer) { } } -func checkUpgrade(d *subproc.StatusInfo) { +func checkUpgrade(d *subproc.StatusInfo) { // TODO: check it once an hour url := "https://api.github.com/repos/komodorio/helm-dashboard/releases/latest" type GHRelease struct { Name string `json:"name"` diff --git a/pkg/dashboard/static/details-view.js b/pkg/dashboard/static/details-view.js index 36c3532..82e1c30 100644 --- a/pkg/dashboard/static/details-view.js +++ b/pkg/dashboard/static/details-view.js @@ -13,7 +13,11 @@ function revisionClicked(namespace, name, self) { $("#sectionDetails .rev-tags .rev-chart").text(elm.chart) $("#sectionDetails .rev-tags .rev-app").text(elm.app_version) $("#sectionDetails .rev-tags .rev-ns").text(getHashParam("namespace")) - $("#sectionDetails .rev-tags .rev-cluster").text(getHashParam("context")) + if (getHashParam("context")) { + $("#sectionDetails .rev-tags .rev-cluster").text(getHashParam("context")) + } else { + $("#sectionDetails .rev-tags .rev-cluster").parent().hide() // TODO: makes UI jumpy, change to showing + } $("#revDescr").text(elm.description).removeClass("text-danger") if (elm.status === "failed") { @@ -158,7 +162,9 @@ function showResources(namespace, chart, revision) { } resBody.empty(); - data = data.sort(function(a, b){return interestingResources.indexOf(a.kind.toUpperCase()) - interestingResources.indexOf(b.kind.toUpperCase())}).reverse(); + data = data.sort(function (a, b) { + return interestingResources.indexOf(a.kind.toUpperCase()) - interestingResources.indexOf(b.kind.toUpperCase()) + }).reverse(); for (let i = 0; i < data.length; i++) { const res = data[i] const resBlock = $(` diff --git a/pkg/dashboard/static/index.html b/pkg/dashboard/static/index.html index 5535d32..e950455 100644 --- a/pkg/dashboard/static/index.html +++ b/pkg/dashboard/static/index.html @@ -75,24 +75,6 @@
- - - @@ -144,9 +126,11 @@
-

Clusters

-
    -
+
+

Clusters

+
    +
+

Namespaces

@@ -165,7 +149,8 @@

Installed ChartsInstalled Charts ()

- +
@@ -182,7 +167,8 @@ - @@ -430,6 +416,21 @@
+ + + + diff --git a/pkg/dashboard/static/repo.js b/pkg/dashboard/static/repo.js index a3340f4..52f0b00 100644 --- a/pkg/dashboard/static/repo.js +++ b/pkg/dashboard/static/repo.js @@ -132,7 +132,8 @@ function repoChartClicked() { window.location.reload() } else { const contexts = $("body").data("contexts") - const contextNamespace = contexts.filter(obj => {return obj.Name === getHashParam("context")})[0].Namespace + const ctxFiltered = contexts.filter(obj => {return obj.Name === getHashParam("context")}); + const contextNamespace = ctxFiltered.length?ctxFiltered[0].Namespace:"" popUpUpgrade(elm, contextNamespace) } } \ No newline at end of file diff --git a/pkg/dashboard/static/scripts.js b/pkg/dashboard/static/scripts.js index a18df58..1d00055 100644 --- a/pkg/dashboard/static/scripts.js +++ b/pkg/dashboard/static/scripts.js @@ -3,12 +3,18 @@ $(function () { $.getJSON("/status").fail(function (xhr) { // maybe /options call in the future reportError("Failed to get tool version", xhr) }).done(function (data) { + $("body").data("status", data) fillToolVersion(data) limNS = data.LimitedToNamespace if (limNS) { $("#limitNamespace").show().find("span").text(limNS) } fillClusters(limNS) + + if (data.ClusterMode) { + $(".bi-power").hide() + $("#clusterFilterBlock").hide() + } }) $.getJSON("/api/scanners").fail(function (xhr) { @@ -47,7 +53,7 @@ function fillClusters(limNS) { const context = getHashParam("context") data.sort((a, b) => (getCleanClusterName(a.Name) > getCleanClusterName(b.Name)) - (getCleanClusterName(a.Name) < getCleanClusterName(b.Name))) fillClusterList(data, context); - sendStats('contexts', {'status': 'success', length:data.length}); + sendStats('contexts', {'status': 'success', length: data.length}); $.getJSON("/api/kube/namespaces").fail(function (xhr) { reportError("Failed to get namespaces", xhr) }).done(function (res) { @@ -220,7 +226,7 @@ function fillNamespaceList(data) { if (filteredNamespace.split('+').includes(elm.metadata.name)) { opt.find("input").prop("checked", true) } - } else if (curContextNamespaces && curContextNamespaces[0].Namespace === elm.metadata.name) { + } else if (curContextNamespaces.length && curContextNamespaces[0].Namespace === elm.metadata.name) { opt.find("input").prop("checked", true) setFilteredNamespaces([elm.metadata.name]) } diff --git a/pkg/dashboard/subproc/data.go b/pkg/dashboard/subproc/data.go index 2d9a74d..f319ea0 100644 --- a/pkg/dashboard/subproc/data.go +++ b/pkg/dashboard/subproc/data.go @@ -35,6 +35,7 @@ type StatusInfo struct { Analytics bool LimitedToNamespace string CacheHitRatio float64 + ClusterMode bool } func (d *DataLayer) runCommand(cmd ...string) (string, error) { @@ -74,10 +75,17 @@ func (d *DataLayer) CheckConnectivity() error { } if len(contexts) < 1 { - return errors.New("did not find any kubectl contexts configured") + log.Debugf("Did not find any contexts, will try checking k8s") + _, err := d.runCommandKubectl("get", "pods") + if err != nil { + log.Debugf("The error were: %s", err) + return errors.New("did not find any kubectl contexts configured") + } + log.Infof("Assuming k8s environment") + d.StatusInfo.ClusterMode = true } - _, err = d.runCommandHelm("--help") // no point in doing is, since the default context may be invalid + _, err = d.runCommandHelm("--help") if err != nil { return err } diff --git a/pkg/dashboard/subproc/kubectl.go b/pkg/dashboard/subproc/kubectl.go index 6773208..ad7aa8f 100644 --- a/pkg/dashboard/subproc/kubectl.go +++ b/pkg/dashboard/subproc/kubectl.go @@ -3,6 +3,7 @@ package subproc import ( "encoding/json" v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "os" "regexp" "sort" "strings" @@ -31,6 +32,12 @@ type KubeContext struct { } func (d *DataLayer) ListContexts() (res []KubeContext, err error) { + res = []KubeContext{} + + if os.Getenv("HD_CLUSTER_MODE") != "" { + return res, nil + } + out, err := d.runCommandKubectl("config", "get-contexts") if err != nil { return nil, err diff --git a/pkg/dashboard/subproc/repos.go b/pkg/dashboard/subproc/repos.go index bedc933..b18cbf5 100644 --- a/pkg/dashboard/subproc/repos.go +++ b/pkg/dashboard/subproc/repos.go @@ -12,6 +12,7 @@ import ( func (d *DataLayer) ChartRepoList() (res []RepositoryElement, err error) { out, err := d.Cache.String(CacheKeyAllRepos, nil, func() (string, error) { + // TODO: do a bg check, if the state is changed - do reset some caches return d.runCommandHelm("repo", "list", "--output", "json") }) if err != nil { diff --git a/scripts/install_plugin.sh b/scripts/install_plugin.sh index 90b4115..a13e74f 100755 --- a/scripts/install_plugin.sh +++ b/scripts/install_plugin.sh @@ -11,6 +11,7 @@ if [ -n "${HELM_PUSH_PLUGIN_NO_INSTALL_HOOK}" ]; then fi version="$(cat plugin.yaml | grep "version" | cut -d '"' -f 2)" +# TODO: if no version provided, get it from https://api.github.com/repos/komodorio/helm-dashboard/releases/latest echo "Downloading and installing ${name} v${version} ..." url=""