diff --git a/.github/workflows/kubelinter.yaml b/.github/workflows/kubelinter.yaml new file mode 100644 index 00000000..ddfaa766 --- /dev/null +++ b/.github/workflows/kubelinter.yaml @@ -0,0 +1,29 @@ +name: COSI driver + +on: + push: + branches: [main] + pull_request: + branches: ["**"] + +env: + GOPRIVATE: github.com/dell/* + TOKEN: ${{ secrets.GH_DELL_ACCESS }} + +jobs: + kube-linter: + name: Kube Linter + runs-on: ubuntu-latest + steps: + - name: Configure git for private modules + run: | + git config --global url."https://csmbot:$TOKEN@github.com".insteadOf "https://github.com" + echo "machine github.com login csmbot password $TOKEN" >> ~/.netrc + - name: Checkout the code + uses: actions/checkout@v3.6.0 + - name: Scan repo with kube-linter + uses: stackrox/kube-linter-action@v1.0.4 + with: + directory: charts/cosi + config: kubelinter-config.yaml + diff --git a/charts/cosi/Chart.yaml b/charts/cosi/Chart.yaml new file mode 100644 index 00000000..157a687d --- /dev/null +++ b/charts/cosi/Chart.yaml @@ -0,0 +1,36 @@ +# Copyright © 2023 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +apiVersion: v2 +name: cosi +description: Container Object Storage Interface (COSI) Driver for Dell ObjectScale + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +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.1.0 + +# 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 +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: 1.0.0-alpha diff --git a/charts/cosi/templates/NOTES.txt b/charts/cosi/templates/NOTES.txt new file mode 100644 index 00000000..dd78f413 --- /dev/null +++ b/charts/cosi/templates/NOTES.txt @@ -0,0 +1,5 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +For more information visit CSM documentation: https://dell.github.io/csm-docs/ diff --git a/charts/cosi/templates/_helpers.tpl b/charts/cosi/templates/_helpers.tpl new file mode 100644 index 00000000..9181052c --- /dev/null +++ b/charts/cosi/templates/_helpers.tpl @@ -0,0 +1,183 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cosi.name" }} + {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cosi.fullname" }} + {{- if .Values.fullnameOverride }} + {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} + {{- else }} + {{- $name := default .Chart.Name .Values.nameOverride }} + {{- if contains $name .Release.Name }} + {{- .Release.Name | trunc 63 | trimSuffix "-" }} + {{- else }} + {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} + {{- end }} + {{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cosi.chart" }} + {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +# COSI driver log level +# Possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +# Default value: 4 +*/}} +{{- define "cosi.logLevel" }} + {{- $logLevelValues := list 0 1 2 3 4 5 6 7 8 9 10 }} + {{- if (has .Values.provisioner.logLevel $logLevelValues) }} + {{- .Values.provisioner.logLevel }} + {{- else }} + {{- 4 }} + {{- end }} +{{- end }} + +{{/* +# COSI driver sidecar log level +# Values are set to the integer value, higher value means more verbose logging +*/}} +{{- define "cosi.provisionerSidecarVerbosity" }} + {{- if (kindIs "int" .Values.sidecar.verbosity) }} + {{- .Values.sidecar.verbosity }} + {{- else }} + {{- 5 }} + {{- end }} +{{- end }} + +{{/* +# COSI driver log format +# Possible values: "json" "text" +# Default value: "json" +*/}} +{{- define "cosi.logFormat" }} + {{- $logFormatValues := list "json" "text" }} + {{- if (has .Values.provisioner.logFormat $logFormatValues) }} + {{- .Values.provisioner.logFormat }} + {{- else }} + {{- "text" }} + {{- end }} +{{- end }} + +{{/* +# COSI driver OTEL endpoint +# Default value is left empty on purpose, to not start any tracing if no argument was provided. +# Default value: "" +*/}} +{{- define "cosi.otelEndpoint" }} + {{- if .Values.provisioner.otelEndpoint }} + {{- .Values.provisioner.otelEndpoint }} + {{- else }} + {{- "" }} + {{- end }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cosi.labels" }} +helm.sh/chart: {{ include "cosi.chart" . }} +{{- include "cosi.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cosi.selectorLabels" }} +app.kubernetes.io/name: {{ include "cosi.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the role to use +*/}} +{{- define "cosi.roleName" }} + {{- if and .Values.rbac.create }} + {{- default (printf "%s" (include "cosi.fullname" .)) .Values.rbac.role.name }} + {{- else }} + {{- .Values.rbac.role.name }} + {{- end }} +{{- end }} + +{{/* +Create the name of the role binding to use +*/}} +{{- define "cosi.roleBindingName" }} + {{- if and .Values.rbac.create }} + {{- default (printf "%s" (include "cosi.fullname" .)) .Values.rbac.roleBinding.name }} + {{- else }} + {{- .Values.rbac.roleBinding.name }} + {{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cosi.serviceAccountName" -}} + {{- if .Values.serviceAccount.create -}} + {{ default (include "cosi.fullname" .) .Values.serviceAccount.name }} + {{- else -}} + {{ default "default" .Values.serviceAccount.name }} + {{- end -}} +{{- end -}} + +{{/* +Create the name of provisioner container +*/}} +{{- define "cosi.provisionerContainerName" }} + {{- default "objectstorage-provisioner" .Values.provisioner.name }} +{{- end }} + +{{/* +Create the name of provisioner sidecar container +*/}} +{{- define "cosi.provisionerSidecarContainerName" }} + {{- default "objectstorage-provisioner-sidecar" .Values.sidecar.name }} +{{- end }} + +{{/* +Create the full name of provisioner image from repository and tag +*/}} +{{- define "cosi.provisionerImageName" }} + {{- .Values.provisioner.image.repository }}:{{ .Values.provisioner.image.tag | default .Chart.AppVersion }} +{{- end }} + +{{/* +Create the full name of provisioner sidecar image from repository and tag +*/}} +{{- define "cosi.provisionerSidecarImageName" }} + {{- .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }} +{{- end }} + +{{/* +Create the secret name +*/}} +{{- define "cosi.secretName" }} + {{- if .Values.configuration.create }} + {{- default (printf "%s-config" (include "cosi.name" . )) .Values.configuration.secretName }} + {{- else }} + {{- .Values.configuration.secretName }} + {{- end }} +{{- end }} + +{{/* +Create the name for secret volume +*/}} +{{- define "cosi.secretVolumeName" }} + {{- printf "%s-config" (include "cosi.name" . ) }} +{{- end }} diff --git a/charts/cosi/templates/deployment.yaml b/charts/cosi/templates/deployment.yaml new file mode 100644 index 00000000..29bf0511 --- /dev/null +++ b/charts/cosi/templates/deployment.yaml @@ -0,0 +1,90 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cosi.fullname" . }} + labels: + {{- include "cosi.labels" . | trim | nindent 4 }} + {{- with .Values.rbac.role.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "cosi.selectorLabels" . | trim | nindent 6 }} + template: + metadata: + labels: + {{- include "cosi.labels" . | trim | nindent 8 }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "cosi.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "cosi.provisionerContainerName" . }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: {{ include "cosi.provisionerImageName" . }} + imagePullPolicy: {{ .Values.provisioner.image.pullPolicy }} + args: + - "--log-level={{ include "cosi.logLevel" . }}" + - "--log-format={{ include "cosi.logFormat" . }}" + - "--otel-endpoint={{ include "cosi.otelEndpoint" . }}" + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ include "cosi.secretVolumeName" . }} + mountPath: /cosi + - name: cosi-socket-dir + mountPath: /var/lib/cosi + - name: {{ include "cosi.provisionerSidecarContainerName" . }} + image: {{ include "cosi.provisionerSidecarImageName" . }} + imagePullPolicy: {{ .Values.sidecar.image.pullPolicy }} + args: + - "-v={{ include "cosi.provisionerSidecarVerbosity" . }}" + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: cosi-socket-dir + mountPath: /var/lib/cosi + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: {{ include "cosi.secretVolumeName" . }} + secret: + secretName: {{ include "cosi.secretName" . }} + - name: cosi-socket-dir + emptyDir: {} diff --git a/charts/cosi/templates/role.yaml b/charts/cosi/templates/role.yaml new file mode 100644 index 00000000..7a76974d --- /dev/null +++ b/charts/cosi/templates/role.yaml @@ -0,0 +1,52 @@ +{{- if .Values.rbac.create -}} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cosi.roleName" . }} + labels: + {{- include "cosi.labels" . | trim | nindent 4 }} + {{- with .Values.rbac.roleBinding.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +rules: +- apiGroups: + - objectstorage.k8s.io # COSI resources are grouped here + resources: # we do not add bucketclasses here, as those are managed by COSI Controller + - buckets + - bucketclaims + - bucketaccesses + - bucketaccessclasses + - buckets/status + - bucketaccesses/status + - bucketclaims/status + - bucketaccessclasses/status + verbs: # CRUD + list/watch + - create + - get + - update + - delete + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases # lease is created during leader election process by COSI Provisioner Sidecar + verbs: # CRUD + list/watch + - create + - get + - update + - delete + - list + - watch +- apiGroups: + - "" # empty for default API group + resources: + - events # events are emmited from COSI Provisioner Sidecar + - secrets # secrets are created by COSI Provisioner Sidecar as a part of access granting + verbs: # CRUD + - create + - get + - update + - delete +{{- end }} diff --git a/charts/cosi/templates/rolebinding.yaml b/charts/cosi/templates/rolebinding.yaml new file mode 100644 index 00000000..632dedb6 --- /dev/null +++ b/charts/cosi/templates/rolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create -}} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cosi.roleBindingName" . }} + labels: + {{- include "cosi.labels" . | trim | nindent 4 }} + {{- with .Values.rbac.roleBinding.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +subjects: + - kind: ServiceAccount + name: {{ include "cosi.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "cosi.roleName" . }} + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/charts/cosi/templates/secret.yaml b/charts/cosi/templates/secret.yaml new file mode 100644 index 00000000..62df6eaf --- /dev/null +++ b/charts/cosi/templates/secret.yaml @@ -0,0 +1,15 @@ +{{- if .Values.configuration.create }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ include "cosi.secretName" . }} + labels: + {{- include "cosi.labels" . | trim | nindent 4 }} + {{- with .Values.configuration.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +data: + config.yaml: {{ toString .Values.configuration.data | b64enc }} +{{- end }} diff --git a/charts/cosi/templates/serviceaccount.yaml b/charts/cosi/templates/serviceaccount.yaml new file mode 100644 index 00000000..6b85d83f --- /dev/null +++ b/charts/cosi/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{ if .Values.rbac.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cosi.serviceAccountName" . }} + labels: + {{- include "cosi.labels" . | trim | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end}} diff --git a/charts/cosi/values.yaml b/charts/cosi/values.yaml new file mode 100644 index 00000000..50d1e4cb --- /dev/null +++ b/charts/cosi/values.yaml @@ -0,0 +1,153 @@ +# Copyright © 2023 Dell Inc. or its subsidiaries. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +# Default values for cosi. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# provisioner specifies parameters for the COSI driver provisioner container. +provisioner: + # name of the COSI driver provisioner container. + name: "objectstorage-provisioner" + # logLevel is the logging level for the COSI driver provisioner, + # Possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + logLevel: 4 + # logFormat is the logging format for the COSI driver provisioner, + # Possible values: "json" "text". + logFormat: "text" + # otelEndpoint specifies the endpoint on which the OTEL Collector is set up and to which data is sent over gRPC. + otelEndpoint: "otel-collector.namespace:4317" + # image specifies the COSI driver provisioner container image. + image: + # repository is the COSI driver provisioner container image repository. + repository: "docker.io/dell/cosi" + # tag is the COSI driver provisioner container image tag. + tag: "v0.1.0" + # pullPolicy is the COSI driver provisioner container image pull policy. + pullPolicy: "IfNotPresent" + +# sidecar specifies parameters for the COSI driver sidecar container. +sidecar: + # name of the COSI driver sidecar container. + name: "objectstorage-provisioner-sidecar" + # verbosity is the logging verbosity for the COSI driver sidecar, higher values are more verbose, + # Possible values: integers from -2,147,483,648 to 2,147,483,647 + # + # Generally the range used is between -4 and 12. However, there may be cases where numbers outside + # that range might provide more information. + # For additional information, refer to the cosi sidecar documentation: + # - https://github.com/kubernetes-sigs/container-object-storage-interface-provisioner-sidecar + verbosity: 5 + # image specifies the COSI driver sidecar container image. + image: + # repository is the COSI driver sidecar container image repository. + repository: "gcr.io/k8s-staging-sig-storage/objectstorage-sidecar/objectstorage-sidecar" + # tag is the COSI driver sidecar container image tag. + tag: "v20230130-v0.1.0-24-gc0cf995" + # pullPolicy is the COSI driver sidecar container image pull policy. + pullPolicy: "IfNotPresent" + +# configuration of the driver can be set with with --set-file configuration.data=path/to/config.yaml +# or created manually and provided with --set configuration.secretName=existing-secret-name +configuration: + # Specifies whether a secret with driver configuration should be created + # If set to false, you must set `configuration.secretName` field to an existing configuration secret name. + create: true + annotations: {} + # name can be used to specify an existing secret name to use for the driver configuration or override the generated name (default `cosi`). + secretName: "" + # data should be provided when installing chart, it will be used to create the Secret with the driver configuration. + # `configuration.create` must be set to `true` for this to work. + data: "" + +# rbac specifies parameters for the COSI driver RBAC resources. +rbac: + # create specifies whether RBAC resources should be created. + create: true + # role specifies parameters for the COSI driver Role. + role: + # annotations to add to the Role resource + annotations: {} + # name of the Role to create (efault `cosi-role`). + name: "" + # roleBinding specifies parameters for the COSI driver RoleBinding. + roleBinding: + # Annotations to add to the RoleBinding + annotations: {} + # name of the RoleBinding to create (default `cosi-rolebinding`). + name: "" + +# serviceAccount specifies parameters for the COSI driver ServiceAccount. +serviceAccount: + # Specifies whether a ServiceAccount should be created + create: true + # Annotations to add to the ServiceAccount + annotations: {} + # The name of the ServiceAccount to create (or just use if `rbac.create=false`, default `cosi-sa`). + name: "" + +# replicaCount specifies the number of replicas of the COSI driver. +replicaCount: 1 +# nameOverride specifies the name override for the COSI driver installation (default is `name` form `Chart.yaml`). +nameOverride: "" +# fullnameOverride specifies the full name override for the COSI driver installation (generated based on release name). +fullnameOverride: "" +# podAnnotations specifies the list of annotations to add to the COSI driver pod. +podAnnotations: {} +# imagePullSecrets specifies the list of image pull secrets. +imagePullSecrets: [] + +# podSecurityContext specifies the security context for the COSI driver pod. +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + # fsGroup: 2000 + +# securityContext specifies the security context for the COSI driver containers. +securityContext: + readOnlyRootFilesystem: true + # capabilities: + # drop: + # - ALL + +# resources specifies the resource limits and requests for the COSI driver containers. +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# autoscaling rules for COSI driver deployment. +autoscaling: + # enabled specifies whether autoscaling is enabled. + enabled: false + # minReplicas specifies the minimum number of replicas. + minReplicas: 1 + # maxReplicas specifies the maximum number of replicas. + maxReplicas: 100 + # targetCPUUtilizationPercentage specifies the target CPU utilization percentage. + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage specifies the target memory utilization percentage. + # targetMemoryUtilizationPercentage: 80 + +# nodeSelector specifies the node selector for the COSI driver pod. +nodeSelector: {} +# tolerations specifies the list of tolerations for the COSI driver pod. +tolerations: [] +# affinity specifies the affinity for the COSI driver pod. +affinity: {} diff --git a/kubelinter-config.yaml b/kubelinter-config.yaml new file mode 100644 index 00000000..9e79fd83 --- /dev/null +++ b/kubelinter-config.yaml @@ -0,0 +1,69 @@ +checks: + # NOTE: Include all checks, comment failing + include: + - "access-to-create-pods" + # - "access-to-secrets" + - "cluster-admin-role-binding" + - "dangling-horizontalpodautoscaler" + - "dangling-ingress" + - "dangling-networkpolicy" + - "dangling-networkpolicypeer-podselector" + - "dangling-service" + - "default-service-account" + - "deprecated-service-account-field" + # - "dnsconfig-options" + - "docker-sock" + - "drop-net-raw-capability" + - "duplicate-env-var" + - "env-var-secret" + - "exposed-services" + - "host-ipc" + - "host-network" + - "host-pid" + - "hpa-minimum-three-replicas" + - "invalid-target-ports" + - "latest-tag" + # - "minimum-three-replicas" + - "mismatching-selector" + - "no-anti-affinity" + - "no-extensions-v1beta" + # - "no-liveness-probe" + # - "no-node-affinity" + - "no-read-only-root-fs" + # - "no-readiness-probe" + # - "no-rolling-update-strategy" + - "non-existent-service-account" + # - "non-isolated-pod" + - "privilege-escalation-container" + - "privileged-container" + - "privileged-ports" + - "read-secret-from-env-var" + # - "required-annotation-email" + # - "required-label-owner" + - "run-as-non-root" + - "sensitive-host-mounts" + - "ssh-port" + - "unsafe-proc-mount" + - "unsafe-sysctls" + # - "unset-cpu-requirements" + # - "unset-memory-requirements" + # - "use-namespace" + - "wildcard-in-rules" + - "writable-host-mount" + + # NOTE: manually exclude failing for documentation, fix them in future or + # comment why are they disabled. + exclude: + - "access-to-secrets" # NOTE: COSI Provisioner Sidecar requires access to secrets + - "dnsconfig-options" + - "minimum-three-replicas" + - "no-liveness-probe" + - "no-node-affinity" + - "no-readiness-probe" + - "no-rolling-update-strategy" + - "non-isolated-pod" + - "required-annotation-email" + - "required-label-owner" + - "unset-cpu-requirements" + - "unset-memory-requirements" + - "use-namespace"