From 1393e827249c31ce83fe3370929fd27ce98099f1 Mon Sep 17 00:00:00 2001 From: Andreas Bleuler Date: Sun, 19 Sep 2021 22:54:46 +0200 Subject: [PATCH 1/4] feat: publish plain manifests, auto-generate docs from CRD --- .github/workflows/acceptance-tests.yaml | 13 +- .github/workflows/generate-crd-docs.yaml | 51 ++ .github/workflows/publish-chart.yaml | 4 +- README.md | 32 +- docs/crd.md | 661 +++++++++++++++++++++++ helm-chart/amalthea/Chart.yaml | 4 +- helm-chart/amalthea/templates/crd.yaml | 39 +- helm-chart/amalthea/values.yaml | 2 +- manifests/config.yaml | 13 + manifests/crd.yaml | 233 ++++++++ manifests/deployment.yaml | 78 +++ manifests/kustomization.yaml | 9 + manifests/networkpolicies.yaml | 62 +++ manifests/rbac/rbac-namespaced.yaml | 66 +++ manifests/rbac/serviceaccount.yaml | 11 + utils/render-chart-manifests.py | 64 +++ 16 files changed, 1317 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/generate-crd-docs.yaml create mode 100644 docs/crd.md create mode 100644 manifests/config.yaml create mode 100644 manifests/crd.yaml create mode 100644 manifests/deployment.yaml create mode 100644 manifests/kustomization.yaml create mode 100644 manifests/networkpolicies.yaml create mode 100644 manifests/rbac/rbac-namespaced.yaml create mode 100644 manifests/rbac/serviceaccount.yaml create mode 100644 utils/render-chart-manifests.py diff --git a/.github/workflows/acceptance-tests.yaml b/.github/workflows/acceptance-tests.yaml index aa7dbf46..193e2858 100644 --- a/.github/workflows/acceptance-tests.yaml +++ b/.github/workflows/acceptance-tests.yaml @@ -3,11 +3,12 @@ name: Acceptance Tests on: pull_request: types: - - opened - - edited - - synchronize - - reopened - - closed + - opened + - edited + - synchronize + - reopened + - closed + workflow_dispatch: jobs: acceptance-tests-images: @@ -85,7 +86,7 @@ jobs: pipenv install --dev cd helm-chart pipenv run chartpress - kind load docker-image $(pipenv run chartpress --list-images) + kind load docker-image $(pipenv run chartpress --list-images) - name: Install Amalthea and Ingress-Nginx run: | VERSION=$(curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/stable.txt) diff --git a/.github/workflows/generate-crd-docs.yaml b/.github/workflows/generate-crd-docs.yaml new file mode 100644 index 00000000..164c41fe --- /dev/null +++ b/.github/workflows/generate-crd-docs.yaml @@ -0,0 +1,51 @@ +name: Autogenerate the manifests and documentation for the CRD + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + generate-crd-docs: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Render chart into manifests + run: | + python -m pip install --upgrade pip pipenv + pipenv install --dev + cd helm-chart/ + pipenv run chartpress --reset + cd ../ + pipenv run python utils/render-chart-manifests.py + - name: Generate CRD documentation + uses: SwissDataScienceCenter/renku-actions/generate-crd-docs@3d6f5abf79b61d969b0d958e42dabb4e9f15392c + env: + RESOURCES: ./manifests/crd.yaml + OUTPUT: ./docs/crd.md + - name: Create pull request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: "chore: update rendered manifests and CRD docs" + delete-branch: true + title: Update rendered manifests and CRD docs + push-image: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Push the image for the current commit + env: + DOCKER_USERNAME: ${{ secrets.RENKU_DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.RENKU_DOCKER_PASSWORD }} + run: | + echo ${DOCKER_PASSWORD} | docker login -u ${DOCKER_USERNAME} --password-stdin + python -m pip install --upgrade pip pipenv + pipenv install --dev + cd helm-chart/ + pipenv run chartpress --tag $(git rev-parse HEAD) --push + pipenv run chartpress --tag latest --push diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index 543c46e7..cbcb06cd 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -1,6 +1,8 @@ name: publish-chart -on: [push] +on: + push: + workflow_dispatch: jobs: publish-chart: diff --git a/README.md b/README.md index 1a365038..914891fd 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,26 @@ This project defines a custom `JupyterServer` for Kubernetes and implements a Kubernetes operator which controls the lifecycle of custom `JupyterServer` objects. +## Installation + +The recommended way of installing Amalthea is through its **helm chart**: + +```bash +helm repo add renku https://swissdatasciencecenter.github.io/helm-charts +helm install amalthea renku/amalthea +``` + +For people who prefer to use plain manifests in combination with tools like +`kustomize`, we provide the rendered templates in the +[manifests directory](https://github.com/SwissDataScienceCenter/amalthea/tree/main/manifests), +together with a basic `kustomization.yaml` file which can serve as a base for +overlays. A basic install equivalent to a helm install using the default values +can be achieved through + +```bash +kubectl apply -k github.com/SwissDataScienceCenter/amalthea/manifests/ +``` + ## Example Once Amalthea is installed in a cluster through the helm chart, deploying a @@ -86,7 +106,7 @@ change K8s resources which are created as part of the custom resource object. The main use case of Amalthea is to provide a layer on top of which developers can build kubernetes-native applications that allow their users to spin-up and -manage Jupyter servers. We do not see Amalthea as a standalone tool +manage Jupyter servers. We do not see Amalthea as a standalone tool used by end users, as creating Jupyter servers with Amalthea requires access to the Kubernetes API. @@ -106,7 +126,7 @@ The intended scope of Amalthea is much smaller than that. Specifically: - Amalthea itself is stateless. All state is stored as Kubernetes objects in etcd. - Amalthea uses the Kubernetes-native ingress- and service concepts for dynamically - adding and removing routes as Jupyter servers come and go, instead of relying on + adding and removing routes as Jupyter servers come and go, instead of relying on an additoinal proxy for routing. ## What's in the repo @@ -160,6 +180,14 @@ For Amalthea development you will need python 3, [kubectl](https://Kubernetes.io/docs/tasks/tools/#kubectl) and [helm](https://helm.sh/docs/intro/install/). +After cloning the repo, you can install the necessary python dependencies and +activate a custom project git hook by running + +```bash +pipenv install --dev +git config core.hooksPath .githooks +``` + ### Kind The easiest way to set up a cluster that will let you develop and test a feature diff --git a/docs/crd.md b/docs/crd.md new file mode 100644 index 00000000..bd3e3068 --- /dev/null +++ b/docs/crd.md @@ -0,0 +1,661 @@ +# API Reference + +Packages: + +- [amalthea.dev/v1alpha1](#amaltheadevv1alpha1) + +# amalthea.dev/v1alpha1 + +Resource Types: + +- [JupyterServer](#jupyterserver) + + + + +## JupyterServer +[↩ Parent](#amaltheadevv1alpha1 ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringamalthea.dev/v1alpha1true
kindstringJupyterServertrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject +
+
false
statusobject +
+
+ Default: map[children:map[] mainPod:map[]]
+
false
+ + +### JupyterServer.spec +[↩ Parent](#jupyterserver) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
authobject +
+
+ Default: map[]
+
false
cullingobject + Options about culling idle servers
+
+ Default: map[]
+
false
jupyterServerobject +
+
+ Default: map[]
+
false
patches[]object + Patches to be applied. Currently only json patches and json merge +patches are supported. +
+
+ Default: []
+
false
routingobject +
+
+ Default: map[]
+
false
storageobject +
+
+ Default: map[]
+
false
+ + +### JupyterServer.spec.auth +[↩ Parent](#jupyterserverspec) + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
oidcobject +
+
+ Default: map[]
+
false
tokenstring + A token that will be passed to the `--ServerApp.token` option when running +the Jupyter server and needed when first accessing the Jupyter server. The +options are: +- By leaving this field empty, a token will be autogenerated and added under the + key `ServerApp.token` to the secret which is created as a child of the custom + resource object. +- Setting the token to an empty string "" runs the Jupyter server container + itself without any authentication. This is recommended when enabling OIDC + as authentication and authorization are then handled by the dedicated plugins. +- Set an actual value here. Note that this string will be stored in clear text + as part of the custom resource object. This option is mostly useful for dev + purposes. +
+
false
+ + +### JupyterServer.spec.auth.oidc +[↩ Parent](#jupyterserverspecauth) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
authorizedEmails[]string + List of users (identified by Email address read from the "email" OIDC claim) +which are allowed to access this Jupyter session. This list is stored as a file +and passed to the `--authenticated-emails-file` option (see +https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#command-line-options). +
+
+ Default: []
+
false
authorizedGroups[]string + List of groups of users (read from the "groups" OIDC claim) which are allowed +to access this Jupyter session. This list passed to the `--allowed-group` option (see +https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#command-line-options). +
+
+ Default: []
+
false
clientIdstring +
+
false
clientSecretobject + The client secret of the application registered with the OIDC provider. This +secret can be given here explicitly as string or through a reference to an +existing secret. Using the secret reference is the preferred option because it +avoids storing the secret in cleartext on the custom resource specification. +
+
false
enabledboolean +
+
+ Default: false
+
false
issuerUrlstring +
+
false
+ + +### JupyterServer.spec.auth.oidc.clientSecret +[↩ Parent](#jupyterserverspecauthoidc) + + + +The client secret of the application registered with the OIDC provider. This +secret can be given here explicitly as string or through a reference to an +existing secret. Using the secret reference is the preferred option because it +avoids storing the secret in cleartext on the custom resource specification. + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
secretKeyRefobject + A regular reference to the key/secret which holds the client secret of the +application registered with the OIDC provider. Note that the secret has to +be in the same namespace in which the custom resource object is going to be +created. +
+
false
valuestring +
+
false
+ + +### JupyterServer.spec.auth.oidc.clientSecret.secretKeyRef +[↩ Parent](#jupyterserverspecauthoidcclientsecret) + + + +A regular reference to the key/secret which holds the client secret of the +application registered with the OIDC provider. Note that the secret has to +be in the same namespace in which the custom resource object is going to be +created. + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring +
+
false
namestring +
+
false
+ + +### JupyterServer.spec.culling +[↩ Parent](#jupyterserverspec) + + + +Options about culling idle servers + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
idleSecondsThresholdinteger + How long should a server be idle for before it is culled. A value of zero +indicates that the server should never be culled. +
+
+ Default: 0
+ Minimum: 0
+
false
+ + +### JupyterServer.spec.jupyterServer +[↩ Parent](#jupyterserverspec) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
defaultUrlstring +
+
+ Default: /lab
+
false
imagestring +
+
+ Default: jupyter/minimal-notebook:latest
+
false
resourcesobject + Regular resource requests, will be set on the main notebook +container. +
+
+ Default: map[]
+
false
rootDirstring + The absolute path to the root/notebook directory for jupyterlab. Should +lead to a subdirectory of or match the path at storage.pvc.mountPath +
+
+ Default: /home/jovyan/work
+
false
+ + +### JupyterServer.spec.patches[index] +[↩ Parent](#jupyterserverspec) + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
patchJSON +
+
false
typeenum +
+
+ Enum: application/json-patch+json, application/merge-patch+json
+
false
+ + +### JupyterServer.spec.routing +[↩ Parent](#jupyterserverspec) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
hoststring +
+
false
ingressAnnotationsobject +
+
+ Default: map[]
+
false
pathstring +
+
+ Default: /
+
false
tlsobject +
+
+ Default: map[]
+
false
+ + +### JupyterServer.spec.routing.tls +[↩ Parent](#jupyterserverspecrouting) + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
enabledboolean +
+
+ Default: false
+
false
secretNamestring +
+
false
+ + +### JupyterServer.spec.storage +[↩ Parent](#jupyterserverspec) + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
pvcobject +
+
+ Default: map[]
+
false
sizestring + Size of the PVC or sizeLimit of the emptyDir volume which +backs the session respectively. +
+
+ Default: 100Mi
+
false
+ + +### JupyterServer.spec.storage.pvc +[↩ Parent](#jupyterserverspecstorage) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
enabledboolean + Wether a PVC should be used to back the session. Defaults +to 'false' in which case an emptyDir volume will be used. +
+
+ Default: false
+
false
mountPathstring + The absolute path to the location where the PVC should be +mounted in the user session pod. +
+
+ Default: /home/jovyan/work
+
false
storageClassNamestring + Storage class to be used for the PVC. If left empty, the +default storage class defined for the cluster will be used. +
+
false
\ No newline at end of file diff --git a/helm-chart/amalthea/Chart.yaml b/helm-chart/amalthea/Chart.yaml index b67f720a..0e5d3774 100644 --- a/helm-chart/amalthea/Chart.yaml +++ b/helm-chart/amalthea/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.0.1-rc1 +version: 0.0.1-set.by.chartpress # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -23,4 +23,4 @@ version: 0.0.1-rc1 # that the appVersion is used as image tag if that is not specified in the values file. For # the time being appVersion should match the chart version for each released version of this # chart. -appVersion: 0.0.1 +appVersion: latest diff --git a/helm-chart/amalthea/templates/crd.yaml b/helm-chart/amalthea/templates/crd.yaml index bbee1983..d8016786 100644 --- a/helm-chart/amalthea/templates/crd.yaml +++ b/helm-chart/amalthea/templates/crd.yaml @@ -58,13 +58,14 @@ spec: default: /lab rootDir: type: string - description: "The absolute path to the root/notebook directory for jupyterlab. Should lead to a subdirectory of or match the path at storage.pvc.mountPath" + description: | + The absolute path to the root/notebook directory for jupyterlab. Should + lead to a subdirectory of or match the path at storage.pvc.mountPath default: /home/jovyan/work - - # TODO: Automatically fetch and inline schema - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#resourcerequirements-v1-core resources: - description: "Regular resource requests, will be set on the main notebook container." + description: | + Regular resource requests, will be set on the main notebook + container. type: object x-kubernetes-preserve-unknown-fields: true default: {} @@ -97,7 +98,9 @@ spec: default: {} properties: size: - description: "Size of the PVC or sizeLimit of the emptyDir volume which backs the session respectively." + description: | + Size of the PVC or sizeLimit of the emptyDir volume which + backs the session respectively. type: string default: 100Mi pvc: @@ -105,15 +108,21 @@ spec: default: {} properties: enabled: - description: "Wether a PVC should be used to back the session. Defaults to 'false' in which case an emptyDir volume will be used." + description: | + Wether a PVC should be used to back the session. Defaults + to 'false' in which case an emptyDir volume will be used. type: boolean default: false storageClassName: type: string - description: "Storage class to be used for the PVC. If left empty, the default storage class defined for the cluster will be used." + description: | + Storage class to be used for the PVC. If left empty, the + default storage class defined for the cluster will be used. mountPath: type: string - description: "The absolute path to the location where the PVC should be mounted in the user session pod." + description: | + The absolute path to the location where the PVC should be + mounted in the user session pod. default: /home/jovyan/work auth: @@ -194,7 +203,9 @@ spec: type: string patches: - description: "Patches to be applied. Currently only json patches and json merge patches are supported." + description: | + Patches to be applied. Currently only json patches and json merge + patches are supported. type: array default: [] items: @@ -207,14 +218,16 @@ spec: - application/merge-patch+json patch: x-kubernetes-preserve-unknown-fields: true - + culling: - description: "Options about culling idle servers." + description: Options about culling idle servers type: object default: {} properties: idleSecondsThreshold: - description: "How long should a server be idle for before it is culled. A value of zero indicates that the server should never be culled." + description: | + How long should a server be idle for before it is culled. A value of zero + indicates that the server should never be culled. type: integer minimum: 0 default: 0 diff --git a/helm-chart/amalthea/values.yaml b/helm-chart/amalthea/values.yaml index 2e336ade..3f91a709 100644 --- a/helm-chart/amalthea/values.yaml +++ b/helm-chart/amalthea/values.yaml @@ -79,7 +79,7 @@ image: repository: renku/amalthea pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "0.0.1-rc1" + tag: "" imagePullSecrets: [] nameOverride: "" diff --git a/manifests/config.yaml b/manifests/config.yaml new file mode 100644 index 00000000..97ee7035 --- /dev/null +++ b/manifests/config.yaml @@ -0,0 +1,13 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: amalthea-config + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +data: + kopf-operator-settings.yaml: "watching:\n # This can fix a problem of a watch stream suddenly falling\n # silent, see https://github.com/nolar/kopf/issues/762#issuecomment-838423267\n client_timeout: 600\n" diff --git a/manifests/crd.yaml b/manifests/crd.yaml new file mode 100644 index 00000000..399e9bd3 --- /dev/null +++ b/manifests/crd.yaml @@ -0,0 +1,233 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: jupyterservers.amalthea.dev +spec: + scope: Namespaced + group: amalthea.dev + names: + kind: JupyterServer + plural: jupyterservers + singular: jupyterserver + shortNames: + - js + versions: + - name: v1alpha1 + served: true + storage: true + additionalPrinterColumns: + - name: Image + type: string + description: The Jupyter server image that is running + jsonPath: .spec.jupyterServer.image + - name: URL + type: string + description: Full URL where the server can be reached + jsonPath: .status.create_fn.fullServerURL + - name: Pod Status + type: string + description: Status of the main pod + jsonPath: .status.mainPod.status.phase + + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + x-kubernetes-preserve-unknown-fields: true + default: + children: {} + mainPod: {} + spec: + type: object + properties: + + jupyterServer: + type: object + default: {} # Stupid, but necessary for the inner defaults do work + properties: + image: + type: string + default: jupyter/minimal-notebook:latest + defaultUrl: + type: string + default: /lab + rootDir: + type: string + description: | + The absolute path to the root/notebook directory for jupyterlab. Should + lead to a subdirectory of or match the path at storage.pvc.mountPath + default: /home/jovyan/work + resources: + description: | + Regular resource requests, will be set on the main notebook + container. + type: object + x-kubernetes-preserve-unknown-fields: true + default: {} + + routing: + type: object + default: {} # Stupid, but necessary for the inner defaults do work + properties: + host: + type: string + path: + type: string + default: "/" + tls: + type: object + default: {} + properties: + enabled: + type: boolean + default: false + secretName: + type: string + ingressAnnotations: + type: object + default: {} + x-kubernetes-preserve-unknown-fields: true + + storage: + type: object + default: {} + properties: + size: + description: | + Size of the PVC or sizeLimit of the emptyDir volume which + backs the session respectively. + type: string + default: 100Mi + pvc: + type: object + default: {} + properties: + enabled: + description: | + Wether a PVC should be used to back the session. Defaults + to 'false' in which case an emptyDir volume will be used. + type: boolean + default: false + storageClassName: + type: string + description: | + Storage class to be used for the PVC. If left empty, the + default storage class defined for the cluster will be used. + mountPath: + type: string + description: | + The absolute path to the location where the PVC should be + mounted in the user session pod. + default: /home/jovyan/work + + auth: + type: object + default: {} # Stupid, but necessary for the inner defaults do work + properties: + token: + description: | + A token that will be passed to the `--ServerApp.token` option when running + the Jupyter server and needed when first accessing the Jupyter server. The + options are: + - By leaving this field empty, a token will be autogenerated and added under the + key `ServerApp.token` to the secret which is created as a child of the custom + resource object. + - Setting the token to an empty string "" runs the Jupyter server container + itself without any authentication. This is recommended when enabling OIDC + as authentication and authorization are then handled by the dedicated plugins. + - Set an actual value here. Note that this string will be stored in clear text + as part of the custom resource object. This option is mostly useful for dev + purposes. + type: string + oidc: + type: object + default: {} + properties: + enabled: + type: boolean + default: False + issuerUrl: + type: string + clientId: + type: string + clientSecret: + description: | + The client secret of the application registered with the OIDC provider. This + secret can be given here explicitly as string or through a reference to an + existing secret. Using the secret reference is the preferred option because it + avoids storing the secret in cleartext on the custom resource specification. + type: object + properties: + value: + type: string + secretKeyRef: + description: | + A regular reference to the key/secret which holds the client secret of the + application registered with the OIDC provider. Note that the secret has to + be in the same namespace in which the custom resource object is going to be + created. + type: object + properties: + name: + type: string + key: + type: string + oneOf: + - required: + - value + - required: + - secretKeyRef + authorizedEmails: + description: | + List of users (identified by Email address read from the "email" OIDC claim) + which are allowed to access this Jupyter session. This list is stored as a file + and passed to the `--authenticated-emails-file` option (see + https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#command-line-options). + type: array + default: [] + items: + type: string + authorizedGroups: + description: | + List of groups of users (read from the "groups" OIDC claim) which are allowed + to access this Jupyter session. This list passed to the `--allowed-group` option (see + https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#command-line-options). + type: array + default: [] + items: + type: string + + patches: + description: | + Patches to be applied. Currently only json patches and json merge + patches are supported. + type: array + default: [] + items: + type: object + properties: + type: + type: string + enum: + - application/json-patch+json + - application/merge-patch+json + patch: + x-kubernetes-preserve-unknown-fields: true + + culling: + description: Options about culling idle servers + type: object + default: {} + properties: + idleSecondsThreshold: + description: | + How long should a server be idle for before it is culled. A value of zero + indicates that the server should never be culled. + type: integer + minimum: 0 + default: 0 diff --git a/manifests/deployment.yaml b/manifests/deployment.yaml new file mode 100644 index 00000000..9856a129 --- /dev/null +++ b/manifests/deployment.yaml @@ -0,0 +1,78 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: amalthea + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +spec: + # There must be only one instance of the kopf operator handling the + # same custom resource object at a time. + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + spec: + serviceAccountName: amalthea + securityContext: + {} + containers: + - name: amalthea + securityContext: + {} + image: "renku/amalthea:latest" + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 300 + args: + - "--verbose" + - "--log-format=json" + - "--namespace=default" + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + env: + - name: CRD_API_GROUP + value: amalthea.dev + - name: CRD_API_VERSION + value: v1alpha1 + - name: CRD_NAME + value: JupyterServer + - name: AMALTHEA_SELECTOR_LABELS + value: | + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + - name: EXTRA_CHILD_RESOURCES + value: "[]" + - name: JUPYTER_SERVER_IDLE_CHECK_INTERVAL_SECONDS + value: "300" + - name: CPU_USAGE_MILLICORES_IDLE_THRESHOLD + value: "500" + volumeMounts: + - name: config + mountPath: /app/config + volumes: + - name: config + configMap: + name: amalthea-config \ No newline at end of file diff --git a/manifests/kustomization.yaml b/manifests/kustomization.yaml new file mode 100644 index 00000000..ca320155 --- /dev/null +++ b/manifests/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ./config.yaml +- ./crd.yaml +- ./deployment.yaml +- ./networkpolicies.yaml +- ./rbac/rbac-namespaced.yaml +- ./rbac/serviceaccount.yaml diff --git a/manifests/networkpolicies.yaml b/manifests/networkpolicies.yaml new file mode 100644 index 00000000..d67f9558 --- /dev/null +++ b/manifests/networkpolicies.yaml @@ -0,0 +1,62 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/networkpolicies.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: amalthea-controller + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/component: controller + policyTypes: + - Ingress +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/networkpolicies.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + namespace: default + name: amalthea-jupyterserver + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/component: jupyterserver + policyTypes: + - Egress + egress: + - to: + # Allow DNS resolution (internal and external) + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + - to: + # Allow access to web outside of cluster by excluding + # IP ranges which are reserved for private networking from + # the allowed range. + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - port: 80 + protocol: TCP + - port: 443 + protocol: TCP diff --git a/manifests/rbac/rbac-namespaced.yaml b/manifests/rbac/rbac-namespaced.yaml new file mode 100644 index 00000000..289e5008 --- /dev/null +++ b/manifests/rbac/rbac-namespaced.yaml @@ -0,0 +1,66 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/rbac/rbac-namespaced.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: amalthea + namespace: default + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +rules: + # Kopf: posting the events about the handlers progress/errors. + - apiGroups: [""] + resources: [events] + verbs: [create] + + # Amalthea: watching & handling for the custom resource we declare. + - apiGroups: [amalthea.dev] + resources: [jupyterservers] + verbs: [list, watch, patch, delete] + + - apiGroups: [""] + resources: [pods] + verbs: [get, list, watch, delete] + + # Amalthea get pod metrics used to cull idle Jupyter servers + - apiGroups: ["metrics.k8s.io"] + resources: [pods] + verbs: [get, list, watch] + + # Amalthea: child resources we produce + # Note that we do not patch/update/delete them ever. + - apiGroups: + - "" + - apps + - networking.k8s.io + resources: + - statefulsets + - persistentvolumeclaims + - services + - ingresses + - secrets + - configmaps + verbs: [create, get, list, watch] +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/rbac/rbac-namespaced.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: amalthea + namespace: default + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: amalthea +subjects: + - kind: ServiceAccount + name: amalthea + namespace: default diff --git a/manifests/rbac/serviceaccount.yaml b/manifests/rbac/serviceaccount.yaml new file mode 100644 index 00000000..e8fe4dbe --- /dev/null +++ b/manifests/rbac/serviceaccount.yaml @@ -0,0 +1,11 @@ +--- +# This manifest is auto-generated from the helm chart, do not modify! +# Source: amalthea/templates/rbac/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: amalthea + labels: + app.kubernetes.io/name: amalthea + app.kubernetes.io/instance: amalthea + app.kubernetes.io/version: "latest" diff --git a/utils/render-chart-manifests.py b/utils/render-chart-manifests.py new file mode 100644 index 00000000..28fbb14e --- /dev/null +++ b/utils/render-chart-manifests.py @@ -0,0 +1,64 @@ +import os +import shutil +import subprocess + +import yaml + +""" +Simple script which renders the helm chart with the default values +and removes some helm specific annotations. For convenience, we also +generate a kustomization file. +""" + +repo_root = ( + subprocess.check_output("git rev-parse --show-toplevel", shell=True) + .decode() + .strip() +) + +rendered_chart = subprocess.check_output( + f"helm template amalthea -n default {repo_root}/helm-chart/amalthea", + shell=True, +).decode() + +lines = rendered_chart.splitlines() +filtered_lines = [line for line in lines if "helm.sh/chart:" not in line] +filtered_lines = [ + line for line in filtered_lines if "app.kubernetes.io/managed-by: Helm" not in line +] +all_manifests = "\n".join(filtered_lines) +manifests = all_manifests.split("---") + +kustomization = { + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "resources": [], +} + +# empty the manifests directory +shutil.rmtree(f"{repo_root}/manifests/", ignore_errors=True) +os.mkdir(f"{repo_root}/manifests/") + +for manifest in manifests: + manifest = manifest.lstrip("\n") + if len(manifest) == 0: + continue + template_path = manifest.splitlines()[0].split("# Source: amalthea/templates/")[1] + out_filename = f"{repo_root}/manifests/{template_path}" + try: + os.makedirs(os.path.dirname(out_filename)) + except FileExistsError: + pass + with open(out_filename, "a+") as outfile: + outfile.write("---\n") + outfile.write( + "# This manifest is auto-generated from the helm chart, do not modify! \n" + ) + outfile.write(manifest) + kustomization["resources"].append(f"./{template_path}") + +# some resources are inside the same file +kustomization["resources"] = sorted(list(set(kustomization["resources"]))) + +with open(f"{repo_root}/manifests/kustomization.yaml", "w") as kustomization_file: + yaml.dump(kustomization, kustomization_file) From 0907a012ad66e37d764715eda313a3834dfa7f07 Mon Sep 17 00:00:00 2001 From: Andreas Bleuler Date: Thu, 23 Sep 2021 09:14:59 +0200 Subject: [PATCH 2/4] chore: add missing description to crd --- helm-chart/amalthea/templates/crd.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helm-chart/amalthea/templates/crd.yaml b/helm-chart/amalthea/templates/crd.yaml index d8016786..fc9c9161 100644 --- a/helm-chart/amalthea/templates/crd.yaml +++ b/helm-chart/amalthea/templates/crd.yaml @@ -48,6 +48,8 @@ spec: jupyterServer: type: object + description: | + Configuration options (such as image to run) for the jupyter server. default: {} # Stupid, but necessary for the inner defaults do work properties: image: From 450c9e73c20dd73f0712e82d611bd7571ad2bd3f Mon Sep 17 00:00:00 2001 From: ableuler Date: Thu, 23 Sep 2021 07:19:06 +0000 Subject: [PATCH 3/4] chore: update rendered manifests and CRD docs --- docs/crd.md | 4 +++- manifests/crd.yaml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/crd.md b/docs/crd.md index bd3e3068..c2dc6922 100644 --- a/docs/crd.md +++ b/docs/crd.md @@ -107,7 +107,8 @@ Resource Types: jupyterServer object -
+ Configuration options (such as image to run) for the jupyter server. +

Default: map[]
@@ -389,6 +390,7 @@ indicates that the server should never be culled. +Configuration options (such as image to run) for the jupyter server. diff --git a/manifests/crd.yaml b/manifests/crd.yaml index 399e9bd3..7bf3103b 100644 --- a/manifests/crd.yaml +++ b/manifests/crd.yaml @@ -48,6 +48,8 @@ spec: jupyterServer: type: object + description: | + Configuration options (such as image to run) for the jupyter server. default: {} # Stupid, but necessary for the inner defaults do work properties: image: From b890c69cef463011fbe6ab1ca28ebf87eb05cbd4 Mon Sep 17 00:00:00 2001 From: Andreas Bleuler Date: Thu, 23 Sep 2021 10:47:47 +0200 Subject: [PATCH 4/4] squashme: add name guard to script --- utils/render-chart-manifests.py | 96 +++++++++++++++++---------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/utils/render-chart-manifests.py b/utils/render-chart-manifests.py index 28fbb14e..ab7da103 100644 --- a/utils/render-chart-manifests.py +++ b/utils/render-chart-manifests.py @@ -10,55 +10,61 @@ generate a kustomization file. """ -repo_root = ( - subprocess.check_output("git rev-parse --show-toplevel", shell=True) - .decode() - .strip() -) +if __name__ == "__main__": -rendered_chart = subprocess.check_output( - f"helm template amalthea -n default {repo_root}/helm-chart/amalthea", - shell=True, -).decode() + repo_root = ( + subprocess.check_output("git rev-parse --show-toplevel", shell=True) + .decode() + .strip() + ) -lines = rendered_chart.splitlines() -filtered_lines = [line for line in lines if "helm.sh/chart:" not in line] -filtered_lines = [ - line for line in filtered_lines if "app.kubernetes.io/managed-by: Helm" not in line -] -all_manifests = "\n".join(filtered_lines) -manifests = all_manifests.split("---") + rendered_chart = subprocess.check_output( + f"helm template amalthea -n default {repo_root}/helm-chart/amalthea", + shell=True, + ).decode() -kustomization = { - "apiVersion": "kustomize.config.k8s.io/v1beta1", - "kind": "Kustomization", - "resources": [], -} + lines = rendered_chart.splitlines() + filtered_lines = [line for line in lines if "helm.sh/chart:" not in line] + filtered_lines = [ + line + for line in filtered_lines + if "app.kubernetes.io/managed-by: Helm" not in line + ] + all_manifests = "\n".join(filtered_lines) + manifests = all_manifests.split("---") -# empty the manifests directory -shutil.rmtree(f"{repo_root}/manifests/", ignore_errors=True) -os.mkdir(f"{repo_root}/manifests/") + kustomization = { + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "resources": [], + } -for manifest in manifests: - manifest = manifest.lstrip("\n") - if len(manifest) == 0: - continue - template_path = manifest.splitlines()[0].split("# Source: amalthea/templates/")[1] - out_filename = f"{repo_root}/manifests/{template_path}" - try: - os.makedirs(os.path.dirname(out_filename)) - except FileExistsError: - pass - with open(out_filename, "a+") as outfile: - outfile.write("---\n") - outfile.write( - "# This manifest is auto-generated from the helm chart, do not modify! \n" - ) - outfile.write(manifest) - kustomization["resources"].append(f"./{template_path}") + # empty the manifests directory + shutil.rmtree(f"{repo_root}/manifests/", ignore_errors=True) + os.mkdir(f"{repo_root}/manifests/") -# some resources are inside the same file -kustomization["resources"] = sorted(list(set(kustomization["resources"]))) + for manifest in manifests: + manifest = manifest.lstrip("\n") + if len(manifest) == 0: + continue + template_path = manifest.splitlines()[0].split("# Source: amalthea/templates/")[ + 1 + ] + out_filename = f"{repo_root}/manifests/{template_path}" + try: + os.makedirs(os.path.dirname(out_filename)) + except FileExistsError: + pass + with open(out_filename, "a+") as outfile: + outfile.write("---\n") + outfile.write( + "# This manifest is auto-generated from the helm chart, do not modify! \n" + ) + outfile.write(manifest) + kustomization["resources"].append(f"./{template_path}") -with open(f"{repo_root}/manifests/kustomization.yaml", "w") as kustomization_file: - yaml.dump(kustomization, kustomization_file) + # some resources are inside the same file + kustomization["resources"] = sorted(list(set(kustomization["resources"]))) + + with open(f"{repo_root}/manifests/kustomization.yaml", "w") as kustomization_file: + yaml.dump(kustomization, kustomization_file)