diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml new file mode 100644 index 0000000..46a8f58 --- /dev/null +++ b/.github/workflows/helm.yml @@ -0,0 +1,44 @@ +name: Helm + +on: + push: + pull_request: + +jobs: + lint: + name: Lint chart + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Helm + uses: azure/setup-helm@bf6a7d304bc2fdb57e0331155b7ebf2c504acf0a # v4 + + - name: helm lint + run: helm lint deploy/charts/marimo-operator + + - name: helm template + run: helm template marimo-operator deploy/charts/marimo-operator --include-crds > /dev/null + + crd-sync: + name: CRD in sync with generated manifests + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version-file: go.mod + + - name: Regenerate manifests + run: make manifests + + - name: Check chart CRDs match config/crd/bases + run: | + if ! git diff --exit-code config/crd/bases deploy/charts/marimo-operator/crds; then + echo "::error::CRDs are out of sync. Run 'make manifests' and commit the result." + exit 1 + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4cccc9..3424fcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,8 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml + # Helm templates are not valid standalone YAML. + exclude: ^deploy/charts/.*/templates/ - id: check-merge-conflict - repo: https://github.com/astral-sh/ruff-pre-commit diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 845432b..0477734 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ go test ./internal/controller/... -run TestReconcile -v make test ``` -**Important:** After modifying types in `api/v1alpha1/`, always run `make manifests && make generate` and commit the generated files. +**Important:** After modifying types in `api/v1alpha1/`, always run `make manifests && make generate` and commit the generated files. `make manifests` also syncs the Helm chart's CRD copy (`deploy/charts/marimo-operator/crds/`), so commit that too. ### Plugin (Python) diff --git a/Makefile b/Makefile index 644ebe5..79a6911 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. "$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + cp config/crd/bases/*.yaml deploy/charts/marimo-operator/crds/ .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/README.md b/README.md index 1a755fe..e5c74d7 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ kubectl apply -f https://raw.githubusercontent.com/marimo-team/marimo-operator/m # Option 2: Install via kustomize kubectl apply -k https://github.com/marimo-team/marimo-operator/config/default +# Option 3: Install via Helm +helm install marimo-operator ./deploy/charts/marimo-operator \ + --namespace marimo-operator-system --create-namespace + # Verify installation kubectl get pods -n marimo-operator-system # Should show: marimo-operator-controller-manager-xxx Running diff --git a/deploy/charts/marimo-operator/.helmignore b/deploy/charts/marimo-operator/.helmignore new file mode 100644 index 0000000..51bcc40 --- /dev/null +++ b/deploy/charts/marimo-operator/.helmignore @@ -0,0 +1,11 @@ +# Patterns to ignore when building Helm packages. +.DS_Store +.git/ +.gitignore +.idea/ +.vscode/ +*.tmproj +*.bak +*.swp +*.orig +ci/ diff --git a/deploy/charts/marimo-operator/Chart.yaml b/deploy/charts/marimo-operator/Chart.yaml new file mode 100644 index 0000000..b1994b3 --- /dev/null +++ b/deploy/charts/marimo-operator/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v2 +name: marimo-operator +description: A Helm chart for the marimo-operator, a Kubernetes operator that manages MarimoNotebook custom resources. +type: application +# Chart version: independent SemVer, bumped on any chart change. +version: 0.1.0 +# Tracks the operator release this chart deploys by default. +appVersion: "v0.3.0" +kubeVersion: ">=1.21.0-0" +keywords: + - marimo + - operator + - notebooks +home: https://github.com/marimo-team/marimo-operator +sources: + - https://github.com/marimo-team/marimo-operator +maintainers: + - name: marimo-team + url: https://github.com/marimo-team +annotations: + artifacthub.io/operator: "true" diff --git a/deploy/charts/marimo-operator/README.md b/deploy/charts/marimo-operator/README.md new file mode 100644 index 0000000..94450c2 --- /dev/null +++ b/deploy/charts/marimo-operator/README.md @@ -0,0 +1,88 @@ +# marimo-operator Helm chart + +Installs the [marimo-operator](https://github.com/marimo-team/marimo-operator) — a Kubernetes +operator that manages `MarimoNotebook` custom resources — including its CRD, controller +`Deployment`, and RBAC. + +## Prerequisites + +- Kubernetes >= 1.21 +- Helm 3 +- Permission to create cluster-scoped resources (CRD, ClusterRole) on first install + +## Install + +```sh +helm install marimo-operator ./deploy/charts/marimo-operator \ + --namespace marimo-operator-system --create-namespace +``` + +The operator installs into the release namespace — set `--namespace` to deploy it anywhere. + +Pin a specific operator image: + +```sh +helm install marimo-operator ./deploy/charts/marimo-operator \ + --namespace marimo-operator-system --create-namespace \ + --set image.tag=v0.3.0 +``` + +## CRD lifecycle + +The `MarimoNotebook` CRD ships in the chart's `crds/` directory. Helm installs it on first +install but **does not upgrade or delete it** on `helm upgrade` / `helm uninstall`. When a new +operator release changes the CRD, apply it manually: + +```sh +kubectl apply -f https://raw.githubusercontent.com/marimo-team/marimo-operator//deploy/charts/marimo-operator/crds/marimo.io_marimos.yaml +``` + +The CRD is generated from the Go API types by `make manifests` and copied into the chart by the +same target, so it stays in lockstep with `config/crd/bases`. + +## Values + +| Key | Default | Description | +|-----|---------|-------------| +| `replicaCount` | `1` | Operator replicas (leader election elects the active one). | +| `image.repository` | `ghcr.io/marimo-team/marimo-operator` | Controller image repository. | +| `image.tag` | `""` | Image tag; defaults to the chart `appVersion`. | +| `image.pullPolicy` | `IfNotPresent` | Image pull policy. | +| `imagePullSecrets` | `[]` | Image pull secrets. | +| `nameOverride` / `fullnameOverride` | `""` | Override generated resource names. | +| `serviceAccount.create` | `true` | Create the controller ServiceAccount. | +| `serviceAccount.name` | `""` | ServiceAccount name (generated when empty). | +| `serviceAccount.annotations` | `{}` | ServiceAccount annotations (e.g. IRSA). | +| `rbac.create` | `true` | Create ClusterRole/Role and bindings. | +| `leaderElection.enabled` | `true` | Enable leader election. | +| `healthProbe.bindAddress` | `:8081` | Health probe bind address. | +| `metrics.enabled` | `true` | Serve the controller metrics endpoint. | +| `metrics.bindAddress` | `:8443` | Metrics bind address. | +| `metrics.secure` | `true` | Serve metrics over HTTPS with authn/authz. | +| `metrics.enableHTTP2` | `false` | Enable HTTP/2 on the metrics server. | +| `metrics.service.type` | `ClusterIP` | Metrics Service type. | +| `metrics.service.port` | `8443` | Metrics Service port. | +| `metrics.serviceMonitor.enabled` | `false` | Create a Prometheus Operator ServiceMonitor. | +| `metrics.serviceMonitor.labels` | `{}` | Extra ServiceMonitor labels (e.g. Prometheus release selector). | +| `metrics.serviceMonitor.interval` | `""` | Scrape interval. | +| `metrics.serviceMonitor.scrapeTimeout` | `""` | Scrape timeout. | +| `metrics.serviceMonitor.insecureSkipVerify` | `true` | Skip TLS verification when scraping. | +| `metrics.serviceMonitor.tlsConfig` | `{}` | Full `tlsConfig` override (replaces `insecureSkipVerify`). | +| `networkPolicy.enabled` | `false` | Restrict metrics ingress with a NetworkPolicy. | +| `networkPolicy.metricsPort` | `8443` | Metrics port allowed by the policy. | +| `networkPolicy.fromNamespaceLabels` | `{metrics: enabled}` | Namespace selector allowed to scrape. | +| `operatorImages.initImage` | `busybox:1.36` | `DEFAULT_INIT_IMAGE` env. | +| `operatorImages.gitImage` | `alpine/git:latest` | `GIT_IMAGE` env. | +| `operatorImages.alpineImage` | `alpine:latest` | `ALPINE_IMAGE` env. | +| `operatorImages.s3fsImage` | `ghcr.io/marimo-team/marimo-operator/s3fs:latest` | `S3FS_IMAGE` env. | +| `extraArgs` | `[]` | Extra controller args. | +| `extraEnv` | `[]` | Extra controller env. | +| `extraVolumes` / `extraVolumeMounts` | `[]` | Extra volumes / mounts. | +| `resources` | req `10m`/`64Mi`, lim `500m`/`128Mi` | Controller resources. | +| `livenessProbe` / `readinessProbe` | see `values.yaml` | Controller probes. | +| `podSecurityContext` | `runAsNonRoot`, `RuntimeDefault` | Pod security context. | +| `securityContext` | restricted | Container security context. | +| `terminationGracePeriodSeconds` | `10` | Pod termination grace period. | +| `podAnnotations` / `podLabels` | `{}` | Extra pod annotations / labels. | +| `nodeSelector` / `tolerations` / `affinity` / `topologySpreadConstraints` | empty | Scheduling controls. | +| `priorityClassName` | `""` | Pod priority class. | diff --git a/deploy/charts/marimo-operator/crds/marimo.io_marimos.yaml b/deploy/charts/marimo-operator/crds/marimo.io_marimos.yaml new file mode 100644 index 0000000..1ab8ba0 --- /dev/null +++ b/deploy/charts/marimo-operator/crds/marimo.io_marimos.yaml @@ -0,0 +1,863 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: marimos.marimo.io +spec: + group: marimo.io + names: + kind: MarimoNotebook + listKind: MarimoNotebookList + plural: marimos + shortNames: + - mo + singular: marimo + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MarimoNotebook is the Schema for the marimos API. + It deploys a marimo notebook server with optional sidecars and persistent storage. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: MarimoNotebookSpec defines the desired state of MarimoNotebook. + properties: + auth: + description: Auth configures authentication + properties: + password: + description: Password references a Secret containing the marimo + password + properties: + secretKeyRef: + description: Name of the Secret + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + required: + - secretKeyRef + type: object + type: object + content: + description: |- + Content is inline notebook content (marimo .py or .md format) + When set, operator creates a ConfigMap and mounts it + type: string + env: + description: Env variables for the marimo container + items: + description: EnvVar represents an environment variable present in + a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. Cannot + be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath is + written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + default: ghcr.io/marimo-team/marimo:latest + description: Image for marimo container + type: string + mode: + default: edit + description: 'Mode is the marimo server mode: "edit" (default) or + "run"' + enum: + - edit + - run + type: string + mounts: + description: |- + Mounts are high-level data source URIs expanded to sidecars + Supported schemes: cw://, sshfs://, rsync:// + items: + type: string + type: array + podOverrides: + description: |- + PodOverrides allows customizing the pod spec via strategic merge patch + Use this for advanced configuration like nodeSelector, tolerations, etc. + x-kubernetes-preserve-unknown-fields: true + port: + default: 2718 + description: Port for marimo server + format: int32 + maximum: 65535 + minimum: 1 + type: integer + resources: + description: Resources for the marimo container + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Limits specifies maximum resources allowed + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Requests specifies minimum resources required + type: object + type: object + sidecars: + description: |- + Sidecars are additional containers that run alongside marimo + They share the PVC volume mounted at /data + items: + description: SidecarSpec defines an additional container that runs + alongside marimo. + properties: + args: + description: Args to pass to the command + items: + type: string + type: array + command: + description: Command overrides the container entrypoint + items: + type: string + type: array + env: + description: Env variables for the sidecar + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + exposePort: + description: ExposePort adds this port to the Service for external + access + format: int32 + maximum: 65535 + minimum: 1 + type: integer + image: + description: Image to use for the sidecar + type: string + name: + description: Name of the sidecar container (must be unique) + type: string + resources: + description: Resources for the sidecar container + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext for the sidecar container + Required for FUSE-based mounts (s3fs, sshfs) which need privileged access + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + required: + - image + - name + type: object + type: array + source: + description: |- + Source is a Git URL to clone notebook content from + The repository is cloned into the PVC via an init container + type: string + storage: + description: Storage configures persistent storage for notebooks + properties: + size: + default: 1Gi + description: Size of the PVC (e.g., "1Gi", "10Gi") + type: string + storageClassName: + description: StorageClassName for the PVC (uses cluster default + if empty) + type: string + type: object + x-kubernetes-validations: + - message: storage is immutable once set + rule: oldSelf == null || self == oldSelf + type: object + x-kubernetes-validations: + - message: storage is required when sidecars are specified + rule: '!(has(self.sidecars) && size(self.sidecars) > 0 && !has(self.storage))' + - message: source and content are mutually exclusive + rule: '!(has(self.source) && has(self.content))' + status: + description: MarimoNotebookStatus defines the observed state of MarimoNotebook. + properties: + conditions: + description: Conditions represent the latest available observations + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + phase: + description: Phase of the notebook (Pending, Running, Failed) + enum: + - Pending + - Running + - Failed + type: string + podName: + description: PodName is the name of the created Pod + type: string + serviceName: + description: ServiceName is the name of the created Service + type: string + sourceHash: + description: SourceHash is a hash of the source URL for change detection + type: string + url: + description: URL to access the notebook (internal service URL) + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/charts/marimo-operator/templates/NOTES.txt b/deploy/charts/marimo-operator/templates/NOTES.txt new file mode 100644 index 0000000..f9e5463 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/NOTES.txt @@ -0,0 +1,17 @@ +marimo-operator has been installed. + + Release: {{ .Release.Name }} + Namespace: {{ .Release.Namespace }} + Chart: {{ .Chart.Name }}-{{ .Chart.Version }} (app {{ .Chart.AppVersion }}) + +Check the controller is running: + + kubectl -n {{ .Release.Namespace }} rollout status deploy/{{ include "marimo-operator.fullname" . }}-controller-manager + +The MarimoNotebook CRD (marimos.marimo.io) is installed from the chart's crds/ directory. +NOTE: Helm does not upgrade or remove CRDs. On a chart upgrade that changes the CRD, +apply it manually: + + kubectl apply -f https://raw.githubusercontent.com/marimo-team/marimo-operator/{{ .Chart.AppVersion }}/deploy/charts/marimo-operator/crds/marimo.io_marimos.yaml + +Create a MarimoNotebook to get started — see the examples/ directory in the repository. diff --git a/deploy/charts/marimo-operator/templates/_helpers.tpl b/deploy/charts/marimo-operator/templates/_helpers.tpl new file mode 100644 index 0000000..3472c20 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "marimo-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Fully qualified app name. +*/}} +{{- define "marimo-operator.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 }} + +{{- define "marimo-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "marimo-operator.labels" -}} +helm.sh/chart: {{ include "marimo-operator.chart" . }} +{{ include "marimo-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. These must stay stable: the Deployment, metrics Service, +ServiceMonitor and NetworkPolicy all select on them, and Deployment selectors +are immutable. +*/}} +{{- define "marimo-operator.selectorLabels" -}} +control-plane: controller-manager +app.kubernetes.io/name: marimo-operator +{{- end }} + +{{/* +ServiceAccount name to use. +*/}} +{{- define "marimo-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (printf "%s-controller-manager" (include "marimo-operator.fullname" .)) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/deployment.yaml b/deploy/charts/marimo-operator/templates/deployment.yaml new file mode 100644 index 0000000..b020a41 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/deployment.yaml @@ -0,0 +1,103 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "marimo-operator.fullname" . }}-controller-manager + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "marimo-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "marimo-operator.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "marimo-operator.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + containers: + - name: manager + command: + - /manager + args: + {{- if .Values.metrics.enabled }} + - --metrics-bind-address={{ .Values.metrics.bindAddress }} + {{- if not .Values.metrics.secure }} + - --metrics-secure=false + {{- end }} + {{- if .Values.metrics.enableHTTP2 }} + - --enable-http2 + {{- end }} + {{- end }} + {{- if .Values.leaderElection.enabled }} + - --leader-elect + {{- end }} + - --health-probe-bind-address={{ .Values.healthProbe.bindAddress }} + {{- with .Values.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: DEFAULT_INIT_IMAGE + value: {{ .Values.operatorImages.initImage | quote }} + - name: GIT_IMAGE + value: {{ .Values.operatorImages.gitImage | quote }} + - name: ALPINE_IMAGE + value: {{ .Values.operatorImages.alpineImage | quote }} + - name: S3FS_IMAGE + value: {{ .Values.operatorImages.s3fsImage | quote }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- with .Values.extraVolumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.extraVolumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/charts/marimo-operator/templates/metrics-service.yaml b/deploy/charts/marimo-operator/templates/metrics-service.yaml new file mode 100644 index 0000000..872b432 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/metrics-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "marimo-operator.fullname" . }}-controller-manager-metrics-service + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.metrics.service.type }} + ports: + - name: https + port: {{ .Values.metrics.service.port }} + protocol: TCP + targetPort: 8443 + selector: + {{- include "marimo-operator.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/networkpolicy.yaml b/deploy/charts/marimo-operator/templates/networkpolicy.yaml new file mode 100644 index 0000000..5761700 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/networkpolicy.yaml @@ -0,0 +1,23 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "marimo-operator.fullname" . }}-allow-metrics-traffic + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "marimo-operator.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + {{- toYaml .Values.networkPolicy.fromNamespaceLabels | nindent 14 }} + ports: + - port: {{ .Values.networkPolicy.metricsPort }} + protocol: TCP +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/rbac-leader-election.yaml b/deploy/charts/marimo-operator/templates/rbac-leader-election.yaml new file mode 100644 index 0000000..66237b2 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/rbac-leader-election.yaml @@ -0,0 +1,57 @@ +{{- if and .Values.rbac.create .Values.leaderElection.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "marimo-operator.fullname" . }}-leader-election-role + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "marimo-operator.fullname" . }}-leader-election-rolebinding + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "marimo-operator.fullname" . }}-leader-election-role +subjects: +- kind: ServiceAccount + name: {{ include "marimo-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/rbac-manager.yaml b/deploy/charts/marimo-operator/templates/rbac-manager.yaml new file mode 100644 index 0000000..87bb312 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/rbac-manager.yaml @@ -0,0 +1,72 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "marimo-operator.fullname" . }}-manager-role + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + - persistentvolumeclaims + - pods + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - marimo.io + resources: + - marimos + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - marimo.io + resources: + - marimos/finalizers + verbs: + - update +- apiGroups: + - marimo.io + resources: + - marimos/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "marimo-operator.fullname" . }}-manager-rolebinding + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "marimo-operator.fullname" . }}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "marimo-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/rbac-metrics.yaml b/deploy/charts/marimo-operator/templates/rbac-metrics.yaml new file mode 100644 index 0000000..4cfe7f0 --- /dev/null +++ b/deploy/charts/marimo-operator/templates/rbac-metrics.yaml @@ -0,0 +1,48 @@ +{{- if and .Values.rbac.create .Values.metrics.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "marimo-operator.fullname" . }}-metrics-auth-role + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "marimo-operator.fullname" . }}-metrics-auth-rolebinding + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "marimo-operator.fullname" . }}-metrics-auth-role +subjects: +- kind: ServiceAccount + name: {{ include "marimo-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "marimo-operator.fullname" . }}-metrics-reader + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/serviceaccount.yaml b/deploy/charts/marimo-operator/templates/serviceaccount.yaml new file mode 100644 index 0000000..a83971d --- /dev/null +++ b/deploy/charts/marimo-operator/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "marimo-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/deploy/charts/marimo-operator/templates/servicemonitor.yaml b/deploy/charts/marimo-operator/templates/servicemonitor.yaml new file mode 100644 index 0000000..e391b8f --- /dev/null +++ b/deploy/charts/marimo-operator/templates/servicemonitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "marimo-operator.fullname" . }}-controller-manager-metrics-monitor + namespace: {{ .Release.Namespace }} + labels: + {{- include "marimo-operator.labels" . | nindent 4 }} + {{- with .Values.metrics.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + {{- with .Values.metrics.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + tlsConfig: + {{- if .Values.metrics.serviceMonitor.tlsConfig }} + {{- toYaml .Values.metrics.serviceMonitor.tlsConfig | nindent 8 }} + {{- else }} + insecureSkipVerify: {{ .Values.metrics.serviceMonitor.insecureSkipVerify }} + {{- end }} + selector: + matchLabels: + {{- include "marimo-operator.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/deploy/charts/marimo-operator/values.schema.json b/deploy/charts/marimo-operator/values.schema.json new file mode 100644 index 0000000..81d58f9 --- /dev/null +++ b/deploy/charts/marimo-operator/values.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "marimo-operator Helm values", + "type": "object", + "properties": { + "replicaCount": { "type": "integer", "minimum": 0 }, + "image": { + "type": "object", + "properties": { + "repository": { "type": "string" }, + "tag": { "type": "string" }, + "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } + }, + "required": ["repository"] + }, + "imagePullSecrets": { "type": "array" }, + "nameOverride": { "type": "string" }, + "fullnameOverride": { "type": "string" }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { "type": "boolean" }, + "name": { "type": "string" }, + "annotations": { "type": "object" } + } + }, + "rbac": { + "type": "object", + "properties": { "create": { "type": "boolean" } } + }, + "leaderElection": { + "type": "object", + "properties": { "enabled": { "type": "boolean" } } + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "bindAddress": { "type": "string" }, + "secure": { "type": "boolean" }, + "enableHTTP2": { "type": "boolean" }, + "service": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "port": { "type": "integer" } + } + }, + "serviceMonitor": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "labels": { "type": "object" }, + "interval": { "type": "string" }, + "scrapeTimeout": { "type": "string" }, + "insecureSkipVerify": { "type": "boolean" }, + "tlsConfig": { "type": "object" } + } + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "metricsPort": { "type": "integer" }, + "fromNamespaceLabels": { "type": "object" } + } + }, + "operatorImages": { + "type": "object", + "properties": { + "initImage": { "type": "string" }, + "gitImage": { "type": "string" }, + "alpineImage": { "type": "string" }, + "s3fsImage": { "type": "string" } + } + }, + "extraArgs": { "type": "array" }, + "extraEnv": { "type": "array" }, + "extraVolumes": { "type": "array" }, + "extraVolumeMounts": { "type": "array" }, + "resources": { "type": "object" }, + "livenessProbe": { "type": "object" }, + "readinessProbe": { "type": "object" }, + "podSecurityContext": { "type": "object" }, + "securityContext": { "type": "object" }, + "terminationGracePeriodSeconds": { "type": "integer" }, + "podAnnotations": { "type": "object" }, + "podLabels": { "type": "object" }, + "nodeSelector": { "type": "object" }, + "tolerations": { "type": "array" }, + "affinity": { "type": "object" }, + "topologySpreadConstraints": { "type": "array" }, + "priorityClassName": { "type": "string" } + } +} diff --git a/deploy/charts/marimo-operator/values.yaml b/deploy/charts/marimo-operator/values.yaml new file mode 100644 index 0000000..0b4d45f --- /dev/null +++ b/deploy/charts/marimo-operator/values.yaml @@ -0,0 +1,118 @@ +# Number of operator replicas. Leader election elects a single active instance. +replicaCount: 1 + +image: + repository: ghcr.io/marimo-team/marimo-operator + # Overrides the image tag. Defaults to the chart appVersion when empty. + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Create the controller ServiceAccount. + create: true + # Name of the ServiceAccount. Generated from the fullname when empty. + name: "" + annotations: {} + +rbac: + # Create the ClusterRole/Role and bindings the operator requires. + create: true + +leaderElection: + # Enable leader election (recommended; required for HA with replicaCount > 1). + enabled: true + +healthProbe: + bindAddress: ":8081" + +metrics: + # Serve the controller-manager metrics endpoint. + enabled: true + bindAddress: ":8443" + # Serve metrics over HTTPS with authn/authz. + secure: true + enableHTTP2: false + service: + type: ClusterIP + port: 8443 + serviceMonitor: + # Create a Prometheus Operator ServiceMonitor (requires the monitoring.coreos.com CRDs). + enabled: false + labels: {} + interval: "" + scrapeTimeout: "" + insecureSkipVerify: true + tlsConfig: {} + +networkPolicy: + # Restrict metrics endpoint ingress to namespaces labelled metrics=enabled. + enabled: false + metricsPort: 8443 + fromNamespaceLabels: + metrics: enabled + +# Images the operator injects into the workloads it manages, exposed as env on the controller. +operatorImages: + initImage: "busybox:1.36" + gitImage: "alpine/git:latest" + alpineImage: "alpine:latest" + s3fsImage: "ghcr.io/marimo-team/marimo-operator/s3fs:latest" + +# Additional container args / env (escape hatches). +extraArgs: [] +extraEnv: [] + # - name: S3_ENDPOINT + # value: "http://example.com" + +extraVolumes: [] +extraVolumeMounts: [] + +resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + +livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + +readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + +podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + +terminationGracePeriodSeconds: 10 + +podAnnotations: {} +podLabels: {} + +nodeSelector: {} +tolerations: [] +affinity: {} +topologySpreadConstraints: [] +priorityClassName: ""