From d91b95bb294719c27fe15cb2b1a15fad791094b4 Mon Sep 17 00:00:00 2001 From: topher Date: Wed, 13 Dec 2023 10:44:41 -0700 Subject: [PATCH 01/42] first run at adding cas --- .pre-commit-config.yaml | 4 + helm/fiftyone-teams-app/README.md | 38 ++++++- .../fiftyone-teams-app/templates/_helpers.tpl | 87 ++++++++++++++ .../templates/cas-deployment.yaml | 74 ++++++++++++ .../templates/cas-service.yaml | 23 ++++ .../fiftyone-teams-app/templates/ingress.yaml | 17 --- helm/fiftyone-teams-app/values.yaml | 107 +++++++++++++++--- 7 files changed, 314 insertions(+), 36 deletions(-) create mode 100644 helm/fiftyone-teams-app/templates/cas-deployment.yaml create mode 100644 helm/fiftyone-teams-app/templates/cas-service.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 731a3507..6fc91434 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,6 +57,10 @@ repos: - docker/README.md - helm/fiftyone-teams-app/README.md.gotmpl - helm/README.md + - repo: https://github.com/gruntwork-io/pre-commit + rev: v0.1.23 + hooks: + - id: helmlint - repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks: diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 690afe02..36407e4d 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -323,16 +323,46 @@ appSettings: | appSettings.tolerations | list | `[]` | Allow the k8s scheduler to schedule fiftyone-app pods with matching taints. [Reference][taints-and-tolerations]. | | appSettings.volumeMounts | list | `[]` | Volume mounts for fiftyone-app. [Reference][volumes]. | | appSettings.volumes | list | `[]` | Volumes for fiftyone-app. [Reference][volumes]. | +| casSettings.affinity | object | `{}` | Affinity and anti-affinity for teams-cas. [Reference][affinity]. | +| casSettings.databaseName | string | `"cas"` | Provide the name for the CAS Database | +| casSettings.env | object | `{}` | | +| casSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | +| casSettings.image.repository | string | `"voxel51/teams-cas"` | Container image for teams-cas. | +| casSettings.image.tag | string | `""` | Image tag for teams-cas. Defaults to the chart version. | +| casSettings.mongodbUriKey | string | `"mongodbConnectionString"` | Provide the key from secret.fiftyone for the CAS MongoDB Connection String | +| casSettings.nodeSelector | object | `{}` | nodeSelector for teams-cas. [Reference][node-selector]. | +| casSettings.podAnnotations | object | `{}` | Annotations for pods for teams-cas. [Reference][annotations]. | +| casSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for teams-cas. [Reference][security-context]. | +| casSettings.replicaCount | int | `2` | Number of pods in the teams-cas deployment's ReplicaSet. [Reference][deployment]. | +| casSettings.resources | object | `{"limits":{},"requests":{}}` | Container resource requests and limits for teams-cas. [Reference][resources]. | +| casSettings.securityContext | object | `{}` | Container security configuration for teams-cas. [Reference][container-security-context]. | +| casSettings.service.annotations | object | `{}` | Service annotations for teams-cas. [Reference][annotations]. | +| casSettings.service.containerPort | int | `3000` | Service container port for teams-cas. | +| casSettings.service.liveness.initialDelaySeconds | int | `15` | Number of seconds to wait before performing the liveness probe for fiftyone-app. [Reference][probes]. | +| casSettings.service.name | string | `"teams-cas"` | Service name. | +| casSettings.service.nodePort | int | `nil` | Service nodePort set only when `casSettings.service.type: NodePort` for teams-cas. | +| casSettings.service.port | int | `80` | Service port. | +| casSettings.service.readiness.initialDelaySeconds | int | `15` | Number of seconds to wait before performing the readiness probe for fiftyone-app. [Reference][probes]. | +| casSettings.service.shortname | string | `"teams-cas"` | Port name (maximum length is 15 characters) for teams-cas. [Reference][ports]. | +| casSettings.service.type | string | `"ClusterIP"` | Service type for teams-cas. [Reference][service-type]. | +| casSettings.tolerations | list | `[]` | Allow the k8s scheduler to schedule teams-cas pods with matching taints. [Reference][taints-and-tolerations]. | +| casSettings.volumeMounts | list | `[]` | Volume mounts for teams-cas. [Reference][volumes]. | +| casSettings.volumes | list | `[]` | Volumes for teams-cas. [Reference][volumes]. | | imagePullSecrets | list | `[]` | Container image registry keys. [Reference][image-pull-secrets]. | | ingress.annotations | object | `{}` | Ingress annotations. [Reference][annotations]. | | ingress.api | object | `{"path":"/*","pathType":"ImplementationSpecific"}` | The ingress rule values for teams-api, when `apiSettings.dnsName` is not empty. [Reference][ingress-rules]. | | ingress.className | string | `""` | Name of the ingress class. When empty, a default Ingress class should be defined. When not empty and Kubernetes version is >1.18.0, this value will be the Ingress class name. [Reference][ingress-default-ingress-class] | | ingress.enabled | bool | `true` | Controls whether to create the ingress. When `false`, uses a pre-existing ingress. [Reference][ingress]. | | ingress.labels | object | `{}` | Additional labels for the ingress. [Reference][labels-and-selectors]. | -| ingress.paths | list | `[]` | Additional ingress rules for the host `teamsAppSettings.dnsName` for the chart managed ingress (when `ingress.enabled: true`). [Reference][ingress-rules]. | -| ingress.teamsApp | object | `{"path":"/*","pathType":"ImplementationSpecific"}` | The ingress rule path values for teams-app. [Reference][ingress-rules]. | -| ingress.teamsApp.path | string | `"/*"` | Path for the FiftyOne Teams App service | -| ingress.teamsApp.pathType | string | `"ImplementationSpecific"` | Ingress path type (ImplementationSpecific, Exact, Prefix) | +| ingress.paths | list | `[{"path":"/cas","pathType":"Prefix","serviceName":"teams-cas","servicePort":80},{"path":"/*","pathType":"ImplementationSpecific","serviceName":"teams-app","servicePort":80}]` | Additional ingress rules for the host `teamsAppSettings.dnsName` for the chart managed ingress (when `ingress.enabled: true`). [Reference][ingress-rules]. | +| ingress.paths[0] | object | `{"path":"/cas","pathType":"Prefix","serviceName":"teams-cas","servicePort":80}` | Ingress path for teams-cas | +| ingress.paths[0].pathType | string | `"Prefix"` | Ingress path type | +| ingress.paths[0].serviceName | string | `"teams-cas"` | Ingress path service name | +| ingress.paths[0].servicePort | int | `80` | Ingress path service port | +| ingress.paths[1] | object | `{"path":"/*","pathType":"ImplementationSpecific","serviceName":"teams-app","servicePort":80}` | Ingress path for teams-app | +| ingress.paths[1].pathType | string | `"ImplementationSpecific"` | Ingress path type | +| ingress.paths[1].serviceName | string | `"teams-app"` | Ingress path service name | +| ingress.paths[1].servicePort | int | `80` | Ingress path service port | | ingress.tlsEnabled | bool | `true` | Controls whether the chart managed ingress contains a `spec.tls` stanza. | | ingress.tlsSecretName | string | `"fiftyone-teams-tls-secret"` | Name of secret containing TLS certificate for teams-app. Certificate should contain the host names `apiSettings.dnsName` and `teamsAppSettings.dnsName`. When `ingress.tlsEnabled=True`, sets's the value of ingress' `spec.tls[0].secretName`. | | namespace.create | bool | `false` | Controls whether to create the namespace. When `false`, the namespace must already exists. | diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index b7ed875e..280c0c8a 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -34,6 +34,17 @@ Create a default name for the fiftyone app service {{- end }} {{- end }} +{{/* +Create a default name for the teams cas service +*/}} +{{- define "teams-cas.name" -}} +{{- if .Values.casSettings.service.name }} +{{- .Values.casSettings.service.name | trunc 63 | trimSuffix "-" }} +{{- else }} +"fiftyone-teams-cas" +{{- end }} +{{- end }} + {{/* Create a default name for the teams api service */}} @@ -118,6 +129,22 @@ APP Combined labels {{ include "fiftyone-app.selectorLabels" . }} {{- end }} +{{/* +CAS Selector labels +*/}} +{{- define "fiftyone-teams-cas.selectorLabels" -}} +app.kubernetes.io/name: {{ include "teams-cas.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +CAS Combined labels +*/}} +{{- define "fiftyone-teams-cas.labels" -}} +{{ include "fiftyone-teams-app.commonLabels" . }} +{{ include "fiftyone-teams-cas.selectorLabels" . }} +{{- end }} + {{/* Plugins Selector labels */}} @@ -206,6 +233,13 @@ Create a merged list of environment variables for fiftyone-teams-api secretKeyRef: name: {{ $secretName }} key: fiftyoneDatabaseName +- name: CAS_BASE_URL + value: {{ printf "http://%s:%.0f/cas/api" .Values.casSettings.service.name .Values.casSettings.service.port | quote }} +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret {{- range $key, $val := .Values.apiSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -251,12 +285,52 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: organizationId +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret {{- range $key, $val := .Values.appSettings.env }} - name: {{ $key }} value: {{ $val | quote }} {{- end }} {{- end -}} +{{/* +Create a merged list of environment variables for fiftyone-teams-cas +*/}} +{{- define "teams-cas.env-vars-list" -}} +{{- $secretName := .Values.secret.name }} +- name: CAS_DATABASE_NAME + value: {{ .Values.casSettings.databaseName }} +- name: CAS_MONGODB_URI + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ .Values.casSettings.mongodbUriKey }} +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret +- name: NEXTAUTH_URL + value: {{ printf "https://%s/cas/api/auth" .Values.teamsAppSettings.dnsName | quote }} +- name: TEAMS_API_DATABASE_NAME + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneDatabaseName +- name: TEAMS_API_MONGODB_URI + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: mongodbConnectionString +{{- range $key, $val := .Values.casSettings.env }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end }} +{{- end -}} + {{/* Create a merged list of environment variables for fiftyone-teams-plugins */}} @@ -298,6 +372,11 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: organizationId +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret {{- range $key, $val := .Values.pluginsSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -351,6 +430,11 @@ Create a merged list of environment variables for fiftyone-teams-app {{- else }} value: {{ printf "https://%s" .Values.teamsAppSettings.dnsName }} {{- end }} +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret - name: FIFTYONE_SERVER_ADDRESS value: "" - name: FIFTYONE_SERVER_PATH_PREFIX @@ -363,6 +447,9 @@ Create a merged list of environment variables for fiftyone-teams-app {{- else }} value: {{ printf "http://%s:%.0f" .Values.appSettings.service.name .Values.appSettings.service.port | quote }} {{- end }} +- name: NEXTAUTH_BASEPATH + value: "/cas/api/auth" + {{- range $key, $val := .Values.teamsAppSettings.env }} - name: {{ $key }} value: {{ $val | quote }} diff --git a/helm/fiftyone-teams-app/templates/cas-deployment.yaml b/helm/fiftyone-teams-app/templates/cas-deployment.yaml new file mode 100644 index 00000000..862cd154 --- /dev/null +++ b/helm/fiftyone-teams-app/templates/cas-deployment.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "teams-cas.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "fiftyone-teams-cas.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.casSettings.replicaCount | default 2 }} + selector: + matchLabels: + {{- include "fiftyone-teams-cas.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.casSettings.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "fiftyone-teams-cas.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.casSettings.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "teams-cas.name" . }} + securityContext: + {{- toYaml .Values.casSettings.securityContext | nindent 12 }} + image: "{{ .Values.casSettings.image.repository }}:{{ .Values.casSettings.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.casSettings.image.pullPolicy | default "Always" }} + env: + {{- include "teams-cas.env-vars-list" . | indent 12 }} + ports: + - name: {{ .Values.casSettings.service.shortname }} + containerPort: {{ .Values.casSettings.service.containerPort | default 3000 }} + protocol: TCP + livenessProbe: + httpGet: + path: /cas/api + port: {{ .Values.casSettings.service.shortname }} + initialDelaySeconds: {{ .Values.casSettings.service.liveness.initialDelaySeconds }} + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /cas/api + port: {{ .Values.casSettings.service.shortname }} + initialDelaySeconds: {{ .Values.casSettings.service.readiness.initialDelaySeconds }} + timeoutSeconds: 5 + resources: + {{- toYaml .Values.casSettings.resources | nindent 12 }} + {{- with .Values.casSettings.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.casSettings.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.casSettings.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.casSettings.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.casSettings.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/fiftyone-teams-app/templates/cas-service.yaml b/helm/fiftyone-teams-app/templates/cas-service.yaml new file mode 100644 index 00000000..fc277c50 --- /dev/null +++ b/helm/fiftyone-teams-app/templates/cas-service.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.casSettings.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "teams-cas.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "fiftyone-teams-cas.labels" . | nindent 4 }} +spec: + type: {{ .Values.casSettings.service.type }} + ports: + - port: {{ .Values.casSettings.service.port }} + targetPort: {{ .Values.casSettings.service.containerPort | default 3000 }} + protocol: TCP + name: http + {{- if and (eq .Values.casSettings.service.type "NodePort") (.Values.casSettings.service.nodePort) }} + nodePort: {{ .Values.casSettings.service.nodePort }} + {{- end }} + selector: + {{- include "fiftyone-teams-cas.selectorLabels" . | nindent 4 }} diff --git a/helm/fiftyone-teams-app/templates/ingress.yaml b/helm/fiftyone-teams-app/templates/ingress.yaml index e153e6c2..a07bf09b 100644 --- a/helm/fiftyone-teams-app/templates/ingress.yaml +++ b/helm/fiftyone-teams-app/templates/ingress.yaml @@ -59,23 +59,6 @@ spec: servicePort: {{ .servicePort }} {{- end }} {{- end }} - {{- else if and .Values.ingress.paths (eq (len .Values.ingress.paths) 1) }} - {{- range .Values.ingress.paths }} - - path: {{ .path }} - {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ include "teams-app.name" $ }} - port: - number: {{ $.Values.teamsAppSettings.service.port }} - {{- else }} - serviceName: {{ include "teams-app.name" $ }} - servicePort: {{ $.Values.teamsAppSettings.service.port }} - {{- end }} - {{- end }} {{- else }} - path: {{ .Values.ingress.teamsApp.path }} {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 5ec36355..523b0622 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -184,6 +184,80 @@ appSettings: # -- Volumes for fiftyone-app. [Reference][volumes]. volumes: [] +# Central Authentication Service (teams-cas) configurations +casSettings: + # -- Provide the name for the CAS Database + databaseName: cas + # Environment Variables are passed to the teams-cas containers + env: {} + image: + # -- Instruct when the kubelet should pull (download) the specified image. + # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. + pullPolicy: Always + # -- Container image for teams-cas. + repository: voxel51/teams-cas + # -- Image tag for teams-cas. Defaults to the chart version. + tag: "" + + # -- Provide the key from secret.fiftyone for the CAS MongoDB Connection String + mongodbUriKey: mongodbConnectionString + + # -- Number of pods in the teams-cas deployment's ReplicaSet. [Reference][deployment]. + replicaCount: 2 + + # 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:'. + # -- Container resource requests and limits for teams-cas. [Reference][resources]. + resources: + limits: {} + # cpu: 2 + # memory: 6Gi + requests: {} + # cpu: 500m + # memory: 512Mi + service: + # -- Service annotations for teams-cas. [Reference][annotations]. + annotations: {} + # -- Service container port for teams-cas. + containerPort: 3000 + liveness: + # -- Number of seconds to wait before performing the liveness probe for fiftyone-app. [Reference][probes]. + initialDelaySeconds: 15 + # -- Service name. + name: teams-cas + # -- (int) Service nodePort set only when `casSettings.service.type: NodePort` for teams-cas. + nodePort: + # -- Service port. + port: 80 + readiness: + # -- Number of seconds to wait before performing the readiness probe for fiftyone-app. [Reference][probes]. + initialDelaySeconds: 15 + # TODO: Kevin/topher - Consider changing name from `shortname` to `portName`. + # -- Port name (maximum length is 15 characters) for teams-cas. [Reference][ports]. + shortname: teams-cas + # -- Service type for teams-cas. [Reference][service-type]. + type: ClusterIP + + # -- Affinity and anti-affinity for teams-cas. [Reference][affinity]. + affinity: {} + # -- nodeSelector for teams-cas. [Reference][node-selector]. + nodeSelector: {} + # -- Annotations for pods for teams-cas. [Reference][annotations]. + podAnnotations: {} + # -- Pod-level security attributes and common container settings for teams-cas. [Reference][security-context]. + podSecurityContext: {} + # TODO: Kevin/topher - Consider renaming `securityContext` to `containerSecurityContext + # -- Container security configuration for teams-cas. [Reference][container-security-context]. + securityContext: {} + # -- Allow the k8s scheduler to schedule teams-cas pods with matching taints. [Reference][taints-and-tolerations]. + tolerations: [] + # -- Volume mounts for teams-cas. [Reference][volumes]. + volumeMounts: [] + # -- Volumes for teams-cas. [Reference][volumes]. + volumes: [] + # -- Container image registry keys. [Reference][image-pull-secrets]. imagePullSecrets: [] @@ -210,21 +284,24 @@ ingress: # -- Additional ingress rules for the host `teamsAppSettings.dnsName` for # the chart managed ingress (when `ingress.enabled: true`). # [Reference][ingress-rules]. - paths: [] - # -- Ingress path - # - path: "" - # -- Ingress path type - # pathType: "" - # -- Ingress path service name - # serviceName: "" - # -- (int) Ingress path service port - # servicePort: - # -- The ingress rule path values for teams-app. [Reference][ingress-rules]. - teamsApp: - # -- Path for the FiftyOne Teams App service - path: /* - # -- Ingress path type (ImplementationSpecific, Exact, Prefix) - pathType: ImplementationSpecific + paths: + # -- Ingress path for teams-cas + - path: /cas + # -- Ingress path type + pathType: Prefix + # -- Ingress path service name + serviceName: teams-cas + # -- (int) Ingress path service port + servicePort: 80 + # -- Ingress path for teams-app + - path: /* + # -- Ingress path type + pathType: ImplementationSpecific + # -- Ingress path service name + serviceName: teams-app + # -- (int) Ingress path service port + servicePort: 80 + # -- Controls whether the chart managed ingress contains a `spec.tls` stanza. tlsEnabled: true From 00e9c80649b3a666c4a08b0fc1cc33c40c8fac57 Mon Sep 17 00:00:00 2001 From: topher Date: Wed, 21 Feb 2024 13:55:03 -0500 Subject: [PATCH 02/42] Add new configuration for CAS "no legacy" mode (#86) * refactor CAS for no-legacy deployment * fiftyone-app and teams-plugins too * `NEXTAUTH_URL` is back! --- helm/fiftyone-teams-app/README.md | 10 +- .../fiftyone-teams-app/templates/_helpers.tpl | 100 ++++++++++++------ helm/fiftyone-teams-app/values.yaml | 25 +++-- 3 files changed, 96 insertions(+), 39 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 36407e4d..67e17d87 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -324,12 +324,15 @@ appSettings: | appSettings.volumeMounts | list | `[]` | Volume mounts for fiftyone-app. [Reference][volumes]. | | appSettings.volumes | list | `[]` | Volumes for fiftyone-app. [Reference][volumes]. | | casSettings.affinity | object | `{}` | Affinity and anti-affinity for teams-cas. [Reference][affinity]. | -| casSettings.databaseName | string | `"cas"` | Provide the name for the CAS Database | -| casSettings.env | object | `{}` | | +| casSettings.env.CAS_DATABASE_NAME | string | `"cas"` | Provide the name for the CAS database | +| casSettings.env.CAS_DEFAULT_USER_ROLE | string | `"GUEST"` | Set the default user role for new users One of `GUEST`, `COLLABORATOR`, `MEMBER`, `ADMIN` | +| casSettings.env.CAS_LOG_LEVEL | string | `"INFO"` | Set the CAS Log Level One of `DEBUG`, `INFO`, `WARN`, `ERROR` | +| casSettings.env.CAS_MONGODB_URI_KEY | string | `"mongodbConnectionString"` | The key from `secret.fiftyone` that contains the CAS MongoDB Connection String. | +| casSettings.env.ENABLE_LEGACY_MODE | bool | `true` | Toggle CAS Legacy Mode, which continues to use Auth0 integration | +| casSettings.env.FEATURE_FLAG_ENABLE_INVITATIONS | bool | `true` | Allow Admins to invite users by email NOTE: This is not supported when ENABLE_LEGACY_MODE is `false` | | casSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | casSettings.image.repository | string | `"voxel51/teams-cas"` | Container image for teams-cas. | | casSettings.image.tag | string | `""` | Image tag for teams-cas. Defaults to the chart version. | -| casSettings.mongodbUriKey | string | `"mongodbConnectionString"` | Provide the key from secret.fiftyone for the CAS MongoDB Connection String | | casSettings.nodeSelector | object | `{}` | nodeSelector for teams-cas. [Reference][node-selector]. | | casSettings.podAnnotations | object | `{}` | Annotations for pods for teams-cas. [Reference][annotations]. | | casSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for teams-cas. [Reference][security-context]. | @@ -406,6 +409,7 @@ appSettings: | secret.fiftyone.clientSecret | string | `""` | Voxel51-provided Auth0 Client Secret. | | secret.fiftyone.cookieSecret | string | `""` | A randomly generated string for cookie encryption. To generate, run `openssl rand -hex 32`. | | secret.fiftyone.encryptionKey | string | `""` | Encryption key for storage credentials. [Reference][fiftyone-encryption-key]. | +| secret.fiftyone.fiftyoneAuthSecret | string | `""` | A randomly generated string for CAS Authentication. | | secret.fiftyone.fiftyoneDatabaseName | string | `""` | MongoDB Database Name for FiftyOne Teams. | | secret.fiftyone.mongodbConnectionString | string | `""` | MongoDB Connection String. [Reference][mongodb-connection-string]. | | secret.fiftyone.organizationId | string | `""` | Voxel51-provided Auth0 Organization ID. | diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index 280c0c8a..330774ff 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -193,6 +193,7 @@ Create a merged list of environment variables for fiftyone-teams-api */}} {{- define "fiftyone-teams-api.env-vars-list" -}} {{- $secretName := .Values.secret.name }} +{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} - name: AUTH0_API_CLIENT_ID valueFrom: secretKeyRef: @@ -213,6 +214,7 @@ Create a merged list of environment variables for fiftyone-teams-api secretKeyRef: name: {{ $secretName }} key: clientId +{{- end }} - name: FIFTYONE_DATABASE_NAME valueFrom: secretKeyRef: @@ -253,6 +255,11 @@ Create a merged list of environment variables for fiftyone-app {{- $secretName := .Values.secret.name }} - name: API_URL value: {{ printf "http://%s:%.0f" .Values.apiSettings.service.name .Values.apiSettings.service.port | quote }} +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret - name: FIFTYONE_DATABASE_NAME valueFrom: secretKeyRef: @@ -268,11 +275,7 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: encryptionKey -- name: FIFTYONE_TEAMS_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain +{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} - name: FIFTYONE_TEAMS_AUDIENCE value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" - name: FIFTYONE_TEAMS_CLIENT_ID @@ -280,16 +283,17 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: clientId -- name: FIFTYONE_TEAMS_ORGANIZATION +- name: FIFTYONE_TEAMS_DOMAIN valueFrom: secretKeyRef: name: {{ $secretName }} - key: organizationId -- name: FIFTYONE_AUTH_SECRET + key: auth0Domain +- name: FIFTYONE_TEAMS_ORGANIZATION valueFrom: secretKeyRef: name: {{ $secretName }} - key: fiftyoneAuthSecret + key: organizationId +{{- end }} {{- range $key, $val := .Values.appSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -301,20 +305,49 @@ Create a merged list of environment variables for fiftyone-teams-cas */}} {{- define "teams-cas.env-vars-list" -}} {{- $secretName := .Values.secret.name }} -- name: CAS_DATABASE_NAME - value: {{ .Values.casSettings.databaseName }} - name: CAS_MONGODB_URI valueFrom: secretKeyRef: name: {{ $secretName }} - key: {{ .Values.casSettings.mongodbUriKey }} + key: {{ .Values.casSettings.env.CAS_MONGODB_URI_KEY }} - name: FIFTYONE_AUTH_SECRET valueFrom: secretKeyRef: name: {{ $secretName }} key: fiftyoneAuthSecret -- name: NEXTAUTH_URL - value: {{ printf "https://%s/cas/api/auth" .Values.teamsAppSettings.dnsName | quote }} +{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +- name: AUTH0_AUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: clientId +- name: AUTH0_AUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: clientSecret +- name: AUTH0_DOMAIN + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: auth0Domain +- name: AUTH0_ISSUER_BASE_URL + value: "https://$(AUTH0_DOMAIN)" +- name: AUTH0_MGMT_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: apiClientId +- name: AUTH0_MGMT_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: apiClientSecret +- name: AUTH0_ORGANIZATION + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: organizationId - name: TEAMS_API_DATABASE_NAME valueFrom: secretKeyRef: @@ -325,6 +358,9 @@ Create a merged list of environment variables for fiftyone-teams-cas secretKeyRef: name: {{ $secretName }} key: mongodbConnectionString +{{- end }} +- name: NEXTAUTH_URL + value: {{ printf "https://%s/cas/api/auth" .Values.teamsAppSettings.dnsName | quote }} {{- range $key, $val := .Values.casSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -338,6 +374,13 @@ Create a merged list of environment variables for fiftyone-teams-plugins {{- $secretName := .Values.secret.name }} - name: API_URL value: {{ printf "http://%s:%.0f" .Values.apiSettings.service.name .Values.apiSettings.service.port | quote }} +- name: FIFTYONE_AUTH_SECRET + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneAuthSecret +- name: FIFTYONE_DATABASE_ADMIN + value: "false" - name: FIFTYONE_DATABASE_NAME valueFrom: secretKeyRef: @@ -348,18 +391,12 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: mongodbConnectionString -- name: FIFTYONE_DATABASE_ADMIN - value: "false" - name: FIFTYONE_ENCRYPTION_KEY valueFrom: secretKeyRef: name: {{ $secretName }} key: encryptionKey -- name: FIFTYONE_TEAMS_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain +{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} - name: FIFTYONE_TEAMS_AUDIENCE value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" - name: FIFTYONE_TEAMS_CLIENT_ID @@ -367,16 +404,17 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: clientId -- name: FIFTYONE_TEAMS_ORGANIZATION +- name: FIFTYONE_TEAMS_DOMAIN valueFrom: secretKeyRef: name: {{ $secretName }} - key: organizationId -- name: FIFTYONE_AUTH_SECRET + key: auth0Domain +- name: FIFTYONE_TEAMS_ORGANIZATION valueFrom: secretKeyRef: name: {{ $secretName }} - key: fiftyoneAuthSecret + key: organizationId +{{- end }} {{- range $key, $val := .Values.pluginsSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -391,11 +429,7 @@ Create a merged list of environment variables for fiftyone-teams-app {{- $secretName := .Values.secret.name }} - name: API_URL value: {{ printf "http://%s:%.0f" .Values.apiSettings.service.name .Values.apiSettings.service.port | quote }} -- name: AUTH0_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain +{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} - name: AUTH0_AUDIENCE value: "https://$(AUTH0_DOMAIN)/api/v2/" - name: AUTH0_BASE_URL @@ -410,6 +444,11 @@ Create a merged list of environment variables for fiftyone-teams-app secretKeyRef: name: {{ $secretName }} key: clientSecret +- name: AUTH0_DOMAIN + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: auth0Domain - name: AUTH0_ISSUER_BASE_URL value: "https://$(AUTH0_DOMAIN)" - name: AUTH0_ORGANIZATION @@ -422,6 +461,7 @@ Create a merged list of environment variables for fiftyone-teams-app secretKeyRef: name: {{ $secretName }} key: cookieSecret +{{- end }} - name: FIFTYONE_API_URI {{- if .Values.teamsAppSettings.fiftyoneApiOverride }} value: {{ .Values.teamsAppSettings.fiftyoneApiOverride }} diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 523b0622..b7cefd21 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -186,10 +186,24 @@ appSettings: # Central Authentication Service (teams-cas) configurations casSettings: - # -- Provide the name for the CAS Database - databaseName: cas # Environment Variables are passed to the teams-cas containers - env: {} + env: + # -- Provide the name for the CAS database + CAS_DATABASE_NAME: cas + # -- Set the default user role for new users + # One of `GUEST`, `COLLABORATOR`, `MEMBER`, `ADMIN` + CAS_DEFAULT_USER_ROLE: GUEST + # -- Set the CAS Log Level + # One of `DEBUG`, `INFO`, `WARN`, `ERROR` + CAS_LOG_LEVEL: INFO + # -- The key from `secret.fiftyone` that contains the CAS MongoDB Connection + # String. + CAS_MONGODB_URI_KEY: mongodbConnectionString + # -- Toggle CAS Legacy Mode, which continues to use Auth0 integration + ENABLE_LEGACY_MODE: true + # -- Allow Admins to invite users by email + # NOTE: This is not supported when ENABLE_LEGACY_MODE is `false` + FEATURE_FLAG_ENABLE_INVITATIONS: true image: # -- Instruct when the kubelet should pull (download) the specified image. # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. @@ -199,9 +213,6 @@ casSettings: # -- Image tag for teams-cas. Defaults to the chart version. tag: "" - # -- Provide the key from secret.fiftyone for the CAS MongoDB Connection String - mongodbUriKey: mongodbConnectionString - # -- Number of pods in the teams-cas deployment's ReplicaSet. [Reference][deployment]. replicaCount: 2 @@ -441,6 +452,8 @@ secret: cookieSecret: "" # -- Encryption key for storage credentials. [Reference][fiftyone-encryption-key]. encryptionKey: "" + # -- A randomly generated string for CAS Authentication. + fiftyoneAuthSecret: "" serviceAccount: # -- Service Account annotations. [Reference][annotations]. From 5a848f740bc527cd996a748e8622f25cc0256509 Mon Sep 17 00:00:00 2001 From: topher Date: Wed, 28 Feb 2024 17:10:12 -0500 Subject: [PATCH 03/42] refactor FIFTYONE_AUTH_MODE (#90) * refactor FIFTYONE_AUTH_MODE * Update helm/fiftyone-teams-app/values.yaml Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Signed-off-by: topher --------- Signed-off-by: topher --- helm/fiftyone-teams-app/README.md | 4 ++-- helm/fiftyone-teams-app/templates/_helpers.tpl | 10 +++++----- helm/fiftyone-teams-app/values.yaml | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 64ac80ed..83246c86 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -328,8 +328,8 @@ appSettings: | casSettings.env.CAS_DEFAULT_USER_ROLE | string | `"GUEST"` | Set the default user role for new users One of `GUEST`, `COLLABORATOR`, `MEMBER`, `ADMIN` | | casSettings.env.CAS_LOG_LEVEL | string | `"INFO"` | Set the CAS Log Level One of `DEBUG`, `INFO`, `WARN`, `ERROR` | | casSettings.env.CAS_MONGODB_URI_KEY | string | `"mongodbConnectionString"` | The key from `secret.fiftyone` that contains the CAS MongoDB Connection String. | -| casSettings.env.ENABLE_LEGACY_MODE | bool | `true` | Toggle CAS Legacy Mode, which continues to use Auth0 integration | -| casSettings.env.FEATURE_FLAG_ENABLE_INVITATIONS | bool | `true` | Allow Admins to invite users by email NOTE: This is not supported when ENABLE_LEGACY_MODE is `false` | +| casSettings.env.FEATURE_FLAG_ENABLE_INVITATIONS | bool | `true` | Allow Admins to invite users by email NOTE: This is not supported when FIFTYONE_AUTH_MODE is `internal` | +| casSettings.env.FIFTYONE_AUTH_MODE | string | `"internal"` | Configure Authentication Mode. One of `legacy` or `internal` | | casSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | casSettings.image.repository | string | `"voxel51/teams-cas"` | Container image for teams-cas. | | casSettings.image.tag | string | `""` | Image tag for teams-cas. Defaults to the chart version. | diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index 330774ff..1a93db8a 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -193,7 +193,7 @@ Create a merged list of environment variables for fiftyone-teams-api */}} {{- define "fiftyone-teams-api.env-vars-list" -}} {{- $secretName := .Values.secret.name }} -{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: AUTH0_API_CLIENT_ID valueFrom: secretKeyRef: @@ -275,7 +275,7 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: encryptionKey -{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: FIFTYONE_TEAMS_AUDIENCE value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" - name: FIFTYONE_TEAMS_CLIENT_ID @@ -315,7 +315,7 @@ Create a merged list of environment variables for fiftyone-teams-cas secretKeyRef: name: {{ $secretName }} key: fiftyoneAuthSecret -{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: AUTH0_AUTH_CLIENT_ID valueFrom: secretKeyRef: @@ -396,7 +396,7 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: encryptionKey -{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: FIFTYONE_TEAMS_AUDIENCE value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" - name: FIFTYONE_TEAMS_CLIENT_ID @@ -429,7 +429,7 @@ Create a merged list of environment variables for fiftyone-teams-app {{- $secretName := .Values.secret.name }} - name: API_URL value: {{ printf "http://%s:%.0f" .Values.apiSettings.service.name .Values.apiSettings.service.port | quote }} -{{- if .Values.casSettings.env.ENABLE_LEGACY_MODE }} +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: AUTH0_AUDIENCE value: "https://$(AUTH0_DOMAIN)/api/v2/" - name: AUTH0_BASE_URL diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index d08087bf..b12f93bc 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -199,11 +199,12 @@ casSettings: # -- The key from `secret.fiftyone` that contains the CAS MongoDB Connection # String. CAS_MONGODB_URI_KEY: mongodbConnectionString - # -- Toggle CAS Legacy Mode, which continues to use Auth0 integration - ENABLE_LEGACY_MODE: true # -- Allow Admins to invite users by email - # NOTE: This is not supported when ENABLE_LEGACY_MODE is `false` + # NOTE: This is not supported when FIFTYONE_AUTH_MODE is `internal` FEATURE_FLAG_ENABLE_INVITATIONS: true + # -- Configure Authentication Mode. + # One of `legacy` or `internal` + FIFTYONE_AUTH_MODE: internal image: # -- Instruct when the kubelet should pull (download) the specified image. # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. From a0c1f6cfcc76ec8bd3db558f334efe61b8ec844f Mon Sep 17 00:00:00 2001 From: topher Date: Wed, 28 Feb 2024 18:36:42 -0500 Subject: [PATCH 04/42] Update Docker Compose for 1.6.0.b1 (#89) * docker compose starter for CAS * fixes DATBASE typo * some Doc updates around compose.yaml * they renamed the variable * Update docker/README.md Signed-off-by: topher * address PR comments * found some more links in sentences * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update CONTRIBUTING.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * Update helm/fiftyone-teams-app/README.md.gotmpl Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher --------- Signed-off-by: topher Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> --- CONTRIBUTING.md | 20 ++- docker/README.md | 21 ++- docker/example-nginx-path-routing.conf | 13 +- docker/example-nginx-site.conf | 8 + docker/internal-auth/common-services.yaml | 146 ++++++++++++++++++ .../compose.dedicated-plugins.yaml | 34 ++++ docker/internal-auth/compose.plugins.yaml | 31 ++++ docker/internal-auth/compose.yaml | 20 +++ docker/internal-auth/env.template | 68 ++++++++ docker/{ => legacy-auth}/common-services.yaml | 62 ++++---- .../compose.dedicated-plugins.yaml | 0 docker/{ => legacy-auth}/compose.plugins.yaml | 0 docker/{ => legacy-auth}/compose.yaml | 0 docker/{ => legacy-auth}/env.template | 0 helm/README.md | 6 +- helm/docs/plugins-storage.md | 9 +- helm/fiftyone-teams-app/README.md | 52 +++++-- helm/fiftyone-teams-app/README.md.gotmpl | 53 +++++-- helm/local-self-signed-example/README.md | 4 +- 19 files changed, 471 insertions(+), 76 deletions(-) create mode 100644 docker/internal-auth/common-services.yaml create mode 100644 docker/internal-auth/compose.dedicated-plugins.yaml create mode 100644 docker/internal-auth/compose.plugins.yaml create mode 100644 docker/internal-auth/compose.yaml create mode 100644 docker/internal-auth/env.template rename docker/{ => legacy-auth}/common-services.yaml (100%) rename docker/{ => legacy-auth}/compose.dedicated-plugins.yaml (100%) rename docker/{ => legacy-auth}/compose.plugins.yaml (100%) rename docker/{ => legacy-auth}/compose.yaml (100%) rename docker/{ => legacy-auth}/env.template (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85105fc5..9cf37bfb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,8 @@ ``` 1. Set skaffold secrets. - See [skaffold](#skaffold) + See + [skaffold](#skaffold) 1. Run skaffold @@ -293,7 +294,8 @@ GCP project `computer-vision-team`, configure minikube and skaffold minikube addons enable gcp-auth ``` -1. In [skaffold.yaml](./skaffold.yaml) +1. In + [skaffold.yaml](./skaffold.yaml) comment `imagePullSecrets` for the helm release named `fiftyone-teams-app` in `setValueTemplates.imagePullSecrets[0].name=regcred` @@ -407,7 +409,8 @@ To run released images from Docker hub, configure minikube and Skaffold --namespace "${NAMESPACE}" \ ``` -1. In [skaffold.yaml](./skaffold.yaml) +1. In + [skaffold.yaml](./skaffold.yaml) set `imagePullSecrets` for the helm release named `fiftyone-teams-app` in `setValueTemplates.imagePullSecrets[0].name=regcred` @@ -482,7 +485,9 @@ This section assumes the use of TLS certificates and the `https` protocol. > _Note:_ For local development with, we use the > Auth0 Tenant `dev-fiftyone` and the Auth0 Application `local-dev`. > The `local-dev` app contains the setting Allowed Callback URLs -> (aka Redirect URLs) with [https://local.fiftyone.ai](https://local.fiftyone.ai). +> (aka Redirect URLs) with +> [https://local.fiftyone.ai](https://local.fiftyone.ai) +> . > In `skaffold.yaml`, in both `appSettings.env` and `teamsAppSettings.env`, > either omit `APP_USE_HTTPS=false` or set `APP_USE_HTTPS=true` > for the app to set the Redirect URL's protocol to `https`. @@ -536,10 +541,11 @@ With the port forward running, > _Note:_ For local development, we use the Auth0 Tenant `dev-fiftyone` and > the Auth0 Application `local-dev` contains the setting Allowed Callback URLs -> (aka Redirect URLs) with [http://localhost:3000](http://localhost:3000). +> (aka Redirect URLs) with +> [http://localhost:3000](http://localhost:3000). > In `skaffold.yaml` we set `APP_USE_HTTPS=false` > to prohibit the app from setting the Redirect URL protocol to `https`. > Must be set in both `appSettings.env` and `teamsAppSettings.env`. > Without this setting, the app code makes the callback URL -> [https://localhost:3000](https://localhost:3000) and Auth0 -> throws a Callback URL mismatch error. +> [https://localhost:3000](https://localhost:3000) +> and Auth0 throws a Callback URL mismatch error. diff --git a/docker/README.md b/docker/README.md index f0afcb5f..d3a4875e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -48,11 +48,12 @@ For Docker Hub credentials, please contact your Voxel51 support team. ## Initial Installation vs. Upgrades -When performing an initial installation, in `compose.yaml` set +When performing an initial installation, in `compose.override.yaml` set `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: true`. When performing a FiftyOne Teams upgrade, set `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: false`. -See [Upgrade Process Recommendations](#upgrade-process-recommendations). +See +[Upgrade Process Recommendations](#upgrade-process-recommendations). The environment variable `FIFTYONE_DATABASE_ADMIN` controls whether the database may be migrated. @@ -130,7 +131,8 @@ Supported locations are network mounted filesystems and cloud storage folders. loaded in the `fiftyone-api` container have full edit capabilities to this bucket -See the [configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) +See the +[configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) for other configuration values that control the behavior of automatic snapshot archival. #### Enabling FiftyOne Teams Authenticated API @@ -173,14 +175,15 @@ There are three modes for plugins 1. Plugins run in a dedicated `teams-plugins` deployment - To enable this mode, use the file [./compose.dedicated-plugins.yaml](./compose.dedicated-plugins.yaml) - instead of the + instead of [./compose.yaml](./compose.yaml) - Containers need the following access to plugin storage - `teams-plugins` requires `read` - `fiftyone-api` requires `read-write` - - If you are [using a proxy](#environment-proxies), add the - `teams-plugins` service name to your `no_proxy` and - `NO_PROXY` environment variables. + - If you are + [using a proxy](#environment-proxies), + add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` + environment variables. - Example `docker compose` command for this mode ```shell @@ -334,7 +337,7 @@ versions prior to FiftyOne Teams version 1.1.0: for details) 1. [Upgrade to FiftyOne Teams version 1.5.6](#deploying-fiftyone-teams) with `FIFTYONE_DATABASE_ADMIN=true` - (this is not the default in the `compose.yaml` for this release). + (this is not the default for this release). - **NOTE:** FiftyOne SDK users will lose access to the FiftyOne Teams Database at this step until they upgrade to `fiftyone==0.15.6` 1. Upgrade your FiftyOne SDKs to version 0.15.6 @@ -404,6 +407,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: 1. Rename the `env.template` file to `.env` 1. Edit the `.env` file, setting the parameters required for this deployment. [See table below](#fiftyone-teams-environment-variables). + 1. Create a `compose.override.yaml` with any configuration overrides for + this deployment. 1. In the same directory, run ```shell diff --git a/docker/example-nginx-path-routing.conf b/docker/example-nginx-path-routing.conf index 12493f6a..2dbe1003 100644 --- a/docker/example-nginx-path-routing.conf +++ b/docker/example-nginx-path-routing.conf @@ -1,11 +1,16 @@ +upstream teams-api { + server localhost:8000; +} + upstream teams-app { server localhost:3000; } -upstream teams-api { - server localhost:8000; +upstream teams-cas { + server localhost:3030; } + server { server_name your.server.name; @@ -13,6 +18,10 @@ server { proxy_buffers 4 512k; proxy_buffer_size 256k; + location /cas { + proxy_pass http://teams-cas; + } + location /_pymongo { # default is 1M; api needs higher for large mongo requests client_max_body_size 100M; diff --git a/docker/example-nginx-site.conf b/docker/example-nginx-site.conf index 8366a580..be5ac983 100644 --- a/docker/example-nginx-site.conf +++ b/docker/example-nginx-site.conf @@ -2,6 +2,10 @@ upstream teams-app { server localhost:3000; } +upstream teams-cas { + server localhost:3030; +} + server { server_name your.server.name; @@ -9,6 +13,10 @@ server { proxy_buffers 4 512k; proxy_buffer_size 256k; + location /cas { + proxy_pass http://teams-cas; + } + location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/docker/internal-auth/common-services.yaml b/docker/internal-auth/common-services.yaml new file mode 100644 index 00000000..9856bac9 --- /dev/null +++ b/docker/internal-auth/common-services.yaml @@ -0,0 +1,146 @@ +--- +services: + fiftyone-app-common: + image: voxel51/fiftyone-app:v1.6.0 + environment: + API_URL: ${API_URL} + FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} + FIFTYONE_DATABASE_ADMIN: false + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 + FIFTYONE_DEFAULT_APP_PORT: 5151 + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_INTERNAL_SERVICE: true + FIFTYONE_MEDIA_CACHE_APP_IMAGES: false + FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + ports: + - ${FIFTYONE_DEFAULT_APP_ADDRESS}:${FIFTYONE_DEFAULT_APP_PORT}:5151 + restart: always + + teams-api-common: + image: voxel51/fiftyone-teams-api:v1.6.0 + environment: + CAS_BASE_URL: ${CAS_BASE_URL} + FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_ENV: ${FIFTYONE_ENV} + FIFTYONE_INTERNAL_SERVICE: true + GRAPHQL_DEFAULT_LIMIT: ${GRAPHQL_DEFAULT_LIMIT} + LOGGING_LEVEL: ${API_LOGGING_LEVEL:-INFO} + MONGO_DEFAULT_DB: ${FIFTYONE_DATABASE_NAME} + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + ports: + - ${API_BIND_ADDRESS}:${API_BIND_PORT}:8000 + restart: always + + teams-app-common: + image: voxel51/fiftyone-teams-app:v1.6.0 + environment: + API_URL: ${API_URL} + APP_USE_HTTPS: ${APP_USE_HTTPS:-true} + FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} + FIFTYONE_APP_ALLOW_MEDIA_EXPORT: ${FIFTYONE_APP_ALLOW_MEDIA_EXPORT:-true} + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0 + FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} + FIFTYONE_SERVER_ADDRESS: "" + FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams + FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} + NEXTAUTH_BASEPATH: /cas/api/auth + NODE_ENV: production + RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED: false + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # GLOBAL_AGENT_HTTPS_PROXY: ${HTTPS_PROXY_URL} + # GLOBAL_AGENT_HTTP_PROXY: ${HTTP_PROXY_URL} + # GLOBAL_AGENT_NO_PROXY: ${NO_PROXY_LIST} + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # ROARR_LOG: false + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + ports: + - ${APP_BIND_ADDRESS}:${APP_BIND_PORT}:3000 + restart: always + + teams-cas-common: + image: voxel51/fiftyone-teams-cas:v1.6.0 + environment: + CAS_DATABASE_NAME: ${CAS_DATABASE_NAME} + CAS_DEFAULT_USER_ROLE: ${CAS_DEFAULT_USER_ROLE} + CAS_LOG_LEVEL: ${CAS_LOG_LEVEL} + CAS_MONGODB_URI: ${CAS_MONGO_DB_URI:-$FIFTYONE_DATABASE_URI} + FEATURE_FLAG_ENABLE_INVITATIONS: false + FIFTYONE_AUTH_MODE: internal + FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} + NEXTAUTH_URL: ${BASE_URL}/cas/api/auth + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # GLOBAL_AGENT_HTTPS_PROXY: ${HTTPS_PROXY_URL} + # GLOBAL_AGENT_HTTP_PROXY: ${HTTP_PROXY_URL} + # GLOBAL_AGENT_NO_PROXY: ${NO_PROXY_LIST} + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # ROARR_LOG: false + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + ports: + - ${CAS_BIND_ADDRESS}:${CAS_BIND_PORT}:3000 + restart: always + + teams-plugins-common: + image: voxel51/fiftyone-app:v1.6.0 + environment: + API_URL: ${API_URL} + FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} + FIFTYONE_DATABASE_ADMIN: false + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 + FIFTYONE_DEFAULT_APP_PORT: 5151 + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_INTERNAL_SERVICE: true + FIFTYONE_MEDIA_CACHE_APP_IMAGES: false + FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 + FIFTYONE_PLUGINS_CACHE_ENABLED: true + FIFTYONE_PLUGINS_DIR: /opt/plugins + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + restart: always diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml new file mode 100644 index 00000000..21cb4926 --- /dev/null +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -0,0 +1,34 @@ +--- +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common + teams-api: + extends: + file: common-services.yaml + service: teams-api-common + environment: + FIFTYONE_PLUGINS_DIR: /opt/plugins + volumes: + - plugins-vol:/opt/plugins + teams-app: + extends: + file: common-services.yaml + service: teams-app-common + environment: + FIFTYONE_TEAMS_PLUGIN_URL: ${FIFTYONE_TEAMS_PLUGIN_URL} + teams-cas: + extends: + file: common-services.yaml + service: teams-cas-common + teams-plugins: + extends: + file: common-services.yaml + service: teams-plugins-common + volumes: + - plugins-vol:/opt/plugins:ro +volumes: + plugins-vol: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml new file mode 100644 index 00000000..40d0f960 --- /dev/null +++ b/docker/internal-auth/compose.plugins.yaml @@ -0,0 +1,31 @@ +--- +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common + environment: + FIFTYONE_PLUGINS_CACHE_ENABLED: true + FIFTYONE_PLUGINS_DIR: /opt/plugins + volumes: + - plugins-vol:/opt/plugins:ro + teams-api: + extends: + file: common-services.yaml + service: teams-api-common + environment: + FIFTYONE_PLUGINS_DIR: /opt/plugins + volumes: + - plugins-vol:/opt/plugins + teams-app: + extends: + file: common-services.yaml + service: teams-app-common + teams-cas: + extends: + file: common-services.yaml + service: teams-cas-common +volumes: + plugins-vol: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml new file mode 100644 index 00000000..10a516c9 --- /dev/null +++ b/docker/internal-auth/compose.yaml @@ -0,0 +1,20 @@ +--- +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common + teams-api: + extends: + file: common-services.yaml + service: teams-api-common + teams-app: + extends: + file: common-services.yaml + service: teams-app-common + teams-cas: + extends: + file: common-services.yaml + service: teams-cas-common diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template new file mode 100644 index 00000000..af937f56 --- /dev/null +++ b/docker/internal-auth/env.template @@ -0,0 +1,68 @@ +# This should be the URL your end-users will connect to +BASE_URL=https://example.fiftyone.ai + +# This should be set to the URI your end-users will use to connect to the API +# This could be the same as AUTH0_BASE_URL +FIFTYONE_API_URI=https://example-api.fiftyone.ai + +# This should be a MongoDB Connection String for your database +FIFTYONE_DATABASE_URI="mongodb://username:password@mongodb-example.fiftyone.ai:27017/?authSource=admin" +# If you are using a different MongoDB Connection String for your CAS database, +# set it here +# CAS_DATABASE_URI="mongodb://username:password@mongodb-cas-example.fiftyone.ai:27017/?authSource=admin" + +# FIFTYONE_AUTH_SECRET is a random string used to authenticate to the CAS service +# This can be any string you care to use generated by any mechanism you prefer. +# This is used for inter-service authentication and for the SuperUser to +# authenticate at the CAS UI to configure the Central Authentication Service. +FIFTYONE_AUTH_SECRET= + +# This key is required and is used to encrypt storage credentials in the MongoDB +# do NOT lose this key! +# generate keys by executing the following in python: +# +# from cryptography.fernet import Fernet +# print(Fernet.generate_key().decode()) +# +FIFTYONE_ENCRYPTION_KEY= + +# FiftyOne Teams API container configuration +API_BIND_ADDRESS=127.0.0.1 +API_BIND_PORT=8000 +API_LOGGING_LEVEL=INFO +# The following is a docker-compose link and will work in most situations +API_URL=http://teams-api:8000 +FIFTYONE_ENV=production +GRAPHQL_DEFAULT_LIMIT=10 + +# FiftyOne App Configuration +FIFTYONE_DEFAULT_APP_ADDRESS=127.0.0.1 +FIFTYONE_DEFAULT_APP_PORT=5151 +FIFTYONE_DATABASE_NAME=fiftyone + +# FiftyOne Teams App Configuration +# Set to true if using SSL on the frontend +APP_USE_HTTPS=true +APP_BIND_ADDRESS=127.0.0.1 +APP_BIND_PORT=3000 + +# FiftyOne Teams CAS Configuration +CAS_BASE_URL=http://teams-cas:3000/cas/api +CAS_BIND_ADDRESS=127.0.0.1 +CAS_BIND_PORT=3030 +CAS_DATABASE_NAME=fiftyone-cas +CAS_DEFAULT_USER_ROLE=GUEST +CAS_LOG_LEVEL=INFO + +# The following are docker-compose links and will work in most situations +FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151 +# This only gets used with a dedicated teams-plugins service +FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151 + +# Environment Proxy settings, if you're using a proxy to get to the Internet +# set these to appropriate values and uncomment the associated settings in the `compose.yaml` +# HTTP_PROXY_URL=http://proxy.yourcompany.tld:3128 +# HTTPS_PROXY_URL=https://proxy.yourcompany.tld:3128 +# +# You must include the container service names in your NO_PROXY_LIST +# NO_PROXY_LIST: teams-api, teams-app, fiftyone-app, teams-plugins, otherservers.yourcompany.tld diff --git a/docker/common-services.yaml b/docker/legacy-auth/common-services.yaml similarity index 100% rename from docker/common-services.yaml rename to docker/legacy-auth/common-services.yaml index 2c6172bf..d07e6268 100644 --- a/docker/common-services.yaml +++ b/docker/legacy-auth/common-services.yaml @@ -1,5 +1,36 @@ --- services: + fiftyone-app-common: + image: voxel51/fiftyone-app:v1.5.6 + environment: + API_URL: ${API_URL} + FIFTYONE_DATABASE_ADMIN: false + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 + FIFTYONE_DEFAULT_APP_PORT: 5151 + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_INTERNAL_SERVICE: true + FIFTYONE_MEDIA_CACHE_APP_IMAGES: false + FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 + FIFTYONE_TEAMS_AUDIENCE: ${AUTH0_AUDIENCE} + FIFTYONE_TEAMS_CLIENT_ID: ${AUTH0_CLIENT_ID} + FIFTYONE_TEAMS_DOMAIN: ${AUTH0_DOMAIN} + FIFTYONE_TEAMS_ORGANIZATION: ${AUTH0_ORGANIZATION} + # If you are routing through a proxy server you will want to set + # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env + # then add the following environment variables to your + # `compose.override.yaml` + # HTTPS_PROXY: ${HTTPS_PROXY_URL} + # HTTP_PROXY: ${HTTP_PROXY_URL} + # NO_PROXY: ${NO_PROXY_LIST} + # http_proxy: ${HTTP_PROXY_URL} + # https_proxy: ${HTTPS_PROXY_URL} + # no_proxy: ${NO_PROXY_LIST} + ports: + - ${FIFTYONE_DEFAULT_APP_ADDRESS}:${FIFTYONE_DEFAULT_APP_PORT}:5151 + restart: always + teams-api-common: image: voxel51/fiftyone-teams-api:v1.5.6 environment: @@ -67,37 +98,6 @@ services: - ${APP_BIND_ADDRESS}:${APP_BIND_PORT}:3000 restart: always - fiftyone-app-common: - image: voxel51/fiftyone-app:v1.5.6 - environment: - API_URL: ${API_URL} - FIFTYONE_DATABASE_ADMIN: false - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 - FIFTYONE_DEFAULT_APP_PORT: 5151 - FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} - FIFTYONE_INTERNAL_SERVICE: true - FIFTYONE_MEDIA_CACHE_APP_IMAGES: false - FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 - FIFTYONE_TEAMS_AUDIENCE: ${AUTH0_AUDIENCE} - FIFTYONE_TEAMS_CLIENT_ID: ${AUTH0_CLIENT_ID} - FIFTYONE_TEAMS_DOMAIN: ${AUTH0_DOMAIN} - FIFTYONE_TEAMS_ORGANIZATION: ${AUTH0_ORGANIZATION} - # If you are routing through a proxy server you will want to set - # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env - # then add the following environment variables to your - # `compose.override.yaml` - # HTTPS_PROXY: ${HTTPS_PROXY_URL} - # HTTP_PROXY: ${HTTP_PROXY_URL} - # NO_PROXY: ${NO_PROXY_LIST} - # http_proxy: ${HTTP_PROXY_URL} - # https_proxy: ${HTTPS_PROXY_URL} - # no_proxy: ${NO_PROXY_LIST} - ports: - - ${FIFTYONE_DEFAULT_APP_ADDRESS}:${FIFTYONE_DEFAULT_APP_PORT}:5151 - restart: always - teams-plugins-common: image: voxel51/fiftyone-app:v1.5.6 environment: diff --git a/docker/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml similarity index 100% rename from docker/compose.dedicated-plugins.yaml rename to docker/legacy-auth/compose.dedicated-plugins.yaml diff --git a/docker/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml similarity index 100% rename from docker/compose.plugins.yaml rename to docker/legacy-auth/compose.plugins.yaml diff --git a/docker/compose.yaml b/docker/legacy-auth/compose.yaml similarity index 100% rename from docker/compose.yaml rename to docker/legacy-auth/compose.yaml diff --git a/docker/env.template b/docker/legacy-auth/env.template similarity index 100% rename from docker/env.template rename to docker/legacy-auth/env.template diff --git a/helm/README.md b/helm/README.md index 2e70fa30..b2859515 100644 --- a/helm/README.md +++ b/helm/README.md @@ -42,7 +42,8 @@ This directory contains resources and information related to Helm deployments For the chart documentation, see the fiftyone-teams-app/README.md file. - `gke-example` contains additional kubernetes resources to install FiftyOne Teams on Google Kubernetes Engine (GKE). - See [A Full Deployment Example on GKE](#a-full-deployment-example-on-gke). + See + [A Full Deployment Example on GKE](#a-full-deployment-example-on-gke). - Files - `values.yaml` is example of overrides for the chart's defaults for a deployment @@ -206,7 +207,8 @@ helm install fiftyone-mongodb \ Wait until the MongoDB pods are in the `Ready` state before beginning the "Install FiftyOne Teams App" instructions. -While waiting, [configure a DNS entry](#obtain-a-global-static-ip-address-and-configure-a-dns-entry). +While waiting, +[configure a DNS entry](#obtain-a-global-static-ip-address-and-configure-a-dns-entry). To determine the state of the `fiftyone-mongodb` pods, run diff --git a/helm/docs/plugins-storage.md b/helm/docs/plugins-storage.md index 2cbf0447..2eb73706 100644 --- a/helm/docs/plugins-storage.md +++ b/helm/docs/plugins-storage.md @@ -24,9 +24,12 @@ storage for FiftyOne Teams Plugins using Alternate storage solutions vary based on cloud providers and infrastructure services. PVCs may be configured using provider specific services -* Google Cloud [Filestore](https://cloud.google.com/filestore/docs) -* Amazon [EFS](https://aws.amazon.com/efs/) -* Azure [Files](https://learn.microsoft.com/en-us/azure/storage/files/). +* Google Cloud + [Filestore](https://cloud.google.com/filestore/docs) +* Amazon + [EFS](https://aws.amazon.com/efs/) +* Azure + [Files](https://learn.microsoft.com/en-us/azure/storage/files/). ## NFS Share diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 83246c86..a564bf13 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -111,7 +111,8 @@ Supported locations are network mounted filesystems and cloud storage folders. [cloud credentials](https://docs.voxel51.com/teams/installation.html#cloud-credentials) loaded in the `teams-api` deployment have full edit capabilities to this bucket -See the [configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) +See the +[configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) for other configuration values that control the behavior of automatic snapshot archival. ### FiftyOne Teams Authenticated API @@ -162,8 +163,9 @@ There are three modes for plugins at the `FIFTYONE_PLUGINS_DIR` path - `ReadOnly` permission to the `teams-plugins` deployment at the `FIFTYONE_PLUGINS_DIR` path - - If you are [using a proxy](#proxies), add the - `teams-plugins` service name to your `no_proxy` and + - If you are + [using a proxy](#proxies), + add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` environment variables. Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. @@ -212,29 +214,57 @@ To configure this, set the following environment variables on ```yaml http_proxy: http://proxy.yourcompany.tld:3128 https_proxy: https://proxy.yourcompany.tld:3128 - no_proxy: , , + no_proxy: fiftyone-app, teams-app, teams-api, teams-cas, HTTP_PROXY: http://proxy.yourcompany.tld:3128 HTTPS_PROXY: https://proxy.yourcompany.tld:3128 - NO_PROXY: , , + NO_PROXY: fiftyone-app, teams-app, teams-api, teams-cas, ``` + > **NOTE**: If you have enabled a + > [dedicated `teams-plugins`](#fiftyone-teams-plugins) + > deployment you will need to include `teams-plugins` in your `NO_PROXY` and + > `no_proxy` configurations + + --- + + > **NOTE**: If you have overridden your service names with `*.service.name` + > you will need to include the override service names in your `NO_PROXY` and + > `no_proxy` configurations instead + 1. The pod based on the `fiftyone-teams-app` image (`teamsAppSettings.env`) ```yaml GLOBAL_AGENT_HTTP_PROXY: http://proxy.yourcompany.tld:3128 GLOBAL_AGENT_HTTPS_PROXY: https://proxy.yourconpay.tld:3128 - GLOBAL_AGENT_NO_PROXY: , , + GLOBAL_AGENT_NO_PROXY: fiftyone-app, teams-app, teams-api, teams-cas, ``` -The `NO_PROXY` and `GLOBAL_AGENT_NO_PROXY` values must include the Kubernetes -service names that may communicate without going through a proxy server. + > **NOTE**: If you have enabled a + > [dedicated `teams-plugins`](#fiftyone-teams-plugins) + > deployment you will need to include `teams-plugins` in your + > `GLOBAL_AGENT_NO_PROXY` configuration + + --- + + > **NOTE**: If you have overridden your service names with `*.service.name` + > you will need to include the override service names in your + > `GLOBAL_AGENT_NO_PROXY` configuration instead + +The `NO_PROXY`, `no_proxy`, and `GLOBAL_AGENT_NO_PROXY` values must include the +Kubernetes service names that may communicate without going through a proxy +server. By default, these service names are -- `teams-api` -- `teams-app` - `fiftyone-app` +- `teams-app` +- `teams-api` +- `teams-cas` + +This list may also include `teams-plugins` if you have enabled a dedicated +plugins service. -If the service names were overridden in `*.service.name`, use these values instead. +If the service names were overridden in `*.service.name`, use the override +values instead. By default, the Global Agent Proxy will log all outbound connections and identify which connections are routed through the proxy. diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index b359e480..e42aa476 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -113,7 +113,8 @@ Supported locations are network mounted filesystems and cloud storage folders. [cloud credentials](https://docs.voxel51.com/teams/installation.html#cloud-credentials) loaded in the `teams-api` deployment have full edit capabilities to this bucket -See the [configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) +See the +[configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) for other configuration values that control the behavior of automatic snapshot archival. ### FiftyOne Teams Authenticated API @@ -164,8 +165,9 @@ There are three modes for plugins at the `FIFTYONE_PLUGINS_DIR` path - `ReadOnly` permission to the `teams-plugins` deployment at the `FIFTYONE_PLUGINS_DIR` path - - If you are [using a proxy](#proxies), add the - `teams-plugins` service name to your `no_proxy` and + - If you are + [using a proxy](#proxies), + add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` environment variables. Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. @@ -214,29 +216,58 @@ To configure this, set the following environment variables on ```yaml http_proxy: http://proxy.yourcompany.tld:3128 https_proxy: https://proxy.yourcompany.tld:3128 - no_proxy: , , + no_proxy: fiftyone-app, teams-app, teams-api, teams-cas, HTTP_PROXY: http://proxy.yourcompany.tld:3128 HTTPS_PROXY: https://proxy.yourcompany.tld:3128 - NO_PROXY: , , + NO_PROXY: fiftyone-app, teams-app, teams-api, teams-cas, ``` + > **NOTE**: If you have enabled a + > [dedicated `teams-plugins`](#fiftyone-teams-plugins) + > deployment you will need to include `teams-plugins` in your `NO_PROXY` and + > `no_proxy` configurations + + --- + + > **NOTE**: If you have overridden your service names with `*.service.name` + > you will need to include the override service names in your `NO_PROXY` and + > `no_proxy` configurations instead + 1. The pod based on the `fiftyone-teams-app` image (`teamsAppSettings.env`) ```yaml GLOBAL_AGENT_HTTP_PROXY: http://proxy.yourcompany.tld:3128 GLOBAL_AGENT_HTTPS_PROXY: https://proxy.yourconpay.tld:3128 - GLOBAL_AGENT_NO_PROXY: , , + GLOBAL_AGENT_NO_PROXY: fiftyone-app, teams-app, teams-api, teams-cas, ``` -The `NO_PROXY` and `GLOBAL_AGENT_NO_PROXY` values must include the Kubernetes -service names that may communicate without going through a proxy server. + > **NOTE**: If you have enabled a + > [dedicated `teams-plugins`](#fiftyone-teams-plugins) + > deployment you will need to include `teams-plugins` in your + > `GLOBAL_AGENT_NO_PROXY` configuration + + --- + + > **NOTE**: If you have overridden your service names with `*.service.name` + > you will need to include the override service names in your + > `GLOBAL_AGENT_NO_PROXY` configuration instead + + +The `NO_PROXY`, `no_proxy`, and `GLOBAL_AGENT_NO_PROXY` values must include the +Kubernetes service names that may communicate without going through a proxy +server. By default, these service names are -- `teams-api` -- `teams-app` - `fiftyone-app` +- `teams-app` +- `teams-api` +- `teams-cas` + +This list may also include `teams-plugins` if you have enabled a dedicated +plugins service. -If the service names were overridden in `*.service.name`, use these values instead. +If the service names were overridden in `*.service.name`, use the override +values instead. By default, the Global Agent Proxy will log all outbound connections and identify which connections are routed through the proxy. diff --git a/helm/local-self-signed-example/README.md b/helm/local-self-signed-example/README.md index 0aa84b74..d6a73ca8 100644 --- a/helm/local-self-signed-example/README.md +++ b/helm/local-self-signed-example/README.md @@ -11,7 +11,9 @@ We order the resource creation by This ordering avoids errors during resource cleanup and avoids using Helm to manage CRDs. -See [../../skaffold-cert-manager.yaml](../../skaffold-cert-manager.yaml). +See +[../../skaffold-cert-manager.yaml](../../skaffold-cert-manager.yaml) +. The ingress will be annotated to obtain a certificate from cert-manger for its defined host. From 809bc62b3e1c3dfa20953341a43d22a73a4f5c99 Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 29 Feb 2024 16:57:02 -0500 Subject: [PATCH 05/42] More CAS Env Var Updates (#93) * update deployment env vars * quote boolean env var values --- docker/internal-auth/common-services.yaml | 7 +- .../compose.dedicated-plugins.yaml | 1 + docker/internal-auth/compose.plugins.yaml | 1 + docker/internal-auth/env.template | 6 +- helm/fiftyone-teams-app/README.md | 8 +- .../fiftyone-teams-app/templates/_helpers.tpl | 120 +++--------------- helm/fiftyone-teams-app/values.yaml | 21 +-- 7 files changed, 46 insertions(+), 118 deletions(-) diff --git a/docker/internal-auth/common-services.yaml b/docker/internal-auth/common-services.yaml index 9856bac9..2f416b1c 100644 --- a/docker/internal-auth/common-services.yaml +++ b/docker/internal-auth/common-services.yaml @@ -32,6 +32,7 @@ services: image: voxel51/fiftyone-teams-api:v1.6.0 environment: CAS_BASE_URL: ${CAS_BASE_URL} + FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} @@ -60,6 +61,7 @@ services: environment: API_URL: ${API_URL} APP_USE_HTTPS: ${APP_USE_HTTPS:-true} + FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} FIFTYONE_APP_ALLOW_MEDIA_EXPORT: ${FIFTYONE_APP_ALLOW_MEDIA_EXPORT:-true} FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0 @@ -67,7 +69,6 @@ services: FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} - NEXTAUTH_BASEPATH: /cas/api/auth NODE_ENV: production RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED: false # If you are routing through a proxy server you will want to set @@ -93,9 +94,9 @@ services: environment: CAS_DATABASE_NAME: ${CAS_DATABASE_NAME} CAS_DEFAULT_USER_ROLE: ${CAS_DEFAULT_USER_ROLE} - CAS_LOG_LEVEL: ${CAS_LOG_LEVEL} CAS_MONGODB_URI: ${CAS_MONGO_DB_URI:-$FIFTYONE_DATABASE_URI} - FEATURE_FLAG_ENABLE_INVITATIONS: false + CAS_URL: ${BASE_URL} + DEBUG: ${CAS_DEBUG} FIFTYONE_AUTH_MODE: internal FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} NEXTAUTH_URL: ${BASE_URL}/cas/api/auth diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index 21cb4926..37e4c9f6 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -11,6 +11,7 @@ services: file: common-services.yaml service: teams-api-common environment: + FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins volumes: - plugins-vol:/opt/plugins diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 40d0f960..92ea8bfe 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -16,6 +16,7 @@ services: file: common-services.yaml service: teams-api-common environment: + FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins volumes: - plugins-vol:/opt/plugins diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index af937f56..9eb7980a 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -51,8 +51,12 @@ CAS_BASE_URL=http://teams-cas:3000/cas/api CAS_BIND_ADDRESS=127.0.0.1 CAS_BIND_PORT=3030 CAS_DATABASE_NAME=fiftyone-cas +# CAS_DEBUG defines what CAS logs to display +# e.g. `cas:*` - shows all cas logs +# `cas:*:info` - shows only CAS INFO logs +# `cas:*,-cas:*:debug` - shows all cas logs except DEBUG logs +CAS_DEBUG="cas:*,-cas:*:debug" CAS_DEFAULT_USER_ROLE=GUEST -CAS_LOG_LEVEL=INFO # The following are docker-compose links and will work in most situations FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151 diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index a564bf13..60971e33 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -354,12 +354,12 @@ appSettings: | appSettings.volumeMounts | list | `[]` | Volume mounts for fiftyone-app. [Reference][volumes]. | | appSettings.volumes | list | `[]` | Volumes for fiftyone-app. [Reference][volumes]. | | casSettings.affinity | object | `{}` | Affinity and anti-affinity for teams-cas. [Reference][affinity]. | +| casSettings.enable_invitations | bool | `true` | Allow ADMINs to invite users by email NOTE: This is currently not supported when `FIFTYONE_AUTH_MODE: internal` | | casSettings.env.CAS_DATABASE_NAME | string | `"cas"` | Provide the name for the CAS database | | casSettings.env.CAS_DEFAULT_USER_ROLE | string | `"GUEST"` | Set the default user role for new users One of `GUEST`, `COLLABORATOR`, `MEMBER`, `ADMIN` | -| casSettings.env.CAS_LOG_LEVEL | string | `"INFO"` | Set the CAS Log Level One of `DEBUG`, `INFO`, `WARN`, `ERROR` | -| casSettings.env.CAS_MONGODB_URI_KEY | string | `"mongodbConnectionString"` | The key from `secret.fiftyone` that contains the CAS MongoDB Connection String. | -| casSettings.env.FEATURE_FLAG_ENABLE_INVITATIONS | bool | `true` | Allow Admins to invite users by email NOTE: This is not supported when FIFTYONE_AUTH_MODE is `internal` | -| casSettings.env.FIFTYONE_AUTH_MODE | string | `"internal"` | Configure Authentication Mode. One of `legacy` or `internal` | +| casSettings.env.CAS_MONGODB_URI_KEY | string | `"mongodbConnectionString"` | The key from `secret.fiftyone.name` that contains the CAS MongoDB Connection String. | +| casSettings.env.DEBUG | string | `"cas:*,-cas:*:debug"` | Set the log level for CAS examples: `DEBUG: cas:*` - shows all CAS logs `DEBUG: cas:*:info` - shows all CAS INFO logs `DEBUG: cas:*,-cas:*:debug` - shows all CAS logs except DEBUG logs | +| casSettings.env.FIFTYONE_AUTH_MODE | string | `"legacy"` | Configure Authentication Mode. One of `legacy` or `internal` | | casSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | casSettings.image.repository | string | `"voxel51/teams-cas"` | Container image for teams-cas. | | casSettings.image.tag | string | `""` | Image tag for teams-cas. Defaults to the chart version. | diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index 1a93db8a..22154331 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -193,28 +193,19 @@ Create a merged list of environment variables for fiftyone-teams-api */}} {{- define "fiftyone-teams-api.env-vars-list" -}} {{- $secretName := .Values.secret.name }} -{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} -- name: AUTH0_API_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: apiClientId -- name: AUTH0_API_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: apiClientSecret -- name: AUTH0_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain -- name: AUTH0_CLIENT_ID +- name: CAS_BASE_URL + value: {{ printf "http://%s:%.0f/cas/api" .Values.casSettings.service.name .Values.casSettings.service.port | quote }} +- name: FEATURE_FLAG_ENABLE_INVITATIONS +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "internal" }} + value: "false" +{{- else }} + value: "{{ .Values.casSettings.enable_invitations }}" +{{- end }} +- name: FIFTYONE_AUTH_SECRET valueFrom: secretKeyRef: name: {{ $secretName }} - key: clientId -{{- end }} + key: fiftyoneAuthSecret - name: FIFTYONE_DATABASE_NAME valueFrom: secretKeyRef: @@ -235,13 +226,6 @@ Create a merged list of environment variables for fiftyone-teams-api secretKeyRef: name: {{ $secretName }} key: fiftyoneDatabaseName -- name: CAS_BASE_URL - value: {{ printf "http://%s:%.0f/cas/api" .Values.casSettings.service.name .Values.casSettings.service.port | quote }} -- name: FIFTYONE_AUTH_SECRET - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: fiftyoneAuthSecret {{- range $key, $val := .Values.apiSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -275,25 +259,6 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: encryptionKey -{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} -- name: FIFTYONE_TEAMS_AUDIENCE - value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" -- name: FIFTYONE_TEAMS_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: clientId -- name: FIFTYONE_TEAMS_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain -- name: FIFTYONE_TEAMS_ORGANIZATION - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: organizationId -{{- end }} {{- range $key, $val := .Values.appSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -310,11 +275,15 @@ Create a merged list of environment variables for fiftyone-teams-cas secretKeyRef: name: {{ $secretName }} key: {{ .Values.casSettings.env.CAS_MONGODB_URI_KEY }} +- name: CAS_URL + value: {{ printf "https://%s" .Values.teamsAppSettings.dnsName | quote }} - name: FIFTYONE_AUTH_SECRET valueFrom: secretKeyRef: name: {{ $secretName }} key: fiftyoneAuthSecret +- name: NEXTAUTH_URL + value: {{ printf "https://%s/cas/api/auth" .Values.teamsAppSettings.dnsName | quote }} {{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} - name: AUTH0_AUTH_CLIENT_ID valueFrom: @@ -359,8 +328,6 @@ Create a merged list of environment variables for fiftyone-teams-cas name: {{ $secretName }} key: mongodbConnectionString {{- end }} -- name: NEXTAUTH_URL - value: {{ printf "https://%s/cas/api/auth" .Values.teamsAppSettings.dnsName | quote }} {{- range $key, $val := .Values.casSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -396,25 +363,6 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: encryptionKey -{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} -- name: FIFTYONE_TEAMS_AUDIENCE - value: "https://$(FIFTYONE_TEAMS_DOMAIN)/api/v2/" -- name: FIFTYONE_TEAMS_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: clientId -- name: FIFTYONE_TEAMS_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain -- name: FIFTYONE_TEAMS_ORGANIZATION - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: organizationId -{{- end }} {{- range $key, $val := .Values.pluginsSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -429,38 +377,11 @@ Create a merged list of environment variables for fiftyone-teams-app {{- $secretName := .Values.secret.name }} - name: API_URL value: {{ printf "http://%s:%.0f" .Values.apiSettings.service.name .Values.apiSettings.service.port | quote }} -{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "legacy" }} -- name: AUTH0_AUDIENCE - value: "https://$(AUTH0_DOMAIN)/api/v2/" -- name: AUTH0_BASE_URL - value: {{ printf "https://%s" .Values.teamsAppSettings.dnsName | quote }} -- name: AUTH0_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: clientId -- name: AUTH0_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: clientSecret -- name: AUTH0_DOMAIN - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: auth0Domain -- name: AUTH0_ISSUER_BASE_URL - value: "https://$(AUTH0_DOMAIN)" -- name: AUTH0_ORGANIZATION - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: organizationId -- name: AUTH0_SECRET - valueFrom: - secretKeyRef: - name: {{ $secretName }} - key: cookieSecret +- name: FEATURE_FLAG_ENABLE_INVITATIONS +{{- if eq .Values.casSettings.env.FIFTYONE_AUTH_MODE "internal" }} + value: "false" +{{- else }} + value: "{{ .Values.casSettings.enable_invitations }}" {{- end }} - name: FIFTYONE_API_URI {{- if .Values.teamsAppSettings.fiftyoneApiOverride }} @@ -487,9 +408,6 @@ Create a merged list of environment variables for fiftyone-teams-app {{- else }} value: {{ printf "http://%s:%.0f" .Values.appSettings.service.name .Values.appSettings.service.port | quote }} {{- end }} -- name: NEXTAUTH_BASEPATH - value: "/cas/api/auth" - {{- range $key, $val := .Values.teamsAppSettings.env }} - name: {{ $key }} value: {{ $val | quote }} diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index b12f93bc..bf52fd2d 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -186,6 +186,9 @@ appSettings: # Central Authentication Service (teams-cas) configurations casSettings: + # -- Allow ADMINs to invite users by email + # NOTE: This is currently not supported when `FIFTYONE_AUTH_MODE: internal` + enable_invitations: true # Environment Variables are passed to the teams-cas containers env: # -- Provide the name for the CAS database @@ -193,18 +196,18 @@ casSettings: # -- Set the default user role for new users # One of `GUEST`, `COLLABORATOR`, `MEMBER`, `ADMIN` CAS_DEFAULT_USER_ROLE: GUEST - # -- Set the CAS Log Level - # One of `DEBUG`, `INFO`, `WARN`, `ERROR` - CAS_LOG_LEVEL: INFO - # -- The key from `secret.fiftyone` that contains the CAS MongoDB Connection - # String. + # -- The key from `secret.fiftyone.name` that contains the CAS MongoDB + # Connection String. CAS_MONGODB_URI_KEY: mongodbConnectionString - # -- Allow Admins to invite users by email - # NOTE: This is not supported when FIFTYONE_AUTH_MODE is `internal` - FEATURE_FLAG_ENABLE_INVITATIONS: true + # -- Set the log level for CAS + # examples: + # `DEBUG: cas:*` - shows all CAS logs + # `DEBUG: cas:*:info` - shows all CAS INFO logs + # `DEBUG: cas:*,-cas:*:debug` - shows all CAS logs except DEBUG logs + DEBUG: cas:*,-cas:*:debug # -- Configure Authentication Mode. # One of `legacy` or `internal` - FIFTYONE_AUTH_MODE: internal + FIFTYONE_AUTH_MODE: legacy image: # -- Instruct when the kubelet should pull (download) the specified image. # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. From cb53fe4bb95f4a47b1b0b9bb6a386d8c2f7f7963 Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 29 Feb 2024 23:04:52 -0500 Subject: [PATCH 06/42] bumps for version 1.6.0.beta1 --- docker/README.md | 24 ++++++++++----------- docker/internal-auth/common-services.yaml | 12 +++++------ docker/legacy-auth/common-services.yaml | 10 ++++----- helm/fiftyone-teams-app/Chart.yaml | 4 ++-- helm/fiftyone-teams-app/README.md | 26 +++++++++++------------ helm/fiftyone-teams-app/README.md.gotmpl | 22 +++++++++---------- helm/fiftyone-teams-app/values.yaml | 2 +- helm/gke-example/values.yaml | 2 +- helm/values.yaml | 4 ++-- 9 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docker/README.md b/docker/README.md index d3a4875e..cf0e6aa5 100644 --- a/docker/README.md +++ b/docker/README.md @@ -302,7 +302,7 @@ For example, `compose.override.yaml` might look like: ```yaml services: fiftyone-app: - image: voxel51/fiftyone-app-torch:v1.5.6 + image: voxel51/fiftyone-app-torch:v1.6.0 ``` For more information, see the docs for @@ -318,7 +318,7 @@ create a new IdP or modify your existing configuration. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.15.6 SDK (database version 0.23.5) is _NOT_ backwards-compatible +The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible with FiftyOne Teams Database Versions prior to 0.19.0. The FiftyOne 0.10.x SDK is not forwards compatible with current FiftyOne Teams Database Versions. @@ -335,12 +335,12 @@ versions prior to FiftyOne Teams version 1.1.0: (see [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/env.template#L17) for details) -1. [Upgrade to FiftyOne Teams version 1.5.6](#deploying-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) with `FIFTYONE_DATABASE_ADMIN=true` (this is not the default for this release). - **NOTE:** FiftyOne SDK users will lose access to the - FiftyOne Teams Database at this step until they upgrade to `fiftyone==0.15.6` -1. Upgrade your FiftyOne SDKs to version 0.15.6 + FiftyOne Teams Database at this step until they upgrade to `fiftyone==0.16.0` +1. Upgrade your FiftyOne SDKs to version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -358,10 +358,10 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later -The FiftyOne 0.15.6 SDK is backwards-compatible with +The FiftyOne 0.16.0 SDK is backwards-compatible with FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.5.6 -database (version 0.23.5) with any FiftyOne SDK before 0.15.6. +You will not be able to connect to a FiftyOne Teams 1.6.0 +database (version 0.23.5) with any FiftyOne SDK before 0.16.0. Voxel51 always recommends using the latest version of the FiftyOne SDK compatible with your FiftyOne Teams deployment. @@ -377,8 +377,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. [Upgrade to FiftyOne Teams version 1.5.6](#deploying-fiftyone-teams) -1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.15.6 +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) +1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -388,8 +388,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - - **NOTE** Any FiftyOne SDK less than 0.15.6 will lose database connectivity - at this point. Upgrading to `fiftyone==0.15.6` is required + - **NOTE** Any FiftyOne SDK less than 0.16.0 will lose database connectivity + at this point. Upgrading to `fiftyone==0.16.0` is required 1. To ensure that all datasets are now at version 0.23.5, run diff --git a/docker/internal-auth/common-services.yaml b/docker/internal-auth/common-services.yaml index 2f416b1c..54c369de 100644 --- a/docker/internal-auth/common-services.yaml +++ b/docker/internal-auth/common-services.yaml @@ -1,7 +1,7 @@ --- services: fiftyone-app-common: - image: voxel51/fiftyone-app:v1.6.0 + image: voxel51/fiftyone-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} @@ -29,7 +29,7 @@ services: restart: always teams-api-common: - image: voxel51/fiftyone-teams-api:v1.6.0 + image: voxel51/fiftyone-teams-api:v1.6.0-beta.1 environment: CAS_BASE_URL: ${CAS_BASE_URL} FEATURE_FLAG_ENABLE_INVITATIONS: false @@ -57,14 +57,14 @@ services: restart: always teams-app-common: - image: voxel51/fiftyone-teams-app:v1.6.0 + image: voxel51/fiftyone-teams-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} APP_USE_HTTPS: ${APP_USE_HTTPS:-true} FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} FIFTYONE_APP_ALLOW_MEDIA_EXPORT: ${FIFTYONE_APP_ALLOW_MEDIA_EXPORT:-true} - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams @@ -90,7 +90,7 @@ services: restart: always teams-cas-common: - image: voxel51/fiftyone-teams-cas:v1.6.0 + image: voxel51/fiftyone-teams-cas:v1.6.0-beta.1 environment: CAS_DATABASE_NAME: ${CAS_DATABASE_NAME} CAS_DEFAULT_USER_ROLE: ${CAS_DEFAULT_USER_ROLE} @@ -119,7 +119,7 @@ services: restart: always teams-plugins-common: - image: voxel51/fiftyone-app:v1.6.0 + image: voxel51/fiftyone-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} diff --git a/docker/legacy-auth/common-services.yaml b/docker/legacy-auth/common-services.yaml index d07e6268..fc513ced 100644 --- a/docker/legacy-auth/common-services.yaml +++ b/docker/legacy-auth/common-services.yaml @@ -1,7 +1,7 @@ --- services: fiftyone-app-common: - image: voxel51/fiftyone-app:v1.5.6 + image: voxel51/fiftyone-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} FIFTYONE_DATABASE_ADMIN: false @@ -32,7 +32,7 @@ services: restart: always teams-api-common: - image: voxel51/fiftyone-teams-api:v1.5.6 + image: voxel51/fiftyone-teams-api:v1.6.0-beta.1 environment: AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID} AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} @@ -62,7 +62,7 @@ services: restart: always teams-app-common: - image: voxel51/fiftyone-teams-app:v1.5.6 + image: voxel51/fiftyone-teams-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} @@ -74,7 +74,7 @@ services: AUTH0_SECRET: ${AUTH0_SECRET} APP_USE_HTTPS: ${APP_USE_HTTPS:-true} FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.15.6 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} @@ -99,7 +99,7 @@ services: restart: always teams-plugins-common: - image: voxel51/fiftyone-app:v1.5.6 + image: voxel51/fiftyone-app:v1.6.0-beta.1 environment: API_URL: ${API_URL} FIFTYONE_DATABASE_ADMIN: false diff --git a/helm/fiftyone-teams-app/Chart.yaml b/helm/fiftyone-teams-app/Chart.yaml index ddfe8fcd..5c3a7b33 100644 --- a/helm/fiftyone-teams-app/Chart.yaml +++ b/helm/fiftyone-teams-app/Chart.yaml @@ -4,6 +4,6 @@ name: fiftyone-teams-app namespace: fiftyone-teams description: FiftyOne Teams is the enterprise version of the open source [FiftyOne](https://github.com/voxel51/fiftyone) project. type: application -version: 1.5.6 -appVersion: "v1.5.6" +version: 1.6.0-beta.1 +appVersion: "v1.6.0-beta.1" icon: https://voxel51.com/images/logo/voxel51-logo-horz-color-600dpi.png diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 60971e33..b7fe259e 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -15,7 +15,7 @@ # fiftyone-teams-app -![Version: 1.5.6](https://img.shields.io/badge/Version-1.5.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.5.6](https://img.shields.io/badge/AppVersion-v1.5.6-informational?style=flat-square) +![Version: 1.6.0-beta.1](https://img.shields.io/badge/Version-1.6.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.6.0-beta.1](https://img.shields.io/badge/AppVersion-v1.6.0--beta.1-informational?style=flat-square) FiftyOne Teams is the enterprise version of the open source [FiftyOne](https://github.com/voxel51/fiftyone) project. @@ -456,7 +456,7 @@ appSettings: | teamsAppSettings.dnsName | string | `""` | DNS Name for the teams-app service. Used in the chart managed ingress (`spec.tls.hosts` and `spec.rules[0].host`) and teams-app deployment environment variable `AUTH0_BASE_URL`. | | teamsAppSettings.env.APP_USE_HTTPS | bool | `true` | Controls the protocol of the teams-app. Configure your ingress to match. When `true`, uses the https protocol. When `false`, uses the http protocol. | | teamsAppSettings.env.FIFTYONE_APP_ALLOW_MEDIA_EXPORT | bool | `true` | When `false`, disables media export options | -| teamsAppSettings.env.FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION | string | `"0.15.6"` | The recommended fiftyone SDK version that will be displayed in the install modal (i.e. `pip install ... fiftyone==0.11.0`). | +| teamsAppSettings.env.FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION | string | `"0.16.0b1"` | The recommended fiftyone SDK version that will be displayed in the install modal (i.e. `pip install ... fiftyone==0.11.0`). | | teamsAppSettings.env.FIFTYONE_APP_THEME | string | `"dark"` | The default theme configuration. `dark`: Theme will be dark when user visits for the first time. `light`: Theme will be light theme when user visits for the first time. `always-dark`: Sets dark theme on each refresh (overrides user theme changes in the app). `always-light`: Sets light theme on each refresh (overrides user theme changes in the app). | | teamsAppSettings.env.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED | bool | `false` | Disable duplicate atom/selector key checking that generated false-positive errors. [Reference][recoil-env]. | | teamsAppSettings.fiftyoneApiOverride | string | `""` | Overrides the `FIFTYONE_API_URI` environment variable. When set `FIFTYONE_API_URI` controls the value shown in the API Key Modal providing guidance for connecting to the FiftyOne Teams API. `FIFTYONE_API_URI` uses the value from apiSettings.dnsName if it is set, or uses the teamsAppSettings.dnsName | @@ -494,7 +494,7 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.15.6 SDK (database version 0.23.5) is _NOT_ backwards-compatible +The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible with FiftyOne Teams Database Versions prior to 0.19.0. The FiftyOne 0.10.x SDK is not forwards compatible with current FiftyOne Teams Database Versions. @@ -507,12 +507,12 @@ versions prior to FiftyOne Teams version 1.1.0: 1. In your `values.yaml`, set the required [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) environment variable -1. [Upgrade to FiftyOne Teams version 1.5.6](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the - > FiftyOne Teams Database until they upgrade to `fiftyone==0.15.6` -1. Upgrade your FiftyOne SDKs to version 0.15.6 + > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` +1. Upgrade your FiftyOne SDKs to version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -530,10 +530,10 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later -The FiftyOne 0.15.6 SDK is backwards-compatible with +The FiftyOne 0.16.0 SDK is backwards-compatible with FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.5.6 -database (version 0.23.5) with any FiftyOne SDK before 0.15.6. +You will not be able to connect to a FiftyOne Teams 1.6.0 +database (version 0.23.5) with any FiftyOne SDK before 0.16.0. We recommend using the latest version of the FiftyOne SDK compatible with your FiftyOne Teams deployment. @@ -545,8 +545,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. [Upgrade to FiftyOne Teams version 1.5.6](#launch-fiftyone-teams) -1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.15.6 +1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -556,8 +556,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.15.6 will lose database connectivity - > at this point. Upgrading to `fiftyone==0.15.6` is required + > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose database connectivity + > at this point. Upgrading to `fiftyone==0.16.0` is required 1. Validate that all datasets are now at version 0.23.5, by running diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index e42aa476..744c35f9 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -318,7 +318,7 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.15.6 SDK (database version 0.23.5) is _NOT_ backwards-compatible +The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible with FiftyOne Teams Database Versions prior to 0.19.0. The FiftyOne 0.10.x SDK is not forwards compatible with current FiftyOne Teams Database Versions. @@ -331,12 +331,12 @@ versions prior to FiftyOne Teams version 1.1.0: 1. In your `values.yaml`, set the required [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) environment variable -1. [Upgrade to FiftyOne Teams version 1.5.6](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the - > FiftyOne Teams Database until they upgrade to `fiftyone==0.15.6` -1. Upgrade your FiftyOne SDKs to version 0.15.6 + > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` +1. Upgrade your FiftyOne SDKs to version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -354,10 +354,10 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later -The FiftyOne 0.15.6 SDK is backwards-compatible with +The FiftyOne 0.16.0 SDK is backwards-compatible with FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.5.6 -database (version 0.23.5) with any FiftyOne SDK before 0.15.6. +You will not be able to connect to a FiftyOne Teams 1.6.0 +database (version 0.23.5) with any FiftyOne SDK before 0.16.0. We recommend using the latest version of the FiftyOne SDK compatible with your FiftyOne Teams deployment. @@ -369,8 +369,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. [Upgrade to FiftyOne Teams version 1.5.6](#launch-fiftyone-teams) -1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.15.6 +1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` @@ -380,8 +380,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.15.6 will lose database connectivity - > at this point. Upgrading to `fiftyone==0.15.6` is required + > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose database connectivity + > at this point. Upgrading to `fiftyone==0.16.0` is required 1. Validate that all datasets are now at version 0.23.5, by running diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index bf52fd2d..98244578 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -497,7 +497,7 @@ teamsAppSettings: FIFTYONE_APP_ALLOW_MEDIA_EXPORT: true # -- The recommended fiftyone SDK version that will be displayed in the # install modal (i.e. `pip install ... fiftyone==0.11.0`). - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.15.6 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 # -- The default theme configuration. # `dark`: Theme will be dark when user visits for the first time. # `light`: Theme will be light theme when user visits for the first time. diff --git a/helm/gke-example/values.yaml b/helm/gke-example/values.yaml index a65e8813..1e9a1506 100644 --- a/helm/gke-example/values.yaml +++ b/helm/gke-example/values.yaml @@ -33,7 +33,7 @@ secret: # appSettings: # env: -# # FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.5.6 installs +# # FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 installs # # If you are performing a new install or an upgrade from v1.0 or earlier # # you may want to set this value to `true`. # # Please see https://helm.fiftyone.ai/#initial-installation-vs-upgrades for details diff --git a/helm/values.yaml b/helm/values.yaml index 5b9a1309..512780d5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -40,12 +40,12 @@ secret: # FIFTYONE_PLUGINS_DIR: /opt/plugins # Set FIFTYONE_TEAMS_VERSION_OVERRIDE to override the `Install FiftyOne` # bash command in the `Settings > Install FiftyOne` modal -# FIFTYONE_TEAMS_VERSION_OVERRIDE: pip install --index-url https://privatepypi.internal.org fiftyone==0.15.6 +# FIFTYONE_TEAMS_VERSION_OVERRIDE: pip install --index-url https://privatepypi.internal.org fiftyone==0.16.0 # appSettings: # env: -# FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.5.6 installs +# FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 installs # If you are performing a new install or an upgrade from v1.0 or earlier you may want to set # this value to `true`. # Please see https://helm.fiftyone.ai/#initial-installation-vs-upgrades for details From 1d79983efc146bdddf4f89bd88221c5aab9ea939 Mon Sep 17 00:00:00 2001 From: topher Date: Fri, 1 Mar 2024 11:03:48 -0500 Subject: [PATCH 07/42] updates for v1.6.0-beta.1 --- helm/fiftyone-teams-app/README.md | 2 +- helm/fiftyone-teams-app/values.yaml | 4 ++++ helm/values.yaml | 34 +++++++++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index b7fe259e..073044a6 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -439,7 +439,7 @@ appSettings: | secret.fiftyone.clientSecret | string | `""` | Voxel51-provided Auth0 Client Secret. | | secret.fiftyone.cookieSecret | string | `""` | A randomly generated string for cookie encryption. To generate, run `openssl rand -hex 32`. | | secret.fiftyone.encryptionKey | string | `""` | Encryption key for storage credentials. [Reference][fiftyone-encryption-key]. | -| secret.fiftyone.fiftyoneAuthSecret | string | `""` | A randomly generated string for CAS Authentication. | +| secret.fiftyone.fiftyoneAuthSecret | string | `""` | A randomly generated string for CAS Authentication. This can be any string you care to use generated by any mechanism you prefer. This is used for inter-service authentication and for the SuperUser to authenticate at the CAS UI to configure the Central Authentication Service. | | secret.fiftyone.fiftyoneDatabaseName | string | `""` | MongoDB Database Name for FiftyOne Teams. | | secret.fiftyone.mongodbConnectionString | string | `""` | MongoDB Connection String. [Reference][mongodb-connection-string]. | | secret.fiftyone.organizationId | string | `""` | Voxel51-provided Auth0 Organization ID. | diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 98244578..3251a491 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -457,6 +457,10 @@ secret: # -- Encryption key for storage credentials. [Reference][fiftyone-encryption-key]. encryptionKey: "" # -- A randomly generated string for CAS Authentication. + # This can be any string you care to use generated by any mechanism you + # prefer. + # This is used for inter-service authentication and for the SuperUser to + # authenticate at the CAS UI to configure the Central Authentication Service. fiftyoneAuthSecret: "" serviceAccount: diff --git a/helm/values.yaml b/helm/values.yaml index 512780d5..490818f7 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -8,13 +8,14 @@ secret: fiftyone: - # These secrets come from Voxel51 - apiClientId: - apiClientSecret: - auth0Domain: - clientId: - clientSecret: - organizationId: + # These secrets come from Voxel51 and are only used when + # `casSettings.env.FIFTYONE_AUTH_MODE` is set to `legacy` + # apiClientId: + # apiClientSecret: + # auth0Domain: + # clientId: + # clientSecret: + # organizationId: # These secrets come from your MongoDB implementation fiftyoneDatabaseName: fiftyone mongodbConnectionString: mongodb://username:password@somehostname/?authSource=admin @@ -28,6 +29,12 @@ secret: # from cryptography.fernet import Fernet # print(Fernet.generate_key().decode()) encryptionKey: + # This secret is a random string used to authenticate to the CAS service. + # This can be any string you care to use generated by any mechanism you + # prefer. + # This is used for inter-service authentication and for the SuperUser to + # authenticate at the CAS UI to configure the Central Authentication Service. + fiftyoneAuthSecret: # apiSettings: # Set `dnsName` to expose the API with host-based routing. @@ -43,17 +50,22 @@ secret: # FIFTYONE_TEAMS_VERSION_OVERRIDE: pip install --index-url https://privatepypi.internal.org fiftyone==0.16.0 -# appSettings: -# env: -# FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 installs +appSettings: + env: +# FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 # If you are performing a new install or an upgrade from v1.0 or earlier you may want to set # this value to `true`. # Please see https://helm.fiftyone.ai/#initial-installation-vs-upgrades for details -# FIFTYONE_DATABASE_ADMIN: false + FIFTYONE_DATABASE_ADMIN: true # Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are # enabling plugins in the `fiftyone-app` deployment # See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm#enabling-fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins +# FIFTYONE_PLUGINS_CACHE_ENABLED: true + +# casSettings: +# env: +# FIFTYONE_AUTH_MODE: internal # pluginsSettings: # enabled: true From c6942d77e354028533a330a3a3029ffc1f5f4c25 Mon Sep 17 00:00:00 2001 From: topher Date: Fri, 1 Mar 2024 16:34:20 -0500 Subject: [PATCH 08/42] updates for v1.6.0-beta.2 --- docker/README.md | 11 +++++++---- docker/internal-auth/common-services.yaml | 12 ++++++------ docker/internal-auth/env.template | 2 +- docker/legacy-auth/common-services.yaml | 10 +++++----- helm/fiftyone-teams-app/Chart.yaml | 4 ++-- helm/fiftyone-teams-app/README.md | 7 ++++--- helm/fiftyone-teams-app/README.md.gotmpl | 4 +++- helm/fiftyone-teams-app/values.yaml | 2 +- helm/values.yaml | 6 +++--- 9 files changed, 32 insertions(+), 26 deletions(-) diff --git a/docker/README.md b/docker/README.md index cf0e6aa5..4f44ae86 100644 --- a/docker/README.md +++ b/docker/README.md @@ -253,7 +253,8 @@ To configure this, set following environment variables on NO_PROXY: ${NO_PROXY_LIST} ``` -1. All containers based on the `fiftyone-teams-app` image +1. All containers based on the `fiftyone-teams-app` and `fiftyone-teams-cas` + images ```yaml GLOBAL_AGENT_HTTP_PROXY: ${HTTP_PROXY_URL} @@ -268,16 +269,18 @@ By default these service names are - `fiftyone-app` - `teams-api` - `teams-app` +- `teams-cas` - `teams-plugins` Examples of these settings are included in the FiftyOne Teams configuration files -- [common-services.yaml](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/common-services.yaml) -- [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/env.template) +- [common-services.yaml](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/internal-auth/common-services.yaml) +- [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/internal-auth/env.template) By default, the Global Agent Proxy will log all outbound connections and identify which connections are routed through the proxy. -To reduce the logging verbosity, add this environment variable to your `teamsAppSettings.env` +To reduce the logging verbosity, add this environment variable to your +`teams-app` and `teams-cas` services. ```ini ROARR_LOG: false diff --git a/docker/internal-auth/common-services.yaml b/docker/internal-auth/common-services.yaml index 54c369de..99aca5dc 100644 --- a/docker/internal-auth/common-services.yaml +++ b/docker/internal-auth/common-services.yaml @@ -1,7 +1,7 @@ --- services: fiftyone-app-common: - image: voxel51/fiftyone-app:v1.6.0-beta.1 + image: voxel51/fiftyone-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} @@ -29,7 +29,7 @@ services: restart: always teams-api-common: - image: voxel51/fiftyone-teams-api:v1.6.0-beta.1 + image: voxel51/fiftyone-teams-api:v1.6.0-beta.2 environment: CAS_BASE_URL: ${CAS_BASE_URL} FEATURE_FLAG_ENABLE_INVITATIONS: false @@ -57,14 +57,14 @@ services: restart: always teams-app-common: - image: voxel51/fiftyone-teams-app:v1.6.0-beta.1 + image: voxel51/fiftyone-teams-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} APP_USE_HTTPS: ${APP_USE_HTTPS:-true} FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} FIFTYONE_APP_ALLOW_MEDIA_EXPORT: ${FIFTYONE_APP_ALLOW_MEDIA_EXPORT:-true} - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b2 FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams @@ -90,7 +90,7 @@ services: restart: always teams-cas-common: - image: voxel51/fiftyone-teams-cas:v1.6.0-beta.1 + image: voxel51/fiftyone-teams-cas:v1.6.0-beta.2 environment: CAS_DATABASE_NAME: ${CAS_DATABASE_NAME} CAS_DEFAULT_USER_ROLE: ${CAS_DEFAULT_USER_ROLE} @@ -119,7 +119,7 @@ services: restart: always teams-plugins-common: - image: voxel51/fiftyone-app:v1.6.0-beta.1 + image: voxel51/fiftyone-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 9eb7980a..884749d7 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -2,7 +2,7 @@ BASE_URL=https://example.fiftyone.ai # This should be set to the URI your end-users will use to connect to the API -# This could be the same as AUTH0_BASE_URL +# This could be the same as AUTH0_BASE_URL if you are using path-based routing FIFTYONE_API_URI=https://example-api.fiftyone.ai # This should be a MongoDB Connection String for your database diff --git a/docker/legacy-auth/common-services.yaml b/docker/legacy-auth/common-services.yaml index fc513ced..a8ef38bd 100644 --- a/docker/legacy-auth/common-services.yaml +++ b/docker/legacy-auth/common-services.yaml @@ -1,7 +1,7 @@ --- services: fiftyone-app-common: - image: voxel51/fiftyone-app:v1.6.0-beta.1 + image: voxel51/fiftyone-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} FIFTYONE_DATABASE_ADMIN: false @@ -32,7 +32,7 @@ services: restart: always teams-api-common: - image: voxel51/fiftyone-teams-api:v1.6.0-beta.1 + image: voxel51/fiftyone-teams-api:v1.6.0-beta.2 environment: AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID} AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} @@ -62,7 +62,7 @@ services: restart: always teams-app-common: - image: voxel51/fiftyone-teams-app:v1.6.0-beta.1 + image: voxel51/fiftyone-teams-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} @@ -74,7 +74,7 @@ services: AUTH0_SECRET: ${AUTH0_SECRET} APP_USE_HTTPS: ${APP_USE_HTTPS:-true} FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b2 FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} @@ -99,7 +99,7 @@ services: restart: always teams-plugins-common: - image: voxel51/fiftyone-app:v1.6.0-beta.1 + image: voxel51/fiftyone-app:v1.6.0-beta.2 environment: API_URL: ${API_URL} FIFTYONE_DATABASE_ADMIN: false diff --git a/helm/fiftyone-teams-app/Chart.yaml b/helm/fiftyone-teams-app/Chart.yaml index 5c3a7b33..060f97b8 100644 --- a/helm/fiftyone-teams-app/Chart.yaml +++ b/helm/fiftyone-teams-app/Chart.yaml @@ -4,6 +4,6 @@ name: fiftyone-teams-app namespace: fiftyone-teams description: FiftyOne Teams is the enterprise version of the open source [FiftyOne](https://github.com/voxel51/fiftyone) project. type: application -version: 1.6.0-beta.1 -appVersion: "v1.6.0-beta.1" +version: 1.6.0-beta.2 +appVersion: "v1.6.0-beta.2" icon: https://voxel51.com/images/logo/voxel51-logo-horz-color-600dpi.png diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 073044a6..dd775b48 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -15,7 +15,7 @@ # fiftyone-teams-app -![Version: 1.6.0-beta.1](https://img.shields.io/badge/Version-1.6.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.6.0-beta.1](https://img.shields.io/badge/AppVersion-v1.6.0--beta.1-informational?style=flat-square) +![Version: 1.6.0-beta.2](https://img.shields.io/badge/Version-1.6.0--beta.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.6.0-beta.2](https://img.shields.io/badge/AppVersion-v1.6.0--beta.2-informational?style=flat-square) FiftyOne Teams is the enterprise version of the open source [FiftyOne](https://github.com/voxel51/fiftyone) project. @@ -231,7 +231,8 @@ To configure this, set the following environment variables on > you will need to include the override service names in your `NO_PROXY` and > `no_proxy` configurations instead -1. The pod based on the `fiftyone-teams-app` image (`teamsAppSettings.env`) +1. The deployments based on the `fiftyone-teams-app` (`teamsAppSettings.env`) or + `fiftyone-teams-cas` (`casSettings.env`) images ```yaml GLOBAL_AGENT_HTTP_PROXY: http://proxy.yourcompany.tld:3128 @@ -456,7 +457,7 @@ appSettings: | teamsAppSettings.dnsName | string | `""` | DNS Name for the teams-app service. Used in the chart managed ingress (`spec.tls.hosts` and `spec.rules[0].host`) and teams-app deployment environment variable `AUTH0_BASE_URL`. | | teamsAppSettings.env.APP_USE_HTTPS | bool | `true` | Controls the protocol of the teams-app. Configure your ingress to match. When `true`, uses the https protocol. When `false`, uses the http protocol. | | teamsAppSettings.env.FIFTYONE_APP_ALLOW_MEDIA_EXPORT | bool | `true` | When `false`, disables media export options | -| teamsAppSettings.env.FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION | string | `"0.16.0b1"` | The recommended fiftyone SDK version that will be displayed in the install modal (i.e. `pip install ... fiftyone==0.11.0`). | +| teamsAppSettings.env.FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION | string | `"0.16.0b2"` | The recommended fiftyone SDK version that will be displayed in the install modal (i.e. `pip install ... fiftyone==0.11.0`). | | teamsAppSettings.env.FIFTYONE_APP_THEME | string | `"dark"` | The default theme configuration. `dark`: Theme will be dark when user visits for the first time. `light`: Theme will be light theme when user visits for the first time. `always-dark`: Sets dark theme on each refresh (overrides user theme changes in the app). `always-light`: Sets light theme on each refresh (overrides user theme changes in the app). | | teamsAppSettings.env.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED | bool | `false` | Disable duplicate atom/selector key checking that generated false-positive errors. [Reference][recoil-env]. | | teamsAppSettings.fiftyoneApiOverride | string | `""` | Overrides the `FIFTYONE_API_URI` environment variable. When set `FIFTYONE_API_URI` controls the value shown in the API Key Modal providing guidance for connecting to the FiftyOne Teams API. `FIFTYONE_API_URI` uses the value from apiSettings.dnsName if it is set, or uses the teamsAppSettings.dnsName | diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 744c35f9..9b6af2b0 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -233,7 +233,9 @@ To configure this, set the following environment variables on > you will need to include the override service names in your `NO_PROXY` and > `no_proxy` configurations instead -1. The pod based on the `fiftyone-teams-app` image (`teamsAppSettings.env`) +1. The deployments based on the `fiftyone-teams-app` (`teamsAppSettings.env`) or + `fiftyone-teams-cas` (`casSettings.env`) images + ```yaml GLOBAL_AGENT_HTTP_PROXY: http://proxy.yourcompany.tld:3128 diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 3251a491..d5a5c6b9 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -501,7 +501,7 @@ teamsAppSettings: FIFTYONE_APP_ALLOW_MEDIA_EXPORT: true # -- The recommended fiftyone SDK version that will be displayed in the # install modal (i.e. `pip install ... fiftyone==0.11.0`). - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b1 + FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b2 # -- The default theme configuration. # `dark`: Theme will be dark when user visits for the first time. # `light`: Theme will be light theme when user visits for the first time. diff --git a/helm/values.yaml b/helm/values.yaml index 490818f7..3c6964bd 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -63,9 +63,9 @@ appSettings: # FIFTYONE_PLUGINS_DIR: /opt/plugins # FIFTYONE_PLUGINS_CACHE_ENABLED: true -# casSettings: -# env: -# FIFTYONE_AUTH_MODE: internal +casSettings: + env: + FIFTYONE_AUTH_MODE: internal # pluginsSettings: # enabled: true From ce8ac0cd4c0c97fb4afbe18afda8d2534ea559b6 Mon Sep 17 00:00:00 2001 From: topher Date: Fri, 1 Mar 2024 18:55:30 -0500 Subject: [PATCH 09/42] consistent image name --- helm/fiftyone-teams-app/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index d5a5c6b9..d91e6ac8 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -213,7 +213,7 @@ casSettings: # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. pullPolicy: Always # -- Container image for teams-cas. - repository: voxel51/teams-cas + repository: voxel51/fiftyone-teams-cas # -- Image tag for teams-cas. Defaults to the chart version. tag: "" From fb6cc763d671a4e6601241b254584cffbaede4ff Mon Sep 17 00:00:00 2001 From: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:18:44 -0700 Subject: [PATCH 10/42] [AS-110] Add Unit Tests (Compose and Helm) for v1.6.0 (#97) * test: Add Unit Tests for the helm chart templates (#88) * test: Add the go library terratest for Helm template unit testing with testify test suites. Add unit tests for all of our Helm templates. Added pre-commit hook for `go-fmt`. Add make targets for running unit test. * docs: Add documentation in `tests/testing.md`. * test: Change helm unit tests that used `s.True(reflect.DeepEqual(...)` to be `s.Equal(...)` to show the diff between the expected and the actual. Update test expected chart version from `0.15.6` to `0.15.7` to match the release version. * test: Add unit testing for Docker Compose. Organize unit tests into `helm` and `compose` directories. Update make targets (adding new for compose). * docs: Update docs `testing.md`. * test: Adapt docker compose unit tests for CAS beta (v1.6.0-beta.2) * test: Adapt helm unit tests for CAS beta (v1.6.0-beta.2) --- .gitignore | 6 + .pre-commit-config.yaml | 14 +- .tool-versions | 1 + Makefile | 30 +- .../compose.dedicated-plugins.yaml | 8 +- docker/legacy-auth/compose.plugins.yaml | 18 +- docker/legacy-auth/compose.yaml | 8 +- docker/legacy-auth/env.template | 2 +- tests/go.mod | 100 + tests/go.sum | 303 +++ tests/testing.md | 133 ++ tests/unit/compose/common_test.go | 8 + .../docker-compose-internal-auth_test.go | 919 ++++++++ .../docker-compose-legacy-auth_test.go | 916 ++++++++ tests/unit/helm/api-deployment_test.go | 1538 +++++++++++++ tests/unit/helm/api-service_test.go | 422 ++++ tests/unit/helm/app-deployment_test.go | 1498 ++++++++++++ tests/unit/helm/app-hpa_test.go | 555 +++++ tests/unit/helm/app-service_test.go | 422 ++++ tests/unit/helm/cas-deployment_test.go | 1624 +++++++++++++ tests/unit/helm/cas-service_test.go | 422 ++++ tests/unit/helm/chartInfo.go | 19 + tests/unit/helm/common_test.go | 6 + tests/unit/helm/ingress_test.go | 871 +++++++ tests/unit/helm/namespace_test.go | 97 + tests/unit/helm/plugins-deployment_test.go | 2023 +++++++++++++++++ tests/unit/helm/plugins-hpa_test.go | 555 +++++ tests/unit/helm/plugins-service_test.go | 567 +++++ tests/unit/helm/secrets_test.go | 275 +++ tests/unit/helm/serviceaccount_test.go | 284 +++ tests/unit/helm/teams-app-deployment_test.go | 1433 ++++++++++++ tests/unit/helm/teams-app-hpa_test.go | 557 +++++ tests/unit/helm/teams-app-service_test.go | 426 ++++ 33 files changed, 16038 insertions(+), 22 deletions(-) create mode 100644 tests/go.mod create mode 100644 tests/go.sum create mode 100644 tests/testing.md create mode 100644 tests/unit/compose/common_test.go create mode 100644 tests/unit/compose/docker-compose-internal-auth_test.go create mode 100644 tests/unit/compose/docker-compose-legacy-auth_test.go create mode 100644 tests/unit/helm/api-deployment_test.go create mode 100644 tests/unit/helm/api-service_test.go create mode 100644 tests/unit/helm/app-deployment_test.go create mode 100644 tests/unit/helm/app-hpa_test.go create mode 100644 tests/unit/helm/app-service_test.go create mode 100644 tests/unit/helm/cas-deployment_test.go create mode 100644 tests/unit/helm/cas-service_test.go create mode 100644 tests/unit/helm/chartInfo.go create mode 100644 tests/unit/helm/common_test.go create mode 100644 tests/unit/helm/ingress_test.go create mode 100644 tests/unit/helm/namespace_test.go create mode 100644 tests/unit/helm/plugins-deployment_test.go create mode 100644 tests/unit/helm/plugins-hpa_test.go create mode 100644 tests/unit/helm/plugins-service_test.go create mode 100644 tests/unit/helm/secrets_test.go create mode 100644 tests/unit/helm/serviceaccount_test.go create mode 100644 tests/unit/helm/teams-app-deployment_test.go create mode 100644 tests/unit/helm/teams-app-hpa_test.go create mode 100644 tests/unit/helm/teams-app-service_test.go diff --git a/.gitignore b/.gitignore index 3c28ddbf..c6afdbed 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ compose.override.yaml .env helm/gke-example/voxel51-docker.json helm/gke-example/values-*.yaml +tests/unit/*/test_output.log +tests/unit/*/test_output/* +tests/unit/*/test_reports/* +tests/integration/*/test_output.log +tests/integration/*/test_output/* +tests/integration/*/test_reports/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc5f06bd..923f88e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,11 @@ repos: rev: v1.5.4 hooks: - id: forbid-tabs + exclude_types: + - go + - go-mod + - go-sum + - makefile - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.36.0 hooks: @@ -31,7 +36,7 @@ repos: rev: v2.2.5 hooks: - id: codespell - exclude: helm/local-self-signed-example/cert-manger-crds + exclude: helm/local-self-signed-example/cert-manger-crds|tests/go.sum - repo: https://github.com/adrienverge/yamllint rev: v1.32.0 hooks: @@ -71,4 +76,9 @@ repos: - id: detect-secrets args: - --exclude-secrets - - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976)' + - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' + - repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-fmt + - id: go-mod-tidy diff --git a/.tool-versions b/.tool-versions index d41763fb..51d88cd8 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,5 @@ cmctl 1.13.3 +golang 1.21.6 helm 3.13.2 helm-docs 1.11.3 kubectl 1.27.4 diff --git a/Makefile b/Makefile index fe16c993..7cf29ae2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ SHELL := $(SHELL) -e +ASDF := $(shell asdf where golang) # Help .PHONY: $(shell sed -n -e '/^$$/ { n ; /^[^ .\#][^ ]*:/ { s/:.*$$// ; p ; } ; }' $(MAKEFILE_LIST)) @@ -88,9 +89,34 @@ port-forward-api: ## port forward to service `teams-api` on the host port 8000 port-forward-mongo: ## port forward to service `mongodb` on the host port 27017 kubectl port-forward --namespace fiftyone-teams svc/mongodb 27017:27017 --context minikube -helm-repos: # add helm repos for the project +helm-repos: ## add helm repos for the project helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add jetstack https://charts.jetstack.io -tunnel: +tunnel: ## run minikube tunnel to access the k8s ingress via localhost () minikube tunnel + +test-unit-compose: ## run go test on the tests/unit/compose directory + @cd tests/unit/compose; \ + go test -count=1 -timeout=10m -v -tags unit + +test-unit-helm: ## run go test on the tests/unit/helm directory + @cd tests/unit/helm; \ + go test -count=1 -timeout=10m -v -tags unit + +test-unit-compose-interleaved: install-terratest-log-parser ## run go test on the tests/unit/compose directory and run the terratest_log_parser for reports + @cd tests/unit/compose; \ + rm -rf test_reports; \ + mkdir test_reports; \ + go test -count=1 -timeout=10m -v -tags unit | tee test_output.log; \ + ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output + +test-unit-helm-interleaved: install-terratest-log-parser ## run go test on the tests/unit/helm directory and run the terratest_log_parser for reports + @cd tests/unit/helm; \ + rm -rf test_reports; \ + mkdir test_reports; \ + go test -count=1 -timeout=10m -v -tags unit | tee test_output.log; \ + ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output + +install-terratest-log-parser: ## install terratest_log_parser + go install github.com/gruntwork-io/terratest/cmd/terratest_log_parser@latest diff --git a/docker/legacy-auth/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml index 28ec1397..a9436a62 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -2,6 +2,10 @@ # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common teams-api: extends: file: common-services.yaml @@ -16,10 +20,6 @@ services: service: teams-app-common environment: FIFTYONE_TEAMS_PLUGIN_URL: ${FIFTYONE_TEAMS_PLUGIN_URL} - fiftyone-app: - extends: - file: common-services.yaml - service: fiftyone-app-common teams-plugins: extends: file: common-services.yaml diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index cb4c7529..a3a31aec 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -2,6 +2,15 @@ # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common + environment: + FIFTYONE_PLUGINS_CACHE_ENABLED: true + FIFTYONE_PLUGINS_DIR: /opt/plugins + volumes: + - plugins-vol:/opt/plugins:ro teams-api: extends: file: common-services.yaml @@ -14,14 +23,5 @@ services: extends: file: common-services.yaml service: teams-app-common - fiftyone-app: - extends: - file: common-services.yaml - service: fiftyone-app-common - environment: - FIFTYONE_PLUGINS_CACHE_ENABLED: true - FIFTYONE_PLUGINS_DIR: /opt/plugins - volumes: - - plugins-vol:/opt/plugins:ro volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index 93ebc52b..808bb6ef 100644 --- a/docker/legacy-auth/compose.yaml +++ b/docker/legacy-auth/compose.yaml @@ -2,6 +2,10 @@ # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: + fiftyone-app: + extends: + file: common-services.yaml + service: fiftyone-app-common teams-api: extends: file: common-services.yaml @@ -10,7 +14,3 @@ services: extends: file: common-services.yaml service: teams-app-common - fiftyone-app: - extends: - file: common-services.yaml - service: fiftyone-app-common diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index 8466a138..c4c8f716 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -1,4 +1,4 @@ -#Auth0 Configuration - This all comes from Voxel51 +# Auth0 Configuration - This all comes from Voxel51 AUTH0_API_CLIENT_ID= AUTH0_API_CLIENT_SECRET= AUTH0_AUDIENCE= diff --git a/tests/go.mod b/tests/go.mod new file mode 100644 index 00000000..49640d17 --- /dev/null +++ b/tests/go.mod @@ -0,0 +1,100 @@ +module github.com/voxel51/fiftyone-teams-app-deploy + +go 1.21.6 + +require ( + github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 + github.com/gruntwork-io/terratest v0.46.11 + github.com/stretchr/testify v1.8.4 + gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.28.4 + k8s.io/apimachinery v0.28.4 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-sql-driver/mysql v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gonvenience/bunt v1.3.5 // indirect + github.com/gonvenience/neat v1.3.12 // indirect + github.com/gonvenience/term v1.0.2 // indirect + github.com/gonvenience/text v1.0.7 // indirect + github.com/gonvenience/wrap v1.1.2 // indirect + github.com/gonvenience/ytbx v1.4.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gruntwork-io/go-commons v0.8.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/homeport/dyff v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pquerna/otp v1.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/urfave/cli v1.22.2 // indirect + github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/client-go v0.28.4 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/tests/go.sum b/tests/go.sum new file mode 100644 index 00000000..5de4c593 --- /dev/null +++ b/tests/go.sum @@ -0,0 +1,303 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gonvenience/bunt v1.3.5 h1:wSQquifvwEWtzn27k1ngLfeLaStyt0k1b/K6TrlCNAs= +github.com/gonvenience/bunt v1.3.5/go.mod h1:7ApqkVBEWvX04oJ28Q2WeI/BvJM6VtukaJAU/q/pTs8= +github.com/gonvenience/neat v1.3.12 h1:xwIyRbJcG9LgcDYys+HHLH9DqqHeQsUpS5CfBUeskbs= +github.com/gonvenience/neat v1.3.12/go.mod h1:8OljAIgPelN0uPPO94VBqxK+Kz98d6ZFwHDg5o/PfkE= +github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= +github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= +github.com/gonvenience/text v1.0.7 h1:YmIqmgTwxnACYCG59DykgMbomwteYyNhAmEUEJtPl14= +github.com/gonvenience/text v1.0.7/go.mod h1:OAjH+mohRszffLY6OjgQcUXiSkbrIavooFpfIt1ZwAs= +github.com/gonvenience/wrap v1.1.2 h1:xPKxNwL1HCguwyM+HlP/1CIuc9LRd7k8RodLwe9YTZA= +github.com/gonvenience/wrap v1.1.2/go.mod h1:GiryBSXoI3BAAhbWD1cZVj7RZmtiu0ERi/6R6eJfslI= +github.com/gonvenience/ytbx v1.4.4 h1:jQopwyaLsVGuwdxSiN4WkXjsEaFNPJ3V4lUj7eyEpzo= +github.com/gonvenience/ytbx v1.4.4/go.mod h1:w37+MKCPcCMY/jpPNmEklD4xKqrOAVBO6kIWW2+uI6M= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= +github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= +github.com/gruntwork-io/terratest v0.46.11 h1:1Z9G18I2FNuH87Ro0YtjW4NH9ky4GDpfzE7+ivkPeB8= +github.com/gruntwork-io/terratest v0.46.11/go.mod h1:DVZG/s7eP1u3KOQJJfE6n7FDriMWpDvnj85XIlZMEM8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/homeport/dyff v1.6.0 h1:AN+ikld0Fy+qx34YE7655b/bpWuxS6cL9k852pE2GUc= +github.com/homeport/dyff v1.6.0/go.mod h1:FlAOFYzeKvxmU5nTrnG+qrlJVWpsFew7pt8L99p5q8k= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/tests/testing.md b/tests/testing.md new file mode 100644 index 00000000..7e7c5dc3 --- /dev/null +++ b/tests/testing.md @@ -0,0 +1,133 @@ +# Testing + +Test types + +* Unit + * Quick + * Test Helm template logic + * Validate Helm values result in the expected rendered YAML kubernetes resources + * Helm template syntax checks +* Integration + * Slower as they require installation into a Kubernetes cluster + * Deploy to the rendered YAML to Kubernetes and validate functionality + * Validate kubernetes resources via queries + +Please use Test Driven Development and write tests +before making changes to the Helm chart. + +## Tools + +[Terratest](https://terratest.gruntwork.io/docs/#getting-started) +is a Go Library for testing Infrastructure as Code. +Terratest supports tests for Helm Charts. + +Go test supports +[Subtests](https://go.dev/blog/subtests) +with table-driven tests. + +Since we use Go test's parallelism, we use +[terratest_log_parser](https://terratest.gruntwork.io/docs/testing-best-practices/debugging-interleaved-test-output/) +to organize the interleaved test output. +This is instrumented in our make targets. + +The library +[Testify](https://github.com/stretchr/testify) +provides suite features for better test organization. + +We implemented various patterns from these tools in our tests. + +## Environment Initialization + +1. Install go + + ```shell + make asdf + ``` + +## Unit Tests + +The unit tests are named after the corresponding Helm templates. + +For example, the test +`tests/unit/helm/api-deployment_test.go` +covers the Helm template +`helm/fiftyone-teams-app/templates/api-deployment.yaml`. + +### Running Unit Tests + +Run tests tagged with `unit` or the more specific +tag (found at the top of the test file). + +* Without interleaved test output (good for rapid testing cycle) + + ```shell + # From repo root + make test-unit-compose + make test-unit-helm + ``` + +* With interleaved test output (good for CI runs) + + ```shell + # From repo root + cd test/unit/helm + + # replace the tag `unit` with any build tag + go test -count=1 -timeout=3m -v -tags unit + ``` + +* To run a specific test function, + for example within `plugins-service_test.go`, + matching the regex of the test function name `TestMetadataLabels` + + ```shell + cd test/unit/helm + go test \ + -count=1 \ + -timeout=30s \ + -v \ + -tags unit \ + plugins-service_test.go \ + common_test.go \ + chartInfo.go \ + -testify.m '^(TestMetadataLabels|)$' + ``` + + > **Note:** Include the files `common_test.go` and `chartInfo.go` + > to avoid `undefined` errors for `chartPath` and `chartInfo` + > **Note:** We pass `-count=1` to disable test caching. + +### Writing Unit Tests + +To avoid code duplication, consider +adding items to `tests/unit/helm/common_test.go`. +When adding a new build tag, add the new tag to +the test file and to `tests/unit/helm/common_test.go`. +Currently, `common_test.go` contains the +variable `chartPath` used in all of the tests. + +For structures (structs), there are two approaches. +Either write + +* Go code referencing the type for each field +* JSON (easily converted from YAML) and unmarshall it into the struct + +See +[Debugging interleaved test output](https://terratest.gruntwork.io/docs/testing-best-practices/debugging-interleaved-test-output/#installing-the-utility-binaries). + +## Additional Links + +* [Automated Testing for Kubernetes and Helm Charts using Terratest](https://github.com/gruntwork-io/terratest-helm-testing-example) +* [terratest/examples/helm-basic-example](https://github.com/gruntwork-io/terratest/tree/master/examples/helm-basic-example) + +* [A Tour of Go](https://go.dev/tour/) +* Kubernetes API Library + * [apps/v1 Deployment](https://pkg.go.dev/k8s.io/api/apps/v1#Deployment) + +## Testing Setup + +* [Install delve](https://github.com/go-delve/delve/tree/master/Documentation/installation) + +## VSCode Setup + +* [Debugging](https://github.com/golang/vscode-go/wiki/debugging) diff --git a/tests/unit/compose/common_test.go b/tests/unit/compose/common_test.go new file mode 100644 index 00000000..229dc064 --- /dev/null +++ b/tests/unit/compose/common_test.go @@ -0,0 +1,8 @@ +//go:build docker || compose || unit || unitComposeInternalAuth || unitComposeLegacyAuth +// +build docker compose unit unitComposeInternalAuth unitComposeLegacyAuth + +package unit + +const ( + envFixtureFilePath = "../../fixtures/docker/.env" +) diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go new file mode 100644 index 00000000..f4a57c9a --- /dev/null +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -0,0 +1,919 @@ +//go:build docker || compose || unit || unitComposeInternalAuth +// +build docker compose unit unitComposeInternalAuth + +package unit + +import ( + "context" + "fmt" + "strings" + + "path/filepath" + + "testing" + + "github.com/compose-spec/compose-go/v2/cli" + "github.com/compose-spec/compose-go/v2/types" + + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const ( + dockerInternalAuthDir = "../../../docker/internal-auth" +) + +var internalAuthComposeFile = filepath.Join(dockerInternalAuthDir, "compose.yaml") +var internalAuthComposePluginsFile = filepath.Join(dockerInternalAuthDir, "compose.plugins.yaml") +var internalAuthComposeDedicatedPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.dedicated-plugins.yaml") +var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") + +type commonServicesInternalAuthDockerComposeTest struct { + suite.Suite + composeFilePath string + projectName string + dotEnvFiles []string +} + +func TestDockerComposeInternalAuth(t *testing.T) { + t.Parallel() + + _, err := filepath.Abs(dockerInternalAuthDir) + require.NoError(t, err) + + suite.Run(t, &commonServicesInternalAuthDockerComposeTest{ + Suite: suite.Suite{}, + composeFilePath: dockerInternalAuthDir, + projectName: "fiftyone-compose-test", + dotEnvFiles: []string{ + internalAuthEnvTemplateFilePath, + envFixtureFilePath, + }, + }) +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { + testCases := []struct { + name string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []string + }{ + { + "compose", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + "teams-cas", + }, + }, + { + "composePlugins", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + "teams-cas", + }, + }, + { + "composeDedicatedPlugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + "teams-cas", + "teams-plugins", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.ServiceNames(), fmt.Sprintf("%s - Service Names should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-app:v1.6.0-beta.2", + }, + { + "defaultTeamsApi", + "teams-api", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-teams-api:v1.6.0-beta.2", + }, + { + "defaultTeamsApp", + "teams-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-teams-app:v1.6.0-beta.2", + }, + { + "defaultTeamsCas", + "teams-cas", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-teams-cas:v1.6.0-beta.2", + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/fiftyone-app:v1.6.0-beta.2", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Image, fmt.Sprintf("%s - Image should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + }, + }, + { + "defaultTeamsApi", + "teams-api", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []string{ + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + }, + }, + { + "defaultTeamsApp", + "teams-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + }, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []string{ + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_MODE=internal", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + }, + }, + { + "pluginsFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + }, + }, + { + "pluginsTeamsApi", + "teams-api", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + }, + }, + { + "pluginsTeamsApp", + "teams-app", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + }, + }, + { + "pluginsTeamsCas", + "teams-cas", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_MODE=internal", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + }, + }, + { + "dedicatedPluginsFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + }, + }, + { + "dedicatedPluginsTeamsApi", + "teams-api", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + }, + }, + { + "dedicatedPluginsTeamsApp", + "teams-app", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + "FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151", + }, + }, + { + "dedicatedPluginsTeamsCas", + "teams-cas", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_MODE=internal", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + }, + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(types.NewMappingWithEquals(testCase.expected), project.Services[testCase.serviceName].Environment, fmt.Sprintf("%s - Environment should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServicePorts() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []types.ServicePortConfig + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 5151, + Published: "5151", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsApi", + "teams-api", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 8000, + Published: "8000", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsApp", + "teams-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 3000, + Published: "3000", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 3000, + Published: "3030", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Ports, fmt.Sprintf("%s - Ports should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServiceRestart() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsApi", + "teams-api", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsApp", + "teams-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Restart, fmt.Sprintf("%s - Restart should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesInternalAuthDockerComposeTest) TestServiceVolumes() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []types.ServiceVolumeConfig + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsApi", + "teams-api", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsApp", + "teams-app", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "pluginsFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: true, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "pluginsTeamsApi", + "teams-api", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "pluginsTeamsApp", + "teams-app", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "pluginsTeamsCas", + "teams-cas", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsFiftyoneApp", + "fiftyone-app", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + // []types.ServiceVolumeConfig{}, + nil, + }, + { + "dedicatedPluginsTeamsApi", + "teams-api", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "dedicatedPluginsTeamsApp", + "teams-app", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsTeamsCas", + "teams-cas", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: true, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Volumes, fmt.Sprintf("%s - Service Volumes should be equal", testCase.name)) + }) + } +} +func (s *commonServicesInternalAuthDockerComposeTest) TestVolumes() { + testCases := []struct { + name string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected types.Volumes + }{ + { + "default", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "plugins", + []string{internalAuthComposePluginsFile}, + s.dotEnvFiles, + types.Volumes{ + "plugins-vol": { + Name: "fiftyone-compose-test_plugins-vol", + }, + }, + }, + { + "dedicatedPlugins", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + types.Volumes{ + "plugins-vol": { + Name: "fiftyone-compose-test_plugins-vol", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerInternalAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(subT, string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Volumes, fmt.Sprintf("%s - Volumes should be equal", testCase.name)) + }) + } +} diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go new file mode 100644 index 00000000..756b97e3 --- /dev/null +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -0,0 +1,916 @@ +//go:build docker || compose || unit || unitComposeLegacyAuth +// +build docker compose unit unitComposeLegacyAuth + +package unit + +import ( + "context" + "fmt" + "strings" + + "path/filepath" + + "testing" + + "github.com/compose-spec/compose-go/v2/cli" + "github.com/compose-spec/compose-go/v2/types" + + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const ( + dockerLegacyAuthDir = "../../../docker/legacy-auth" +) + +var legacyAuthComposeFile = filepath.Join(dockerLegacyAuthDir, "compose.yaml") +var legacyAuthComposePluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.plugins.yaml") +var legacyAuthComposeDedicatedPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.dedicated-plugins.yaml") +var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") + +type commonServicesLegacyAuthDockerComposeTest struct { + suite.Suite + composeFilePath string + projectName string + dotEnvFiles []string +} + +func TestDockerComposeLegacyAuth(t *testing.T) { + t.Parallel() + + _, err := filepath.Abs(dockerLegacyAuthDir) + require.NoError(t, err) + + suite.Run(t, &commonServicesLegacyAuthDockerComposeTest{ + Suite: suite.Suite{}, + composeFilePath: dockerLegacyAuthDir, + projectName: "fiftyone-compose-test", + dotEnvFiles: []string{ + legacyAuthEnvTemplateFilePath, + envFixtureFilePath, + }, + }) +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { + testCases := []struct { + name string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []string + }{ + { + "compose", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + }, + }, + { + "composePlugins", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + }, + }, + { + "composeDedicatedPlugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "teams-api", + "teams-app", + "teams-plugins", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.ServiceNames(), fmt.Sprintf("%s - Service Names should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-app:v1.6.0-beta.2", + }, + { + "defaultTeamsApi", + "teams-api", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-teams-api:v1.6.0-beta.2", + }, + { + "defaultTeamsApp", + "teams-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/fiftyone-teams-app:v1.6.0-beta.2", + }, + { + "defaultTeamsCas", + "teams-cas", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "", + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/fiftyone-app:v1.6.0-beta.2", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Image, fmt.Sprintf("%s - Image should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", + "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", + "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", + "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", + }, + }, + { + "defaultTeamsApi", + "teams-api", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []string{ + "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_DOMAIN=test-auth0-domain", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + }, + }, + { + "defaultTeamsApp", + "teams-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_BASE_URL=https://example.fiftyone.ai", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_ORGANIZATION=test-auth0-organization", + "AUTH0_SECRET=test-auth0-secret", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + }, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "pluginsFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", + "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", + "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", + "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", + }, + }, + { + "pluginsTeamsApi", + "teams-api", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_DOMAIN=test-auth0-domain", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + }, + }, + { + "pluginsTeamsApp", + "teams-app", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_BASE_URL=https://example.fiftyone.ai", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_ORGANIZATION=test-auth0-organization", + "AUTH0_SECRET=test-auth0-secret", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + }, + }, + { + "pluginsTeamsCas", + "teams-cas", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + // []string{}, + nil, + }, + { + "dedicatedPluginsFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", + "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", + "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", + "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", + }, + }, + { + "dedicatedPluginsTeamsApi", + "teams-api", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_DOMAIN=test-auth0-domain", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_ENV=production", + "FIFTYONE_INTERNAL_SERVICE=true", + "GRAPHQL_DEFAULT_LIMIT=10", + "LOGGING_LEVEL=INFO", + "MONGO_DEFAULT_DB=fiftyone", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + }, + }, + { + "dedicatedPluginsTeamsApp", + "teams-app", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "APP_USE_HTTPS=true", + "AUTH0_AUDIENCE=test-auth0-audience", + "AUTH0_BASE_URL=https://example.fiftyone.ai", + "AUTH0_CLIENT_ID=test-auth0-client-id", + "AUTH0_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_ORGANIZATION=test-auth0-organization", + "AUTH0_SECRET=test-auth0-secret", + "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_SERVER_ADDRESS=", + "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", + "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "NODE_ENV=production", + "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", + "FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151", + }, + }, + { + "dedicatedPluginsTeamsCas", + "teams-cas", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + // []string{""}, + // []string{}, + nil, + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []string{ + "API_URL=http://teams-api:8000", + "FIFTYONE_DATABASE_ADMIN=false", + "FIFTYONE_DATABASE_NAME=fiftyone", + "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", + "FIFTYONE_DEFAULT_APP_PORT=5151", + "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", + "FIFTYONE_INTERNAL_SERVICE=true", + "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", + "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", + "FIFTYONE_PLUGINS_CACHE_ENABLED=true", + "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", + "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", + "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", + "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + if testCase.expected == nil { + s.Nil(project.Services[testCase.serviceName].Environment, fmt.Sprintf("%s - Environment should be equal", testCase.name)) + } else { + s.Equal(types.NewMappingWithEquals(testCase.expected), project.Services[testCase.serviceName].Environment, fmt.Sprintf("%s - Environment should be equal", testCase.name)) + } + + }) + } +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServicePorts() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []types.ServicePortConfig + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 5151, + Published: "5151", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsApi", + "teams-api", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 8000, + Published: "8000", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsApp", + "teams-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 3000, + Published: "3000", + Protocol: "tcp", + Extensions: nil, + }, + }, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Ports, fmt.Sprintf("%s - Ports should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceRestart() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected string + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsApi", + "teams-api", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsApp", + "teams-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "", + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + types.RestartPolicyAlways, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Restart, fmt.Sprintf("%s - Restart should be equal", testCase.name)) + }) + } +} + +func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceVolumes() { + testCases := []struct { + name string + serviceName string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected []types.ServiceVolumeConfig + }{ + { + "defaultFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsApi", + "teams-api", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsApp", + "teams-app", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "defaultTeamsCas", + "teams-cas", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "pluginsFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: true, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "pluginsTeamsApi", + "teams-api", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "pluginsTeamsApp", + "teams-app", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "pluginsTeamsCas", + "teams-cas", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsFiftyoneApp", + "fiftyone-app", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + // []types.ServiceVolumeConfig{}, + nil, + }, + { + "dedicatedPluginsTeamsApi", + "teams-api", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + { + "dedicatedPluginsTeamsApp", + "teams-app", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsTeamsCas", + "teams-cas", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + nil, + }, + { + "dedicatedPluginsTeamsPlugins", + "teams-plugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "plugins-vol", + Target: "/opt/plugins", + ReadOnly: true, + Volume: &types.ServiceVolumeVolume{}, + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Services[testCase.serviceName].Volumes, fmt.Sprintf("%s - Service Volumes should be equal", testCase.name)) + }) + } +} +func (s *commonServicesLegacyAuthDockerComposeTest) TestVolumes() { + testCases := []struct { + name string + configPaths []string // file paths to one or more Compose files. + envFiles []string // file paths to ".env" files with additional environment variable data + expected types.Volumes + }{ + { + "default", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + nil, + }, + { + "plugins", + []string{legacyAuthComposePluginsFile}, + s.dotEnvFiles, + types.Volumes{ + "plugins-vol": { + Name: "fiftyone-compose-test_plugins-vol", + }, + }, + }, + { + "dedicatedPlugins", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + types.Volumes{ + "plugins-vol": { + Name: "fiftyone-compose-test_plugins-vol", + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + projectOptions, err := cli.NewProjectOptions( + testCase.configPaths, + cli.WithWorkingDirectory(dockerLegacyAuthDir), + cli.WithName(s.projectName), + cli.WithEnvFiles(testCase.envFiles...), + cli.WithDotEnv, + ) + s.NoError(err) + + project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) + s.NoError(err) + + // Log Output + projectYAML, err := project.MarshalYAML() + s.NoError(err) + // The only next line only prints timestamp on the first line of the yaml file + // logger.Log(s.T(), string(projectYAML)) + for _, line := range strings.Split(string(projectYAML), "\n") { + logger.Log(subT, line) + } + + s.Equal(testCase.expected, project.Volumes, fmt.Sprintf("%s - Volumes should be equal", testCase.name)) + }) + } +} diff --git a/tests/unit/helm/api-deployment_test.go b/tests/unit/helm/api-deployment_test.go new file mode 100644 index 00000000..27d05493 --- /dev/null +++ b/tests/unit/helm/api-deployment_test.go @@ -0,0 +1,1538 @@ +//go:build kubeall || helm || unit || unitApiDeployment +// +build kubeall helm unit unitApiDeployment + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/api/resource" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type deploymentApiTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestDeploymentApiTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &deploymentApiTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/api-deployment.yaml"}, + }) +} + +func (s *deploymentApiTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app": "teams-api", + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-api", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + map[string]string{ + "app": "test-service-name", + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentApiTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-api", + }, + { + "overrideMetadataName", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Name, "Deployment name should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, *deployment.Spec.Replicas, "Replica count should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerCount() { + testCases := []struct { + name string + values map[string]string + expected int + }{ + { + "defaultValues", + nil, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerEnv() { + testCases := []struct { + name string + values map[string]string + expected func(envVars []corev1.EnvVar) + }{ + { + "defaultValues", // legacy auth mode + nil, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_BASE_URL", + "value": "http://teams-cas:80/cas/api" + }, + { + "name": "FEATURE_FLAG_ENABLE_INVITATIONS", + "value": "true" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "MONGO_DEFAULT_DB", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_ENV", + "value": "production" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "GRAPHQL_DEFAULT_LIMIT", + "value": "10" + }, + { + "name": "LOGGING_LEVEL", + "value": "INFO" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "overrideEnv", // legacy auth mode + map[string]string{ + "apiSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_BASE_URL", + "value": "http://teams-cas:80/cas/api" + }, + { + "name": "FEATURE_FLAG_ENABLE_INVITATIONS", + "value": "true" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "MONGO_DEFAULT_DB", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_ENV", + "value": "production" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "GRAPHQL_DEFAULT_LIMIT", + "value": "10" + }, + { + "name": "LOGGING_LEVEL", + "value": "INFO" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "internalAuthMode", + map[string]string{ + "casSettings.env.FIFTYONE_AUTH_MODE": "internal", + "apiSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_BASE_URL", + "value": "http://teams-cas:80/cas/api" + }, + { + "name": "FEATURE_FLAG_ENABLE_INVITATIONS", + "value": "false" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "MONGO_DEFAULT_DB", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_ENV", + "value": "production" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "GRAPHQL_DEFAULT_LIMIT", + "value": "10" + }, + { + "name": "LOGGING_LEVEL", + "value": "INFO" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Env) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerImage() { + + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + fmt.Sprintf("voxel51/fiftyone-teams-api:%s", chartAppVersion), + }, + { + "overrideImageTag", + map[string]string{ + "apiSettings.image.tag": "testTag", + }, + "voxel51/fiftyone-teams-api:testTag", + }, + { + "overrideImageRepository", + map[string]string{ + "apiSettings.image.repository": "ghcr.io/fiftyone-teams-api", + }, + fmt.Sprintf("ghcr.io/fiftyone-teams-api:%s", chartAppVersion), + }, + { + "overrideImageVersionAndRepository", + map[string]string{ + "apiSettings.image.tag": "testTag", + "apiSettings.image.repository": "ghcr.io/fiftyone-teams-api", + }, + "ghcr.io/fiftyone-teams-api:testTag", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Image, "Image values should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerImagePullPolicy() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "Always", + }, + { + "overrideImagePullPolicy", + map[string]string{ + "apiSettings.image.pullPolicy": "IfNotPresent", + }, + "IfNotPresent", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Convert type returned by `.ImagePullPolicy` to a string + s.Equal(testCase.expected, string(deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy), "Image pull policy should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-api", + }, + { + "overrideServiceAccountName", + map[string]string{ + "apiSettings.service.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Name, "Container name should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerLivenessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/health/", + "port": "teams-api" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + { + "overrideServiceLivenessInitialDelaySecondsAndShortName", + map[string]string{ + "apiSettings.service.liveness.initialDelaySeconds": "30", + "apiSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/health/", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].LivenessProbe) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ContainerPort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "teams-api", + "containerPort": 8000, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceContainerPortAndShortName", + map[string]string{ + "apiSettings.service.containerPort": "8051", + "apiSettings.service.shortname": "test-service-shortname", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "test-service-shortname", + "containerPort": 8051, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Ports) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerReadinessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/health/", + "port": "teams-api" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + { + "overrideServiceReadinessInitialDelaySecondsAndShortName", + map[string]string{ + "apiSettings.service.readiness.initialDelaySeconds": "30", + "apiSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/health/", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].ReadinessProbe) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerResourceRequirements() { + testCases := []struct { + name string + values map[string]string + expected func(resourceRequirements corev1.ResourceRequirements) + }{ + { + "defaultValues", + nil, + func(resourceRequirements corev1.ResourceRequirements) { + s.Equal(resourceRequirements.Limits, corev1.ResourceList{}, "Limits should be equal") + s.Equal(resourceRequirements.Requests, corev1.ResourceList{}, "Requests should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + { + "overrideResources", + map[string]string{ + "apiSettings.resources.limits.cpu": "1", + "apiSettings.resources.limits.memory": "1Gi", + "apiSettings.resources.requests.cpu": "500m", + "apiSettings.resources.requests.memory": "512Mi", + }, + func(resourceRequirements corev1.ResourceRequirements) { + resourceExpected := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("1"), + "memory": resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + s.Equal(resourceExpected, resourceRequirements, "should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Resources) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(securityContext *corev1.SecurityContext) + }{ + { + "defaultValues", + nil, + func(securityContext *corev1.SecurityContext) { + s.Nil(securityContext.AllowPrivilegeEscalation, "should be nil") + s.Nil(securityContext.Capabilities, "should be nil") + s.Nil(securityContext.Privileged, "should be nil") + s.Nil(securityContext.ProcMount, "should be nil") + s.Nil(securityContext.ReadOnlyRootFilesystem, "should be nil") + s.Nil(securityContext.RunAsGroup, "should be nil") + s.Nil(securityContext.RunAsNonRoot, "should be nil") + s.Nil(securityContext.RunAsUser, "should be nil") + s.Nil(securityContext.SeccompProfile, "should be nil") + s.Nil(securityContext.SELinuxOptions, "should be nil") + s.Nil(securityContext.WindowsOptions, "should be nil") + }, + }, + { + "overrideSecurityContext", + map[string]string{ + "apiSettings.securityContext.runAsGroup": "3000", + "apiSettings.securityContext.runAsUser": "1000", + }, + func(securityContext *corev1.SecurityContext) { + s.Equal(int64(3000), *securityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *securityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].SecurityContext) + }) + } +} + +func (s *deploymentApiTemplateTest) TestContainerVolumeMounts() { + testCases := []struct { + name string + values map[string]string + expected func(volumeMounts []corev1.VolumeMount) + }{ + { + "defaultValues", + nil, + func(volumeMounts []corev1.VolumeMount) { + s.Nil(volumeMounts, "VolumeMounts should be nil") + }, + }, + { + "overrideVolumeMountsSingle", + map[string]string{ + "apiSettings.volumeMounts[0].mountPath": "/test-data-volume", + "apiSettings.volumeMounts[0].name": "test-volume", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume", + "name": "test-volume" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + { + "overrideVolumeMountsMultiple", + map[string]string{ + "apiSettings.volumeMounts[0].mountPath": "/test-data-volume1", + "apiSettings.volumeMounts[0].name": "test-volume1", + "apiSettings.volumeMounts[1].mountPath": "/test-data-volume2", + "apiSettings.volumeMounts[1].name": "test-volume2", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume1", + "name": "test-volume1" + }, + { + "mountPath": "/test-data-volume2", + "name": "test-volume2" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].VolumeMounts) + }) + } +} + +func (s *deploymentApiTemplateTest) TestAffinity() { + testCases := []struct { + name string + values map[string]string + expected func(affinity *corev1.Affinity) + }{ + { + "defaultValues", + nil, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "overrideAffinity", + map[string]string{ + "apiSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "disktype", + "apiSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "In", + "apiSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]": "ssd", + }, + func(affinity *corev1.Affinity) { + affinityJSON := `{ + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "disktype", + "operator": "In", + "values": [ + "ssd" + ] + } + ] + } + ] + } + } + }` + var expectedAffinity corev1.Affinity + err := json.Unmarshal([]byte(affinityJSON), &expectedAffinity) + s.NoError(err) + + s.Equal(expectedAffinity, *affinity, "Affinity should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Affinity) + }) + } +} + +func (s *deploymentApiTemplateTest) TestImagePullSecrets() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "overrideImagePullSecrets", + map[string]string{ + "imagePullSecrets[0].name": "test-pull-secret", + }, + "test-pull-secret", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == "" { + s.Nil(deployment.Spec.Template.Spec.ImagePullSecrets, "Image pull secret should be nil") + } else { + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ImagePullSecrets[0].Name, "Image pull secret should be equal.") + } + }) + } +} + +func (s *deploymentApiTemplateTest) TestNodeSelector() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideNodeSelector", + map[string]string{ + "apiSettings.nodeSelector.disktype": "ssd", + }, + map[string]string{ + "disktype": "ssd", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.Spec.NodeSelector[key] + s.Equal(value, foundValue, "NodeSelector should contain all set labels.") + } + }) + } +} + +func (s *deploymentApiTemplateTest) TestPodAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overridePodAnnotations", + map[string]string{ + "apiSettings.podAnnotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == nil { + s.Nil(deployment.Spec.Template.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *deploymentApiTemplateTest) TestPodSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(podSecurityContext *corev1.PodSecurityContext) + }{ + { + "defaultValues", + nil, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") + s.Nil(podSecurityContext.RunAsGroup, "should be nil") + s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") + s.Nil(podSecurityContext.RunAsUser, "should be nil") + s.Nil(podSecurityContext.SeccompProfile, "should be nil") + s.Nil(podSecurityContext.SELinuxOptions, "should be nil") + s.Nil(podSecurityContext.SupplementalGroups, "should be nil") + s.Nil(podSecurityContext.Sysctls, "should be nil") + s.Nil(podSecurityContext.WindowsOptions, "should be nil") + }, + }, + { + "overridePodSecurityContext", + map[string]string{ + "apiSettings.podSecurityContext.fsGroup": "2000", + "apiSettings.podSecurityContext.runAsGroup": "3000", + "apiSettings.podSecurityContext.runAsUser": "1000", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Equal(int64(2000), *podSecurityContext.FSGroup, "fsGroup should be 2000") + s.Equal(int64(3000), *podSecurityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.SecurityContext) + }) + } +} + +func (s *deploymentApiTemplateTest) TestSelectorMatchLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app": "teams-api", + "app.kubernetes.io/name": "teams-api", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorMatchLabels", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + map[string]string{ + "app": "test-service-name", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Selector Labels and Template Metadata Labels use the same helm template. + // Check both. + for key, value := range testCase.expected { + + foundValue := deployment.Spec.Selector.MatchLabels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + + foundValue = deployment.Spec.Template.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentApiTemplateTest) TestServiceAccountName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideServiceAccountName", + map[string]string{ + "serviceAccount.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ServiceAccountName, "Service account name should be equal.") + }) + } +} + +func (s *deploymentApiTemplateTest) TestTolerations() { + testCases := []struct { + name string + values map[string]string + expected func(tolerations []corev1.Toleration) + }{ + { + "defaultValues", + nil, + func(tolerations []corev1.Toleration) { + s.Nil(tolerations, "should be nil") + }, + }, + { + "overrideTolerations", + map[string]string{ + "apiSettings.tolerations[0].key": "example-key", + "apiSettings.tolerations[0].operator": "Exists", + "apiSettings.tolerations[0].effect": "NoSchedule", + }, + func(tolerations []corev1.Toleration) { + tolerationJSON := `[ + { + "key": "example-key", + "operator": "Exists", + "effect": "NoSchedule" + } + ]` + var expectedTolerations []corev1.Toleration + err := json.Unmarshal([]byte(tolerationJSON), &expectedTolerations) + s.NoError(err) + + s.Len(tolerations, 1, "Should only have 1 toleration") + s.Equal(expectedTolerations[0], tolerations[0], "Toleration should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Tolerations) + }) + } +} + +func (s *deploymentApiTemplateTest) TestVolumes() { + testCases := []struct { + name string + values map[string]string + expected func(volumes []corev1.Volume) + }{ + { + "defaultValues", + nil, + func(volumes []corev1.Volume) { + s.Nil(volumes, "Volumes should be nil") + }, + }, + { + "overrideVolumesSingle", + map[string]string{ + "apiSettings.volumes[0].name": "test-volume", + "apiSettings.volumes[0].hostPath.path": "/test-volume", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume", + "hostPath": { + "path": "/test-volume" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + { + "overrideVolumesMultiple", + map[string]string{ + "apiSettings.volumes[0].name": "test-volume1", + "apiSettings.volumes[0].hostPath.path": "/test-volume1", + "apiSettings.volumes[1].name": "pvc1", + "apiSettings.volumes[1].persistentVolumeClaim.claimName": "pvc1", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume1", + "hostPath": { + "path": "/test-volume1" + } + }, + { + "name": "pvc1", + "persistentVolumeClaim": { + "claimName": "pvc1" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Volumes) + }) + } +} diff --git a/tests/unit/helm/api-service_test.go b/tests/unit/helm/api-service_test.go new file mode 100644 index 00000000..7a691072 --- /dev/null +++ b/tests/unit/helm/api-service_test.go @@ -0,0 +1,422 @@ +//go:build kubeall || helm || unit || unitApiService +// +build kubeall helm unit unitApiService + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type serviceApiTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServiceApiTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &serviceApiTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/api-service.yaml"}, + }) +} + +func (s *serviceApiTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "apiSettings.service.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + if testCase.expected == nil { + s.Nil(service.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *serviceApiTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-api", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *serviceApiTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-api", + }, + { + "overrideMetadataName", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Name, "Name should be equal.") + }) + } +} + +func (s *serviceApiTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *serviceApiTemplateTest) TestPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ServicePort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 8000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithoutPortNumber", + map[string]string{ + "apiSettings.service.type": "NodePort", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 8000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithPortNumber", + map[string]string{ + "apiSettings.service.type": "NodePort", + "apiSettings.service.nodePort": "9999", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 8000, + "protocol": "TCP", + "name": "http", + "nodePort": 9999 + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceServicePortValues", + map[string]string{ + "apiSettings.service.containerPort": "8001", + "apiSettings.service.port": "88", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 88, + "targetPort": 8001, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + testCase.expected(service.Spec.Ports) + }) + } +} + +func (s *serviceApiTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "ClusterIP", + }, + { + "overrideSelectorLabels", + map[string]string{ + "apiSettings.service.type": "NodePort", + }, + "NodePort", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, string(service.Spec.Type), "Type should be equal.") + }) + } +} + +func (s *serviceApiTemplateTest) TestSelectorLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "teams-api", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorLabels", + map[string]string{ + "apiSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.Spec.Selector[key] + s.Equal(value, foundValue, "Selector labels should contain all set labels.") + } + }) + } +} diff --git a/tests/unit/helm/app-deployment_test.go b/tests/unit/helm/app-deployment_test.go new file mode 100644 index 00000000..4ad5ddcf --- /dev/null +++ b/tests/unit/helm/app-deployment_test.go @@ -0,0 +1,1498 @@ +//go:build kubeall || helm || unit || unitAppDeployment +// +build kubeall helm unit unitAppDeployment + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/api/resource" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type deploymentAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestDeploymentAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &deploymentAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/app-deployment.yaml"}, + }) +} + +func (s *deploymentAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-app", + }, + { + "overrideMetadataName", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Name, "Deployment name should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 2, + }, + { + "overrideReplicaCount", + map[string]string{ + "appSettings.replicaCount": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, *deployment.Spec.Replicas, "Replica count should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerCount() { + testCases := []struct { + name string + values map[string]string + expected int + }{ + { + "defaultValues", + nil, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerEnv() { + testCases := []struct { + name string + values map[string]string + expected func(envVars []corev1.EnvVar) + }{ + { + "defaultValues", // legacy auth mode + nil, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "overrideEnv", // legacy auth mode + map[string]string{ + "appSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "internalAuthMode", + map[string]string{ + "casSettings.env.FIFTYONE_AUTH_MODE": "internal", + "appSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Env) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerImage() { + + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + fmt.Sprintf("voxel51/fiftyone-app:%s", chartAppVersion), + }, + { + "overrideImageTag", + map[string]string{ + "appSettings.image.tag": "testTag", + }, + "voxel51/fiftyone-app:testTag", + }, + { + "overrideImageRepository", + map[string]string{ + "appSettings.image.repository": "ghcr.io/fiftyone-app", + }, + fmt.Sprintf("ghcr.io/fiftyone-app:%s", chartAppVersion), + }, + { + "overrideImageVersionAndRepository", + map[string]string{ + "appSettings.image.tag": "testTag", + "appSettings.image.repository": "ghcr.io/fiftyone-app", + }, + "ghcr.io/fiftyone-app:testTag", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Image, "Image values should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerImagePullPolicy() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "Always", + }, + { + "overrideImagePullPolicy", + map[string]string{ + "appSettings.image.pullPolicy": "IfNotPresent", + }, + "IfNotPresent", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Convert type returned by `.ImagePullPolicy` to a string + s.Equal(testCase.expected, string(deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy), "Image pull policy should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-app", + }, + { + "overrideServiceAccountName", + map[string]string{ + "appSettings.service.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Name, "Container name should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerLivenessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "fiftyone-app" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + { + "overrideServiceLivenessInitialDelaySecondsAndShortName", + map[string]string{ + "appSettings.service.liveness.initialDelaySeconds": "30", + "appSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].LivenessProbe) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ContainerPort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "fiftyone-app", + "containerPort": 5151, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceContainerPortAndShortName", + map[string]string{ + "appSettings.service.containerPort": "5155", + "appSettings.service.shortname": "test-service-shortname", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "test-service-shortname", + "containerPort": 5155, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Ports) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerReadinessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "fiftyone-app" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + { + "overrideServiceReadinessInitialDelaySecondsAndShortName", + map[string]string{ + "appSettings.service.readiness.initialDelaySeconds": "30", + "appSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].ReadinessProbe) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerResourceRequirements() { + testCases := []struct { + name string + values map[string]string + expected func(resourceRequirements corev1.ResourceRequirements) + }{ + { + "defaultValues", + nil, + func(resourceRequirements corev1.ResourceRequirements) { + s.Equal(resourceRequirements.Limits, corev1.ResourceList{}, "Limits should be equal") + s.Equal(resourceRequirements.Requests, corev1.ResourceList{}, "Requests should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + { + "overrideResources", + map[string]string{ + "appSettings.resources.limits.cpu": "1", + "appSettings.resources.limits.memory": "1Gi", + "appSettings.resources.requests.cpu": "500m", + "appSettings.resources.requests.memory": "512Mi", + }, + func(resourceRequirements corev1.ResourceRequirements) { + resourceExpected := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("1"), + "memory": resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + s.Equal(resourceExpected, resourceRequirements, "should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Resources) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(securityContext *corev1.SecurityContext) + }{ + { + "defaultValues", + nil, + func(securityContext *corev1.SecurityContext) { + s.Nil(securityContext.AllowPrivilegeEscalation, "should be nil") + s.Nil(securityContext.Capabilities, "should be nil") + s.Nil(securityContext.Privileged, "should be nil") + s.Nil(securityContext.ProcMount, "should be nil") + s.Nil(securityContext.ReadOnlyRootFilesystem, "should be nil") + s.Nil(securityContext.RunAsGroup, "should be nil") + s.Nil(securityContext.RunAsNonRoot, "should be nil") + s.Nil(securityContext.RunAsUser, "should be nil") + s.Nil(securityContext.SeccompProfile, "should be nil") + s.Nil(securityContext.SELinuxOptions, "should be nil") + s.Nil(securityContext.WindowsOptions, "should be nil") + }, + }, + { + "overrideSecurityContext", + map[string]string{ + "appSettings.securityContext.runAsGroup": "3000", + "appSettings.securityContext.runAsUser": "1000", + }, + func(securityContext *corev1.SecurityContext) { + s.Equal(int64(3000), *securityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *securityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].SecurityContext) + }) + } +} + +func (s *deploymentAppTemplateTest) TestContainerVolumeMounts() { + testCases := []struct { + name string + values map[string]string + expected func(volumeMounts []corev1.VolumeMount) + }{ + { + "defaultValues", + nil, + func(volumeMounts []corev1.VolumeMount) { + s.Nil(volumeMounts, "VolumeMounts should be nil") + }, + }, + { + "overrideVolumeMountsSingle", + map[string]string{ + "appSettings.volumeMounts[0].mountPath": "/test-data-volume", + "appSettings.volumeMounts[0].name": "test-volume", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume", + "name": "test-volume" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + { + "overrideVolumeMountsMultiple", + map[string]string{ + "appSettings.volumeMounts[0].mountPath": "/test-data-volume1", + "appSettings.volumeMounts[0].name": "test-volume1", + "appSettings.volumeMounts[1].mountPath": "/test-data-volume2", + "appSettings.volumeMounts[1].name": "test-volume2", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume1", + "name": "test-volume1" + }, + { + "mountPath": "/test-data-volume2", + "name": "test-volume2" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].VolumeMounts) + }) + } +} + +func (s *deploymentAppTemplateTest) TestAffinity() { + testCases := []struct { + name string + values map[string]string + expected func(affinity *corev1.Affinity) + }{ + { + "defaultValues", + nil, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "overrideAffinity", + map[string]string{ + "appSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "disktype", + "appSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "In", + "appSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]": "ssd", + }, + func(affinity *corev1.Affinity) { + affinityJSON := `{ + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "disktype", + "operator": "In", + "values": [ + "ssd" + ] + } + ] + } + ] + } + } + }` + var expectedAffinity corev1.Affinity + err := json.Unmarshal([]byte(affinityJSON), &expectedAffinity) + s.NoError(err) + + s.Equal(expectedAffinity, *affinity, "Affinity should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Affinity) + }) + } +} + +func (s *deploymentAppTemplateTest) TestImagePullSecrets() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "overrideImagePullSecrets", + map[string]string{ + "imagePullSecrets[0].name": "test-pull-secret", + }, + "test-pull-secret", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == "" { + s.Nil(deployment.Spec.Template.Spec.ImagePullSecrets, "Image pull secret should be nil") + } else { + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ImagePullSecrets[0].Name, "Image pull secret should be equal.") + } + }) + } +} + +func (s *deploymentAppTemplateTest) TestNodeSelector() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideNodeSelector", + map[string]string{ + "appSettings.nodeSelector.disktype": "ssd", + }, + map[string]string{ + "disktype": "ssd", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.Spec.NodeSelector[key] + s.Equal(value, foundValue, "NodeSelector should contain all set labels.") + } + }) + } +} + +func (s *deploymentAppTemplateTest) TestPodAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overridePodAnnotations", + map[string]string{ + "appSettings.podAnnotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == nil { + s.Nil(deployment.Spec.Template.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *deploymentAppTemplateTest) TestPodSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(podSecurityContext *corev1.PodSecurityContext) + }{ + { + "defaultValues", + nil, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") + s.Nil(podSecurityContext.RunAsGroup, "should be nil") + s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") + s.Nil(podSecurityContext.RunAsUser, "should be nil") + s.Nil(podSecurityContext.SeccompProfile, "should be nil") + s.Nil(podSecurityContext.SELinuxOptions, "should be nil") + s.Nil(podSecurityContext.SupplementalGroups, "should be nil") + s.Nil(podSecurityContext.Sysctls, "should be nil") + s.Nil(podSecurityContext.WindowsOptions, "should be nil") + }, + }, + { + "overridePodSecurityContext", + map[string]string{ + "appSettings.podSecurityContext.fsGroup": "2000", + "appSettings.podSecurityContext.runAsGroup": "3000", + "appSettings.podSecurityContext.runAsUser": "1000", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Equal(int64(2000), *podSecurityContext.FSGroup, "fsGroup should be 2000") + s.Equal(int64(3000), *podSecurityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.SecurityContext) + }) + } +} + +func (s *deploymentAppTemplateTest) TestSelectorMatchLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorMatchLabels", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Selector Labels and Template Metadata Labels use the same helm template. + // Check both. + for key, value := range testCase.expected { + + foundValue := deployment.Spec.Selector.MatchLabels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + + foundValue = deployment.Spec.Template.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentAppTemplateTest) TestServiceAccountName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideServiceAccountName", + map[string]string{ + "serviceAccount.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ServiceAccountName, "Service account name should be equal.") + }) + } +} + +func (s *deploymentAppTemplateTest) TestTolerations() { + testCases := []struct { + name string + values map[string]string + expected func(tolerations []corev1.Toleration) + }{ + { + "defaultValues", + nil, + func(tolerations []corev1.Toleration) { + s.Nil(tolerations, "should be nil") + }, + }, + { + "overrideTolerations", + map[string]string{ + "appSettings.tolerations[0].key": "example-key", + "appSettings.tolerations[0].operator": "Exists", + "appSettings.tolerations[0].effect": "NoSchedule", + }, + func(tolerations []corev1.Toleration) { + tolerationJSON := `[ + { + "key": "example-key", + "operator": "Exists", + "effect": "NoSchedule" + } + ]` + var expectedTolerations []corev1.Toleration + err := json.Unmarshal([]byte(tolerationJSON), &expectedTolerations) + s.NoError(err) + + s.Len(tolerations, 1, "Should only have 1 toleration") + s.Equal(expectedTolerations[0], tolerations[0], "Toleration should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Tolerations) + }) + } +} + +func (s *deploymentAppTemplateTest) TestVolumes() { + testCases := []struct { + name string + values map[string]string + expected func(volumes []corev1.Volume) + }{ + { + "defaultValues", + nil, + func(volumes []corev1.Volume) { + s.Nil(volumes, "Volumes should be nil") + }, + }, + { + "overrideVolumesSingle", + map[string]string{ + "appSettings.volumes[0].name": "test-volume", + "appSettings.volumes[0].hostPath.path": "/test-volume", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume", + "hostPath": { + "path": "/test-volume" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + { + "overrideVolumesMultiple", + map[string]string{ + "appSettings.volumes[0].name": "test-volume1", + "appSettings.volumes[0].hostPath.path": "/test-volume1", + "appSettings.volumes[1].name": "pvc1", + "appSettings.volumes[1].persistentVolumeClaim.claimName": "pvc1", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume1", + "hostPath": { + "path": "/test-volume1" + } + }, + { + "name": "pvc1", + "persistentVolumeClaim": { + "claimName": "pvc1" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Volumes) + }) + } +} diff --git a/tests/unit/helm/app-hpa_test.go b/tests/unit/helm/app-hpa_test.go new file mode 100644 index 00000000..fde9ff3c --- /dev/null +++ b/tests/unit/helm/app-hpa_test.go @@ -0,0 +1,555 @@ +//go:build kubeall || helm || unit || unitAppHpa +// +build kubeall helm unit unitAppHpa + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + autoscalingv2 "k8s.io/api/autoscaling/v2" +) + +type horizontalPodAutoscalerAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestHorizontalPodAutoscalerAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &horizontalPodAutoscalerAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/app-hpa.yaml"}, + }) +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Nil(hpa.ObjectMeta.Labels, "Labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + for key, value := range testCase.expected { + foundValue := hpa.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + } + }) + } +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + "fiftyone-app", + }, + { + "overrideMetadataName", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Name, "Metadata name should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Name, "Metadata name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Namespace, "Metadata namespace should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Namespace, "Namespace name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestScaleTargetRef() { + testCases := []struct { + name string + values map[string]string + expected func(ref autoscalingv2.CrossVersionObjectReference) + }{ + { + "defaultValues", + nil, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{}` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "fiftyone-app" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "overrideServiceName", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.service.name": "test-service-name", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "test-service-name" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.ScaleTargetRef, "Scale TargetRef should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.ScaleTargetRef) + } + }) + } +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestMaxReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + 20, + }, + { + "overrideMaxReplicas", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.autoscaling.maxReplicas": "19", + }, + 19, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MaxReplicas, "maxReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.Spec.MaxReplicas, "maxReplicas name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerAppTemplateTest) TestMinReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + 2, + }, + { + "overrideMinReplicas", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.autoscaling.minReplicas": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MinReplicas, "minReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, *hpa.Spec.MinReplicas, "minReplicas name should be equal.") + } + }) + } +} +func (s *horizontalPodAutoscalerAppTemplateTest) TestMetrics() { + testCases := []struct { + name string + values map[string]string + expected func(metrics []autoscalingv2.MetricSpec) + }{ + { + "defaultValues", + nil, + func(metrics []autoscalingv2.MetricSpec) { + s.Empty(metrics, "metricSpec should not be set") + }, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + { + "overrideTargetCpuAndMemory", + map[string]string{ + "appSettings.autoscaling.enabled": "true", + "appSettings.autoscaling.targetCPUUtilizationPercentage": "99", + "appSettings.autoscaling.targetMemoryUtilizationPercentage": "98", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 99 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 98 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.Metrics, "metrics should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.Metrics) + } + }) + } +} diff --git a/tests/unit/helm/app-service_test.go b/tests/unit/helm/app-service_test.go new file mode 100644 index 00000000..8910f85d --- /dev/null +++ b/tests/unit/helm/app-service_test.go @@ -0,0 +1,422 @@ +//go:build kubeall || helm || unit || unitAppService +// +build kubeall helm unit unitAppService + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type serviceAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServiceAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &serviceAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/app-service.yaml"}, + }) +} + +func (s *serviceAppTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "appSettings.service.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + if testCase.expected == nil { + s.Nil(service.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *serviceAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *serviceAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-app", + }, + { + "overrideMetadataName", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Name, "Name should be equal.") + }) + } +} + +func (s *serviceAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *serviceAppTemplateTest) TestPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ServicePort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithoutPortNumber", + map[string]string{ + "appSettings.service.type": "NodePort", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithPortNumber", + map[string]string{ + "appSettings.service.type": "NodePort", + "appSettings.service.nodePort": "9999", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http", + "nodePort": 9999 + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceServicePortValues", + map[string]string{ + "appSettings.service.containerPort": "8001", + "appSettings.service.port": "88", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 88, + "targetPort": 8001, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + testCase.expected(service.Spec.Ports) + }) + } +} + +func (s *serviceAppTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "ClusterIP", + }, + { + "overrideSelectorLabels", + map[string]string{ + "appSettings.service.type": "NodePort", + }, + "NodePort", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, string(service.Spec.Type), "Type should be equal.") + }) + } +} + +func (s *serviceAppTemplateTest) TestSelectorLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorLabels", + map[string]string{ + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.Spec.Selector[key] + s.Equal(value, foundValue, "Selector labels should contain all set labels.") + } + }) + } +} diff --git a/tests/unit/helm/cas-deployment_test.go b/tests/unit/helm/cas-deployment_test.go new file mode 100644 index 00000000..638112ab --- /dev/null +++ b/tests/unit/helm/cas-deployment_test.go @@ -0,0 +1,1624 @@ +//go:build kubeall || helm || unit || unitCasDeployment +// +build kubeall helm unit unitCasDeployment + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/api/resource" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type deploymentCasTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestDeploymentCasTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &deploymentCasTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/cas-deployment.yaml"}, + }) +} + +func (s *deploymentCasTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-cas", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentCasTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-cas", + }, + { + "overrideMetadataName", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Name, "Deployment name should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 2, + }, + { + "overrideReplicaCount", + map[string]string{ + "casSettings.replicaCount": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, *deployment.Spec.Replicas, "Replica count should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerCount() { + testCases := []struct { + name string + values map[string]string + expected int + }{ + { + "defaultValues", + nil, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerEnv() { + testCases := []struct { + name string + values map[string]string + expected func(envVars []corev1.EnvVar) + }{ + { + "defaultValues", // legacy auth mode + nil, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_MONGODB_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "CAS_URL", + "value": "https://" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "NEXTAUTH_URL", + "value": "https:///cas/api/auth" + }, + { + "name": "AUTH0_AUTH_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "clientId" + } + } + }, + { + "name": "AUTH0_AUTH_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "clientSecret" + } + } + }, + { + "name": "AUTH0_DOMAIN", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "auth0Domain" + } + } + }, + { + "name": "AUTH0_ISSUER_BASE_URL", + "value": "https://$(AUTH0_DOMAIN)" + }, + { + "name": "AUTH0_MGMT_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "apiClientId" + } + } + }, + { + "name": "AUTH0_MGMT_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "apiClientSecret" + } + } + }, + { + "name": "AUTH0_ORGANIZATION", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "organizationId" + } + } + }, + { + "name": "TEAMS_API_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "TEAMS_API_MONGODB_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "CAS_DATABASE_NAME", + "value": "cas" + }, + { + "name": "CAS_DEFAULT_USER_ROLE", + "value": "GUEST" + }, + { + "name": "CAS_MONGODB_URI_KEY", + "value": "mongodbConnectionString" + }, + { + "name": "DEBUG", + "value": "cas:*,-cas:*:debug" + }, + { + "name": "FIFTYONE_AUTH_MODE", + "value": "legacy" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "overrideEnv", // legacy auth mode + map[string]string{ + "casSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_MONGODB_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "CAS_URL", + "value": "https://" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "NEXTAUTH_URL", + "value": "https:///cas/api/auth" + }, + { + "name": "AUTH0_AUTH_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "clientId" + } + } + }, + { + "name": "AUTH0_AUTH_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "clientSecret" + } + } + }, + { + "name": "AUTH0_DOMAIN", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "auth0Domain" + } + } + }, + { + "name": "AUTH0_ISSUER_BASE_URL", + "value": "https://$(AUTH0_DOMAIN)" + }, + { + "name": "AUTH0_MGMT_CLIENT_ID", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "apiClientId" + } + } + }, + { + "name": "AUTH0_MGMT_CLIENT_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "apiClientSecret" + } + } + }, + { + "name": "AUTH0_ORGANIZATION", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "organizationId" + } + } + }, + { + "name": "TEAMS_API_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "TEAMS_API_MONGODB_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "CAS_DATABASE_NAME", + "value": "cas" + }, + { + "name": "CAS_DEFAULT_USER_ROLE", + "value": "GUEST" + }, + { + "name": "CAS_MONGODB_URI_KEY", + "value": "mongodbConnectionString" + }, + { + "name": "DEBUG", + "value": "cas:*,-cas:*:debug" + }, + { + "name": "FIFTYONE_AUTH_MODE", + "value": "legacy" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "internalAuthMode", + map[string]string{ + "casSettings.env.FIFTYONE_AUTH_MODE": "internal", + "casSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "CAS_MONGODB_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "CAS_URL", + "value": "https://" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "NEXTAUTH_URL", + "value": "https:///cas/api/auth" + }, + { + "name": "CAS_DATABASE_NAME", + "value": "cas" + }, + { + "name": "CAS_DEFAULT_USER_ROLE", + "value": "GUEST" + }, + { + "name": "CAS_MONGODB_URI_KEY", + "value": "mongodbConnectionString" + }, + { + "name": "DEBUG", + "value": "cas:*,-cas:*:debug" + }, + { + "name": "FIFTYONE_AUTH_MODE", + "value": "internal" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Env) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerImage() { + + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + fmt.Sprintf("voxel51/fiftyone-teams-cas:%s", chartAppVersion), + }, + { + "overrideImageTag", + map[string]string{ + "casSettings.image.tag": "testTag", + }, + "voxel51/fiftyone-teams-cas:testTag", + }, + { + "overrideImageRepository", + map[string]string{ + "casSettings.image.repository": "ghcr.io/fiftyone-teams-cas", + }, + fmt.Sprintf("ghcr.io/fiftyone-teams-cas:%s", chartAppVersion), + }, + { + "overrideImageVersionAndRepository", + map[string]string{ + "casSettings.image.tag": "testTag", + "casSettings.image.repository": "ghcr.io/fiftyone-teams-cas", + }, + "ghcr.io/fiftyone-teams-cas:testTag", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Image, "Image values should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerImagePullPolicy() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "Always", + }, + { + "overrideImagePullPolicy", + map[string]string{ + "casSettings.image.pullPolicy": "IfNotPresent", + }, + "IfNotPresent", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Convert type returned by `.ImagePullPolicy` to a string + s.Equal(testCase.expected, string(deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy), "Image pull policy should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-cas", + }, + { + "overrideServiceAccountName", + map[string]string{ + "casSettings.service.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Name, "Container name should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerLivenessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/cas/api", + "port": "teams-cas" + }, + "initialDelaySeconds": 15, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + { + "overrideServiceLivenessInitialDelaySecondsAndShortName", + map[string]string{ + "casSettings.service.liveness.initialDelaySeconds": "30", + "casSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/cas/api", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].LivenessProbe) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ContainerPort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "teams-cas", + "containerPort": 3000, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceContainerPortAndShortName", + map[string]string{ + "casSettings.service.containerPort": "3001", + "casSettings.service.shortname": "test-service-shortname", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "test-service-shortname", + "containerPort": 3001, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Ports) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerReadinessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/cas/api", + "port": "teams-cas" + }, + "initialDelaySeconds": 15, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + { + "overrideServiceReadinessInitialDelaySecondsAndShortName", + map[string]string{ + "casSettings.service.readiness.initialDelaySeconds": "30", + "casSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/cas/api", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].ReadinessProbe) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerResourceRequirements() { + testCases := []struct { + name string + values map[string]string + expected func(resourceRequirements corev1.ResourceRequirements) + }{ + { + "defaultValues", + nil, + func(resourceRequirements corev1.ResourceRequirements) { + s.Equal(resourceRequirements.Limits, corev1.ResourceList{}, "Limits should be equal") + s.Equal(resourceRequirements.Requests, corev1.ResourceList{}, "Requests should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + { + "overrideResources", + map[string]string{ + "casSettings.resources.limits.cpu": "1", + "casSettings.resources.limits.memory": "1Gi", + "casSettings.resources.requests.cpu": "500m", + "casSettings.resources.requests.memory": "512Mi", + }, + func(resourceRequirements corev1.ResourceRequirements) { + resourceExpected := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("1"), + "memory": resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + s.Equal(resourceExpected, resourceRequirements, "should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Resources) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(securityContext *corev1.SecurityContext) + }{ + { + "defaultValues", + nil, + func(securityContext *corev1.SecurityContext) { + s.Nil(securityContext.AllowPrivilegeEscalation, "should be nil") + s.Nil(securityContext.Capabilities, "should be nil") + s.Nil(securityContext.Privileged, "should be nil") + s.Nil(securityContext.ProcMount, "should be nil") + s.Nil(securityContext.ReadOnlyRootFilesystem, "should be nil") + s.Nil(securityContext.RunAsGroup, "should be nil") + s.Nil(securityContext.RunAsNonRoot, "should be nil") + s.Nil(securityContext.RunAsUser, "should be nil") + s.Nil(securityContext.SeccompProfile, "should be nil") + s.Nil(securityContext.SELinuxOptions, "should be nil") + s.Nil(securityContext.WindowsOptions, "should be nil") + }, + }, + { + "overrideSecurityContext", + map[string]string{ + "casSettings.securityContext.runAsGroup": "3000", + "casSettings.securityContext.runAsUser": "1000", + }, + func(securityContext *corev1.SecurityContext) { + s.Equal(int64(3000), *securityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *securityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].SecurityContext) + }) + } +} + +func (s *deploymentCasTemplateTest) TestContainerVolumeMounts() { + testCases := []struct { + name string + values map[string]string + expected func(volumeMounts []corev1.VolumeMount) + }{ + { + "defaultValues", + nil, + func(volumeMounts []corev1.VolumeMount) { + s.Nil(volumeMounts, "VolumeMounts should be nil") + }, + }, + { + "overrideVolumeMountsSingle", + map[string]string{ + "casSettings.volumeMounts[0].mountPath": "/test-data-volume", + "casSettings.volumeMounts[0].name": "test-volume", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume", + "name": "test-volume" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + { + "overrideVolumeMountsMultiple", + map[string]string{ + "casSettings.volumeMounts[0].mountPath": "/test-data-volume1", + "casSettings.volumeMounts[0].name": "test-volume1", + "casSettings.volumeMounts[1].mountPath": "/test-data-volume2", + "casSettings.volumeMounts[1].name": "test-volume2", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume1", + "name": "test-volume1" + }, + { + "mountPath": "/test-data-volume2", + "name": "test-volume2" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].VolumeMounts) + }) + } +} + +func (s *deploymentCasTemplateTest) TestAffinity() { + testCases := []struct { + name string + values map[string]string + expected func(affinity *corev1.Affinity) + }{ + { + "defaultValues", + nil, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "overrideAffinity", + map[string]string{ + "casSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "disktype", + "casSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "In", + "casSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]": "ssd", + }, + func(affinity *corev1.Affinity) { + affinityJSON := `{ + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "disktype", + "operator": "In", + "values": [ + "ssd" + ] + } + ] + } + ] + } + } + }` + var expectedAffinity corev1.Affinity + err := json.Unmarshal([]byte(affinityJSON), &expectedAffinity) + s.NoError(err) + + s.Equal(expectedAffinity, *affinity, "Affinity should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Affinity) + }) + } +} + +func (s *deploymentCasTemplateTest) TestImagePullSecrets() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "overrideImagePullSecrets", + map[string]string{ + "imagePullSecrets[0].name": "test-pull-secret", + }, + "test-pull-secret", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == "" { + s.Nil(deployment.Spec.Template.Spec.ImagePullSecrets, "Image pull secret should be nil") + } else { + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ImagePullSecrets[0].Name, "Image pull secret should be equal.") + } + }) + } +} + +func (s *deploymentCasTemplateTest) TestNodeSelector() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideNodeSelector", + map[string]string{ + "casSettings.nodeSelector.disktype": "ssd", + }, + map[string]string{ + "disktype": "ssd", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.Spec.NodeSelector[key] + s.Equal(value, foundValue, "NodeSelector should contain all set labels.") + } + }) + } +} + +func (s *deploymentCasTemplateTest) TestPodAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overridePodAnnotations", + map[string]string{ + "casSettings.podAnnotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == nil { + s.Nil(deployment.Spec.Template.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *deploymentCasTemplateTest) TestPodSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(podSecurityContext *corev1.PodSecurityContext) + }{ + { + "defaultValues", + nil, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") + s.Nil(podSecurityContext.RunAsGroup, "should be nil") + s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") + s.Nil(podSecurityContext.RunAsUser, "should be nil") + s.Nil(podSecurityContext.SeccompProfile, "should be nil") + s.Nil(podSecurityContext.SELinuxOptions, "should be nil") + s.Nil(podSecurityContext.SupplementalGroups, "should be nil") + s.Nil(podSecurityContext.Sysctls, "should be nil") + s.Nil(podSecurityContext.WindowsOptions, "should be nil") + }, + }, + { + "overridePodSecurityContext", + map[string]string{ + "casSettings.podSecurityContext.fsGroup": "2000", + "casSettings.podSecurityContext.runAsGroup": "3000", + "casSettings.podSecurityContext.runAsUser": "1000", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Equal(int64(2000), *podSecurityContext.FSGroup, "fsGroup should be 2000") + s.Equal(int64(3000), *podSecurityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.SecurityContext) + }) + } +} + +func (s *deploymentCasTemplateTest) TestSelectorMatchLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "teams-cas", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorMatchLabels", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Selector Labels and Template Metadata Labels use the same helm template. + // Check both. + for key, value := range testCase.expected { + + foundValue := deployment.Spec.Selector.MatchLabels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + + foundValue = deployment.Spec.Template.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentCasTemplateTest) TestServiceAccountName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideServiceAccountName", + map[string]string{ + "serviceAccount.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ServiceAccountName, "Service account name should be equal.") + }) + } +} + +func (s *deploymentCasTemplateTest) TestTolerations() { + testCases := []struct { + name string + values map[string]string + expected func(tolerations []corev1.Toleration) + }{ + { + "defaultValues", + nil, + func(tolerations []corev1.Toleration) { + s.Nil(tolerations, "should be nil") + }, + }, + { + "overrideTolerations", + map[string]string{ + "casSettings.tolerations[0].key": "example-key", + "casSettings.tolerations[0].operator": "Exists", + "casSettings.tolerations[0].effect": "NoSchedule", + }, + func(tolerations []corev1.Toleration) { + tolerationJSON := `[ + { + "key": "example-key", + "operator": "Exists", + "effect": "NoSchedule" + } + ]` + var expectedTolerations []corev1.Toleration + err := json.Unmarshal([]byte(tolerationJSON), &expectedTolerations) + s.NoError(err) + + s.Len(tolerations, 1, "Should only have 1 toleration") + s.Equal(expectedTolerations[0], tolerations[0], "Toleration should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Tolerations) + }) + } +} + +func (s *deploymentCasTemplateTest) TestVolumes() { + testCases := []struct { + name string + values map[string]string + expected func(volumes []corev1.Volume) + }{ + { + "defaultValues", + nil, + func(volumes []corev1.Volume) { + s.Nil(volumes, "Volumes should be nil") + }, + }, + { + "overrideVolumesSingle", + map[string]string{ + "casSettings.volumes[0].name": "test-volume", + "casSettings.volumes[0].hostPath.path": "/test-volume", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume", + "hostPath": { + "path": "/test-volume" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + { + "overrideVolumesMultiple", + map[string]string{ + "casSettings.volumes[0].name": "test-volume1", + "casSettings.volumes[0].hostPath.path": "/test-volume1", + "casSettings.volumes[1].name": "pvc1", + "casSettings.volumes[1].persistentVolumeClaim.claimName": "pvc1", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume1", + "hostPath": { + "path": "/test-volume1" + } + }, + { + "name": "pvc1", + "persistentVolumeClaim": { + "claimName": "pvc1" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Volumes) + }) + } +} diff --git a/tests/unit/helm/cas-service_test.go b/tests/unit/helm/cas-service_test.go new file mode 100644 index 00000000..16df3ceb --- /dev/null +++ b/tests/unit/helm/cas-service_test.go @@ -0,0 +1,422 @@ +//go:build kubeall || helm || unit || unitCasService +// +build kubeall helm unit unitCasService + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type serviceCasTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServiceCasTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &serviceCasTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/cas-service.yaml"}, + }) +} + +func (s *serviceCasTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "casSettings.service.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + if testCase.expected == nil { + s.Nil(service.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *serviceCasTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-cas", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *serviceCasTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-cas", + }, + { + "overrideMetadataName", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Name, "Name should be equal.") + }) + } +} + +func (s *serviceCasTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *serviceCasTemplateTest) TestPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ServicePort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithoutPortNumber", + map[string]string{ + "casSettings.service.type": "NodePort", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithPortNumber", + map[string]string{ + "casSettings.service.type": "NodePort", + "casSettings.service.nodePort": "9999", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http", + "nodePort": 9999 + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceServicePortValues", + map[string]string{ + "casSettings.service.containerPort": "3001", + "casSettings.service.port": "88", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 88, + "targetPort": 3001, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + testCase.expected(service.Spec.Ports) + }) + } +} + +func (s *serviceCasTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "ClusterIP", + }, + { + "overrideSelectorLabels", + map[string]string{ + "casSettings.service.type": "NodePort", + }, + "NodePort", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, string(service.Spec.Type), "Type should be equal.") + }) + } +} + +func (s *serviceCasTemplateTest) TestSelectorLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "teams-cas", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorLabels", + map[string]string{ + "casSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.Spec.Selector[key] + s.Equal(value, foundValue, "Selector labels should contain all set labels.") + } + }) + } +} diff --git a/tests/unit/helm/chartInfo.go b/tests/unit/helm/chartInfo.go new file mode 100644 index 00000000..de119a54 --- /dev/null +++ b/tests/unit/helm/chartInfo.go @@ -0,0 +1,19 @@ +package unit + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "gopkg.in/yaml.v3" +) + +func chartInfo(t *testing.T, chartPath string) (map[string]interface{}, error) { + // Get chart info. + output, err := helm.RunHelmCommandAndGetOutputE(t, &helm.Options{}, "show", "chart", chartPath) + if err != nil { + return nil, err + } + cInfo := map[string]interface{}{} + err = yaml.Unmarshal([]byte(output), &cInfo) + return cInfo, err +} diff --git a/tests/unit/helm/common_test.go b/tests/unit/helm/common_test.go new file mode 100644 index 00000000..2ce0909b --- /dev/null +++ b/tests/unit/helm/common_test.go @@ -0,0 +1,6 @@ +//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService +// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService + +package unit + +const chartPath = "../../../helm/fiftyone-teams-app/" diff --git a/tests/unit/helm/ingress_test.go b/tests/unit/helm/ingress_test.go new file mode 100644 index 00000000..7af6972a --- /dev/null +++ b/tests/unit/helm/ingress_test.go @@ -0,0 +1,871 @@ +//go:build kubeall || helm || unit || unitIngress +// +build kubeall helm unit unitIngress + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + networkingv1 "k8s.io/api/networking/v1" +) + +type ingressTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestIngressTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &ingressTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/ingress.yaml"}, + }) +} + +func (s *ingressTemplateTest) TestDisabled() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-test-fiftyone-teams-app", + }, + { + "defaultValuesIngressDisabled", + map[string]string{ + "ingress.enabled": "false", + }, + "", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/ingress.yaml in chart") + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + s.Empty(ingress.ObjectMeta.Name, "Name should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + s.Equal(testCase.expected, ingress.ObjectMeta.Name, "Name should be set") + } + }) + } +} + +// TODO: Unit Test with different k8s versions +// Given kubernetes version >=1.14 and <1.19-0, when ingress is enabled then `apiVersion: networking.k8s.io/v1beta1` +// Given kubernetes version <1.14 , when ingress is enabled then `apiVersion: extensions/v1beta1` +func (s *ingressTemplateTest) TestApiVersion() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "networking.k8s.io/v1", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + s.Equal(testCase.expected, ingress.TypeMeta.APIVersion, "API Version should be equal") + }) + } +} + +// TODO: Unit Test with different k8s versions +// Given kubernetes version > 1.18-0, when ingress.className is not "" and `ingress.annotations` does not have key `"kubernetes.io/ingress.class"`, then set values.ingress.annotations `"kubernetes.io/ingress.class": {{ .Values.ingress.className }}` +func (s *ingressTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "ingress.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + if testCase.expected == nil { + s.Nil(ingress.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := ingress.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *ingressTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for the ingress. + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideIngressLabels", + map[string]string{ + "ingress.labels.test-label-key": "test-label-value", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + "test-label-key": "test-label-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + for key, value := range testCase.expected { + foundValue := ingress.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +// .Chart.Name = "fiftyone-teams-app" +// .Release.Name = "fiftyone-test" +func (s *ingressTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-test-fiftyone-teams-app", + }, + { + "overrideFullnameOverride", + map[string]string{ + "fullnameOverride": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + s.Equal(testCase.expected, ingress.ObjectMeta.Name, "Ingress name should be equal.") + }) + } +} + +func (s *ingressTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + s.Equal(testCase.expected, ingress.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +// TODO: Unit Test with different k8s versions +// Given kubernetes version <1.18-0, when ingress.className is set, then `spec` should not contain `ingressClassName` +func (s *ingressTemplateTest) TestIngressClassName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "overrideIngressClassName", + map[string]string{ + "ingress.className": "nginx", + }, + "nginx", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + if testCase.expected == "" { + s.Nil(ingress.Spec.IngressClassName) + } else { + s.Equal(testCase.expected, *ingress.Spec.IngressClassName, "Ingress class name should be equal.") + } + }) + } +} + +func (s *ingressTemplateTest) TestTls() { + testCases := []struct { + name string + values map[string]string + expected func(tls []networkingv1.IngressTLS) + }{ + { + "defaultValues", + nil, + func(tls []networkingv1.IngressTLS) { + expectedJSON := `[ + { + "hosts": [ + "" + ], + "secretName": "fiftyone-teams-tls-secret" + } + ]` + var expectedTls []networkingv1.IngressTLS + err := json.Unmarshal([]byte(expectedJSON), &expectedTls) + s.NoError(err) + s.Equal(expectedTls, tls, "TLS should be equal") + }, + }, + { + "overrideTlsEnabled", + map[string]string{ + "ingress.tlsEnabled": "false", + }, + func(tls []networkingv1.IngressTLS) { + s.Nil(tls, "TLS should be nil") + }, + }, + { + "overrideDnsNamesAndTlsSecretName", + map[string]string{ + "apiSettings.dnsName": "teams-api.fiftyone.ai", + "teamsAppSettings.dnsName": "teams-app.fiftyone.ai", + "ingress.tlsSecretName": "test-secret", + }, + func(tls []networkingv1.IngressTLS) { + expectedJSON := `[ + { + "hosts": [ + "teams-app.fiftyone.ai", + "teams-api.fiftyone.ai" + ], + "secretName": "test-secret" + } + ]` + var expectedTls []networkingv1.IngressTLS + err := json.Unmarshal([]byte(expectedJSON), &expectedTls) + s.NoError(err) + s.Equal(expectedTls, tls, "TLS should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + testCase.expected(ingress.Spec.TLS) + }) + } +} + +// TODO: Resume here. Add test cases to cover all of the variants of the rules +// TODO: Test k8s versions when 1.18-0 and 1.19-0 +func (s *ingressTemplateTest) TestRules() { + testCases := []struct { + name string + values map[string]string + expected func(tls []networkingv1.IngressRule) + }{ + { + "defaultValues", + nil, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "", + "http": { + "paths": [ + { + "path": "/cas", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-cas", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/*", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "teams-app", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + { + "overridePaths", + map[string]string{ + "ingress.paths[0].path": "/test-cas", + "ingress.paths[0].pathType": "ImplementationSpecific", + "ingress.paths[0].serviceName": "test-teams-cas", + "ingress.paths[0].servicePort": "81", + "ingress.paths[1].path": "/**", + "ingress.paths[1].pathType": "Prefix", + "ingress.paths[1].serviceName": "test-teams-app", + "ingress.paths[1].servicePort": "82", + }, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "", + "http": { + "paths": [ + { + "path": "/test-cas", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "test-teams-cas", + "port": { + "number": 81 + } + } + } + }, + { + "path": "/**", + "pathType": "Prefix", + "backend": { + "service": { + "name": "test-teams-app", + "port": { + "number": 82 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + { + "overrideTeamsAppSettingsDnsName", + map[string]string{ + "teamsAppSettings.dnsName": "teams-app.fiftyone.ai", + }, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "teams-app.fiftyone.ai", + "http": { + "paths": [ + { + "path": "/cas", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-cas", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/*", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "teams-app", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + { + "overrideApiSettingsDnsName", + map[string]string{ + "apiSettings.dnsName": "teams-api.fiftyone.ai", + }, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "", + "http": { + "paths": [ + { + "path": "/cas", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-cas", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/*", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "teams-app", + "port": { + "number": 80 + } + } + } + } + ] + } + }, + { + "host": "teams-api.fiftyone.ai", + "http": { + "paths": [ + { + "path": "/*", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "teams-api", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + { + "overrideBothDnsNames", + map[string]string{ + "apiSettings.dnsName": "teams-api.fiftyone.ai", + "apiSettings.service.name": "test-service-name-teams-api", + "apiSettings.service.port": "81", + "ingress.api.path": "/test-api-path", + "ingress.api.pathType": "prefix", + "teamsAppSettings.dnsName": "teams-app.fiftyone.ai", + }, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "teams-app.fiftyone.ai", + "http": { + "paths": [ + { + "path": "/cas", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-cas", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/*", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "teams-app", + "port": { + "number": 80 + } + } + } + } + ] + } + }, + { + "host": "teams-api.fiftyone.ai", + "http": { + "paths": [ + { + "path": "/test-api-path", + "pathType": "prefix", + "backend": { + "service": { + "name": "test-service-name-teams-api", + "port": { + "number": 81 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + { + "overridePathsWithPathBasedRouting", + map[string]string{ + "teamsAppSettings.dnsName": "teams-app.fiftyone.ai", + "ingress.paths[0].path": "/cas", + "ingress.paths[0].pathType": "Prefix", + "ingress.paths[0].serviceName": "teams-cas", + "ingress.paths[0].servicePort": "80", + "ingress.paths[1].path": "/_pymongo", + "ingress.paths[1].pathType": "Prefix", + "ingress.paths[1].serviceName": "teams-api", + "ingress.paths[1].servicePort": "80", + "ingress.paths[2].path": "/health", + "ingress.paths[2].pathType": "Prefix", + "ingress.paths[2].serviceName": "teams-api", + "ingress.paths[2].servicePort": "80", + "ingress.paths[3].path": "/graphql/v1", + "ingress.paths[3].pathType": "Prefix", + "ingress.paths[3].serviceName": "teams-api", + "ingress.paths[3].servicePort": "80", + "ingress.paths[4].path": "/file", + "ingress.paths[4].pathType": "Prefix", + "ingress.paths[4].serviceName": "teams-api", + "ingress.paths[4].servicePort": "80", + "ingress.paths[5].path": "/", + "ingress.paths[5].pathType": "Prefix", + "ingress.paths[5].serviceName": "teams-app", + "ingress.paths[5].servicePort": "80", + }, + func(tls []networkingv1.IngressRule) { + expectedJSON := `[ + { + "host": "teams-app.fiftyone.ai", + "http": { + "paths": [ + { + "path": "/cas", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-cas", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/_pymongo", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-api", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/health", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-api", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/graphql/v1", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-api", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/file", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-api", + "port": { + "number": 80 + } + } + } + }, + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "teams-app", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ]` + var expectedRules []networkingv1.IngressRule + err := json.Unmarshal([]byte(expectedJSON), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, tls, "Rules should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var ingress networkingv1.Ingress + helm.UnmarshalK8SYaml(subT, output, &ingress) + + testCase.expected(ingress.Spec.Rules) + }) + } +} diff --git a/tests/unit/helm/namespace_test.go b/tests/unit/helm/namespace_test.go new file mode 100644 index 00000000..f2ac9dda --- /dev/null +++ b/tests/unit/helm/namespace_test.go @@ -0,0 +1,97 @@ +//go:build kubeall || helm || unit || unitNamespace +// +build kubeall helm unit unitNamespace + +package unit + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type namespaceTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestNamespaceTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &namespaceTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/namespace.yaml"}, + }) +} + +func (s *namespaceTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesWithNamespaceCreateEnabled", + map[string]string{ + "namespace.create": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.create": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/namespace.yaml in chart") + + var namespace corev1.Namespace + helm.UnmarshalK8SYaml(subT, output, &namespace) + + s.Empty(namespace.ObjectMeta.Name, "Name should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var namespace corev1.Namespace + helm.UnmarshalK8SYaml(subT, output, &namespace) + + s.Equal(testCase.expected, namespace.ObjectMeta.Name, "Namespace name should be equal.") + } + }) + } +} diff --git a/tests/unit/helm/plugins-deployment_test.go b/tests/unit/helm/plugins-deployment_test.go new file mode 100644 index 00000000..0714ca5f --- /dev/null +++ b/tests/unit/helm/plugins-deployment_test.go @@ -0,0 +1,2023 @@ +//go:build kubeall || helm || unit || unitPluginsDeployment +// +build kubeall helm unit unitPluginsDeployment + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/api/resource" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type deploymentPluginsTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestDeploymentPluginsTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &deploymentPluginsTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/plugins-deployment.yaml"}, + }) +} + +func (s *deploymentPluginsTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-plugins", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.ObjectMeta.Labels, "Labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "teams-plugins", + }, + { + "overrideMetadataName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Empty(deployment.ObjectMeta.Name, "Metadata name should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Name, "Deployment name should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "pluginsSettings.enabled": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Empty(deployment.ObjectMeta.Namespace, "Metadata namespace should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Namespace, "Namespace name should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + 2, + }, + { + "overrideReplicaCount", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.replicaCount": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Empty(&deployment.Spec.Replicas, "Replica count should be nil.") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, *deployment.Spec.Replicas, "Replica count should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerCount() { + testCases := []struct { + name string + values map[string]string + expected int + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerEnv() { + testCases := []struct { + name string + values map[string]string + expected func(envVars []corev1.EnvVar) + }{ + { + "defaultValues", + nil, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "defaultValuesPluginsEnabled", // legacy auth mode + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "overrideEnv", // legacy auth mode + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "internalAuthMode", + map[string]string{ + "casSettings.env.FIFTYONE_AUTH_MODE": "internal", + "pluginsSettings.enabled": "true", + "pluginsSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_DATABASE_ADMIN", + "value": "false" + }, + { + "name": "FIFTYONE_DATABASE_NAME", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneDatabaseName" + } + } + }, + { + "name": "FIFTYONE_DATABASE_URI", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "mongodbConnectionString" + } + } + }, + { + "name": "FIFTYONE_ENCRYPTION_KEY", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "encryptionKey" + } + } + }, + { + "name": "FIFTYONE_INTERNAL_SERVICE", + "value": "true" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_APP_IMAGES", + "value": "false" + }, + { + "name": "FIFTYONE_MEDIA_CACHE_SIZE_BYTES", + "value": "-1" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Env) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerImage() { + + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + fmt.Sprintf("voxel51/fiftyone-app:%s", chartAppVersion), + }, + { + "overrideImageTag", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.image.tag": "testTag", + }, + "voxel51/fiftyone-app:testTag", + }, + { + "overrideImageRepository", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.image.repository": "ghcr.io/fiftyone-app", + }, + fmt.Sprintf("ghcr.io/fiftyone-app:%s", chartAppVersion), + }, + { + "overrideImageVersionAndRepository", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.image.tag": "testTag", + "pluginsSettings.image.repository": "ghcr.io/fiftyone-app", + }, + "ghcr.io/fiftyone-app:testTag", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Image, "Image values should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerImagePullPolicy() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "Always", + }, + { + "overrideImagePullPolicy", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.image.pullPolicy": "IfNotPresent", + }, + "IfNotPresent", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, string(deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy), "Image pull policy should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "teams-plugins", + }, + { + "overrideServiceAccountName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Name, "Container name should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerLivenessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + s.Empty(probe, "Liveness Probes should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "teams-plugins" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + { + "overrideServiceLivenessInitialDelaySecondsAndShortName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.liveness.initialDelaySeconds": "30", + "pluginsSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].LivenessProbe) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ContainerPort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ContainerPort) { + s.Empty(ports, "Ports should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "teams-plugins", + "containerPort": 5151, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceContainerPortAndShortName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.containerPort": "5155", + "pluginsSettings.service.shortname": "test-service-shortname", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "test-service-shortname", + "containerPort": 5155, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Ports) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerReadinessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + s.Empty(probe, "Readiness Probes should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "teams-plugins" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + { + "overrideServiceReadinessInitialDelaySecondsAndShortName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.readiness.initialDelaySeconds": "30", + "pluginsSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "tcpSocket": { + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].ReadinessProbe) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerResourceRequirements() { + testCases := []struct { + name string + values map[string]string + expected func(resourceRequirements corev1.ResourceRequirements) + }{ + { + "defaultValues", + nil, + func(resourceRequirements corev1.ResourceRequirements) { + s.Empty(resourceRequirements, "Resource Requirements should be empty") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(resourceRequirements corev1.ResourceRequirements) { + s.Equal(resourceRequirements.Limits, corev1.ResourceList{}, "Limits should be equal") + s.Equal(resourceRequirements.Requests, corev1.ResourceList{}, "Requests should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + { + "overrideResources", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.resources.limits.cpu": "1", + "pluginsSettings.resources.limits.memory": "1Gi", + "pluginsSettings.resources.requests.cpu": "500m", + "pluginsSettings.resources.requests.memory": "512Mi", + }, + func(resourceRequirements corev1.ResourceRequirements) { + resourceExpected := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("1"), + "memory": resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + s.Equal(resourceExpected, resourceRequirements, "should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Resources) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(securityContext *corev1.SecurityContext) + }{ + { + "defaultValues", + nil, + func(securityContext *corev1.SecurityContext) { + s.Empty(securityContext, "should be not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(securityContext *corev1.SecurityContext) { + s.Nil(securityContext.AllowPrivilegeEscalation, "should be nil") + s.Nil(securityContext.Capabilities, "should be nil") + s.Nil(securityContext.Privileged, "should be nil") + s.Nil(securityContext.ProcMount, "should be nil") + s.Nil(securityContext.ReadOnlyRootFilesystem, "should be nil") + s.Nil(securityContext.RunAsGroup, "should be nil") + s.Nil(securityContext.RunAsNonRoot, "should be nil") + s.Nil(securityContext.RunAsUser, "should be nil") + s.Nil(securityContext.SeccompProfile, "should be nil") + s.Nil(securityContext.SELinuxOptions, "should be nil") + s.Nil(securityContext.WindowsOptions, "should be nil") + }, + }, + { + "overrideSecurityContext", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.securityContext.runAsGroup": "3000", + "pluginsSettings.securityContext.runAsUser": "1000", + }, + func(securityContext *corev1.SecurityContext) { + s.Equal(int64(3000), *securityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *securityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].SecurityContext) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestContainerVolumeMounts() { + testCases := []struct { + name string + values map[string]string + expected func(volumeMounts []corev1.VolumeMount) + }{ + { + "defaultValues", + nil, + func(volumeMounts []corev1.VolumeMount) { + s.Empty(volumeMounts, "VolumeMounts should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(volumeMounts []corev1.VolumeMount) { + s.Nil(volumeMounts, "VolumeMounts should be nil") + }, + }, + { + "overrideVolumeMountsSingle", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.volumeMounts[0].mountPath": "/test-data-volume", + "pluginsSettings.volumeMounts[0].name": "test-volume", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume", + "name": "test-volume" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + { + "overrideVolumeMountsMultiple", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.volumeMounts[0].mountPath": "/test-data-volume1", + "pluginsSettings.volumeMounts[0].name": "test-volume1", + "pluginsSettings.volumeMounts[1].mountPath": "/test-data-volume2", + "pluginsSettings.volumeMounts[1].name": "test-volume2", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume1", + "name": "test-volume1" + }, + { + "mountPath": "/test-data-volume2", + "name": "test-volume2" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].VolumeMounts) + } + + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestAffinity() { + testCases := []struct { + name string + values map[string]string + expected func(affinity *corev1.Affinity) + }{ + { + "defaultValues", + nil, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "overrideAffinity", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "disktype", + "pluginsSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "In", + "pluginsSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]": "ssd", + }, + func(affinity *corev1.Affinity) { + affinityJSON := `{ + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "disktype", + "operator": "In", + "values": [ + "ssd" + ] + } + ] + } + ] + } + } + }` + var expectedAffinity corev1.Affinity + err := json.Unmarshal([]byte(affinityJSON), &expectedAffinity) + s.NoError(err) + + s.Equal(expectedAffinity, *affinity, "Affinity should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Affinity) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestImagePullSecrets() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "", + }, + { + "overrideImagePullSecrets", + map[string]string{ + "pluginsSettings.enabled": "true", + "imagePullSecrets[0].name": "test-pull-secret", + }, + "test-pull-secret", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == "" { + s.Nil(deployment.Spec.Template.Spec.ImagePullSecrets, "Image pull secret should be nil") + } else { + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ImagePullSecrets[0].Name, "Image pull secret should be equal.") + } + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestNodeSelector() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + nil, + }, + { + "overrideNodeSelector", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.nodeSelector.disktype": "ssd", + }, + map[string]string{ + "disktype": "ssd", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.Spec.NodeSelector[key] + s.Equal(value, foundValue, "NodeSelector should contain all set labels.") + } + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestPodAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + nil, + }, + { + "overridePodAnnotations", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.podAnnotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == nil { + s.Nil(deployment.Spec.Template.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestPodSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(podSecurityContext *corev1.PodSecurityContext) + }{ + { + "defaultValues", + nil, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Empty(podSecurityContext.FSGroup, "should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") + s.Nil(podSecurityContext.RunAsGroup, "should be nil") + s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") + s.Nil(podSecurityContext.RunAsUser, "should be nil") + s.Nil(podSecurityContext.SeccompProfile, "should be nil") + s.Nil(podSecurityContext.SELinuxOptions, "should be nil") + s.Nil(podSecurityContext.SupplementalGroups, "should be nil") + s.Nil(podSecurityContext.Sysctls, "should be nil") + s.Nil(podSecurityContext.WindowsOptions, "should be nil") + }, + }, + { + "overridePodSecurityContext", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.podSecurityContext.fsGroup": "2000", + "pluginsSettings.podSecurityContext.runAsGroup": "3000", + "pluginsSettings.podSecurityContext.runAsUser": "1000", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Equal(int64(2000), *podSecurityContext.FSGroup, "fsGroup should be 2000") + s.Equal(int64(3000), *podSecurityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.SecurityContext) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestSelectorMatchLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + map[string]string{ + "app.kubernetes.io/name": "teams-plugins", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorMatchLabels", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Selector Labels and Template Metadata Labels use the same helm template. + // Check both. + for key, value := range testCase.expected { + + foundValue := deployment.Spec.Selector.MatchLabels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + + foundValue = deployment.Spec.Template.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + } + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestServiceAccountName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideServiceAccountName", + map[string]string{ + "pluginsSettings.enabled": "true", + "serviceAccount.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ServiceAccountName, "Service account name should be equal.") + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestTolerations() { + testCases := []struct { + name string + values map[string]string + expected func(tolerations []corev1.Toleration) + }{ + { + "defaultValues", + nil, + func(tolerations []corev1.Toleration) { + s.Empty(tolerations, "should not be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(tolerations []corev1.Toleration) { + s.Nil(tolerations, "should be nil") + }, + }, + { + "overrideTolerations", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.tolerations[0].key": "example-key", + "pluginsSettings.tolerations[0].operator": "Exists", + "pluginsSettings.tolerations[0].effect": "NoSchedule", + }, + func(tolerations []corev1.Toleration) { + tolerationJSON := `[ + { + "key": "example-key", + "operator": "Exists", + "effect": "NoSchedule" + } + ]` + var expectedTolerations []corev1.Toleration + err := json.Unmarshal([]byte(tolerationJSON), &expectedTolerations) + s.NoError(err) + + s.Len(tolerations, 1, "Should only have 1 toleration") + s.Equal(expectedTolerations[0], tolerations[0], "Toleration should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Tolerations) + } + }) + } +} + +func (s *deploymentPluginsTemplateTest) TestVolumes() { + testCases := []struct { + name string + values map[string]string + expected func(volumes []corev1.Volume) + }{ + { + "defaultValues", + nil, + func(volumes []corev1.Volume) { + s.Empty(volumes, "Volumes should be be set") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(volumes []corev1.Volume) { + s.Nil(volumes, "Volumes should be nil") + }, + }, + { + "overrideVolumesSingle", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.volumes[0].name": "test-volume", + "pluginsSettings.volumes[0].hostPath.path": "/test-volume", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume", + "hostPath": { + "path": "/test-volume" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + { + "overrideVolumesMultiple", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.volumes[0].name": "test-volume1", + "pluginsSettings.volumes[0].hostPath.path": "/test-volume1", + "pluginsSettings.volumes[1].name": "pvc1", + "pluginsSettings.volumes[1].persistentVolumeClaim.claimName": "pvc1", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume1", + "hostPath": { + "path": "/test-volume1" + } + }, + { + "name": "pvc1", + "persistentVolumeClaim": { + "claimName": "pvc1" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + // when vars are set outside of the if statement, they aren't accessible from within the conditional + if testCase.values == nil { + options := &helm.Options{SetValues: testCase.values} + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + + s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") + var deployment appsv1.Deployment + + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Nil(deployment.Spec.Template.Spec.Containers) + } else { + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Volumes) + } + + }) + } +} diff --git a/tests/unit/helm/plugins-hpa_test.go b/tests/unit/helm/plugins-hpa_test.go new file mode 100644 index 00000000..26bb9100 --- /dev/null +++ b/tests/unit/helm/plugins-hpa_test.go @@ -0,0 +1,555 @@ +//go:build kubeall || helm || unit || unitHpa || unitHpaPlugins +// +build kubeall helm unit unitHpa unitHpaPlugins + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + autoscalingv2 "k8s.io/api/autoscaling/v2" +) + +type horizontalPodAutoscalerPluginsTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestHorizontalPodAutoscalerPluginsTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &horizontalPodAutoscalerPluginsTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/plugins-hpa.yaml"}, + }) +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-plugins", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Nil(hpa.ObjectMeta.Labels, "Labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + for key, value := range testCase.expected { + foundValue := hpa.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + } + }) + } +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + "teams-plugins", + }, + { + "overrideMetadataName", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Name, "Metadata name should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Name, "Metadata name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Namespace, "Metadata namespace should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Namespace, "Namespace name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestScaleTargetRef() { + testCases := []struct { + name string + values map[string]string + expected func(ref autoscalingv2.CrossVersionObjectReference) + }{ + { + "defaultValues", + nil, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{}` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "teams-plugins" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "overrideServiceName", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "test-service-name" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.ScaleTargetRef, "Scale TargetRef should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.ScaleTargetRef) + } + }) + } +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMaxReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + 20, + }, + { + "overrideMaxReplicas", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.autoscaling.maxReplicas": "19", + }, + 19, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MaxReplicas, "maxReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.Spec.MaxReplicas, "maxReplicas name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMinReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + 2, + }, + { + "overrideMinReplicas", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.autoscaling.minReplicas": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MinReplicas, "minReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, *hpa.Spec.MinReplicas, "minReplicas name should be equal.") + } + }) + } +} +func (s *horizontalPodAutoscalerPluginsTemplateTest) TestMetrics() { + testCases := []struct { + name string + values map[string]string + expected func(metrics []autoscalingv2.MetricSpec) + }{ + { + "defaultValues", + nil, + func(metrics []autoscalingv2.MetricSpec) { + s.Empty(metrics, "metricSpec should not be set") + }, + }, + { + "defaultValuesPluginsHpaEnabled", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + { + "overrideTargetCpuAndMemory", + map[string]string{ + "pluginsSettings.autoscaling.enabled": "true", + "pluginsSettings.autoscaling.targetCPUUtilizationPercentage": "99", + "pluginsSettings.autoscaling.targetMemoryUtilizationPercentage": "98", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 99 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 98 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.Metrics, "metrics should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.Metrics) + } + }) + } +} diff --git a/tests/unit/helm/plugins-service_test.go b/tests/unit/helm/plugins-service_test.go new file mode 100644 index 00000000..eb2948f2 --- /dev/null +++ b/tests/unit/helm/plugins-service_test.go @@ -0,0 +1,567 @@ +//go:build kubeall || helm || unit || unitPluginsService +// +build kubeall helm unit unitPluginsService + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type servicePluginsTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServicePluginsTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &servicePluginsTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/plugins-service.yaml"}, + }) +} + +func (s *servicePluginsTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Nil(service.ObjectMeta.Annotations, "Annotations should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "teams-plugins", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Nil(service.ObjectMeta.Labels, "Labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set annotations.") + } + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "teams-plugins", + }, + { + "overrideMetadataName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + { + "overrideMetadataName", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Empty(service.ObjectMeta.Name, "Name should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Name, "Name name should be equal.") + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "pluginsSettings.enabled": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Empty(service.ObjectMeta.Namespace, "Namespace should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Namespace, "Namespace name should be equal.") + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ServicePort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithoutPortNumber", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.type": "NodePort", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithPortNumber", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.type": "NodePort", + "pluginsSettings.service.nodePort": "9999", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 5151, + "protocol": "TCP", + "name": "http", + "nodePort": 9999 + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceServicePortValues", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.containerPort": "8001", + "pluginsSettings.service.port": "88", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 88, + "targetPort": 8001, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Nil(service.Spec.Ports, "Ports should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + testCase.expected(service.Spec.Ports) + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + "ClusterIP", + }, + { + "overrideSelectorLabels", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.type": "NodePort", + }, + "NodePort", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Empty(service.ObjectMeta.Name, "Type should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, string(service.Spec.Type), "Type should be equal.") + } + }) + } +} + +func (s *servicePluginsTemplateTest) TestSelectorLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesPluginsEnabled", + map[string]string{ + "pluginsSettings.enabled": "true", + }, + map[string]string{ + "app.kubernetes.io/name": "teams-plugins", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorLabels", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "test-service-name", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/plugins-service.yaml in chart") + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Nil(service.Spec.Selector, "Selector labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.Spec.Selector[key] + s.Equal(value, foundValue, "Selector labels should contain all set labels.") + } + } + }) + } +} diff --git a/tests/unit/helm/secrets_test.go b/tests/unit/helm/secrets_test.go new file mode 100644 index 00000000..8c5fd530 --- /dev/null +++ b/tests/unit/helm/secrets_test.go @@ -0,0 +1,275 @@ +//go:build kubeall || helm || unit || unitSecrets +// +build kubeall helm unit unitSecrets + +package unit + +import ( + // "encoding/json" + // "fmt" + "path/filepath" + // "reflect" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type secretsTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestSecretsTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &secretsTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/secrets.yaml"}, + }) +} + +func (s *secretsTemplateTest) TestDisabled() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams-secrets", + }, + { + "defaultValuesSecretsDisabled", + map[string]string{ + "secret.create": "false", + }, + "", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/secrets.yaml in chart") + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + s.Empty(secret.ObjectMeta.Name, "Name should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + s.Equal(testCase.expected, secret.ObjectMeta.Name, "Name should be set") + } + }) + } +} + +func (s *secretsTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams-secrets", + }, + { + "overrideName", + map[string]string{ + "secret.name": "test-secret-name", + }, + "test-secret-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + s.Equal(testCase.expected, secret.ObjectMeta.Name, "Name name should be equal.") + }) + } +} + +func (s *secretsTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + s.Equal(testCase.expected, secret.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *secretsTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "Opaque", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + s.Equal(testCase.expected, string(secret.Type), "Type name should be Opaque.") + }) + } +} + +func (s *secretsTemplateTest) TestData() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "apiClientId": "", + "apiClientSecret": "", + "auth0Domain": "", + "clientId": "", + "clientSecret": "", + "cookieSecret": "", + "encryptionKey": "", + "fiftyoneDatabaseName": "", + "mongodbConnectionString": "", + "organizationId": "", + }, + }, + { + "overrideSecretFiftyone", + map[string]string{ + "secret.fiftyone.apiClientId": "test-client-id", + "secret.fiftyone.apiClientSecret": "test-client-secret", + "secret.fiftyone.auth0Domain": "test-auth0-domain", + "secret.fiftyone.clientId": "test-client-id", + "secret.fiftyone.clientSecret": "test-client-secret", + "secret.fiftyone.cookieSecret": "test-cookie-secret", + "secret.fiftyone.encryptionKey": "test-encryption-key", + "secret.fiftyone.fiftyoneDatabaseName": "test-fiftyone-database", + "secret.fiftyone.mongodbConnectionString": "test-mongodb-connection-string", + "secret.fiftyone.organizationId": "test-organization-id", + }, + map[string]string{ + "apiClientId": "test-client-id", + "apiClientSecret": "test-client-secret", + "auth0Domain": "test-auth0-domain", + "clientId": "test-client-id", + "clientSecret": "test-client-secret", + "cookieSecret": "test-cookie-secret", + "encryptionKey": "test-encryption-key", + "fiftyoneDatabaseName": "test-fiftyone-database", + "mongodbConnectionString": "test-mongodb-connection-string", + "organizationId": "test-organization-id", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var secret corev1.Secret + helm.UnmarshalK8SYaml(subT, output, &secret) + + for key, value := range testCase.expected { + foundValue := secret.Data[key] + s.Equal(value, string(foundValue), "Data should contain all set values.") + } + }) + } +} diff --git a/tests/unit/helm/serviceaccount_test.go b/tests/unit/helm/serviceaccount_test.go new file mode 100644 index 00000000..0e139fde --- /dev/null +++ b/tests/unit/helm/serviceaccount_test.go @@ -0,0 +1,284 @@ +//go:build kubeall || helm || unit || unitServiceAccount +// +build kubeall helm unit unitServiceAccount + +package unit + +import ( + // "encoding/json" + "fmt" + "path/filepath" + // "reflect" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type serviceAccountTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServiceAccountTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &serviceAccountTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/serviceaccount.yaml"}, + }) +} + +func (s *serviceAccountTemplateTest) TestDisabled() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "defaultValuesSecretsDisabled", + map[string]string{ + "serviceAccount.create": "false", + }, + "", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/serviceaccount.yaml in chart") + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + s.Empty(serviceAccount.ObjectMeta.Name, "Name should be empty") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + s.Equal(testCase.expected, serviceAccount.ObjectMeta.Name, "Name should be set") + } + }) + } +} + +func (s *serviceAccountTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideName", + map[string]string{ + "serviceAccount.name": "test-service-account-name", + }, + "test-service-account-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + s.Equal(testCase.expected, serviceAccount.ObjectMeta.Name, "Name name should be equal.") + }) + } +} + +func (s *serviceAccountTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + s.Equal(testCase.expected, serviceAccount.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *serviceAccountTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "serviceAccount.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + if testCase.expected == nil { + s.Nil(serviceAccount.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := serviceAccount.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *serviceAccountTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for the serviceAccount. + "appSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var serviceAccount corev1.ServiceAccount + helm.UnmarshalK8SYaml(subT, output, &serviceAccount) + + for key, value := range testCase.expected { + foundValue := serviceAccount.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} diff --git a/tests/unit/helm/teams-app-deployment_test.go b/tests/unit/helm/teams-app-deployment_test.go new file mode 100644 index 00000000..8c5915aa --- /dev/null +++ b/tests/unit/helm/teams-app-deployment_test.go @@ -0,0 +1,1433 @@ +//go:build kubeall || helm || unit || unitTeamsAppDeployment +// +build kubeall helm unit unitTeamsAppDeployment + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/api/resource" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type deploymentTeamsAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestDeploymentTeamsAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &deploymentTeamsAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/teams-app-deployment.yaml"}, + }) +} + +func (s *deploymentTeamsAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for teams-app. + "teamsAppSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-app", + }, + { + "overrideMetadataName", + map[string]string{ + "teamsAppSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Name, "Deployment name should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 2, + }, + { + "overrideReplicaCount", + map[string]string{ + "teamsAppSettings.replicaCount": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, *deployment.Spec.Replicas, "Replica count should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerCount() { + testCases := []struct { + name string + values map[string]string + expected int + }{ + { + "defaultValues", + nil, + 1, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, len(deployment.Spec.Template.Spec.Containers), "Container count should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerEnv() { + testCases := []struct { + name string + values map[string]string + expected func(envVars []corev1.EnvVar) + }{ + { + "defaultValues", // legacy auth mode + nil, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FEATURE_FLAG_ENABLE_INVITATIONS", + "value": "true" + }, + { + "name": "FIFTYONE_API_URI", + "value": "https://" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_SERVER_ADDRESS", + "value": "" + }, + { + "name": "FIFTYONE_SERVER_PATH_PREFIX", + "value": "/api/proxy/fiftyone-teams" + }, + { + "name": "FIFTYONE_TEAMS_PROXY_URL", + "value": "http://fiftyone-app:80" + }, + { + "name": "FIFTYONE_TEAMS_PLUGIN_URL", + "value": "http://fiftyone-app:80" + }, + { + "name": "APP_USE_HTTPS", + "value": "true" + }, + { + "name": "FIFTYONE_APP_ALLOW_MEDIA_EXPORT", + "value": "true" + }, + { + "name": "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION", + "value": "0.16.0b2" + }, + { + "name": "FIFTYONE_APP_THEME", + "value": "dark" + }, + { + "name": "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED", + "value": "false" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + { + "overrideEnv", + map[string]string{ + "teamsAppSettings.env.TEST_KEY": "TEST_VALUE", + }, + func(envVars []corev1.EnvVar) { + expectedEnvVarJSON := `[ + { + "name": "API_URL", + "value": "http://teams-api:80" + }, + { + "name": "FEATURE_FLAG_ENABLE_INVITATIONS", + "value": "true" + }, + { + "name": "FIFTYONE_API_URI", + "value": "https://" + }, + { + "name": "FIFTYONE_AUTH_SECRET", + "valueFrom": { + "secretKeyRef": { + "name": "fiftyone-teams-secrets", + "key": "fiftyoneAuthSecret" + } + } + }, + { + "name": "FIFTYONE_SERVER_ADDRESS", + "value": "" + }, + { + "name": "FIFTYONE_SERVER_PATH_PREFIX", + "value": "/api/proxy/fiftyone-teams" + }, + { + "name": "FIFTYONE_TEAMS_PROXY_URL", + "value": "http://fiftyone-app:80" + }, + { + "name": "FIFTYONE_TEAMS_PLUGIN_URL", + "value": "http://fiftyone-app:80" + }, + { + "name": "APP_USE_HTTPS", + "value": "true" + }, + { + "name": "FIFTYONE_APP_ALLOW_MEDIA_EXPORT", + "value": "true" + }, + { + "name": "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION", + "value": "0.16.0b2" + }, + { + "name": "FIFTYONE_APP_THEME", + "value": "dark" + }, + { + "name": "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED", + "value": "false" + }, + { + "name": "TEST_KEY", + "value": "TEST_VALUE" + } + ]` + var expectedEnvVars []corev1.EnvVar + err := json.Unmarshal([]byte(expectedEnvVarJSON), &expectedEnvVars) + s.NoError(err) + s.Equal(expectedEnvVars, envVars, "Envs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Env) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerImage() { + + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + fmt.Sprintf("voxel51/fiftyone-teams-app:%s", chartAppVersion), + }, + { + "overrideImageTag", + map[string]string{ + "teamsAppSettings.image.tag": "testTag", + }, + "voxel51/fiftyone-teams-app:testTag", + }, + { + "overrideImageRepository", + map[string]string{ + "teamsAppSettings.image.repository": "ghcr.io/fiftyone-teams-app", + }, + fmt.Sprintf("ghcr.io/fiftyone-teams-app:%s", chartAppVersion), + }, + { + "overrideImageVersionAndRepository", + map[string]string{ + "teamsAppSettings.image.tag": "testTag", + "teamsAppSettings.image.repository": "ghcr.io/fiftyone-teams-app", + }, + "ghcr.io/fiftyone-teams-app:testTag", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Image, "Image values should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerImagePullPolicy() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "Always", + }, + { + "overrideImagePullPolicy", + map[string]string{ + "teamsAppSettings.image.pullPolicy": "IfNotPresent", + }, + "IfNotPresent", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Convert type returned by `.ImagePullPolicy` to a string + s.Equal(testCase.expected, string(deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy), "Image pull policy should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-app", + }, + { + "overrideServiceAccountName", + map[string]string{ + "teamsAppSettings.service.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.Containers[0].Name, "Container name should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerLivenessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/api/hello", + "port": "teams-app" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + { + "overrideServiceLivenessInitialDelaySecondsAndShortName", + map[string]string{ + "teamsAppSettings.service.liveness.initialDelaySeconds": "30", + "teamsAppSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/api/hello", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Liveness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].LivenessProbe) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ContainerPort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "teams-app", + "containerPort": 3000, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceContainerPortAndShortName", + map[string]string{ + "teamsAppSettings.service.containerPort": "3001", + "teamsAppSettings.service.shortname": "test-service-shortname", + }, + func(ports []corev1.ContainerPort) { + expectedPortsJSON := `[ + { + "name": "test-service-shortname", + "containerPort": 3001, + "protocol": "TCP" + } + ]` + var expectedPorts []corev1.ContainerPort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Ports) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerReadinessProbe() { + testCases := []struct { + name string + values map[string]string + expected func(probe *corev1.Probe) + }{ + { + "defaultValues", + nil, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/api/hello", + "port": "teams-app" + }, + "initialDelaySeconds": 45, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + { + "overrideServiceReadinessInitialDelaySecondsAndShortName", + map[string]string{ + "teamsAppSettings.service.readiness.initialDelaySeconds": "30", + "teamsAppSettings.service.shortname": "test-service-shortname", + }, + func(probe *corev1.Probe) { + expectedProbeJSON := `{ + "httpGet": { + "path": "/api/hello", + "port": "test-service-shortname" + }, + "initialDelaySeconds": 30, + "timeoutSeconds": 5 + }` + var expectedProbe *corev1.Probe + err := json.Unmarshal([]byte(expectedProbeJSON), &expectedProbe) + s.NoError(err) + s.Equal(expectedProbe, probe, "Readiness Probes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].ReadinessProbe) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerResourceRequirements() { + testCases := []struct { + name string + values map[string]string + expected func(resourceRequirements corev1.ResourceRequirements) + }{ + { + "defaultValues", + nil, + func(resourceRequirements corev1.ResourceRequirements) { + s.Equal(resourceRequirements.Limits, corev1.ResourceList{}, "Limits should be equal") + s.Equal(resourceRequirements.Requests, corev1.ResourceList{}, "Requests should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + { + "overrideResources", + map[string]string{ + "teamsAppSettings.resources.limits.cpu": "1", + "teamsAppSettings.resources.limits.memory": "1Gi", + "teamsAppSettings.resources.requests.cpu": "500m", + "teamsAppSettings.resources.requests.memory": "512Mi", + }, + func(resourceRequirements corev1.ResourceRequirements) { + resourceExpected := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": resource.MustParse("1"), + "memory": resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("500m"), + "memory": resource.MustParse("512Mi"), + }, + } + s.Equal(resourceExpected, resourceRequirements, "should be equal") + s.Nil(resourceRequirements.Claims, "should be nil") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].Resources) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(securityContext *corev1.SecurityContext) + }{ + { + "defaultValues", + nil, + func(securityContext *corev1.SecurityContext) { + s.Nil(securityContext.AllowPrivilegeEscalation, "should be nil") + s.Nil(securityContext.Capabilities, "should be nil") + s.Nil(securityContext.Privileged, "should be nil") + s.Nil(securityContext.ProcMount, "should be nil") + s.Nil(securityContext.ReadOnlyRootFilesystem, "should be nil") + s.Nil(securityContext.RunAsGroup, "should be nil") + s.Nil(securityContext.RunAsNonRoot, "should be nil") + s.Nil(securityContext.RunAsUser, "should be nil") + s.Nil(securityContext.SeccompProfile, "should be nil") + s.Nil(securityContext.SELinuxOptions, "should be nil") + s.Nil(securityContext.WindowsOptions, "should be nil") + }, + }, + { + "overrideSecurityContext", + map[string]string{ + "teamsAppSettings.securityContext.runAsGroup": "3000", + "teamsAppSettings.securityContext.runAsUser": "1000", + }, + func(securityContext *corev1.SecurityContext) { + s.Equal(int64(3000), *securityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *securityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].SecurityContext) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestContainerVolumeMounts() { + testCases := []struct { + name string + values map[string]string + expected func(volumeMounts []corev1.VolumeMount) + }{ + { + "defaultValues", + nil, + func(volumeMounts []corev1.VolumeMount) { + s.Nil(volumeMounts, "VolumeMounts should be nil") + }, + }, + { + "overrideVolumeMountsSingle", + map[string]string{ + "teamsAppSettings.volumeMounts[0].mountPath": "/test-data-volume", + "teamsAppSettings.volumeMounts[0].name": "test-volume", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume", + "name": "test-volume" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + { + "overrideVolumeMountsMultiple", + map[string]string{ + "teamsAppSettings.volumeMounts[0].mountPath": "/test-data-volume1", + "teamsAppSettings.volumeMounts[0].name": "test-volume1", + "teamsAppSettings.volumeMounts[1].mountPath": "/test-data-volume2", + "teamsAppSettings.volumeMounts[1].name": "test-volume2", + }, + func(volumeMounts []corev1.VolumeMount) { + expectedJSON := `[ + { + "mountPath": "/test-data-volume1", + "name": "test-volume1" + }, + { + "mountPath": "/test-data-volume2", + "name": "test-volume2" + } + ]` + var expectedVolumeMounts []corev1.VolumeMount + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumeMounts) + s.NoError(err) + s.Equal(expectedVolumeMounts, volumeMounts, "Volume Mounts should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Containers[0].VolumeMounts) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestAffinity() { + testCases := []struct { + name string + values map[string]string + expected func(affinity *corev1.Affinity) + }{ + { + "defaultValues", + nil, + func(affinity *corev1.Affinity) { + s.Nil(affinity, "should be nil") + }, + }, + { + "overrideAffinity", + map[string]string{ + "teamsAppSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "disktype", + "teamsAppSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "In", + "teamsAppSettings.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values[0]": "ssd", + }, + func(affinity *corev1.Affinity) { + affinityJSON := `{ + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "disktype", + "operator": "In", + "values": [ + "ssd" + ] + } + ] + } + ] + } + } + }` + var expectedAffinity corev1.Affinity + err := json.Unmarshal([]byte(affinityJSON), &expectedAffinity) + s.NoError(err) + + s.Equal(expectedAffinity, *affinity, "Affinity should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Affinity) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestImagePullSecrets() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "overrideImagePullSecrets", + map[string]string{ + "imagePullSecrets[0].name": "test-pull-secret", + }, + "test-pull-secret", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == "" { + s.Nil(deployment.Spec.Template.Spec.ImagePullSecrets, "Image pull secret should be nil") + } else { + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ImagePullSecrets[0].Name, "Image pull secret should be equal.") + } + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestNodeSelector() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideNodeSelector", + map[string]string{ + "teamsAppSettings.nodeSelector.disktype": "ssd", + }, + map[string]string{ + "disktype": "ssd", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.Spec.NodeSelector[key] + s.Equal(value, foundValue, "NodeSelector should contain all set labels.") + } + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestPodAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overridePodAnnotations", + map[string]string{ + "teamsAppSettings.podAnnotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + if testCase.expected == nil { + s.Nil(deployment.Spec.Template.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := deployment.Spec.Template.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestPodSecurityContext() { + testCases := []struct { + name string + values map[string]string + expected func(podSecurityContext *corev1.PodSecurityContext) + }{ + { + "defaultValues", + nil, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") + s.Nil(podSecurityContext.RunAsGroup, "should be nil") + s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") + s.Nil(podSecurityContext.RunAsUser, "should be nil") + s.Nil(podSecurityContext.SeccompProfile, "should be nil") + s.Nil(podSecurityContext.SELinuxOptions, "should be nil") + s.Nil(podSecurityContext.SupplementalGroups, "should be nil") + s.Nil(podSecurityContext.Sysctls, "should be nil") + s.Nil(podSecurityContext.WindowsOptions, "should be nil") + }, + }, + { + "overridePodSecurityContext", + map[string]string{ + "teamsAppSettings.podSecurityContext.fsGroup": "2000", + "teamsAppSettings.podSecurityContext.runAsGroup": "3000", + "teamsAppSettings.podSecurityContext.runAsUser": "1000", + }, + func(podSecurityContext *corev1.PodSecurityContext) { + s.Equal(int64(2000), *podSecurityContext.FSGroup, "fsGroup should be 2000") + s.Equal(int64(3000), *podSecurityContext.RunAsGroup, "runAsGroup should be 3000") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.SecurityContext) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestSelectorMatchLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorMatchLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for teams-app. + "teamsAppSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + // Selector Labels and Template Metadata Labels use the same helm template. + // Check both. + for key, value := range testCase.expected { + + foundValue := deployment.Spec.Selector.MatchLabels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + + foundValue = deployment.Spec.Template.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Selector Labels should contain all set labels.") + } + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestServiceAccountName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideServiceAccountName", + map[string]string{ + "serviceAccount.name": "test-service-account", + }, + "test-service-account", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + s.Equal(testCase.expected, deployment.Spec.Template.Spec.ServiceAccountName, "Service account name should be equal.") + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestTolerations() { + testCases := []struct { + name string + values map[string]string + expected func(tolerations []corev1.Toleration) + }{ + { + "defaultValues", + nil, + func(tolerations []corev1.Toleration) { + s.Nil(tolerations, "should be nil") + }, + }, + { + "overrideTolerations", + map[string]string{ + "teamsAppSettings.tolerations[0].key": "example-key", + "teamsAppSettings.tolerations[0].operator": "Exists", + "teamsAppSettings.tolerations[0].effect": "NoSchedule", + }, + func(tolerations []corev1.Toleration) { + tolerationJSON := `[ + { + "key": "example-key", + "operator": "Exists", + "effect": "NoSchedule" + } + ]` + var expectedTolerations []corev1.Toleration + err := json.Unmarshal([]byte(tolerationJSON), &expectedTolerations) + s.NoError(err) + + s.Len(tolerations, 1, "Should only have 1 toleration") + s.Equal(expectedTolerations[0], tolerations[0], "Toleration should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Tolerations) + }) + } +} + +func (s *deploymentTeamsAppTemplateTest) TestVolumes() { + testCases := []struct { + name string + values map[string]string + expected func(volumes []corev1.Volume) + }{ + { + "defaultValues", + nil, + func(volumes []corev1.Volume) { + s.Nil(volumes, "Volumes should be nil") + }, + }, + { + "overrideVolumesSingle", + map[string]string{ + "teamsAppSettings.volumes[0].name": "test-volume", + "teamsAppSettings.volumes[0].hostPath.path": "/test-volume", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume", + "hostPath": { + "path": "/test-volume" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + { + "overrideVolumesMultiple", + map[string]string{ + "teamsAppSettings.volumes[0].name": "test-volume1", + "teamsAppSettings.volumes[0].hostPath.path": "/test-volume1", + "teamsAppSettings.volumes[1].name": "pvc1", + "teamsAppSettings.volumes[1].persistentVolumeClaim.claimName": "pvc1", + }, + func(volumes []corev1.Volume) { + expectedJSON := `[ + { + "name": "test-volume1", + "hostPath": { + "path": "/test-volume1" + } + }, + { + "name": "pvc1", + "persistentVolumeClaim": { + "claimName": "pvc1" + } + } + ]` + var expectedVolumes []corev1.Volume + err := json.Unmarshal([]byte(expectedJSON), &expectedVolumes) + s.NoError(err) + s.Equal(expectedVolumes, volumes, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(subT, output, &deployment) + + testCase.expected(deployment.Spec.Template.Spec.Volumes) + }) + } +} diff --git a/tests/unit/helm/teams-app-hpa_test.go b/tests/unit/helm/teams-app-hpa_test.go new file mode 100644 index 00000000..2ebd0082 --- /dev/null +++ b/tests/unit/helm/teams-app-hpa_test.go @@ -0,0 +1,557 @@ +//go:build kubeall || helm || unit || unitHpa || unitTeamsAppHpa +// +build kubeall helm unit unitHpa unitTeamsAppHpa + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + autoscalingv2 "k8s.io/api/autoscaling/v2" +) + +type horizontalPodAutoscalerTeamsAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestHorizontalPodAutoscalerTeamsAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &horizontalPodAutoscalerTeamsAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/teams-app-hpa.yaml"}, + }) +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for teams-app. + "teamsAppSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Nil(hpa.ObjectMeta.Labels, "Labels should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + for key, value := range testCase.expected { + foundValue := hpa.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + } + }) + } +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + "teams-app", + }, + { + "overrideMetadataName", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "teamsAppSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Name, "Metadata name should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Name, "Metadata name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "", + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == "" { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.ObjectMeta.Namespace, "Metadata namespace should be nil") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.ObjectMeta.Namespace, "Namespace name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestScaleTargetRef() { + testCases := []struct { + name string + values map[string]string + expected func(ref autoscalingv2.CrossVersionObjectReference) + }{ + { + "defaultValues", + nil, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{}` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "teams-app" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + { + "overrideServiceName", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "teamsAppSettings.service.name": "test-service-name", + }, + func(ref autoscalingv2.CrossVersionObjectReference) { + expectedRefJSON := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "test-service-name" + }` + var expectedRef autoscalingv2.CrossVersionObjectReference + err := json.Unmarshal([]byte(expectedRefJSON), &expectedRef) + s.NoError(err) + s.Equal(expectedRef, ref, "Scale Target Refs should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.ScaleTargetRef, "Scale TargetRef should be nil") + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.ScaleTargetRef) + } + }) + } +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMaxReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + 5, + }, + { + "overrideMaxReplicas", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "teamsAppSettings.autoscaling.maxReplicas": "9", + }, + 9, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MaxReplicas, "maxReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, hpa.Spec.MaxReplicas, "maxReplicas name should be equal.") + } + }) + } +} + +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMinReplicas() { + testCases := []struct { + name string + values map[string]string + expected int32 + }{ + { + "defaultValues", + nil, + 0, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + 2, + }, + { + "overrideMinReplicas", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "teamsAppSettings.autoscaling.minReplicas": "3", + }, + 3, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.expected == 0 { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.MinReplicas, "minReplicas should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Equal(testCase.expected, *hpa.Spec.MinReplicas, "minReplicas name should be equal.") + } + }) + } +} +func (s *horizontalPodAutoscalerTeamsAppTemplateTest) TestMetrics() { + testCases := []struct { + name string + values map[string]string + expected func(metrics []autoscalingv2.MetricSpec) + }{ + { + "defaultValues", + nil, + func(metrics []autoscalingv2.MetricSpec) { + s.Empty(metrics, "metricSpec should not be set") + }, + }, + { + "defaultValuesAppHpaEnabled", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 80 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + { + "overrideTargetCpuAndMemory", + map[string]string{ + "teamsAppSettings.autoscaling.enabled": "true", + "teamsAppSettings.autoscaling.targetCPUUtilizationPercentage": "99", + "teamsAppSettings.autoscaling.targetMemoryUtilizationPercentage": "98", + }, + func(metrics []autoscalingv2.MetricSpec) { + expectedJSON := `[ + { + "type": "Resource", + "resource": { + "name": "cpu", + "target": { + "type": "Utilization", + "averageUtilization": 99 + } + } + }, + { + "type": "Resource", + "resource": { + "name": "memory", + "target": { + "type": "Utilization", + "averageUtilization": 98 + } + } + } + ]` + var expectedMetrics []autoscalingv2.MetricSpec + err := json.Unmarshal([]byte(expectedJSON), &expectedMetrics) + s.NoError(err) + s.Equal(expectedMetrics, metrics, "Volumes should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + + if testCase.values == nil { + output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/teams-app-hpa.yaml in chart") + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + s.Empty(hpa.Spec.Metrics, "metrics should be empty") + + } else { + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var hpa autoscalingv2.HorizontalPodAutoscaler + helm.UnmarshalK8SYaml(subT, output, &hpa) + + testCase.expected(hpa.Spec.Metrics) + } + }) + } +} diff --git a/tests/unit/helm/teams-app-service_test.go b/tests/unit/helm/teams-app-service_test.go new file mode 100644 index 00000000..e4c7c764 --- /dev/null +++ b/tests/unit/helm/teams-app-service_test.go @@ -0,0 +1,426 @@ +//go:build kubeall || helm || unit || unitTeamsAppService +// +build kubeall helm unit unitTeamsAppService + +package unit + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + corev1 "k8s.io/api/core/v1" +) + +type serviceTeamsAppTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestServiceTeamsAppTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &serviceTeamsAppTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/teams-app-service.yaml"}, + }) +} + +func (s *serviceTeamsAppTemplateTest) TestMetadataAnnotations() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + nil, + }, + { + "overrideAnnotations", + map[string]string{ + "teamsAppSettings.service.annotations.annotation-1": "annotation-1-value", + }, + map[string]string{ + "annotation-1": "annotation-1-value", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + if testCase.expected == nil { + s.Nil(service.ObjectMeta.Annotations, "Annotations should be nil") + } else { + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Annotations[key] + s.Equal(value, foundValue, "Annotations should contain all set annotations.") + } + } + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestMetadataLabels() { + // Get chart info (to later obtain the chart's appVersion) + cInfo, err := chartInfo(s.T(), s.chartPath) + s.NoError(err) + + // Get appVersion from chart info + chartAppVersion, exists := cInfo["appVersion"] + s.True(exists, "failed to get app version from chart info") + + // Get version from chart info + chartVersion, exists := cInfo["version"] + s.True(exists, "failed to get version from chart info") + + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideMetadataLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for teams-app. + "teamsAppSettings.service.name": "test-service-name", + }, + map[string]string{ + "helm.sh/chart": fmt.Sprintf("fiftyone-teams-app-%s", chartVersion), + "app.kubernetes.io/version": fmt.Sprintf("%s", chartAppVersion), + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.ObjectMeta.Labels[key] + s.Equal(value, foundValue, "Labels should contain all set labels.") + } + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestMetadataName() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "teams-app", + }, + { + "overrideMetadataName", + map[string]string{ + "teamsAppSettings.service.name": "test-service-name", + }, + "test-service-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Name, "Name should be equal.") + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestMetadataNamespace() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "fiftyone-teams", + }, + { + "overrideNamespaceName", + map[string]string{ + "namespace.name": "test-namespace-name", + }, + "test-namespace-name", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, service.ObjectMeta.Namespace, "Namespace name should be equal.") + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestPorts() { + testCases := []struct { + name string + values map[string]string + expected func(port []corev1.ServicePort) + }{ + { + "defaultValues", + nil, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithoutPortNumber", + map[string]string{ + "teamsAppSettings.service.type": "NodePort", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideNodePortWithPortNumber", + map[string]string{ + "teamsAppSettings.service.type": "NodePort", + "teamsAppSettings.service.nodePort": "9999", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 80, + "targetPort": 3000, + "protocol": "TCP", + "name": "http", + "nodePort": 9999 + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + { + "overrideServiceServicePortValues", + map[string]string{ + "teamsAppSettings.service.containerPort": "3001", + "teamsAppSettings.service.port": "88", + }, + func(ports []corev1.ServicePort) { + expectedPortsJSON := `[ + { + "port": 88, + "targetPort": 3001, + "protocol": "TCP", + "name": "http" + } + ]` + var expectedPorts []corev1.ServicePort + err := json.Unmarshal([]byte(expectedPortsJSON), &expectedPorts) + s.NoError(err) + s.Equal(expectedPorts, ports, "Ports should be equal") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + testCase.expected(service.Spec.Ports) + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestType() { + testCases := []struct { + name string + values map[string]string + expected string + }{ + { + "defaultValues", + nil, + "ClusterIP", + }, + { + "overrideSelectorLabels", + map[string]string{ + "teamsAppSettings.service.type": "NodePort", + }, + "NodePort", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + s.Equal(testCase.expected, string(service.Spec.Type), "Type should be equal.") + }) + } +} + +func (s *serviceTeamsAppTemplateTest) TestSelectorLabels() { + testCases := []struct { + name string + values map[string]string + expected map[string]string + }{ + { + "defaultValues", + nil, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + { + "overrideSelectorLabels", + map[string]string{ + // Unlike teams-api, fiftyone-app, and teams-plugins, setting `teamsAppSettings.service.name` + // does not affect the label `app.kubernetes.io/name` for teams-app. + "teamsAppSettings.service.name": "test-service-name", + }, + map[string]string{ + "app.kubernetes.io/name": "fiftyone-teams-app", + "app.kubernetes.io/instance": "fiftyone-test", + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var service corev1.Service + helm.UnmarshalK8SYaml(subT, output, &service) + + for key, value := range testCase.expected { + foundValue := service.Spec.Selector[key] + s.Equal(value, foundValue, "Selector labels should contain all set labels.") + } + }) + } +} From 1bff95ec6a17daf4549592b4a354a89b7987ca83 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Mon, 25 Mar 2024 09:00:53 -0600 Subject: [PATCH 11/42] adding fixture .env for unit tests (that was previously not added due to match in `gitignore`) --- tests/fixtures/docker/.env | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/fixtures/docker/.env diff --git a/tests/fixtures/docker/.env b/tests/fixtures/docker/.env new file mode 100644 index 00000000..c7f5185c --- /dev/null +++ b/tests/fixtures/docker/.env @@ -0,0 +1,18 @@ +# Data for Unit Tests +# Auth0 +AUTH0_API_CLIENT_ID="test-auth0-api-client-id" +AUTH0_API_CLIENT_SECRET="test-auth0-api-client-secret" +AUTH0_AUDIENCE="test-auth0-audience" +AUTH0_CLIENT_ID="test-auth0-client-id" +AUTH0_CLIENT_SECRET="test-auth0-client-secret" +AUTH0_DOMAIN="test-auth0-domain" +AUTH0_ISSUER_BASE_URL="test-auth0-issuer-base-url" +AUTH0_ORGANIZATION="test-auth0-organization" +AUTH0_SECRET="test-auth0-secret" + +# FiftyOne +FIFTYONE_DATABASE_URI="mongodb://root:test-secret@mongodb.local/?authSource=admin" +FIFTYONE_ENCRYPTION_KEY="test-fiftyone-encryption-key" + +# Internal Auth +FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" From f5838c0d703813fd35e5294266b3a36f0828c0bb Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Tue, 26 Mar 2024 10:30:38 -0600 Subject: [PATCH 12/42] docs: docker copmose - update install instructions to reduce confusion about when to run the database migration. --- docker/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docker/README.md b/docker/README.md index 4f44ae86..2c9ad62f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -409,7 +409,9 @@ upgrading from FiftyOne Teams version 1.1.0 or later: files (included in this repository), 1. Rename the `env.template` file to `.env` 1. Edit the `.env` file, setting the parameters required for this deployment. - [See table below](#fiftyone-teams-environment-variables). + See the + [FiftyOne Teams Environment Variables](#fiftyone-teams-environment-variables) + table. 1. Create a `compose.override.yaml` with any configuration overrides for this deployment. 1. In the same directory, run @@ -424,19 +426,24 @@ upgrading from FiftyOne Teams version 1.1.0 or later: FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` + > **NOTE**: Skip this step when performing an initial installation with + > `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: true`. + > For more information, see + > [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) + 1. To ensure that all datasets are now at version 0.23.5, run ```shell fiftyone migrate --info ``` -The FiftyOne Teams App is now exposed on port 3000. +The FiftyOne Teams App is now exposed on port `3000`. An SSL endpoint (Load Balancer or Nginx Proxy or something similar) will need to be configured to route traffic from the SSL endpoint -to port 3000 on the host running the FiftyOne Teams App. +to port `3000` on the host running the FiftyOne Teams App. An example nginx site configuration that forwards http traffic to -https, and https traffic for `your.server.name` to port 3000. +https, and https traffic for `your.server.name` to port `3000`. See [./example-nginx-site.conf](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/example-nginx-site.conf). From 7c7cec8c3daf71760132a0607f02b93dc427130e Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Tue, 26 Mar 2024 18:27:31 -0600 Subject: [PATCH 13/42] docs: fix the directions around setting FIFTYONE_DATABASE_ADMIN to true for the initial installation and false after a successful installation. --- docker/README.md | 50 ++++++++++++++++--------- helm/fiftyone-teams-app/README.md | 62 ++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/docker/README.md b/docker/README.md index 2c9ad62f..5acbc5d7 100644 --- a/docker/README.md +++ b/docker/README.md @@ -404,7 +404,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: ## Deploying FiftyOne Teams -1. Install docker-compose +1. Install + [Docker Compose](https://docs.docker.com/compose/install/) 1. From a directory containing the files `compose.yaml` and `env.template` files (included in this repository), 1. Rename the `env.template` file to `.env` @@ -414,28 +415,43 @@ upgrading from FiftyOne Teams version 1.1.0 or later: table. 1. Create a `compose.override.yaml` with any configuration overrides for this deployment. -1. In the same directory, run + 1. For the first installation, set - ```shell - docker-compose up -d - ``` + ```yaml + services: + fiftyone-app-common: + environment: + FIFTYONE_DATABASE_ADMIN: true + ``` -1. Have the admin run to upgrade all datasets +1. Deploy FiftyOne Teams + 1. In the same directory, run - ```shell - FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all - ``` + ```shell + docker-compose up -d + ``` - > **NOTE**: Skip this step when performing an initial installation with - > `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: true`. - > For more information, see - > [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) +1. After the successful installation, and logging into Fiftyone Teams + 1. In `compose.override.yaml` remove the `FIFTYONE_DATABASE_ADMIN` override -1. To ensure that all datasets are now at version 0.23.5, run + ```yaml + services: + fiftyone-app-common: + environment: + # FIFTYONE_DATABASE_ADMIN: true + ``` - ```shell - fiftyone migrate --info - ``` + > **Note**: This example shows commenting this line, + > however you may remove the line. + + or set it to `false` like in + + ```yaml + services: + fiftyone-app-common: + environment: + # FIFTYONE_DATABASE_ADMIN: false + ``` The FiftyOne Teams App is now exposed on port `3000`. An SSL endpoint (Load Balancer or Nginx Proxy or something similar) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index dd775b48..4c07413f 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -37,7 +37,7 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. - [From Early Adopter Versions (Versions less than 1.0)](#from-early-adopter-versions-versions-less-than-10) - [From Before FiftyOne Teams Version 1.1.0](#from-before-fiftyone-teams-version-110) - [From FiftyOne Teams Version 1.1.0 and later](#from-fiftyone-teams-version-110-and-later) -- [Launch FiftyOne Teams](#launch-fiftyone-teams) +- [Deploying FiftyOne Teams](#deploying-fiftyone-teams) @@ -508,7 +508,7 @@ versions prior to FiftyOne Teams version 1.1.0: 1. In your `values.yaml`, set the required [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) environment variable -1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the @@ -546,7 +546,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with @@ -566,13 +566,24 @@ upgrading from FiftyOne Teams version 1.1.0 or later: fiftyone migrate --info ``` -## Launch FiftyOne Teams +## Deploying FiftyOne Teams A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). -1. Edit the `values.yaml` file -1. Deploy FiftyOne Teams with `helm install` +1. Install + [Helm](https://helm.sh/docs/intro/install/) +1. In the values file (typically `values.yaml`) provided by Voxel51 + set the deployment specific configurations + 1. For the first installation, set + + ```yaml + appSettings: + env: + FIFTYONE_DATABASE_ADMIN: true + ``` + +1. Deploy FiftyOne Teams 1. For a new installation, run ```shell @@ -581,6 +592,27 @@ A minimal example `values.yaml` may be found helm install fiftyone-teams-app voxel51/fiftyone-teams-app -f ./values.yaml ``` + 1. After the successful installation, and logging into Fiftyone Teams + 1. In your values file, remove the + `appSettings.env.FIFTYONE_DATABASE_ADMIN` override + + ```yaml + appSettings: + env: + # FIFTYONE_DATABASE_ADMIN: true + ``` + + > **Note**: This example shows commenting this line, + > however you may remove the line. + + or set it to `false` like in + + ```yaml + appSettings: + env: + FIFTYONE_DATABASE_ADMIN: false + ``` + 1. To upgrade an existing helm installation, run ```shell @@ -599,6 +631,24 @@ A minimal example `values.yaml` may be found > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml > ``` +1. When `appSettings.env.FIFTYONE_DATABASE_ADMIN` is not `true`, + have the admin run to upgrade all datasets + + ```shell + FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all + ``` + + > **NOTE**: Skip this step when performing an initial installation with + > `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: true`. + > For more information, see + > [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) + +1. To ensure that all datasets are now at version 0.23.5, run + + ```shell + fiftyone migrate --info + ``` + [affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ [annotations]: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ From d1621358746e16673af0ae2b601a667815ac357d Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Tue, 26 Mar 2024 18:30:51 -0600 Subject: [PATCH 14/42] docs: fix indentation --- docker/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/README.md b/docker/README.md index 5acbc5d7..d84106d5 100644 --- a/docker/README.md +++ b/docker/README.md @@ -425,11 +425,11 @@ upgrading from FiftyOne Teams version 1.1.0 or later: ``` 1. Deploy FiftyOne Teams - 1. In the same directory, run + 1. In the same directory, run - ```shell - docker-compose up -d - ``` + ```shell + docker-compose up -d + ``` 1. After the successful installation, and logging into Fiftyone Teams 1. In `compose.override.yaml` remove the `FIFTYONE_DATABASE_ADMIN` override From 91ac5280ca270db181e2a68a50b9f7de2e7b872a Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Tue, 26 Mar 2024 18:32:18 -0600 Subject: [PATCH 15/42] docs: fix indentation --- docker/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docker/README.md b/docker/README.md index d84106d5..c501a3d0 100644 --- a/docker/README.md +++ b/docker/README.md @@ -415,14 +415,14 @@ upgrading from FiftyOne Teams version 1.1.0 or later: table. 1. Create a `compose.override.yaml` with any configuration overrides for this deployment. - 1. For the first installation, set - - ```yaml - services: - fiftyone-app-common: - environment: - FIFTYONE_DATABASE_ADMIN: true - ``` + 1. For the first installation, set + + ```yaml + services: + fiftyone-app-common: + environment: + FIFTYONE_DATABASE_ADMIN: true + ``` 1. Deploy FiftyOne Teams 1. In the same directory, run From b58ccc861cce975c40307ab1099041173042146f Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Wed, 27 Mar 2024 09:35:28 -0600 Subject: [PATCH 16/42] docs: fix broken links. --- CONTRIBUTING.md | 2 +- helm/README.md | 2 +- helm/fiftyone-teams-app/README.md | 4 ++-- helm/fiftyone-teams-app/README.md.gotmpl | 2 +- helm/values.yaml | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cf37bfb..70e27c01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -266,7 +266,7 @@ skaffold dev --profile only-fiftyone Our FiftyOne Teams container images are stored in the private repositories - [Google Artifact Repository (Docker)](#google-artifact-repository) - - Contains private development images created by our + - Contains private development images created by our private repository [Google Cloud Build](https://github.com/voxel51/cloud-build-and-deploy/) CI/CD runs - Development diff --git a/helm/README.md b/helm/README.md index b2859515..c5d16d1c 100644 --- a/helm/README.md +++ b/helm/README.md @@ -114,7 +114,7 @@ These instructions assume you have #### Download the Example Configuration Files Download the example configuration files from the -[voxel51/fiftyone-teams-app-deploy](https://github.com/voxel51/fiftyone-teams-app-deploy/helm/gke-examples) +[voxel51/fiftyone-teams-app-deploy](https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/gke-example) GitHub repository. One way to do this might be: diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index dd775b48..6688b9d0 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -362,7 +362,7 @@ appSettings: | casSettings.env.DEBUG | string | `"cas:*,-cas:*:debug"` | Set the log level for CAS examples: `DEBUG: cas:*` - shows all CAS logs `DEBUG: cas:*:info` - shows all CAS INFO logs `DEBUG: cas:*,-cas:*:debug` - shows all CAS logs except DEBUG logs | | casSettings.env.FIFTYONE_AUTH_MODE | string | `"legacy"` | Configure Authentication Mode. One of `legacy` or `internal` | | casSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | -| casSettings.image.repository | string | `"voxel51/teams-cas"` | Container image for teams-cas. | +| casSettings.image.repository | string | `"voxel51/fiftyone-teams-cas"` | Container image for teams-cas. | | casSettings.image.tag | string | `""` | Image tag for teams-cas. Defaults to the chart version. | | casSettings.nodeSelector | object | `{}` | nodeSelector for teams-cas. [Reference][node-selector]. | | casSettings.podAnnotations | object | `{}` | Annotations for pods for teams-cas. [Reference][annotations]. | @@ -625,5 +625,5 @@ A minimal example `values.yaml` may be found [recoil-env]: https://recoiljs.org/docs/api-reference/core/RecoilEnv/ -[fiftyone-encryption-key]: https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/README.md#storage-credentials-and-fiftyone_encryption_key +[fiftyone-encryption-key]: https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#storage-credentials-and-fiftyone_encryption_key [fiftyone-config]: https://docs.voxel51.com/user_guide/config.html diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 9b6af2b0..bf267ca5 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -450,5 +450,5 @@ A minimal example `values.yaml` may be found [recoil-env]: https://recoiljs.org/docs/api-reference/core/RecoilEnv/ -[fiftyone-encryption-key]: https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/README.md#storage-credentials-and-fiftyone_encryption_key +[fiftyone-encryption-key]: https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#storage-credentials-and-fiftyone_encryption_key [fiftyone-config]: https://docs.voxel51.com/user_guide/config.html diff --git a/helm/values.yaml b/helm/values.yaml index 3c6964bd..7ef1b7f7 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -43,7 +43,7 @@ secret: # env: # Set FIFTYONE_PLUGINS_DIR if you are enabling plugins in a dedicated # `teams-plugins` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm#enabling-fiftyone-teams-plugins +# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins # Set FIFTYONE_TEAMS_VERSION_OVERRIDE to override the `Install FiftyOne` # bash command in the `Settings > Install FiftyOne` modal @@ -59,7 +59,7 @@ appSettings: FIFTYONE_DATABASE_ADMIN: true # Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are # enabling plugins in the `fiftyone-app` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm#enabling-fiftyone-teams-plugins +# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins # FIFTYONE_PLUGINS_CACHE_ENABLED: true @@ -72,7 +72,7 @@ casSettings: # env: # Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are # enabling plugins in a dedicated `teams-plugins` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm#enabling-fiftyone-teams-plugins +# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins # FIFTYONE_PLUGINS_CACHE_ENABLED: true From e794b21958f9f5113603e2d6eb03d38a95d981e4 Mon Sep 17 00:00:00 2001 From: topher Date: Fri, 29 Mar 2024 09:03:00 -0400 Subject: [PATCH 17/42] deploy updates for v1.6.0 (`legacy` and `internal` auth modes) (#104) * refactors compose files for cas in legacy-auth mode * first bug caught by the tests * adds `teams-cas` to NO_PROXY_LIST examples * uses default FIFTYONE_AUTH_MODE settings * fix _some_ of the tests... * fix ther rest of the docker compose legacy auth unit tests. * docs: fix spacing * chore: bump protobuf version to latest to resolve vuln * refactors BASE_URL and AUTH0_BASE_URL to auth-specific compose files * ignores other overrides files * make `values.yaml` reflect defaults for `legacy` mode * docs: change heading in helm readme to match our compose docs. * example for generating random strings * Update docker/internal-auth/env.template Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: topher * works on linux too --------- Signed-off-by: topher Co-authored-by: Kevin DiMichel Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> --- .gitignore | 12 +- .../{internal-auth => }/common-services.yaml | 3 - .../compose.dedicated-plugins.yaml | 27 ++-- docker/internal-auth/compose.plugins.yaml | 24 ++- docker/internal-auth/compose.yaml | 15 +- docker/internal-auth/env.template | 7 +- docker/legacy-auth/common-services.yaml | 130 --------------- .../compose.dedicated-plugins.yaml | 35 +++- docker/legacy-auth/compose.plugins.yaml | 34 +++- docker/legacy-auth/compose.yaml | 25 ++- docker/legacy-auth/env.template | 33 +++- helm/fiftyone-teams-app/README.md | 54 +------ helm/fiftyone-teams-app/README.md.gotmpl | 8 +- helm/values.yaml | 21 +-- tests/go.mod | 2 +- tests/go.sum | 4 +- tests/testing.md | 3 +- .../docker-compose-legacy-auth_test.go | 152 +++++++++++------- 18 files changed, 276 insertions(+), 313 deletions(-) rename docker/{internal-auth => }/common-services.yaml (98%) delete mode 100644 docker/legacy-auth/common-services.yaml diff --git a/.gitignore b/.gitignore index c6afdbed..22161100 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ *.tgz -compose.override.yaml +*~ +.DS_Store .env -helm/gke-example/voxel51-docker.json +compose.override*yaml helm/gke-example/values-*.yaml -tests/unit/*/test_output.log -tests/unit/*/test_output/* -tests/unit/*/test_reports/* +helm/gke-example/voxel51-docker.json tests/integration/*/test_output.log tests/integration/*/test_output/* tests/integration/*/test_reports/* +tests/unit/*/test_output.log +tests/unit/*/test_output/* +tests/unit/*/test_reports/* diff --git a/docker/internal-auth/common-services.yaml b/docker/common-services.yaml similarity index 98% rename from docker/internal-auth/common-services.yaml rename to docker/common-services.yaml index 99aca5dc..cad8fb9f 100644 --- a/docker/internal-auth/common-services.yaml +++ b/docker/common-services.yaml @@ -95,11 +95,8 @@ services: CAS_DATABASE_NAME: ${CAS_DATABASE_NAME} CAS_DEFAULT_USER_ROLE: ${CAS_DEFAULT_USER_ROLE} CAS_MONGODB_URI: ${CAS_MONGO_DB_URI:-$FIFTYONE_DATABASE_URI} - CAS_URL: ${BASE_URL} DEBUG: ${CAS_DEBUG} - FIFTYONE_AUTH_MODE: internal FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} - NEXTAUTH_URL: ${BASE_URL}/cas/api/auth # If you are routing through a proxy server you will want to set # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env # then add the following environment variables to your diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index 37e4c9f6..568aaccf 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -4,32 +4,41 @@ services: fiftyone-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: fiftyone-app-common + teams-api: - extends: - file: common-services.yaml - service: teams-api-common environment: FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: teams-api-common volumes: - plugins-vol:/opt/plugins + teams-app: - extends: - file: common-services.yaml - service: teams-app-common environment: FIFTYONE_TEAMS_PLUGIN_URL: ${FIFTYONE_TEAMS_PLUGIN_URL} + extends: + file: ../common-services.yaml + service: teams-app-common + teams-cas: + environment: + FIFTYONE_AUTH_MODE: internal + NEXTAUTH_URL: ${BASE_URL}/cas/api/auth + CAS_URL: ${BASE_URL} extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-cas-common + teams-plugins: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-plugins-common volumes: - plugins-vol:/opt/plugins:ro + volumes: plugins-vol: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 92ea8bfe..ab116a4c 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -3,30 +3,38 @@ # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: fiftyone-app: - extends: - file: common-services.yaml - service: fiftyone-app-common environment: FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: fiftyone-app-common volumes: - plugins-vol:/opt/plugins:ro + teams-api: - extends: - file: common-services.yaml - service: teams-api-common environment: FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: teams-api-common volumes: - plugins-vol:/opt/plugins + teams-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-app-common + teams-cas: + environment: + CAS_URL: ${BASE_URL} + NEXTAUTH_URL: ${BASE_URL}/cas/api/auth + FIFTYONE_AUTH_MODE: internal extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-cas-common + volumes: plugins-vol: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index 10a516c9..89658501 100644 --- a/docker/internal-auth/compose.yaml +++ b/docker/internal-auth/compose.yaml @@ -4,17 +4,24 @@ services: fiftyone-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: fiftyone-app-common + teams-api: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-api-common + teams-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-app-common + teams-cas: + environment: + CAS_URL: ${BASE_URL} + FIFTYONE_AUTH_MODE: internal + NEXTAUTH_URL: ${BASE_URL}/cas/api/auth extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-cas-common diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 884749d7..6166410a 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -9,10 +9,13 @@ FIFTYONE_API_URI=https://example-api.fiftyone.ai FIFTYONE_DATABASE_URI="mongodb://username:password@mongodb-example.fiftyone.ai:27017/?authSource=admin" # If you are using a different MongoDB Connection String for your CAS database, # set it here -# CAS_DATABASE_URI="mongodb://username:password@mongodb-cas-example.fiftyone.ai:27017/?authSource=admin" +# CAS_MONGODB_URI="mongodb://username:password@mongodb-cas-example.fiftyone.ai:27017/?authSource=admin" # FIFTYONE_AUTH_SECRET is a random string used to authenticate to the CAS service # This can be any string you care to use generated by any mechanism you prefer. +# You could use something like: +# `cat /dev/urandom | LC_CTYPE=C tr -cd '[:graph:]' | head -c 32` +# to generate this string. # This is used for inter-service authentication and for the SuperUser to # authenticate at the CAS UI to configure the Central Authentication Service. FIFTYONE_AUTH_SECRET= @@ -69,4 +72,4 @@ FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151 # HTTPS_PROXY_URL=https://proxy.yourcompany.tld:3128 # # You must include the container service names in your NO_PROXY_LIST -# NO_PROXY_LIST: teams-api, teams-app, fiftyone-app, teams-plugins, otherservers.yourcompany.tld +# NO_PROXY_LIST: fiftyone-app, teams-api, teams-app, teams-cas, teams-plugins, otherservers.yourcompany.tld diff --git a/docker/legacy-auth/common-services.yaml b/docker/legacy-auth/common-services.yaml deleted file mode 100644 index a8ef38bd..00000000 --- a/docker/legacy-auth/common-services.yaml +++ /dev/null @@ -1,130 +0,0 @@ ---- -services: - fiftyone-app-common: - image: voxel51/fiftyone-app:v1.6.0-beta.2 - environment: - API_URL: ${API_URL} - FIFTYONE_DATABASE_ADMIN: false - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 - FIFTYONE_DEFAULT_APP_PORT: 5151 - FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} - FIFTYONE_INTERNAL_SERVICE: true - FIFTYONE_MEDIA_CACHE_APP_IMAGES: false - FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 - FIFTYONE_TEAMS_AUDIENCE: ${AUTH0_AUDIENCE} - FIFTYONE_TEAMS_CLIENT_ID: ${AUTH0_CLIENT_ID} - FIFTYONE_TEAMS_DOMAIN: ${AUTH0_DOMAIN} - FIFTYONE_TEAMS_ORGANIZATION: ${AUTH0_ORGANIZATION} - # If you are routing through a proxy server you will want to set - # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env - # then add the following environment variables to your - # `compose.override.yaml` - # HTTPS_PROXY: ${HTTPS_PROXY_URL} - # HTTP_PROXY: ${HTTP_PROXY_URL} - # NO_PROXY: ${NO_PROXY_LIST} - # http_proxy: ${HTTP_PROXY_URL} - # https_proxy: ${HTTPS_PROXY_URL} - # no_proxy: ${NO_PROXY_LIST} - ports: - - ${FIFTYONE_DEFAULT_APP_ADDRESS}:${FIFTYONE_DEFAULT_APP_PORT}:5151 - restart: always - - teams-api-common: - image: voxel51/fiftyone-teams-api:v1.6.0-beta.2 - environment: - AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID} - AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} - AUTH0_DOMAIN: ${AUTH0_DOMAIN} - AUTH0_API_CLIENT_ID: ${AUTH0_API_CLIENT_ID} - AUTH0_API_CLIENT_SECRET: ${AUTH0_API_CLIENT_SECRET} - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} - FIFTYONE_ENV: ${FIFTYONE_ENV} - FIFTYONE_INTERNAL_SERVICE: true - GRAPHQL_DEFAULT_LIMIT: ${GRAPHQL_DEFAULT_LIMIT} - LOGGING_LEVEL: ${API_LOGGING_LEVEL:-INFO} - MONGO_DEFAULT_DB: ${FIFTYONE_DATABASE_NAME} - # If you are routing through a proxy server you will want to set - # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env - # then add the following environment variables to your - # `compose.override.yaml` - # HTTPS_PROXY: ${HTTPS_PROXY_URL} - # HTTP_PROXY: ${HTTP_PROXY_URL} - # NO_PROXY: ${NO_PROXY_LIST} - # http_proxy: ${HTTP_PROXY_URL} - # https_proxy: ${HTTPS_PROXY_URL} - # no_proxy: ${NO_PROXY_LIST} - ports: - - ${API_BIND_ADDRESS}:${API_BIND_PORT}:8000 - restart: always - - teams-app-common: - image: voxel51/fiftyone-teams-app:v1.6.0-beta.2 - environment: - API_URL: ${API_URL} - AUTH0_AUDIENCE: ${AUTH0_AUDIENCE} - AUTH0_BASE_URL: ${AUTH0_BASE_URL} - AUTH0_CLIENT_ID: ${AUTH0_CLIENT_ID} - AUTH0_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET} - AUTH0_ISSUER_BASE_URL: ${AUTH0_ISSUER_BASE_URL} - AUTH0_ORGANIZATION: ${AUTH0_ORGANIZATION} - AUTH0_SECRET: ${AUTH0_SECRET} - APP_USE_HTTPS: ${APP_USE_HTTPS:-true} - FIFTYONE_API_URI: ${FIFTYONE_API_URI:-"Please contact your Admin for an API URI"} - FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION: 0.16.0b2 - FIFTYONE_SERVER_ADDRESS: "" - FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams - FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} - NODE_ENV: production - RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED: false - # If you are routing through a proxy server you will want to set - # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env - # then add the following environment variables to your - # `compose.override.yaml` - # GLOBAL_AGENT_HTTPS_PROXY: ${HTTPS_PROXY_URL} - # GLOBAL_AGENT_HTTP_PROXY: ${HTTP_PROXY_URL} - # GLOBAL_AGENT_NO_PROXY: ${NO_PROXY_LIST} - # HTTPS_PROXY: ${HTTPS_PROXY_URL} - # HTTP_PROXY: ${HTTP_PROXY_URL} - # NO_PROXY: ${NO_PROXY_LIST} - # ROARR_LOG: false - # http_proxy: ${HTTP_PROXY_URL} - # https_proxy: ${HTTPS_PROXY_URL} - # no_proxy: ${NO_PROXY_LIST} - ports: - - ${APP_BIND_ADDRESS}:${APP_BIND_PORT}:3000 - restart: always - - teams-plugins-common: - image: voxel51/fiftyone-app:v1.6.0-beta.2 - environment: - API_URL: ${API_URL} - FIFTYONE_DATABASE_ADMIN: false - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DEFAULT_APP_ADDRESS: 0.0.0.0 - FIFTYONE_DEFAULT_APP_PORT: 5151 - FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} - FIFTYONE_INTERNAL_SERVICE: true - FIFTYONE_MEDIA_CACHE_APP_IMAGES: false - FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 - FIFTYONE_TEAMS_AUDIENCE: ${AUTH0_AUDIENCE} - FIFTYONE_TEAMS_CLIENT_ID: ${AUTH0_CLIENT_ID} - FIFTYONE_TEAMS_DOMAIN: ${AUTH0_DOMAIN} - FIFTYONE_TEAMS_ORGANIZATION: ${AUTH0_ORGANIZATION} - FIFTYONE_PLUGINS_CACHE_ENABLED: true - FIFTYONE_PLUGINS_DIR: /opt/plugins - # If you are routing through a proxy server you will want to set - # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env - # then add the following environment variables to your - # `compose.override.yaml` - # HTTPS_PROXY: ${HTTPS_PROXY_URL} - # HTTP_PROXY: ${HTTP_PROXY_URL} - # NO_PROXY: ${NO_PROXY_LIST} - # http_proxy: ${HTTP_PROXY_URL} - # https_proxy: ${HTTPS_PROXY_URL} - # no_proxy: ${NO_PROXY_LIST} - restart: always diff --git a/docker/legacy-auth/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml index a9436a62..1c5d26b5 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -4,27 +4,48 @@ services: fiftyone-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: fiftyone-app-common + teams-api: - extends: - file: common-services.yaml - service: teams-api-common environment: FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: teams-api-common volumes: - plugins-vol:/opt/plugins + teams-app: + environment: + FIFTYONE_TEAMS_PLUGIN_URL: ${FIFTYONE_TEAMS_PLUGIN_URL} extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-app-common + + teams-cas: environment: - FIFTYONE_TEAMS_PLUGIN_URL: ${FIFTYONE_TEAMS_PLUGIN_URL} + AUTH0_AUTH_CLIENT_ID: ${AUTH0_CLIENT_ID} + AUTH0_AUTH_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET} + AUTH0_DOMAIN: ${AUTH0_DOMAIN} + AUTH0_ISSUER_BASE_URL: ${AUTH0_ISSUER_BASE_URL} + AUTH0_MGMT_CLIENT_ID: ${AUTH0_API_CLIENT_ID} + AUTH0_MGMT_CLIENT_SECRET: ${AUTH0_API_CLIENT_SECRET} + AUTH0_ORGANIZATION: ${AUTH0_ORGANIZATION} + CAS_URL: ${AUTH0_BASE_URL} + NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth + TEAMS_API_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + TEAMS_API_MONGODB_URI: ${FIFTYONE_DATABASE_URI} + extends: + file: ../common-services.yaml + service: teams-cas-common + teams-plugins: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-plugins-common volumes: - plugins-vol:/opt/plugins:ro + volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index a3a31aec..e02e0136 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -3,25 +3,45 @@ # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: fiftyone-app: - extends: - file: common-services.yaml - service: fiftyone-app-common environment: FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: fiftyone-app-common volumes: - plugins-vol:/opt/plugins:ro + teams-api: - extends: - file: common-services.yaml - service: teams-api-common environment: FIFTYONE_PLUGINS_DIR: /opt/plugins + extends: + file: ../common-services.yaml + service: teams-api-common volumes: - plugins-vol:/opt/plugins + teams-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-app-common + + teams-cas: + extends: + file: ../common-services.yaml + service: teams-cas-common + environment: + AUTH0_AUTH_CLIENT_ID: ${AUTH0_CLIENT_ID} + AUTH0_AUTH_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET} + AUTH0_DOMAIN: ${AUTH0_DOMAIN} + AUTH0_ISSUER_BASE_URL: ${AUTH0_ISSUER_BASE_URL} + AUTH0_MGMT_CLIENT_ID: ${AUTH0_API_CLIENT_ID} + AUTH0_MGMT_CLIENT_SECRET: ${AUTH0_API_CLIENT_SECRET} + AUTH0_ORGANIZATION: ${AUTH0_ORGANIZATION} + CAS_URL: ${AUTH0_BASE_URL} + NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth + TEAMS_API_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + TEAMS_API_MONGODB_URI: ${FIFTYONE_DATABASE_URI} + volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index 808bb6ef..e3bf4a13 100644 --- a/docker/legacy-auth/compose.yaml +++ b/docker/legacy-auth/compose.yaml @@ -4,13 +4,32 @@ services: fiftyone-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: fiftyone-app-common + teams-api: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-api-common + teams-app: extends: - file: common-services.yaml + file: ../common-services.yaml service: teams-app-common + + teams-cas: + environment: + AUTH0_AUTH_CLIENT_ID: ${AUTH0_CLIENT_ID} + AUTH0_AUTH_CLIENT_SECRET: ${AUTH0_CLIENT_SECRET} + AUTH0_DOMAIN: ${AUTH0_DOMAIN} + AUTH0_ISSUER_BASE_URL: ${AUTH0_ISSUER_BASE_URL} + AUTH0_MGMT_CLIENT_ID: ${AUTH0_API_CLIENT_ID} + AUTH0_MGMT_CLIENT_SECRET: ${AUTH0_API_CLIENT_SECRET} + AUTH0_ORGANIZATION: ${AUTH0_ORGANIZATION} + CAS_URL: ${AUTH0_BASE_URL} + NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth + TEAMS_API_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + TEAMS_API_MONGODB_URI: ${FIFTYONE_DATABASE_URI} + extends: + file: ../common-services.yaml + service: teams-cas-common diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index c4c8f716..ab59f8e4 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -15,11 +15,23 @@ AUTH0_SECRET= AUTH0_BASE_URL=https://example.fiftyone.ai # This should be set to the URI your end-users will use to connect to the API -# This could be the same as AUTH0_BASE_URL +# This could be the same as AUTH0_BASE_URL if you are using path-based routing FIFTYONE_API_URI=https://example-api.fiftyone.ai # This should be a MongoDB Connection String for your database FIFTYONE_DATABASE_URI="mongodb://username:password@mongodb-example.fiftyone.ai:27017/?authSource=admin" +# If you are using a different MongoDB Connection String for your CAS database, +# set it here +# CAS_MONGODB_URI="mongodb://username:password@mongodb-cas-example.fiftyone.ai:27017/?authSource=admin" + +# FIFTYONE_AUTH_SECRET is a random string used to authenticate to the CAS service +# This can be any string you care to use generated by any mechanism you prefer. +# You could use something like: +# `cat /dev/urandom | LC_CTYPE=C tr -cd '[:graph:]' | head -c 32` +# to generate this string. +# This is used for inter-service authentication and for the SuperUser to +# authenticate at the CAS UI to configure the Central Authentication Service. +FIFTYONE_AUTH_SECRET= # This key is required and is used to encrypt storage credentials in the MongoDB # do NOT lose this key! @@ -30,7 +42,7 @@ FIFTYONE_DATABASE_URI="mongodb://username:password@mongodb-example.fiftyone.ai:2 # FIFTYONE_ENCRYPTION_KEY= -# API container configuration +# FiftyOne Teams API container configuration API_BIND_ADDRESS=127.0.0.1 API_BIND_PORT=8000 API_LOGGING_LEVEL=INFO @@ -39,8 +51,7 @@ API_URL=http://teams-api:8000 FIFTYONE_ENV=production GRAPHQL_DEFAULT_LIMIT=10 -# Fiftyone App Configuration -FIFTYONE_BASE_DIR=/teams/some-data-directory/ +# FiftyOne App Configuration FIFTYONE_DEFAULT_APP_ADDRESS=127.0.0.1 FIFTYONE_DEFAULT_APP_PORT=5151 FIFTYONE_DATABASE_NAME=fiftyone @@ -51,6 +62,18 @@ APP_USE_HTTPS=true APP_BIND_ADDRESS=127.0.0.1 APP_BIND_PORT=3000 +# FiftyOne Teams CAS Configuration +CAS_BASE_URL=http://teams-cas:3000/cas/api +CAS_BIND_ADDRESS=127.0.0.1 +CAS_BIND_PORT=3030 +CAS_DATABASE_NAME=fiftyone-cas +# CAS_DEBUG defines what CAS logs to display +# e.g. `cas:*` - shows all cas logs +# `cas:*:info` - shows only CAS INFO logs +# `cas:*,-cas:*:debug` - shows all cas logs except DEBUG logs +CAS_DEBUG="cas:*,-cas:*:debug" +CAS_DEFAULT_USER_ROLE=GUEST + # The following are docker-compose links and will work in most situations FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151 # This only gets used with a dedicated teams-plugins service @@ -62,4 +85,4 @@ FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151 # HTTPS_PROXY_URL=https://proxy.yourcompany.tld:3128 # # You must include the container service names in your NO_PROXY_LIST -# NO_PROXY_LIST: teams-api, teams-app, fiftyone-app, teams-plugins, otherservers.yourcompany.tld +# NO_PROXY_LIST: fiftyone-app, teams-api, teams-app, teams-cas, teams-plugins, otherservers.yourcompany.tld diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 6c67b4c2..75badf3f 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -571,19 +571,8 @@ upgrading from FiftyOne Teams version 1.1.0 or later: A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). -1. Install - [Helm](https://helm.sh/docs/intro/install/) -1. In the values file (typically `values.yaml`) provided by Voxel51 - set the deployment specific configurations - 1. For the first installation, set - - ```yaml - appSettings: - env: - FIFTYONE_DATABASE_ADMIN: true - ``` - -1. Deploy FiftyOne Teams +1. Edit the `values.yaml` file +1. Deploy FiftyOne Teams with `helm install` 1. For a new installation, run ```shell @@ -592,27 +581,6 @@ A minimal example `values.yaml` may be found helm install fiftyone-teams-app voxel51/fiftyone-teams-app -f ./values.yaml ``` - 1. After the successful installation, and logging into Fiftyone Teams - 1. In your values file, remove the - `appSettings.env.FIFTYONE_DATABASE_ADMIN` override - - ```yaml - appSettings: - env: - # FIFTYONE_DATABASE_ADMIN: true - ``` - - > **Note**: This example shows commenting this line, - > however you may remove the line. - - or set it to `false` like in - - ```yaml - appSettings: - env: - FIFTYONE_DATABASE_ADMIN: false - ``` - 1. To upgrade an existing helm installation, run ```shell @@ -631,24 +599,6 @@ A minimal example `values.yaml` may be found > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml > ``` -1. When `appSettings.env.FIFTYONE_DATABASE_ADMIN` is not `true`, - have the admin run to upgrade all datasets - - ```shell - FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all - ``` - - > **NOTE**: Skip this step when performing an initial installation with - > `services.fiftyone-app.environment.FIFTYONE_DATABASE_ADMIN: true`. - > For more information, see - > [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) - -1. To ensure that all datasets are now at version 0.23.5, run - - ```shell - fiftyone migrate --info - ``` - [affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ [annotations]: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index bf267ca5..4fcff091 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -39,7 +39,7 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. - [From Early Adopter Versions (Versions less than 1.0)](#from-early-adopter-versions-versions-less-than-10) - [From Before FiftyOne Teams Version 1.1.0](#from-before-fiftyone-teams-version-110) - [From FiftyOne Teams Version 1.1.0 and later](#from-fiftyone-teams-version-110-and-later) -- [Launch FiftyOne Teams](#launch-fiftyone-teams) +- [Deploying FiftyOne Teams](#deploying-fiftyone-teams) @@ -333,7 +333,7 @@ versions prior to FiftyOne Teams version 1.1.0: 1. In your `values.yaml`, set the required [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) environment variable -1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the @@ -371,7 +371,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. [Upgrade to FiftyOne Teams version 1.6.0](#launch-fiftyone-teams) +1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with @@ -391,7 +391,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: fiftyone migrate --info ``` -## Launch FiftyOne Teams +## Deploying FiftyOne Teams A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). diff --git a/helm/values.yaml b/helm/values.yaml index 7ef1b7f7..d03dae35 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -8,14 +8,14 @@ secret: fiftyone: - # These secrets come from Voxel51 and are only used when - # `casSettings.env.FIFTYONE_AUTH_MODE` is set to `legacy` - # apiClientId: - # apiClientSecret: - # auth0Domain: - # clientId: - # clientSecret: - # organizationId: + # These secrets come from Voxel51 and should be omitted when + # `casSettings.env.FIFTYONE_AUTH_MODE` is set to `internal` + apiClientId: + apiClientSecret: + auth0Domain: + clientId: + clientSecret: + organizationId: # These secrets come from your MongoDB implementation fiftyoneDatabaseName: fiftyone mongodbConnectionString: mongodb://username:password@somehostname/?authSource=admin @@ -32,6 +32,9 @@ secret: # This secret is a random string used to authenticate to the CAS service. # This can be any string you care to use generated by any mechanism you # prefer. + # You could use something like: + # `cat /dev/urandom | LC_CTYPE=C tr -cd '[:graph:]' | head -c 32` + # to generate this string. # This is used for inter-service authentication and for the SuperUser to # authenticate at the CAS UI to configure the Central Authentication Service. fiftyoneAuthSecret: @@ -65,7 +68,7 @@ appSettings: casSettings: env: - FIFTYONE_AUTH_MODE: internal + FIFTYONE_AUTH_MODE: legacy # pluginsSettings: # enabled: true diff --git a/tests/go.mod b/tests/go.mod index 49640d17..fe7645ff 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -87,7 +87,7 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/client-go v0.28.4 // indirect diff --git a/tests/go.sum b/tests/go.sum index 5de4c593..0dd051f4 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -264,8 +264,8 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/tests/testing.md b/tests/testing.md index 7e7c5dc3..88ad54c7 100644 --- a/tests/testing.md +++ b/tests/testing.md @@ -41,6 +41,7 @@ We implemented various patterns from these tools in our tests. 1. Install go ```shell + # from repo root make asdf ``` @@ -110,7 +111,7 @@ For structures (structs), there are two approaches. Either write * Go code referencing the type for each field -* JSON (easily converted from YAML) and unmarshall it into the struct +* JSON (easily converted from YAML) and unmarshall it into the struct See [Debugging interleaved test output](https://terratest.gruntwork.io/docs/testing-best-practices/debugging-interleaved-test-output/#installing-the-utility-binaries). diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 756b97e3..071958cd 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -68,6 +68,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "fiftyone-app", "teams-api", "teams-app", + "teams-cas", }, }, { @@ -78,6 +79,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "fiftyone-app", "teams-api", "teams-app", + "teams-cas", }, }, { @@ -88,6 +90,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "fiftyone-app", "teams-api", "teams-app", + "teams-cas", "teams-plugins", }, }, @@ -159,7 +162,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { "teams-cas", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "", + "voxel51/fiftyone-teams-cas:v1.6.0-beta.2", }, { "dedicatedPluginsTeamsPlugins", @@ -218,6 +221,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_ADMIN=false", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -227,10 +231,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", }, }, { @@ -239,11 +239,9 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{legacyAuthComposeFile}, s.dotEnvFiles, []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", @@ -262,15 +260,11 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{ "API_URL=http://teams-api:8000", "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", @@ -283,7 +277,24 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "teams-cas", []string{legacyAuthComposeFile}, s.dotEnvFiles, - nil, + []string{ + "AUTH0_AUTH_CLIENT_ID=test-auth0-client-id", + "AUTH0_AUTH_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_DOMAIN=test-auth0-domain", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_MGMT_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_MGMT_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_ORGANIZATION=test-auth0-organization", + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + "TEAMS_API_DATABASE_NAME=fiftyone", + "TEAMS_API_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + }, }, { "pluginsFiftyoneApp", @@ -292,6 +303,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_ADMIN=false", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -303,10 +315,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_CACHE_ENABLED=true", "FIFTYONE_PLUGINS_DIR=/opt/plugins", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", }, }, { @@ -315,11 +323,9 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", @@ -339,15 +345,11 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{ "API_URL=http://teams-api:8000", "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", @@ -360,8 +362,24 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "teams-cas", []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, - // []string{}, - nil, + []string{ + "AUTH0_AUTH_CLIENT_ID=test-auth0-client-id", + "AUTH0_AUTH_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_DOMAIN=test-auth0-domain", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_MGMT_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_MGMT_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_ORGANIZATION=test-auth0-organization", + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + "TEAMS_API_DATABASE_NAME=fiftyone", + "TEAMS_API_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + }, }, { "dedicatedPluginsFiftyoneApp", @@ -370,6 +388,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_ADMIN=false", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -379,10 +398,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", }, }, { @@ -391,11 +406,9 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", + "CAS_BASE_URL=http://teams-cas:3000/cas/api", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", @@ -415,15 +428,11 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { []string{ "API_URL=http://teams-api:8000", "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", + "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", + "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0b2", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", @@ -437,9 +446,24 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "teams-cas", []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, - // []string{""}, - // []string{}, - nil, + []string{ + "AUTH0_AUTH_CLIENT_ID=test-auth0-client-id", + "AUTH0_AUTH_CLIENT_SECRET=test-auth0-client-secret", + "AUTH0_DOMAIN=test-auth0-domain", + "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", + "AUTH0_MGMT_CLIENT_ID=test-auth0-api-client-id", + "AUTH0_MGMT_CLIENT_SECRET=test-auth0-api-client-secret", + "AUTH0_ORGANIZATION=test-auth0-organization", + "CAS_DATABASE_NAME=fiftyone-cas", + "CAS_DEFAULT_USER_ROLE=GUEST", + "CAS_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + "CAS_URL=https://example.fiftyone.ai", + "DEBUG=cas:*,-cas:*:debug", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", + "NEXTAUTH_URL=https://example.fiftyone.ai/cas/api/auth", + "TEAMS_API_DATABASE_NAME=fiftyone", + "TEAMS_API_MONGODB_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", + }, }, { "dedicatedPluginsTeamsPlugins", @@ -448,6 +472,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", + "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_ADMIN=false", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -459,10 +484,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_CACHE_ENABLED=true", "FIFTYONE_PLUGINS_DIR=/opt/plugins", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", }, }, } @@ -566,7 +587,16 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicePorts() { "teams-cas", []string{legacyAuthComposeFile}, s.dotEnvFiles, - nil, + []types.ServicePortConfig{ + { + Mode: "ingress", + HostIP: "127.0.0.1", + Target: 3000, + Published: "3030", + Protocol: "tcp", + Extensions: nil, + }, + }, }, } @@ -637,7 +667,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceRestart() { "teams-cas", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "", + "always", }, { "dedicatedPluginsTeamsPlugins", From e8a1c78fd54b734826c53283b8ad974bcfffa412 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Fri, 29 Mar 2024 08:45:28 -0700 Subject: [PATCH 18/42] docs: in docker docs, fix brokenlink to teams plugins doc --- docker/docs/expose-teams-api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/docs/expose-teams-api.md b/docker/docs/expose-teams-api.md index 3ef1fe16..c7c303b6 100644 --- a/docker/docs/expose-teams-api.md +++ b/docker/docs/expose-teams-api.md @@ -45,7 +45,8 @@ security implications before using this method. 1. Edit your `.env` file setting `API_BIND_ADDRESS` to `0.0.0.0` 1. Recreate your environment using the - [plugin specific](./README.md#enabling-fiftyone-teams-plugins) command + [plugin specific](../README.md#enabling-fiftyone-teams-plugins) + command ```shell docker compose From 79e9cd9d7a7f944e3ee26b0ccc950bb58f2a675c Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Fri, 29 Mar 2024 14:09:34 -0700 Subject: [PATCH 19/42] In skaffold.yaml, bump chart version from 1.5.5 to 1.5.8. Add new make target `auth` and update CONTRIBUTING.md to use it. --- CONTRIBUTING.md | 4 ++-- Makefile | 4 +++- skaffold.yaml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70e27c01..26763a2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ ## Quickstart to Skaffold in Minikube -1. Auth with gcloud +1. Auth with gcloud for the project `computer-vision-team` ```shell - gcloud auth application-default login + make auth ``` 1. Install the asdf tools diff --git a/Makefile b/Makefile index 7cf29ae2..724c49b2 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ asdf: ## Update plugins, add plugins, install plugins, set local, reshim @echo "Reshimming.." @asdf reshim +auth: + gcloud auth application-default login --project computer-vision-team hooks: ## Install git hooks (pre-commit) @pre-commit install @@ -39,7 +41,7 @@ hooks: ## Install git hooks (pre-commit) pre-commit: ## Run pre-commit against all files @pre-commit run -a -start: ## Run minikube with ingress and gcp-auth +start: ## Run minikube with ingress and gcp-auth # to persist mongodb data, we may want to start minikube with a volume mount # minikube start --mount=true \ # --mount-string=/var/tmp/mongodb_data:/tmp/hostpath-provisioner/fiftyone-teams/mongodb diff --git a/skaffold.yaml b/skaffold.yaml index efec2f1d..cf20b756 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -36,7 +36,7 @@ deploy: releases: - name: fiftyone-teams chartPath: helm/fiftyone-teams-app - version: 1.5.5 + version: 1.5.8 createNamespace: true namespace: fiftyone-teams overrides: From 1fd304ef4f086931425396646b54019aeda91a03 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Wed, 3 Apr 2024 09:50:15 -0700 Subject: [PATCH 20/42] docs: update instructions around the required settings for helm and compose --- docker/README.md | 2 +- docker/internal-auth/env.template | 2 +- helm/fiftyone-teams-app/README.md | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docker/README.md b/docker/README.md index c501a3d0..fecf2ac2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -409,7 +409,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: 1. From a directory containing the files `compose.yaml` and `env.template` files (included in this repository), 1. Rename the `env.template` file to `.env` - 1. Edit the `.env` file, setting the parameters required for this deployment. + 1. Edit the `.env` file, setting all the required parameters. See the [FiftyOne Teams Environment Variables](#fiftyone-teams-environment-variables) table. diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 6166410a..33ef4f2d 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -2,7 +2,7 @@ BASE_URL=https://example.fiftyone.ai # This should be set to the URI your end-users will use to connect to the API -# This could be the same as AUTH0_BASE_URL if you are using path-based routing +# This could be the same as BASE_URL if you are using path-based routing FIFTYONE_API_URI=https://example-api.fiftyone.ai # This should be a MongoDB Connection String for your database diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 75badf3f..6021c2c9 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -571,7 +571,13 @@ upgrading from FiftyOne Teams version 1.1.0 or later: A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). -1. Edit the `values.yaml` file +1. Edit the `values.yaml` file, setting the required parameters including + 1. `secret.fiftyone.mongodbConnectionString` + 1. `secret.fiftyone.cookieSecret` + 1. `secret.fiftyone.encryptionKey` + 1. `secret.fiftyone.fiftyoneAuthSecret` + 1. `apiSettings.dnsName` + 1. `teamsAppSettings.dnsName` 1. Deploy FiftyOne Teams with `helm install` 1. For a new installation, run From a3c9c0bda3c44c506b9f7301873d5f7f6fee6384 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 4 Apr 2024 08:41:01 -0700 Subject: [PATCH 21/42] docs: add more specific information for helm required values and update docker compose to match wording. --- docker/README.md | 2 +- helm/fiftyone-teams-app/README.md | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docker/README.md b/docker/README.md index fecf2ac2..43dfa810 100644 --- a/docker/README.md +++ b/docker/README.md @@ -409,7 +409,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: 1. From a directory containing the files `compose.yaml` and `env.template` files (included in this repository), 1. Rename the `env.template` file to `.env` - 1. Edit the `.env` file, setting all the required parameters. + 1. Edit the `.env` file, setting all the customer provided required settings. See the [FiftyOne Teams Environment Variables](#fiftyone-teams-environment-variables) table. diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 6021c2c9..306a39f8 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -571,13 +571,17 @@ upgrading from FiftyOne Teams version 1.1.0 or later: A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). -1. Edit the `values.yaml` file, setting the required parameters including - 1. `secret.fiftyone.mongodbConnectionString` - 1. `secret.fiftyone.cookieSecret` - 1. `secret.fiftyone.encryptionKey` - 1. `secret.fiftyone.fiftyoneAuthSecret` - 1. `apiSettings.dnsName` - 1. `teamsAppSettings.dnsName` +1. Edit the `values.yaml` file, setting these required customer provided settings + 1. Secrets + 1. `secret.fiftyone.mongodbConnectionString` + 1. `secret.fiftyone.cookieSecret` + 1. `secret.fiftyone.encryptionKey` + 1. `secret.fiftyone.fiftyoneAuthSecret` + + > **Note**: All the secret fields defined in the Helm chart must be set. + > If `secret.create=false`, the secret you provide must contain all the fields + > in the chart's `secret.fiftyone`. + 1. `teamsAppSettings.dnsName` 1. Deploy FiftyOne Teams with `helm install` 1. For a new installation, run From 840341e8cee164c9f5c4bf5e6852918cec90927d Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 4 Apr 2024 19:09:14 -0400 Subject: [PATCH 22/42] adds some documentation around custom plugins images (#110) * adds some documentation around custom plugins images * not sure how that got lost? * fix relative links * one more link fix * uses consistent language --- docker/README.md | 4 ++ docs/custom-plugins.md | 90 ++++++++++++++++++++++++ helm/fiftyone-teams-app/README.md | 16 ++--- helm/fiftyone-teams-app/README.md.gotmpl | 4 ++ 4 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 docs/custom-plugins.md diff --git a/docker/README.md b/docker/README.md index 43dfa810..4d369316 100644 --- a/docker/README.md +++ b/docker/README.md @@ -201,6 +201,10 @@ create a new Docker Volume shared between FiftyOne Teams services. For multi-node deployments, please implement a storage solution allowing the access the deployed plugins. +If you build plugins that have custom dependencies, you will need to build and +use +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) + Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. Early-adopter plugins installed manually must be redeployed using the FiftyOne Teams UI. diff --git a/docs/custom-plugins.md b/docs/custom-plugins.md new file mode 100644 index 00000000..dae246d9 --- /dev/null +++ b/docs/custom-plugins.md @@ -0,0 +1,90 @@ + + +
+

+ +   + + +

+
+ + +--- + +# Custom Plugins Images + +Some plugins have custom python dependencies, which requires the +creation of a new plugins image. This document outlines the steps +Voxel51 recommends for creating those custom plugins containers. + +## Create a New Image From an Existing Voxel51 Image + +By default, dedicated plugins use the `voxel51/fiftyone-app` image. +Basing your custom image on the existing base image will ensure that +the required `fiftyone` packages and configurations are available. + +As an example, you might use the following Dockerfile to build a +custom `your-internal-registry/fiftyone-app-internal` image: + +```dockerfile +ARG TEAMS_IMAGE_NAME + +FROM python:3.10 as wheelhouse + +RUN pip wheel --wheel-dir=/tmp/wheels pandas + +FROM ${TEAMS_IMAGE_NAME} as pandarelease + +RUN --mount=type=cache,from=wheelhouse,target=/wheelhouse,ro \ + pip --no-cache-dir install -q --no-index \ + --find-links=/wheelhouse/tmp/wheels pandas +``` + +With a Dockerfile like this, you could use the following commands to +build, and publish, your image to your internal registry + +```shell +$ TEAMS_VERSION=v1.6.0 +$ docker buildx build --push \ + --build-arg TEAMS_IMAGE_NAME='voxel51/fiftyone-app:${TEAMS_VERSION}' \ + -t your-internal-registry/fiftyone-app-internal:${TEAMS_VERSION} . +``` + +You should upgrade your custom plugins image using the `TEAMS_VERSION` +you plan to use in your FiftyOne Teams Deployment. + +## Using Your Custom Plugins Image in Docker Compose + +Once you have built a custom plugins image, you can add it to your +`compose.override.yaml` using something similar to the following: + +```yaml +services: + teams-plugins: + image: your-internal-registry/fiftyone-app-internal:v1.6.0 +``` + +Please see +[Enabling FiftyOne Teams Plugins](../docker/README.md#enabling-fiftyone-teams-plugins) +for example `docker compose` commands for starting and upgrading your +deployment. + +## Using Your Custom Plugins Image in Helm Deployments + +Once you have build a custom plugins image, you can add it to your +`values.yaml` using something similar to the following: + +```yaml +pluginsSettings: + image: + repository: your-internal-registry/fiftyone-app-internal +``` + +Assuming you have built your custom container with the same version +number as the FiftyOne Teams release, the Helm chart will +automatically use the chart version to pull your image. + +Please see +[FiftyOne Teams Plugins](../helm/fiftyone-teams-app/README.md#fiftyone-teams-plugins) +for additional information regarding `teams-plugins` configuration. diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 306a39f8..79722bd8 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -168,6 +168,10 @@ There are three modes for plugins add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` environment variables. +If you build plugins that have custom dependencies, you will need to build and +use +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) + Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. Early-adopter plugins installed manually must be redeployed using the FiftyOne Teams UI. @@ -571,17 +575,7 @@ upgrading from FiftyOne Teams version 1.1.0 or later: A minimal example `values.yaml` may be found [here](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/helm/values.yaml). -1. Edit the `values.yaml` file, setting these required customer provided settings - 1. Secrets - 1. `secret.fiftyone.mongodbConnectionString` - 1. `secret.fiftyone.cookieSecret` - 1. `secret.fiftyone.encryptionKey` - 1. `secret.fiftyone.fiftyoneAuthSecret` - - > **Note**: All the secret fields defined in the Helm chart must be set. - > If `secret.create=false`, the secret you provide must contain all the fields - > in the chart's `secret.fiftyone`. - 1. `teamsAppSettings.dnsName` +1. Edit the `values.yaml` file 1. Deploy FiftyOne Teams with `helm install` 1. For a new installation, run diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 4fcff091..cd4a26ba 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -170,6 +170,10 @@ There are three modes for plugins add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` environment variables. +If you build plugins that have custom dependencies, you will need to build and +use +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) + Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. Early-adopter plugins installed manually must be redeployed using the FiftyOne Teams UI. From 8c3f0ed424d96eb1396431b69738c454c3d557b7 Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 11 Apr 2024 19:12:45 +0000 Subject: [PATCH 23/42] helm doc updates --- .pre-commit-config.yaml | 34 +++++----- helm/fiftyone-teams-app/README.md | 78 +++++++++++++++++++---- helm/fiftyone-teams-app/README.md.gotmpl | 79 ++++++++++++++++++++---- 3 files changed, 148 insertions(+), 43 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 923f88e2..e9235d8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,16 @@ repos: - go-mod - go-sum - makefile + - repo: https://github.com/norwoodj/helm-docs + rev: v1.11.3 + hooks: + - id: helm-docs + args: + # Make the tool search for charts only under the `helm/fiftyone-teams-app` directory + - --chart-search-root=helm/fiftyone-teams-app + + # A base filename makes it relative to each chart directory found + - --template-files=README.md.gotmpl - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.36.0 hooks: @@ -44,16 +54,6 @@ repos: entry: yamllint --config-file .yamllint.yaml # We exclude helm template files since in raw form, they are not valid yaml exclude: helm/fiftyone-teams-app/templates - - repo: https://github.com/norwoodj/helm-docs - rev: v1.11.3 - hooks: - - id: helm-docs - args: - # Make the tool search for charts only under the `helm/fiftyone-teams-app` directory - - --chart-search-root=helm/fiftyone-teams-app - - # A base filename makes it relative to each chart directory found - - --template-files=README.md.gotmpl - repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs rev: v1.1.2 hooks: @@ -70,13 +70,13 @@ repos: rev: v0.1.23 hooks: - id: helmlint - - repo: https://github.com/Yelp/detect-secrets - rev: v1.4.0 - hooks: - - id: detect-secrets - args: - - --exclude-secrets - - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' + # - repo: https://github.com/Yelp/detect-secrets + # rev: v1.4.0 + # hooks: + # - id: detect-secrets + # args: + # - --exclude-secrets + # - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 79722bd8..f2a1b163 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -490,6 +490,21 @@ appSettings: ## Upgrading From Previous Versions +Voxel51 assumes you are using the published Helm Chart to deploy your FiftyOne +Teams environment. If you are using a custom deployment mechanism you will +want to carefully review the changes in the +[Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) +and update your deployment accordingly. + +> **NOTE**: FiftyOne Teams v1.6.0 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. + ### From Early Adopter Versions (Versions less than 1.0) Please contact your Voxel51 Customer Success @@ -499,22 +514,37 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible -with FiftyOne Teams Database Versions prior to 0.19.0. -The FiftyOne 0.10.x SDK is not forwards compatible -with current FiftyOne Teams Database Versions. -If you are using a FiftyOne SDK version older than 0.11.0, upgrading the Web -server will require upgrading all FiftyOne SDK installations. +> **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires +> upgrading the database and will interrupt all SDK connections. You should +> coordinate this upgrade carefully with your end-users. + +--- -Voxel51 recommends this upgrade process from -versions prior to FiftyOne Teams version 1.1.0: +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. + +--- -1. In your `values.yaml`, set the required - [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) - environment variable +> **NOTE**: Voxel51 recommends upgrading your deployment using +> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +> and +> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +> to +> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +> after confirming your initial upgrade was successful. + +1. In your `values.yaml`, make sure the required + [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods +1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods +1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. + This is not the default value in the Helm Chart and must be overridden. 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) - with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` - (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` 1. Upgrade your FiftyOne SDKs to version 0.16.0 @@ -535,6 +565,21 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. + +--- + +> **NOTE**: Voxel51 recommends upgrading your deployment using +> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +> and +> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +> to +> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +> after confirming your initial upgrade was successful. + The FiftyOne 0.16.0 SDK is backwards-compatible with FiftyOne Teams Database Versions 0.19.0 and later. You will not be able to connect to a FiftyOne Teams 1.6.0 @@ -550,6 +595,13 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default +1. In your `values.yaml`, make sure the required + [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods +1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index cd4a26ba..473998d5 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -315,6 +315,21 @@ appSettings: ## Upgrading From Previous Versions +Voxel51 assumes you are using the published Helm Chart to deploy your FiftyOne +Teams environment. If you are using a custom deployment mechanism you will +want to carefully review the changes in the +[Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) +and update your deployment accordingly. + +> **NOTE**: FiftyOne Teams v1.6.0 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. + ### From Early Adopter Versions (Versions less than 1.0) Please contact your Voxel51 Customer Success @@ -324,22 +339,37 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible -with FiftyOne Teams Database Versions prior to 0.19.0. -The FiftyOne 0.10.x SDK is not forwards compatible -with current FiftyOne Teams Database Versions. -If you are using a FiftyOne SDK version older than 0.11.0, upgrading the Web -server will require upgrading all FiftyOne SDK installations. +> **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires +> upgrading the database and will interrupt all SDK connections. You should +> coordinate this upgrade carefully with your end-users. + +--- -Voxel51 recommends this upgrade process from -versions prior to FiftyOne Teams version 1.1.0: +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. + +--- -1. In your `values.yaml`, set the required - [FIFTYONE_ENCRYPTION_KEY](#storage-credentials-and-fiftyone_encryption_key) - environment variable +> **NOTE**: Voxel51 recommends upgrading your deployment using +> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +> and +> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +> to +> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +> after confirming your initial upgrade was successful. + +1. In your `values.yaml`, make sure the required + [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods +1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods +1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. + This is not the default value in the Helm Chart and must be overridden. 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) - with `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` - (this is not the default value in `values.yaml` and must be overridden). > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` 1. Upgrade your FiftyOne SDKs to version 0.16.0 @@ -360,6 +390,22 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. + +--- + +> **NOTE**: Voxel51 recommends upgrading your deployment using +> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +> and +> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +> to +> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +> after confirming your initial upgrade was successful. + + The FiftyOne 0.16.0 SDK is backwards-compatible with FiftyOne Teams Database Versions 0.19.0 and later. You will not be able to connect to a FiftyOne Teams 1.6.0 @@ -375,6 +421,13 @@ upgrading from FiftyOne Teams version 1.1.0 or later: - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default +1. In your `values.yaml`, make sure the required + [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods +1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` + (or your deployment's equivalent) is set, which will set the + `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI From 0b6feb6fc3cf1ec16a78d23f7ea8897c5a88f5aa Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 11 Apr 2024 14:59:09 -0700 Subject: [PATCH 24/42] test: add intergration tests for docker compose in legacy auth mode. --- .pre-commit-config.yaml | 2 +- Makefile | 45 ++- .../docker/compose.override.darwin.yaml | 9 + tests/fixtures/docker/compose.override.yaml | 4 + .../docker/integration_legacy_auth.env | 48 +++ tests/go.mod | 5 +- tests/go.sum | 10 +- tests/integration/compose/common_test.go | 27 ++ .../docker-compose-legacy-auth_test.go | 280 ++++++++++++++++++ 9 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/docker/compose.override.darwin.yaml create mode 100644 tests/fixtures/docker/compose.override.yaml create mode 100644 tests/fixtures/docker/integration_legacy_auth.env create mode 100644 tests/integration/compose/common_test.go create mode 100644 tests/integration/compose/docker-compose-legacy-auth_test.go diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 923f88e2..9eb2f96e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -76,7 +76,7 @@ repos: - id: detect-secrets args: - --exclude-secrets - - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' + - '(password|REPLACEME|fiftyone-teams-tls-secret|3-9XjJ-gUV?vp\^e\(WUk>LD&lAjh7yEji|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: diff --git a/Makefile b/Makefile index 724c49b2..728a842d 100644 --- a/Makefile +++ b/Makefile @@ -91,12 +91,42 @@ port-forward-api: ## port forward to service `teams-api` on the host port 8000 port-forward-mongo: ## port forward to service `mongodb` on the host port 27017 kubectl port-forward --namespace fiftyone-teams svc/mongodb 27017:27017 --context minikube +tunnel: ## run minikube tunnel to access the k8s ingress via localhost () + minikube tunnel + helm-repos: ## add helm repos for the project helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add jetstack https://charts.jetstack.io -tunnel: ## run minikube tunnel to access the k8s ingress via localhost () - minikube tunnel +clean: clean-unit-compose clean-unit-helm clean-integration-compose clean-integration-helm ## delete all test output and reports + +clean-integration-compose: ## delete docker compose integration test output and reports + rm -rf tests/integration/compose/test_output || true + rm -rf tests/integration/compose/test_reports || true + rm tests/integration/compose/test_output.log || true + +clean-integration-helm: ## delete helm integration test output and reports + rm -rf tests/integration/helm/test_output || true + rm -rf tests/integration/helm/test_reports || true + rm tests/integration/helm/test_output.log || true + +clean-unit-compose: ## delete docker compose unit test output and reports + rm -rf tests/unit/compose/test_output || true + rm -rf tests/unit/compose/test_reports || true + rm tests/unit/compose/test_output.log || true + +clean-unit-helm: ## delete helm unit test output and reports + rm -rf tests/unit/helm/test_output || true + rm -rf tests/unit/helm/test_reports || true + rm tests/unit/helm/test_output.log || true + +dependencies-integration-compose: ## create a (temporary) directory for mongodb container + mkdir -p /tmp/mongodb + +login: ## Docker login to Google Artifact Registry (for accessing internal gcr.io container images) + gcloud auth print-access-token | \ + docker login -u oauth2accesstoken \ + --password-stdin https://us-central1-docker.pkg.dev test-unit-compose: ## run go test on the tests/unit/compose directory @cd tests/unit/compose; \ @@ -120,5 +150,16 @@ test-unit-helm-interleaved: install-terratest-log-parser ## run go test on the go test -count=1 -timeout=10m -v -tags unit | tee test_output.log; \ ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output +test-integration-compose: dependencies-integration-compose ## run go test on the tests/integration/compose directory + @cd tests/integration/compose; \ + go test -count=1 -timeout=10m -v -tags integration + +test-integration-compose-interleaved: install-terratest-log-parser dependencies-integration-compose clean-integration-compose ## run go test on the tests/integration/compose directory and run the terratest_log_parser for reports + @cd tests/integration/compose; \ + rm -rf test_reports; \ + mkdir test_reports; \ + go test -count=1 -timeout=10m -v -tags integration | tee test_output.log; \ + ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output + install-terratest-log-parser: ## install terratest_log_parser go install github.com/gruntwork-io/terratest/cmd/terratest_log_parser@latest diff --git a/tests/fixtures/docker/compose.override.darwin.yaml b/tests/fixtures/docker/compose.override.darwin.yaml new file mode 100644 index 00000000..a6f8355c --- /dev/null +++ b/tests/fixtures/docker/compose.override.darwin.yaml @@ -0,0 +1,9 @@ +services: + fiftyone-app: + platform: linux/amd64 + teams-api: + platform: linux/amd64 + teams-app: + platform: linux/amd64 + teams-cas: + platform: linux/amd64 diff --git a/tests/fixtures/docker/compose.override.yaml b/tests/fixtures/docker/compose.override.yaml new file mode 100644 index 00000000..04116162 --- /dev/null +++ b/tests/fixtures/docker/compose.override.yaml @@ -0,0 +1,4 @@ +services: + fiftyone-app: + environment: + FIFTYONE_DATABASE_ADMIN: true diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env new file mode 100644 index 00000000..815d8311 --- /dev/null +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -0,0 +1,48 @@ +# Data for Integration Tests + +# Auth0 +# CAS doesn't validate the Auth0 Legacy Mode values when it starts. +# Without a web server, we cannot access the Web UI because we get an infinite redirect loop +# resulting in 404s: `Requested URL /api/cas not found` +AUTH0_API_CLIENT_ID="" +AUTH0_API_CLIENT_SECRET="" +AUTH0_AUDIENCE="" +AUTH0_CLIENT_ID="" +AUTH0_CLIENT_SECRET="" +AUTH0_DOMAIN="" +AUTH0_ISSUER_BASE_URL="" +AUTH0_ORGANIZATION="" + +# This is a randomly generated string +AUTH0_SECRET=5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976 + +AUTH0_BASE_URL=http://local.fiftyone.ai + +# FiftyOne +FIFTYONE_DATABASE_URI="mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin" # This is a randomly generated password +FIFTYONE_ENCRYPTION_KEY="btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=" # This is a randomly generated string + +# Internal Auth +FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" + +# MongoDB +MONGODB_PASSWORD="3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji" # This is a randomly generated string # pragma: allowlist secret +MONGODB_USERNAME=root +MONGODB_DIR=/tmp/mongodb +MONGODB_BIND_ADDRESS=127.0.0.1 + +APP_USE_HTTPS=false +FIFTYONE_API_URI=http://local.fiftyone.ai +BASE_URL=http://local.fiftyone.ai + +# Show debug logs +API_LOGGING_LEVEL=DEBUG +FIFTYONE_ENV=development +CAS_DEBUG="cas:*" + +# Image tags +VERSION=v1.6.0 +FIFTYONE_APP_RC=${VERSION}rc14 +FIFTYONE_TEAMS_API_RC=${VERSION}rc13 +FIFTYONE_TEAMS_APP_RC=${VERSION}-rc.11 +FIFTYONE_TEAMS_CAS_RC=${VERSION}-rc.11 diff --git a/tests/go.mod b/tests/go.mod index fe7645ff..a060e16d 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -3,8 +3,8 @@ module github.com/voxel51/fiftyone-teams-app-deploy go 1.21.6 require ( - github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 - github.com/gruntwork-io/terratest v0.46.11 + github.com/compose-spec/compose-go/v2 v2.0.2 + github.com/gruntwork-io/terratest v0.46.13 github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.28.4 @@ -90,6 +90,7 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gotest.tools/v3 v3.4.0 // indirect k8s.io/client-go v0.28.4 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect diff --git a/tests/go.sum b/tests/go.sum index 0dd051f4..8601d7e0 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -8,8 +8,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.2 h1:zhXMV7VWI00Su0LdKt8/sxeXxcjLWhmGmpEyw+ZYznI= +github.com/compose-spec/compose-go/v2 v2.0.2/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -81,8 +81,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= -github.com/gruntwork-io/terratest v0.46.11 h1:1Z9G18I2FNuH87Ro0YtjW4NH9ky4GDpfzE7+ivkPeB8= -github.com/gruntwork-io/terratest v0.46.11/go.mod h1:DVZG/s7eP1u3KOQJJfE6n7FDriMWpDvnj85XIlZMEM8= +github.com/gruntwork-io/terratest v0.46.13 h1:FDaEoZ7DtkomV8pcwLdBV/VsytdjnPRqJkIriYEYwjs= +github.com/gruntwork-io/terratest v0.46.13/go.mod h1:8sxu3Qup8TxtbzOHzq0MUrQffJj/G61/OwlsReaCwpo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= @@ -233,6 +233,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -254,6 +255,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tests/integration/compose/common_test.go b/tests/integration/compose/common_test.go new file mode 100644 index 00000000..692c7976 --- /dev/null +++ b/tests/integration/compose/common_test.go @@ -0,0 +1,27 @@ +//go:build docker || compose || integration || integrationComposeInternalAuth || integrationComposeLegacyAuth +// +build docker compose integration integrationComposeInternalAuth integrationComposeLegacyAuth + +package integration + +import ( + "testing" + "time" + + "github.com/gruntwork-io/terratest/modules/docker" + http_helper "github.com/gruntwork-io/terratest/modules/http-helper" +) + +const ( + envFixtureFilePath = "../../fixtures/docker/integration_legacy_auth.env" +) + +func validate_endpoint(t *testing.T, url string, expectedBody string, expectedStatus int) { + maxRetries := 10 + timeBetweenRetries := 3 * time.Second + http_helper.HttpGetWithRetry(t, url, nil, expectedStatus, expectedBody, maxRetries, timeBetweenRetries) +} + +func get_logs(t *testing.T, dockerOptions *docker.Options, container string) string { + output := docker.RunDockerComposeAndGetStdOut(t, dockerOptions, "logs", container) + return output +} diff --git a/tests/integration/compose/docker-compose-legacy-auth_test.go b/tests/integration/compose/docker-compose-legacy-auth_test.go new file mode 100644 index 00000000..2df2b6ad --- /dev/null +++ b/tests/integration/compose/docker-compose-legacy-auth_test.go @@ -0,0 +1,280 @@ +//go:build docker || compose || integration || integrationComposeLegacyAuth +// +build docker compose integration integrationComposeLegacyAuth + +package integration + +import ( + "fmt" + "runtime" + "strings" + "testing" + + "path/filepath" + + "github.com/gruntwork-io/terratest/modules/docker" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/random" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/compose-spec/compose-go/v2/dotenv" +) + +const ( + dockerLegacyAuthDir = "../../../docker/legacy-auth" + // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) + overrideFile = "../../tests/fixtures/docker/compose.override.yaml" + // To run the containers on macOS arm64, we need to set the platform + darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" + mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" +) + +var legacyAuthComposeFile = "compose.yaml" +var legacyAuthComposePluginsFile = "compose.plugins.yaml" +var legacyAuthComposeDedicatedPluginsFile = "compose.dedicated-plugins.yaml" +var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") + +type commonServicesLegacyAuthDockerComposeUpTest struct { + suite.Suite + composeFilePath string + projectName string + dotEnvFiles []string + overrideFiles []string +} + +func TestDockerComposeUpLegacyAuth(t *testing.T) { + t.Parallel() + + _, err := filepath.Abs(dockerLegacyAuthDir) + require.NoError(t, err) + + // Set the override files used for the tests + overrideFiles := []string{ + overrideFile, + mongodbComposeFile, + } + + // To run the containers on macOS arm64, we need to set the platform + if runtime.GOOS == "darwin" { + overrideFiles = append(overrideFiles, darwinOverrideFile) + } + + suite.Run(t, &commonServicesLegacyAuthDockerComposeUpTest{ + Suite: suite.Suite{}, + composeFilePath: dockerLegacyAuthDir, + projectName: "fiftyone-compose-integration-test", + dotEnvFiles: []string{ + legacyAuthEnvTemplateFilePath, + envFixtureFilePath, + }, + overrideFiles: overrideFiles, + }) +} + +type serviceValidations struct { + name string + url string + responsePayload string + httpResponseCode int + log string +} + +func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { + testCases := []struct { + name string + composeFile string + overrideFiles []string + envFiles []string // file paths to ".env" files with additional environment variable data + expected []serviceValidations + }{ + { + "compose", + legacyAuthComposeFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 200, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + }, + }, + { + "composePlugins", + legacyAuthComposePluginsFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 200, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + }, + }, + { + "composeDedicatedPlugins", + legacyAuthComposeDedicatedPluginsFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 0, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + { + name: "teams-plugins", + url: "", + responsePayload: "", + httpResponseCode: 0, + log: "[INFO] Running on http://0.0.0.0:5151", // same as fiftyone-app since plugins uses or is based on the fiftyone-app image + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + // TODO: If we need parallel, dynamically set mongoDB port and configure the env vars with the custom port + // For now, we cannot perform concurrent runs because mongodb will error on port already in use + // subT.Parallel() + + // TODO: Should we use `--env-file` instead of the library `dotenv.Read` + // to more closely align to real world usage? + // Something like + // + // ```shell + // docker compose \ + // -f tests/fixtures/docker/compose.override.mongodb.yaml \ + // --env-file tests/fixtures/docker/integration_legacy_auth.env + + // up -d + // ``` + // + // Use existing function to get map[string]string of environment variables from .env file(s) + environmentVariables, err := dotenv.Read(s.dotEnvFiles...) + s.NoError(err) + + dockerOptions := &docker.Options{ + ProjectName: "fiftyone-" + strings.ToLower(random.UniqueId()), + WorkingDir: dockerLegacyAuthDir, + EnvVars: environmentVariables, + } + + // In golang, we cannot mix strings with string slice unpacking. + // Let's create a slice that will later be unpacked and used as an argument + // to the variadic function `docker.RunDockerCompose` parameter `args`. + argsUp := []string{} + argsDown := []string{} + args := []string{"-f", testCase.composeFile} + + for _, overrideFile := range s.overrideFiles { + args = append(args, "-f", overrideFile) + } + + argsUp = append(args, "up", "--detach") + argsDown = append(args, "down", "--remove-orphans", "--timeout", "2") + + // Run containers + output := docker.RunDockerCompose( + subT, + dockerOptions, + argsUp..., + ) + // Delete containers after tests complete + defer docker.RunDockerCompose( + subT, + dockerOptions, + argsDown..., + ) + + // Validate system health + for _, expected := range testCase.expected { + logger.Log(subT, fmt.Sprintf("Validating service %s...", expected.name)) + s.Contains(output, fmt.Sprintf("Container %s-%s-1 Started", dockerOptions.ProjectName, expected.name), fmt.Sprintf("%s - %s - docker compose output should contain service container started", testCase.name, expected.name)) + if expected.url != "" { + validate_endpoint(subT, expected.url, expected.responsePayload, expected.httpResponseCode) + } + s.Contains(get_logs(subT, dockerOptions, expected.name), expected.log, fmt.Sprintf("%s - %s - log should contain matching entry", testCase.name, expected.name)) + } + }) + } +} From 52a66a2b0be6a484eaf5910951f0732e0f81f3d8 Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 11 Apr 2024 18:53:41 -0400 Subject: [PATCH 25/42] (mostly) doc updates for v1.6.0 --- .pre-commit-config.yaml | 14 +- docker/README.md | 295 ++++++++++++------ docker/common-services.yaml | 1 - .../compose.dedicated-plugins.yaml | 1 + docker/internal-auth/compose.plugins.yaml | 1 + docker/internal-auth/compose.yaml | 2 + helm/fiftyone-teams-app/README.md | 123 ++++---- helm/fiftyone-teams-app/README.md.gotmpl | 124 ++++---- 8 files changed, 341 insertions(+), 220 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9235d8d..e8a427af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -70,13 +70,13 @@ repos: rev: v0.1.23 hooks: - id: helmlint - # - repo: https://github.com/Yelp/detect-secrets - # rev: v1.4.0 - # hooks: - # - id: detect-secrets - # args: - # - --exclude-secrets - # - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' + - repo: https://github.com/Yelp/detect-secrets + rev: v1.4.0 + hooks: + - id: detect-secrets + args: + - --exclude-secrets + - '(password|REPLACEME|fiftyone-teams-tls-secret|btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=|5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976|test-*|/api/proxy/fiftyone-teams|/opt/plugins)' - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: diff --git a/docker/README.md b/docker/README.md index 4d369316..6f1daae2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,13 +14,14 @@ - [Deploying FiftyOne Teams App with Docker Compose](#deploying-fiftyone-teams-app-with-docker-compose) - [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) - - [FiftyOne Teams Upgrade Notes](#fiftyone-teams-upgrade-notes) - - [Enabling Snapshot Archival](#enabling-snapshot-archival) - - [Enabling FiftyOne Teams Authenticated API](#enabling-fiftyone-teams-authenticated-api) - - [Enabling FiftyOne Teams Plugins](#enabling-fiftyone-teams-plugins) - - [Storage Credentials and `FIFTYONE_ENCRYPTION_KEY`](#storage-credentials-and-fiftyone_encryption_key) - - [Environment Proxies](#environment-proxies) - - [Text Similarity](#text-similarity) + - [FiftyOne Teams Features](#fiftyone-teams-features) + - [Central Authentication Service](#central-authentication-service) + - [Snapshot Archival](#snapshot-archival) + - [FiftyOne Teams Authenticated API](#fiftyone-teams-authenticated-api) + - [FiftyOne Teams Plugins](#fiftyone-teams-plugins) + - [Storage Credentials and `FIFTYONE_ENCRYPTION_KEY`](#storage-credentials-and-fiftyone_encryption_key) + - [Proxies](#proxies) + - [Text Similarity](#text-similarity) - [Upgrade Process Recommendations](#upgrade-process-recommendations) - [From Early Adopter Versions (Versions less than 1.0)](#from-early-adopter-versions-versions-less-than-10) - [From Before FiftyOne Teams Version 1.1.0](#from-before-fiftyone-teams-version-110) @@ -34,13 +35,14 @@ # Deploying FiftyOne Teams App with Docker Compose -We publish container images to these Docker Hub repositories +We publish the following FiftyOne Teams private images to Docker Hub: - `voxel51/fiftyone-app` - `voxel51/fiftyone-app-gpt` - `voxel51/fiftyone-app-torch` - `voxel51/fiftyone-teams-api` - `voxel51/fiftyone-teams-app` +- `voxel51/fiftyone-teams-cas` For Docker Hub credentials, please contact your Voxel51 support team. @@ -98,9 +100,51 @@ quickstart 0.21.2 --- -### FiftyOne Teams Upgrade Notes +## FiftyOne Teams Features -#### Enabling Snapshot Archival +### Central Authentication Service + +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which +requires additional configurations and consumes additional resources. Please +review these notes, and the +[Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +documentation before completing your upgrade. + +Voxel51 recommends upgrading your deployment using +[`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +and +[migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +to +[`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +after confirming your initial upgrade was successful. + +The CAS service requires changes to your `.env` files - a brief summary of those +changes include: + +- adding `FIFTYONE_AUTH_SECRET` variable which populates to each of the + defined services. +- adding the following CAS Service configuration variables: + - `CAS_BASE_URL` + - `CAS_BIND_ADDRESS` + - `CAS_BIND_PORT` + - `CAS_DATABASE_NAME` + - `CAS_DEBUG` + - `CAS_DEFAULT_USER_ROLE` + +Please review these changes in the +[legacy-auth/env.template](legacy-auth/env.template) and in the appropriate +`legacy-auth/compose*` files. + +To upgrade from versions prior to FiftyOne Teams v1.6: + +- copy your `.env` file into the `legacy-auth` directory +- copy your `compose.override.yaml` file into the `legacy-auth` directory +- `cd` into the `legacy-auth` directory +- update your `.env` file to add the variables listed above +- update your `compose.override.yaml` with `teams-cas` changes (if necessary) +- run `docker compose` commands from the `legacy-auth` directory + +### Snapshot Archival Since version v1.5, FiftyOne Teams supports [archiving snapshots](https://docs.voxel51.com/teams/dataset_versioning.html#snapshot-archival) @@ -135,7 +179,7 @@ See the [configuration documentation](https://docs.voxel51.com/teams/dataset_versioning.html#dataset-versioning-configuration) for other configuration values that control the behavior of automatic snapshot archival. -#### Enabling FiftyOne Teams Authenticated API +### FiftyOne Teams Authenticated API FiftyOne Teams v1.3 introduces the capability to connect FiftyOne Teams SDK through the FiftyOne Teams API (instead of creating a direct connection to MongoDB). @@ -145,7 +189,7 @@ To enable the FiftyOne Teams Authenticated API you will need to and [configure your SDK](https://docs.voxel51.com/teams/api_connection.html). -#### Enabling FiftyOne Teams Plugins +### FiftyOne Teams Plugins FiftyOne Teams v1.3+ includes significant enhancements for [Plugins](https://docs.voxel51.com/plugins/index.html) @@ -157,13 +201,14 @@ There are three modes for plugins - No changes are required for this mode 1. Plugins run in the `fiftyone-app` deployment - To enable this mode, use the file - [./compose.plugins.yaml](./compose.plugins.yaml) + [legacy-auth/compose.plugins.yaml](legacy-auth/compose.plugins.yaml) instead of - [./compose.yaml](./compose.yaml) + [legacy-auth/compose.yaml](legacy-auth/compose.yaml) - Containers need the following access to plugin storage - `fiftyone-app` requires `read` - `fiftyone-api` requires `read-write` - - Example `docker compose` command for this mode + - Example `docker compose` command for this mode from the `legacy-auth` + directory ```shell docker compose \ @@ -174,17 +219,18 @@ There are three modes for plugins 1. Plugins run in a dedicated `teams-plugins` deployment - To enable this mode, use the file - [./compose.dedicated-plugins.yaml](./compose.dedicated-plugins.yaml) + [legacy-auth/compose.dedicated-plugins.yaml](legacy-auth/compose.dedicated-plugins.yaml) instead of - [./compose.yaml](./compose.yaml) + [legacy-auth/compose.yaml](./compose.yaml) - Containers need the following access to plugin storage - `teams-plugins` requires `read` - `fiftyone-api` requires `read-write` - If you are - [using a proxy](#environment-proxies), + [using a proxy](#proxies), add the `teams-plugins` service name to your `no_proxy` and `NO_PROXY` environment variables. - - Example `docker compose` command for this mode + - Example `docker compose` command for this mode from the `legacy-auth` + directory ```shell docker compose \ @@ -194,28 +240,29 @@ There are three modes for plugins ``` Both -[./compose.plugins.yaml](./compose.plugins.yaml) +[legacy-auth/compose.plugins.yaml](legacy-auth/compose.plugins.yaml) and -[./compose.dedicated-plugins.yaml](./compose.dedicated-plugins.yaml) +[legacy-auth/compose.dedicated-plugins.yaml](legacy-auth/compose.dedicated-plugins.yaml) create a new Docker Volume shared between FiftyOne Teams services. For multi-node deployments, please implement a storage solution allowing the access the deployed plugins. If you build plugins that have custom dependencies, you will need to build and use -[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/blob/main/docs/custom-plugins.md) -Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. -Early-adopter plugins installed manually must -be redeployed using the FiftyOne Teams UI. +Use the FiftyOne Teams UI to deploy plugins by navigating to +`https:///settings/plugins`. Early-adopter plugins installed +manually must be redeployed using the FiftyOne Teams UI. -#### Storage Credentials and `FIFTYONE_ENCRYPTION_KEY` +### Storage Credentials and `FIFTYONE_ENCRYPTION_KEY` As of FiftyOne Teams 1.1, containers based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -To generate `FIFTYONE_ENCRYPTION_KEY`, run this Python code +To generate `FIFTYONE_ENCRYPTION_KEY`, run this Python code and add the +output to your `.env` file: ```python from cryptography.fernet import Fernet @@ -239,14 +286,15 @@ to manage storage credentials by navigating to FiftyOne Teams version 1.3+ continues to support the use of environment variables to set storage credentials in the application context and is -providing an alternate configuration path for future functionality. +providing an alternate configuration path. -#### Environment Proxies +### Proxies FiftyOne Teams supports routing traffic through proxy servers. -To configure this, set following environment variables on +To configure this, set following environment variables in your +`compose.override.yaml` -1. All containers +1. All services ```yaml http_proxy: ${HTTP_PROXY_URL} @@ -257,7 +305,7 @@ To configure this, set following environment variables on NO_PROXY: ${NO_PROXY_LIST} ``` -1. All containers based on the `fiftyone-teams-app` and `fiftyone-teams-cas` +1. All services based on the `fiftyone-teams-app` and `fiftyone-teams-cas` images ```yaml @@ -278,19 +326,19 @@ By default these service names are Examples of these settings are included in the FiftyOne Teams configuration files -- [common-services.yaml](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/internal-auth/common-services.yaml) -- [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/internal-auth/env.template) +- [common-services.yaml](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/common-services.yaml) +- [legacy-auth/env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/legacy-auth/env.template) By default, the Global Agent Proxy will log all outbound connections and identify which connections are routed through the proxy. To reduce the logging verbosity, add this environment variable to your `teams-app` and `teams-cas` services. -```ini +```yaml ROARR_LOG: false ``` -#### Text Similarity +### Text Similarity FiftyOne Teams version 1.2 and higher supports using text similarity searches for images that are indexed with a model that @@ -325,23 +373,37 @@ create a new IdP or modify your existing configuration. ### From Before FiftyOne Teams Version 1.1.0 -The FiftyOne 0.16.0 SDK (database version 0.23.5) is _NOT_ backwards-compatible -with FiftyOne Teams Database Versions prior to 0.19.0. -The FiftyOne 0.10.x SDK is not forwards compatible -with current FiftyOne Teams Database Versions. -If you are using a FiftyOne SDK older than 0.11.0, upgrading the -Web server will require upgrading all FiftyOne SDK installations. +> **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires +> upgrading the database and will interrupt all SDK connections. You should +> coordinate this upgrade carefully with your end-users. -Voxel51 recommends this upgrade process from -versions prior to FiftyOne Teams version 1.1.0: +--- + +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Central Authentication Service](#central-authentication-service) +> documentation and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. + +--- -1. Make sure your installation includes the required - [FIFTYONE_ENCRYPTION_KEY](#fiftyone-teams-upgrade-notes) +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. + +1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` + directory +1. `cd` into the `legacy-auth` directory +1. Make sure your `.env` file includes the required + `FIFTYONE_ENCRYPTION_KEY` environment variable +1. Make sure your `.env` file includes the required `FIFTYONE_API_URI` + environment variable +1. Make sure your `.env` file includes the required `FIFTYONE_AUTH_SECRET` environment variable -1. Make sure you include the required `FIFTYONE_API_URI` environment variable - (see - [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/env.template#L17) - for details) 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) with `FIFTYONE_DATABASE_ADMIN=true` (this is not the default for this release). @@ -365,21 +427,35 @@ versions prior to FiftyOne Teams version 1.1.0: ### From FiftyOne Teams Version 1.1.0 and later -The FiftyOne 0.16.0 SDK is backwards-compatible with -FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.6.0 -database (version 0.23.5) with any FiftyOne SDK before 0.16.0. +> **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires +> upgrading the database and will interrupt all SDK connections. You should +> coordinate this upgrade carefully with your end-users. + +--- + +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Central Authentication Service](#central-authentication-service) +> documentation and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. -Voxel51 always recommends using the latest version of the -FiftyOne SDK compatible with your FiftyOne Teams deployment. +--- -Voxel51 recommends the following upgrade process for -upgrading from FiftyOne Teams version 1.1.0 or later: +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in +> after the upgrade is complete. This will interrupt active workflows in the +> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully +> with your end-users. -1. Make sure you include the required `FIFTYONE_API_URI` environment variable - (see - [env.template](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/env.template#L17) - for details) +1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` + directory +1. `cd` into the `legacy-auth` directory +1. Make sure your `.env` file includes the required `FIFTYONE_API_URI` + environment variable +1. Make sure your `.env` file includes the required `FIFTYONE_AUTH_SECRET` + environment variable 1. Ensure all FiftyOne SDK users either - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` @@ -457,13 +533,18 @@ upgrading from FiftyOne Teams version 1.1.0 or later: # FIFTYONE_DATABASE_ADMIN: false ``` -The FiftyOne Teams App is now exposed on port `3000`. +The FiftyOne Teams App is now exposed on port `3000` and the FiftyOne Teams CAS +is now exposed on port `3030`. An SSL endpoint (Load Balancer or Nginx Proxy or something similar) will need to be configured to route traffic from the SSL endpoint -to port `3000` on the host running the FiftyOne Teams App. +to port `3000` on the host running the FiftyOne Teams App and to use path-based +routing to route `/cas` traffic to port `3030` on the host running the FiftyOne +Teams CAS. An example nginx site configuration that forwards http traffic to -https, and https traffic for `your.server.name` to port `3000`. +https, https traffic for `your.server.name/cas` to port `3030`, and https +traffic for `your.server.name` to port `3000` + See [./example-nginx-site.conf](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/example-nginx-site.conf). @@ -471,39 +552,51 @@ See ## FiftyOne Teams Environment Variables -| Variable | Purpose | Required | -|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| `API_BIND_ADDRESS` | The host address that `fiftyone-teams-api` should bind to; `127.0.0.1` is appropriate for this in most cases | Yes | -| `API_BIND_PORT` | The host port that `fiftyone-teams-api` should bind to; the default is `8000` | Yes | -| `API_URL` | The URL that `fiftyone-teams-app` should use to communicate with `fiftyone-teams-api`; `teams-api` is the compose service name | Yes | -| `APP_BIND_ADDRESS` | The host address that `fiftyone-teams-app` should bind to; this should be an externally-facing IP in most cases | Yes | -| `APP_BIND_PORT` | The host port that `fiftyone-teams-app` should bind to the default is `3000` | Yes | -| `APP_USE_HTTPS` | Set this to true if your Application endpoint uses TLS; this should be 'true` in most cases' | Yes | -| `AUTH0_API_CLIENT_ID` | The Auth0 API Client ID from Voxel51 | Yes | -| `AUTH0_API_CLIENT_SECRET` | The Auth0 API Client Secret from Voxel51 | Yes | -| `AUTH0_AUDIENCE` | The Auth0 Audience from Voxel51 | Yes | -| `AUTH0_BASE_URL` | The URL where you plan to deploy your FiftyOne Teams application | Yes | -| `AUTH0_CLIENT_ID` | The Auth0 Application Client ID from Voxel51 | Yes | -| `AUTH0_CLIENT_SECRET` | The Auth0 Application Client Secret from Voxel51 | Yes | -| `AUTH0_DOMAIN` | The Auth0 Domain from Voxel51 | Yes | -| `AUTH0_ISSUER_BASE_URL` | The Auth0 Issuer URL from Voxel51 | Yes | -| `AUTH0_ORGANIZATION` | The Auth0 Organization from Voxel51 | Yes | -| `AUTH0_SECRET` | A random string used to encrypt cookies; use something like `openssl rand -hex 32` to generate this string | Yes | -| `FIFTYONE_APP_ALLOW_MEDIA_EXPORT` | Set this to `"false"` if you want to disable media export options | No | -| `FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION` | The recommended fiftyone SDK version. This will be displayed in install modal (i.e. `pip install ... fiftyone==0.11.0`) | No | -| `FIFTYONE_APP_THEME` | The default theme configuration for your FiftyOne Teams application:
 - `dark`: Application will default to dark theme when user visits for the first time
 - `light`: Application will default to light theme when user visits for the first time
 - `always-dark`: Application will default to dark theme on each refresh (even if user changes theme to light within the app)
 - `always-light`: Application will default to light theme on each refresh (even if user changes theme to dark within the app) | No | -| `FIFTYONE_BASE_DIR` | This will be mounted as `/fiftyone` in the `fiftyone-teams-app` container and can be used to pass cloud storage credentials into the environment | No | -| `FIFTYONE_DEFAULT_APP_ADDRESS` | The host address that `fiftyone-app` should bind to; `127.0.0.1` is appropriate for this in most cases | Yes | -| `FIFTYONE_DEFAULT_APP_PORT` | The host port that `fiftyone-app` should bind to; the default is `5151` | Yes | -| `FIFTYONE_ENCRYPTION_KEY` | Used to encrypt storage credentials in MongoDB | Yes | -| `FIFTYONE_ENV` | GraphQL verbosity for the `fiftyone-teams-api` service; `production` will not log every GraphQL query, any other value will | No | -| `FIFTYONE_PLUGINS_DIR` | Persistent directory for plugins to be stored in. `teams-api` must have write access to this directory, all plugin nodes must have read access to this directory. | No | -| `FIFTYONE_SNAPSHOTS_ARCHIVE_PATH` | Full path to network-mounted file system or a cloud storage path to use for snapshot archive storage. The default `None` means archival is disabled. | No | -| `FIFTYONE_SNAPSHOTS_MAX_IN_DB` | The max total number of Snapshots allowed at once. -1 for no limit. If this limit is exceeded then automatic archival is triggered if enabled, otherwise an error is raised. | No | -| `FIFTYONE_SNAPSHOTS_MAX_PER_DATASET` | The max number of Snapshots allowed per dataset. -1 for no limit. If this limit is exceeded then automatic archival is triggered if enabled, otherwise an error is raised. | No | -| `FIFTYONE_SNAPSHOTS_MIN_LAST_LOADED_SEC` | The minimum last-loaded age in seconds (as defined by `now-last_loaded_at`) a snapshot must meet to be considered for automatic archival. This limit is intended to help curtail automatic archival of a snapshot a user is actively working with. The default value is 1 day. | No | -| `FIFTYONE_TEAMS_PROXY_URL` | The URL that `fiftyone-teams-app` will use to proxy requests to `fiftyone-app` | Yes | -| `GRAPHQL_DEFAULT_LIMIT` | Default GraphQL limit for results | No | -| `HTTP_PROXY_URL` | The URL for your environment http proxy | No | -| `HTTPS_PROXY_URL` | The URL for your environment https proxy | No | -| `NO_PROXY_LIST` | The list of servers that should bypass the proxy; if a proxy is in use this must include the list of FiftyOne services (`teams-api, teams-app, fiftyone-app`) | No | +| Variable | Purpose | Required | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | +| `API_BIND_ADDRESS` | The host address that `fiftyone-teams-api` should bind to; `127.0.0.1` is appropriate for this in most cases | Yes | +| `API_BIND_PORT` | The host port that `fiftyone-teams-api` should bind to; the default is `8000` | Yes | +| `API_LOGGING_LEVEL` | Logging Level for `teams-api` service | Yes | +| `API_URL` | The URL that `fiftyone-teams-app` should use to communicate with `fiftyone-teams-api`; `teams-api` is the compose service name | Yes | +| `APP_BIND_ADDRESS` | The host address that `fiftyone-teams-app` should bind to; `127.0.0.1` is appropriate in most cases | Yes | +| `APP_BIND_PORT` | The host port that `fiftyone-teams-app` should bind to the default is `3000` | Yes | +| `APP_USE_HTTPS` | Set this to true if your Application endpoint uses TLS; this should be `true` in most cases' | Yes | +| `AUTH0_API_CLIENT_ID` | The Auth0 API Client ID from Voxel51 | `legacy` auth mode only | +| `AUTH0_API_CLIENT_SECRET` | The Auth0 API Client Secret from Voxel51 | `legacy` auth mode only | +| `AUTH0_AUDIENCE` | The Auth0 Audience from Voxel51 | `legacy` auth mode only | +| `AUTH0_BASE_URL` | The URL where you plan to deploy your FiftyOne Teams application | `legacy` auth mode only | +| `AUTH0_CLIENT_ID` | The Auth0 Application Client ID from Voxel51 | `legacy` auth mode only | +| `AUTH0_CLIENT_SECRET` | The Auth0 Application Client Secret from Voxel51 | `legacy` auth mode only | +| `AUTH0_DOMAIN` | The Auth0 Domain from Voxel51 | `legacy` auth mode only | +| `AUTH0_ISSUER_BASE_URL` | The Auth0 Issuer URL from Voxel51 | `legacy` auth mode only | +| `AUTH0_ORGANIZATION` | The Auth0 Organization from Voxel51 | `legacy` auth mode only | +| `AUTH0_SECRET` | A random string used to encrypt cookies; use something like `openssl rand -hex 32` to generate this string | `legacy` auth mode only | +| `BASE_URL` | The URL where you plan to deploy your FiftyOne Teams | `internal` auth mode only | +| `CAS_BASE_URL` | The URL that FiftyOne Teams Services should use to communicate with `teams-cas`; `teams-cas` is the compose service | Yes | +| `CAS_BIND_ADDRESS` | The host address that `teams-cas` should bind to; `127.0.0.1` is appropriate in most cases | Yes | +| `CAS_BIND_PORT` | The host port that `teams-cas` should bind to; the default is `3030` | Yes | +| `CAS_DATABASE_NAME` | The MongoDB Database that the `teams-cas` service should use; the default is `fiftyone-cas` | Yes | +| `CAS_DEBUG` | The logs that `teams-cas` should provide to stdout; see [debug](https://www.npmjs.com/package/debug) for documentation | Yes | +| `CAS_DEFAULT_USER_ROLE` | The default role when users initially log into the FiftyOne Teams application; the default is `GUEST` | Yes | +| `CAS_MONGODB_URI` | The MongoDB Connection STring for CAS; this will default to `FIFTYONE_DATABASE_URI` | No | +| `FIFTYONE_APP_ALLOW_MEDIA_EXPORT` | Set this to `"false"` if you want to disable media export options | No | +| `FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION` | The recommended fiftyone SDK version. This will be displayed in install modal (i.e. `pip install ... fiftyone==0.11.0`) | No | +| `FIFTYONE_APP_THEME` | The default theme configuration for your FiftyOne Teams application as described [here](https://docs.voxel51.com/user_guide/config.html#configuring-the-app) | No | +| `FIFTYONE_API_URI` | The URI to be displayed in the `Install FiftyOne` Modal and `API Keys` configuration screens | No | +| `FIFTYONE_AUTH_SECRET` | The secret used for services to authenticate with `teams-cas`; also used to login to the SuperAdmin UI | Yes | +| `FIFTYONE_DATABASE_NAME` | The MongoDB Database that `fiftyone-app`, `teams-api`, and `teams-app` use for FiftyOne Teams dataset metadata; the default is `fiftyone` | Yes | +| `FIFTYONE_DATABASE_URI` | The MongoDB Connection String for FiftyOne Teams dataset metadata | Yes | +| `FIFTYONE_DEFAULT_APP_ADDRESS` | The host address that `fiftyone-app` should bind to; `127.0.0.1` is appropriate in most cases | Yes | +| `FIFTYONE_DEFAULT_APP_PORT` | The host port that `fiftyone-app` should bind to; the default is `5151` | Yes | +| `FIFTYONE_ENCRYPTION_KEY` | Used to encrypt storage credentials in MongoDB | Yes | +| `FIFTYONE_ENV` | GraphQL verbosity for the `fiftyone-teams-api` service; `production` will not log every GraphQL query, any other value will | No | +| `FIFTYONE_PLUGINS_DIR` | Persistent directory inside the containers for plugins to be mounted to. `teams-api` must have write access to this directory, all plugin nodes must have read access to this directory. | No | +| `FIFTYONE_SNAPSHOTS_ARCHIVE_PATH` | Full path to network-mounted file system or a cloud storage path to use for snapshot archive storage. The default `None` means archival is disabled. | No | +| `FIFTYONE_SNAPSHOTS_MAX_IN_DB` | The max total number of Snapshots allowed at once. -1 for no limit. If this limit is exceeded then automatic archival is triggered if enabled, otherwise an error is raised. | No | +| `FIFTYONE_SNAPSHOTS_MAX_PER_DATASET` | The max number of Snapshots allowed per dataset. -1 for no limit. If this limit is exceeded then automatic archival is triggered if enabled, otherwise an error is raised. | No | +| `FIFTYONE_SNAPSHOTS_MIN_LAST_LOADED_SEC` | The minimum last-loaded age in seconds (as defined by `now-last_loaded_at`) a snapshot must meet to be considered for automatic archival. This limit is intended to help curtail automatic archival of a snapshot a user is actively working with. The default value is 1 day. | No | +| `FIFTYONE_TEAMS_PROXY_URL` | The URL that `fiftyone-teams-app` will use to proxy requests to `fiftyone-app` | Yes | +| `GRAPHQL_DEFAULT_LIMIT` | Default GraphQL limit for results | No | +| `HTTP_PROXY_URL` | The URL for your environment http proxy | No | +| `HTTPS_PROXY_URL` | The URL for your environment https proxy | No | +| `NO_PROXY_LIST` | The list of servers that should bypass the proxy; if a proxy is in use this must include the list of FiftyOne services (`fiftyone-app, teams-api,teams-app,teams-cas` must be included, `teams-plugins` should be included for dedicated plugins configurations) | No | diff --git a/docker/common-services.yaml b/docker/common-services.yaml index cad8fb9f..8424443a 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -32,7 +32,6 @@ services: image: voxel51/fiftyone-teams-api:v1.6.0-beta.2 environment: CAS_BASE_URL: ${CAS_BASE_URL} - FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_AUTH_SECRET: ${FIFTYONE_AUTH_SECRET} FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index 568aaccf..6b34bcdf 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -9,6 +9,7 @@ services: teams-api: environment: + FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins extends: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index ab116a4c..4d5fb012 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -14,6 +14,7 @@ services: teams-api: environment: + FEATURE_FLAG_ENABLE_INVITATIONS: false FIFTYONE_PLUGINS_CACHE_ENABLED: true FIFTYONE_PLUGINS_DIR: /opt/plugins extends: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index 89658501..ee764c9f 100644 --- a/docker/internal-auth/compose.yaml +++ b/docker/internal-auth/compose.yaml @@ -8,6 +8,8 @@ services: service: fiftyone-app-common teams-api: + environment: + FEATURE_FLAG_ENABLE_INVITATIONS: false extends: file: ../common-services.yaml service: teams-api-common diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index f2a1b163..7304d50b 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -25,7 +25,8 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. - [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) -- [FiftyOne Features](#fiftyone-features) +- [FiftyOne Teams Features](#fiftyone-teams-features) + - [Central Authentication Service](#central-authentication-service) - [Snapshot Archival](#snapshot-archival) - [FiftyOne Teams Authenticated API](#fiftyone-teams-authenticated-api) - [FiftyOne Teams Plugins](#fiftyone-teams-plugins) @@ -41,21 +42,21 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. -We publish container images to these Docker Hub repositories +We publish the following FiftyOne Teams private images to Docker Hub: - `voxel51/fiftyone-app` - `voxel51/fiftyone-app-gpt` - `voxel51/fiftyone-app-torch` - `voxel51/fiftyone-teams-api` - `voxel51/fiftyone-teams-app` +- `voxel51/fiftyone-teams-cas` For Docker Hub credentials, please contact your Voxel51 support team. ## Initial Installation vs. Upgrades Upgrades are more frequent than new installations. -Thus, the chart's default behavior supports -upgrades and the `values.yaml` contains +The chart's default behavior supports upgrades and the `values.yaml` contains ```yaml appSettings: @@ -78,10 +79,32 @@ this environment variable or changing the value to `false`. When performing an upgrade, please review [Upgrading From Previous Versions](#upgrading-from-previous-versions). -## FiftyOne Features +## FiftyOne Teams Features Consider if you will require these settings for your deployment. +### Central Authentication Service + +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which +requires additional configurations and consumes additional resources. Please +review these notes, and the +[Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +documentation before completing your upgrade. + +Voxel51 recommends upgrading your deployment using +[`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +and +[migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +to +[`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +after confirming your initial upgrade was successful. + +The CAS service requires changes to your `values.yaml` files - a brief summary +of those changes include: + +- adding a `fiftyoneAuthSecret` secret to `secret.fiftyone` or the secret + defined by `secret.name` + ### Snapshot Archival Since version v1.5, FiftyOne Teams supports @@ -170,9 +193,9 @@ There are three modes for plugins If you build plugins that have custom dependencies, you will need to build and use -[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/blob/main/docs/custom-plugins.md) -Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. +Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. Early-adopter plugins installed manually must be redeployed using the FiftyOne Teams UI. @@ -182,7 +205,8 @@ Pods based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -The generate an `encryptionKey`, run this Python code +To generate a `secret.fiftyone.encryptionKey`, run this Python code and add the +output to your `values.yaml` override file, or to your deployment's secret: ```python from cryptography.fernet import Fernet @@ -206,7 +230,7 @@ mounted into pods or provided via environment variables. FiftyOne Teams continues to support the use of environment variables to set storage credentials in the application context and is providing an alternate -configuration path for future functionality. +configuration path. ### Proxies @@ -496,7 +520,7 @@ want to carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. -> **NOTE**: FiftyOne Teams v1.6.0 introduces the +> **NOTE**: FiftyOne Teams v1.6 introduces the > Central Authentication Service (CAS) which requires additional configurations > and consumes additional resources. Please review the upgrade instructions > , the @@ -507,10 +531,10 @@ and update your deployment accordingly. ### From Early Adopter Versions (Versions less than 1.0) -Please contact your Voxel51 Customer Success -team member to coordinate this upgrade. -You will need to either create a new Identity Provider (IdP) -or modify your existing configuration to migrate to a new Auth0 Tenant. +Please contact your Voxel51 Customer Success team member to coordinate this +upgrade. +You will need to either create a new Identity Provider (IdP) or modify your +existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 @@ -520,27 +544,27 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. --- +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Central Authentication Service](#central-authentication-service) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. + +--- + > **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in > after the upgrade is complete. This will interrupt active workflows in the > FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully > with your end-users. ---- - -> **NOTE**: Voxel51 recommends upgrading your deployment using -> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -> and -> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -> to -> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) -> after confirming your initial upgrade was successful. - -1. In your `values.yaml`, make sure the required - [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) - (or your deployment's equivalent) is set, which will set the +1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods 1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will set the + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. This is not the default value in the Helm Chart and must be overridden. @@ -557,7 +581,7 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. fiftyone migrate --info ``` - - If not all datasets have been upgraded, have an admin run + - If not all datasets have been upgraded, upgrade all the datasets ```shell FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all @@ -572,51 +596,40 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. --- -> **NOTE**: Voxel51 recommends upgrading your deployment using -> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -> and -> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -> to -> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) -> after confirming your initial upgrade was successful. - -The FiftyOne 0.16.0 SDK is backwards-compatible with -FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.6.0 -database (version 0.23.5) with any FiftyOne SDK before 0.16.0. - -We recommend using the latest version of the FiftyOne SDK -compatible with your FiftyOne Teams deployment. - -We recommend the following upgrade process for -upgrading from FiftyOne Teams version 1.1.0 or later: +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. 1. Ensure all FiftyOne SDK users either - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. In your `values.yaml`, make sure the required - [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) - (or your deployment's equivalent) is set, which will set the +1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods 1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will set the + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` -1. Have an admin run to upgrade all datasets +1. Upgrade all the datasets ```shell FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose database connectivity - > at this point. Upgrading to `fiftyone==0.16.0` is required + > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose connectivity at this + > point. Upgrading to `fiftyone==0.16.0` is required. -1. Validate that all datasets are now at version 0.23.5, by running +1. Validate that all datasets are now at version 0.23.5 ```shell fiftyone migrate --info diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 473998d5..9f8ea621 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -27,7 +27,8 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. - [Initial Installation vs. Upgrades](#initial-installation-vs-upgrades) -- [FiftyOne Features](#fiftyone-features) +- [FiftyOne Teams Features](#fiftyone-teams-features) + - [Central Authentication Service](#central-authentication-service) - [Snapshot Archival](#snapshot-archival) - [FiftyOne Teams Authenticated API](#fiftyone-teams-authenticated-api) - [FiftyOne Teams Plugins](#fiftyone-teams-plugins) @@ -43,21 +44,21 @@ Please contact Voxel51 for more information regarding Fiftyone Teams. -We publish container images to these Docker Hub repositories +We publish the following FiftyOne Teams private images to Docker Hub: - `voxel51/fiftyone-app` - `voxel51/fiftyone-app-gpt` - `voxel51/fiftyone-app-torch` - `voxel51/fiftyone-teams-api` - `voxel51/fiftyone-teams-app` +- `voxel51/fiftyone-teams-cas` For Docker Hub credentials, please contact your Voxel51 support team. ## Initial Installation vs. Upgrades Upgrades are more frequent than new installations. -Thus, the chart's default behavior supports -upgrades and the `values.yaml` contains +The chart's default behavior supports upgrades and the `values.yaml` contains ```yaml appSettings: @@ -80,10 +81,32 @@ this environment variable or changing the value to `false`. When performing an upgrade, please review [Upgrading From Previous Versions](#upgrading-from-previous-versions). -## FiftyOne Features +## FiftyOne Teams Features Consider if you will require these settings for your deployment. +### Central Authentication Service + +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which +requires additional configurations and consumes additional resources. Please +review these notes, and the +[Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +documentation before completing your upgrade. + +Voxel51 recommends upgrading your deployment using +[`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) +and +[migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) +to +[`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) +after confirming your initial upgrade was successful. + +The CAS service requires changes to your `values.yaml` files - a brief summary +of those changes include: + +- adding a `fiftyoneAuthSecret` secret to `secret.fiftyone` or the secret + defined by `secret.name` + ### Snapshot Archival Since version v1.5, FiftyOne Teams supports @@ -172,9 +195,9 @@ There are three modes for plugins If you build plugins that have custom dependencies, you will need to build and use -[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/docs/custom-plugins.md) +[Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/blob/main/docs/custom-plugins.md) -Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. +Use the FiftyOne Teams UI to deploy plugins by navigating to `https:///settings/plugins`. Early-adopter plugins installed manually must be redeployed using the FiftyOne Teams UI. @@ -184,7 +207,8 @@ Pods based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -The generate an `encryptionKey`, run this Python code +To generate a `secret.fiftyone.encryptionKey`, run this Python code and add the +output to your `values.yaml` override file, or to your deployment's secret: ```python from cryptography.fernet import Fernet @@ -208,7 +232,7 @@ mounted into pods or provided via environment variables. FiftyOne Teams continues to support the use of environment variables to set storage credentials in the application context and is providing an alternate -configuration path for future functionality. +configuration path. ### Proxies @@ -321,7 +345,7 @@ want to carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. -> **NOTE**: FiftyOne Teams v1.6.0 introduces the +> **NOTE**: FiftyOne Teams v1.6 introduces the > Central Authentication Service (CAS) which requires additional configurations > and consumes additional resources. Please review the upgrade instructions > , the @@ -332,10 +356,10 @@ and update your deployment accordingly. ### From Early Adopter Versions (Versions less than 1.0) -Please contact your Voxel51 Customer Success -team member to coordinate this upgrade. -You will need to either create a new Identity Provider (IdP) -or modify your existing configuration to migrate to a new Auth0 Tenant. +Please contact your Voxel51 Customer Success team member to coordinate this +upgrade. +You will need to either create a new Identity Provider (IdP) or modify your +existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 @@ -345,27 +369,27 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. --- +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Central Authentication Service](#central-authentication-service) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. + +--- + > **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in > after the upgrade is complete. This will interrupt active workflows in the > FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully > with your end-users. ---- - -> **NOTE**: Voxel51 recommends upgrading your deployment using -> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -> and -> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -> to -> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) -> after confirming your initial upgrade was successful. - -1. In your `values.yaml`, make sure the required - [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) - (or your deployment's equivalent) is set, which will set the +1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods 1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will set the + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. This is not the default value in the Helm Chart and must be overridden. @@ -382,7 +406,7 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. fiftyone migrate --info ``` - - If not all datasets have been upgraded, have an admin run + - If not all datasets have been upgraded, upgrade all the datasets ```shell FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all @@ -397,52 +421,40 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. --- -> **NOTE**: Voxel51 recommends upgrading your deployment using -> [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -> and -> [migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -> to -> [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) -> after confirming your initial upgrade was successful. - - -The FiftyOne 0.16.0 SDK is backwards-compatible with -FiftyOne Teams Database Versions 0.19.0 and later. -You will not be able to connect to a FiftyOne Teams 1.6.0 -database (version 0.23.5) with any FiftyOne SDK before 0.16.0. - -We recommend using the latest version of the FiftyOne SDK -compatible with your FiftyOne Teams deployment. - -We recommend the following upgrade process for -upgrading from FiftyOne Teams version 1.1.0 or later: +> **NOTE**: FiftyOne Teams v1.6 introduces the +> Central Authentication Service (CAS) which requires additional configurations +> and consumes additional resources. Please review the upgrade instructions +> , the +> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) +> and the +> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) +> documentation before completing your upgrade. 1. Ensure all FiftyOne SDK users either - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. In your `values.yaml`, make sure the required - [secret.fiftyone.encryptionKey](#storage-credentials-and-fiftyone_encryption_key) - (or your deployment's equivalent) is set, which will set the +1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods 1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will set the + (or your deployment's equivalent) is set, which will configure the `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI - To obtain the CLI command to install the FiftyOne SDK associated with your FiftyOne Teams version, navigate to `Account > Install FiftyOne` -1. Have an admin run to upgrade all datasets +1. Upgrade all the datasets ```shell FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose database connectivity - > at this point. Upgrading to `fiftyone==0.16.0` is required + > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose connectivity at this + > point. Upgrading to `fiftyone==0.16.0` is required. -1. Validate that all datasets are now at version 0.23.5, by running +1. Validate that all datasets are now at version 0.23.5 ```shell fiftyone migrate --info From 78bd3d946dab65fcc589166a27104fdfcbb8f353 Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 11 Apr 2024 19:25:43 -0400 Subject: [PATCH 26/42] fix `./compose` mistakes --- docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index 6f1daae2..348be36a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -162,7 +162,7 @@ Supported locations are network mounted filesystems and cloud storage folders. - Mount the filesystem to the `fiftyone-api` container (`teams-app` does not need this despite the variable set above). For an example, see - [./compose.plugins.yaml](./compose.plugins.yaml). + [legacy-auth/compose.plugins.yaml](legacy-auth/compose.plugins.yaml). - Cloud storage folder - Set the environment variable `FIFTYONE_SNAPSHOTS_ARCHIVE_PATH` to a cloud storage path (for example @@ -221,7 +221,7 @@ There are three modes for plugins - To enable this mode, use the file [legacy-auth/compose.dedicated-plugins.yaml](legacy-auth/compose.dedicated-plugins.yaml) instead of - [legacy-auth/compose.yaml](./compose.yaml) + [legacy-auth/compose.yaml](legacy-auth/compose.yaml) - Containers need the following access to plugin storage - `teams-plugins` requires `read` - `fiftyone-api` requires `read-write` From 16e0aceb36e1907191614ab2574ffb5ba2a0526f Mon Sep 17 00:00:00 2001 From: topher Date: Thu, 11 Apr 2024 19:30:51 -0400 Subject: [PATCH 27/42] fixes some links --- helm/fiftyone-teams-app/README.md | 15 +++------------ helm/fiftyone-teams-app/README.md.gotmpl | 15 +++------------ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 7304d50b..ef61399c 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -520,15 +520,6 @@ want to carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the -> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) -> and the -> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) -> documentation before completing your upgrade. - ### From Early Adopter Versions (Versions less than 1.0) Please contact your Voxel51 Customer Success team member to coordinate this @@ -549,7 +540,7 @@ existing configuration to migrate to a new Auth0 Tenant. > and consumes additional resources. Please review the upgrade instructions > , the > [Central Authentication Service](#central-authentication-service) -> and the +> notes and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. @@ -600,8 +591,8 @@ existing configuration to migrate to a new Auth0 Tenant. > Central Authentication Service (CAS) which requires additional configurations > and consumes additional resources. Please review the upgrade instructions > , the -> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) -> and the +> [Central Authentication Service](#central-authentication-service) +> notes and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 9f8ea621..bfac0611 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -345,15 +345,6 @@ want to carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the -> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) -> and the -> [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) -> documentation before completing your upgrade. - ### From Early Adopter Versions (Versions less than 1.0) Please contact your Voxel51 Customer Success team member to coordinate this @@ -374,7 +365,7 @@ existing configuration to migrate to a new Auth0 Tenant. > and consumes additional resources. Please review the upgrade instructions > , the > [Central Authentication Service](#central-authentication-service) -> and the +> notes and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. @@ -425,8 +416,8 @@ existing configuration to migrate to a new Auth0 Tenant. > Central Authentication Service (CAS) which requires additional configurations > and consumes additional resources. Please review the upgrade instructions > , the -> [Helm Chart Diff](https://github.com/voxel51/fiftyone-teams-app-deploy/compare/fiftyone-teams-app-1.5.8...fiftyone-teams-app-1.6.0) -> and the +> [Central Authentication Service](#central-authentication-service) +> notes and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. From 6ecfe53c8b1c7c67bcf5433e025070ed65e42a9d Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 11 Apr 2024 16:50:27 -0700 Subject: [PATCH 28/42] test: add intergration tests for docker compose in inernal auth mode. Fix a few things about legacy auth to make room for internal auth. --- .../docker/integration_internal_auth.env | 30 ++ .../docker/integration_legacy_auth.env | 2 +- tests/integration/compose/common_test.go | 7 +- .../docker-compose-internal-auth_test.go | 278 ++++++++++++++++++ .../docker-compose-legacy-auth_test.go | 4 +- 5 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/docker/integration_internal_auth.env create mode 100644 tests/integration/compose/docker-compose-internal-auth_test.go diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env new file mode 100644 index 00000000..2943d37d --- /dev/null +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -0,0 +1,30 @@ +# Data for Integration Tests for Internal Auth Mode + +# FiftyOne +FIFTYONE_DATABASE_URI="mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin" # This is a randomly generated password +FIFTYONE_ENCRYPTION_KEY="btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=" # This is a randomly generated string + +# Internal Auth +FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" + +# MongoDB +MONGODB_PASSWORD="3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji" # This is a randomly generated string # pragma: allowlist secret +MONGODB_USERNAME=root +MONGODB_DIR=/tmp/mongodb +MONGODB_BIND_ADDRESS=127.0.0.1 + +APP_USE_HTTPS=false +FIFTYONE_API_URI=http://local.fiftyone.ai +BASE_URL=http://local.fiftyone.ai + +# Show debug logs +API_LOGGING_LEVEL=DEBUG +FIFTYONE_ENV=development +CAS_DEBUG="cas:*" + +# Image tags +VERSION=v1.6.0 +FIFTYONE_APP_RC=${VERSION}rc14 +FIFTYONE_TEAMS_API_RC=${VERSION}rc13 +FIFTYONE_TEAMS_APP_RC=${VERSION}-rc.11 +FIFTYONE_TEAMS_CAS_RC=${VERSION}-rc.11 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index 815d8311..3eb50699 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -1,4 +1,4 @@ -# Data for Integration Tests +# Data for Integration Tests for Legacy Auth Mode # Auth0 # CAS doesn't validate the Auth0 Legacy Mode values when it starts. diff --git a/tests/integration/compose/common_test.go b/tests/integration/compose/common_test.go index 692c7976..ed0819e3 100644 --- a/tests/integration/compose/common_test.go +++ b/tests/integration/compose/common_test.go @@ -1,5 +1,5 @@ -//go:build docker || compose || integration || integrationComposeInternalAuth || integrationComposeLegacyAuth -// +build docker compose integration integrationComposeInternalAuth integrationComposeLegacyAuth +//go:build docker || compose || integration || integrationComposeInternalAuth || integrationComposeLegacyAuth || integrationComposeInternalAuth +// +build docker compose integration integrationComposeInternalAuth integrationComposeLegacyAuth integrationComposeInternalAuth package integration @@ -12,7 +12,8 @@ import ( ) const ( - envFixtureFilePath = "../../fixtures/docker/integration_legacy_auth.env" + legacyAuthEnvFixtureFilePath = "../../fixtures/docker/integration_legacy_auth.env" + internalAuthFixtureEnvFilePath = "../../fixtures/docker/integration_internal_auth.env" ) func validate_endpoint(t *testing.T, url string, expectedBody string, expectedStatus int) { diff --git a/tests/integration/compose/docker-compose-internal-auth_test.go b/tests/integration/compose/docker-compose-internal-auth_test.go new file mode 100644 index 00000000..fecebbda --- /dev/null +++ b/tests/integration/compose/docker-compose-internal-auth_test.go @@ -0,0 +1,278 @@ +//go:build docker || compose || integration || integrationComposeInternalAuth +// +build docker compose integration integrationComposeInternalAuth + +package integration + +import ( + "fmt" + "runtime" + "strings" + "testing" + + "path/filepath" + + "github.com/gruntwork-io/terratest/modules/docker" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/random" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/compose-spec/compose-go/v2/dotenv" +) + +const ( + dockerInternalAuthDir = "../../../docker/internal-auth" + // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) + overrideFile = "../../tests/fixtures/docker/compose.override.yaml" + // To run the containers on macOS arm64, we need to set the platform + darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" + mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" +) + +var internalAuthComposeFile = "compose.yaml" +var internalAuthComposePluginsFile = "compose.plugins.yaml" +var internalAuthComposeDedicatedPluginsFile = "compose.dedicated-plugins.yaml" +var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") + +type commonServicesInternalAuthDockerComposeUpTest struct { + suite.Suite + composeFilePath string + dotEnvFiles []string + overrideFiles []string +} + +func TestDockerComposeUpInternalAuth(t *testing.T) { + t.Parallel() + + _, err := filepath.Abs(dockerInternalAuthDir) + require.NoError(t, err) + + // Set the override files used for the tests + overrideFiles := []string{ + overrideFile, + mongodbComposeFile, + } + + // To run the containers on macOS arm64, we need to set the platform + if runtime.GOOS == "darwin" { + overrideFiles = append(overrideFiles, darwinOverrideFile) + } + + suite.Run(t, &commonServicesInternalAuthDockerComposeUpTest{ + Suite: suite.Suite{}, + composeFilePath: dockerInternalAuthDir, + dotEnvFiles: []string{ + internalAuthEnvTemplateFilePath, + internalAuthFixtureEnvFilePath, + }, + overrideFiles: overrideFiles, + }) +} + +type serviceValidations struct { + name string + url string + responsePayload string + httpResponseCode int + log string +} + +func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { + testCases := []struct { + name string + composeFile string + overrideFiles []string + envFiles []string // file paths to ".env" files with additional environment variable data + expected []serviceValidations + }{ + { + "compose", + internalAuthComposeFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 200, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + }, + }, + { + "composePlugins", + internalAuthComposePluginsFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 200, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + }, + }, + { + "composeDedicatedPlugins", + internalAuthComposeDedicatedPluginsFile, + s.overrideFiles, + s.dotEnvFiles, + []serviceValidations{ + { + name: "teams-api", + url: "http://127.0.0.1:8000/health", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: "[INFO] Starting worker", + }, + { + name: "teams-app", + url: "http://127.0.0.1:3000/api/hello", + responsePayload: `{"name":"John Doe"}`, + httpResponseCode: 200, + log: "Listening on port 3000", + }, + { + name: "teams-cas", + url: "http://127.0.0.1:3030/cas/api", + responsePayload: `{"status":"available"}`, + httpResponseCode: 200, + log: " ✓ Ready in", + }, + // ordering this last to avoid test flakes where testing for log before the container is running + { + name: "fiftyone-app", + url: "", + responsePayload: "", + httpResponseCode: 0, + log: "[INFO] Running on http://0.0.0.0:5151", + }, + { + name: "teams-plugins", + url: "", + responsePayload: "", + httpResponseCode: 0, + log: "[INFO] Running on http://0.0.0.0:5151", // same as fiftyone-app since plugins uses or is based on the fiftyone-app image + }, + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + s.Run(testCase.name, func() { + subT := s.T() + // TODO: If we need parallel, dynamically set mongoDB port and configure the env vars with the custom port + // For now, we cannot perform concurrent runs because mongodb will error on port already in use + // subT.Parallel() + + // TODO: Should we use `--env-file` instead of the library `dotenv.Read` + // to more closely align to real world usage? + // Something like + // + // ```shell + // docker compose \ + // -f tests/fixtures/docker/compose.override.mongodb.yaml \ + // --env-file tests/fixtures/docker/integration_internal_auth.env + + // up -d + // ``` + // + // Use existing function to get map[string]string of environment variables from .env file(s) + environmentVariables, err := dotenv.Read(s.dotEnvFiles...) + s.NoError(err) + + dockerOptions := &docker.Options{ + ProjectName: "fiftyone-" + strings.ToLower(random.UniqueId()), + WorkingDir: dockerInternalAuthDir, + EnvVars: environmentVariables, + } + + // In golang, we cannot mix strings with string slice unpacking. + // Let's create a slice that will later be unpacked and used as an argument + // to the variadic function `docker.RunDockerCompose` parameter `args`. + argsUp := []string{} + argsDown := []string{} + args := []string{"-f", testCase.composeFile} + + for _, overrideFile := range s.overrideFiles { + args = append(args, "-f", overrideFile) + } + + argsUp = append(args, "up", "--detach") + argsDown = append(args, "down", "--remove-orphans", "--timeout", "2") + + // Run containers + output := docker.RunDockerCompose( + subT, + dockerOptions, + argsUp..., + ) + // Delete containers after tests complete + defer docker.RunDockerCompose( + subT, + dockerOptions, + argsDown..., + ) + + // Validate system health + for _, expected := range testCase.expected { + logger.Log(subT, fmt.Sprintf("Validating service %s...", expected.name)) + s.Contains(output, fmt.Sprintf("Container %s-%s-1 Started", dockerOptions.ProjectName, expected.name), fmt.Sprintf("%s - %s - docker compose output should contain service container started", testCase.name, expected.name)) + if expected.url != "" { + validate_endpoint(subT, expected.url, expected.responsePayload, expected.httpResponseCode) + } + s.Contains(get_logs(subT, dockerOptions, expected.name), expected.log, fmt.Sprintf("%s - %s - log should contain matching entry", testCase.name, expected.name)) + } + }) + } +} diff --git a/tests/integration/compose/docker-compose-legacy-auth_test.go b/tests/integration/compose/docker-compose-legacy-auth_test.go index 2df2b6ad..258871b6 100644 --- a/tests/integration/compose/docker-compose-legacy-auth_test.go +++ b/tests/integration/compose/docker-compose-legacy-auth_test.go @@ -38,7 +38,6 @@ var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.temp type commonServicesLegacyAuthDockerComposeUpTest struct { suite.Suite composeFilePath string - projectName string dotEnvFiles []string overrideFiles []string } @@ -63,10 +62,9 @@ func TestDockerComposeUpLegacyAuth(t *testing.T) { suite.Run(t, &commonServicesLegacyAuthDockerComposeUpTest{ Suite: suite.Suite{}, composeFilePath: dockerLegacyAuthDir, - projectName: "fiftyone-compose-integration-test", dotEnvFiles: []string{ legacyAuthEnvTemplateFilePath, - envFixtureFilePath, + legacyAuthEnvFixtureFilePath, }, overrideFiles: overrideFiles, }) From 2d0637e7d52c504cb28500673e27bf2e5a88a1ec Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 11 Apr 2024 17:31:25 -0700 Subject: [PATCH 29/42] test: fixed make files and tests to allow running both tests. --- Makefile | 43 +++++++++++-------- tests/integration/compose/common_test.go | 13 ++++++ .../docker-compose-internal-auth_test.go | 13 ------ .../docker-compose-legacy-auth_test.go | 13 ------ 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 728a842d..b0d807ce 100644 --- a/Makefile +++ b/Makefile @@ -101,23 +101,21 @@ helm-repos: ## add helm repos for the project clean: clean-unit-compose clean-unit-helm clean-integration-compose clean-integration-helm ## delete all test output and reports clean-integration-compose: ## delete docker compose integration test output and reports - rm -rf tests/integration/compose/test_output || true - rm -rf tests/integration/compose/test_reports || true + rm -rf tests/integration/compose/test_output_internal || true + rm -rf tests/integration/compose/test_output_legacy || true rm tests/integration/compose/test_output.log || true clean-integration-helm: ## delete helm integration test output and reports - rm -rf tests/integration/helm/test_output || true - rm -rf tests/integration/helm/test_reports || true + rm -rf tests/integration/helm/test_output_internal || true + rm -rf tests/integration/helm/test_output_legacy || true rm tests/integration/helm/test_output.log || true clean-unit-compose: ## delete docker compose unit test output and reports rm -rf tests/unit/compose/test_output || true - rm -rf tests/unit/compose/test_reports || true rm tests/unit/compose/test_output.log || true clean-unit-helm: ## delete helm unit test output and reports rm -rf tests/unit/helm/test_output || true - rm -rf tests/unit/helm/test_reports || true rm tests/unit/helm/test_output.log || true dependencies-integration-compose: ## create a (temporary) directory for mongodb container @@ -138,28 +136,39 @@ test-unit-helm: ## run go test on the tests/unit/helm directory test-unit-compose-interleaved: install-terratest-log-parser ## run go test on the tests/unit/compose directory and run the terratest_log_parser for reports @cd tests/unit/compose; \ - rm -rf test_reports; \ - mkdir test_reports; \ + rm -rf test_output/*; \ go test -count=1 -timeout=10m -v -tags unit | tee test_output.log; \ ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output test-unit-helm-interleaved: install-terratest-log-parser ## run go test on the tests/unit/helm directory and run the terratest_log_parser for reports @cd tests/unit/helm; \ - rm -rf test_reports; \ - mkdir test_reports; \ + rm -rf test_output/*; \ go test -count=1 -timeout=10m -v -tags unit | tee test_output.log; \ ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output -test-integration-compose: dependencies-integration-compose ## run go test on the tests/integration/compose directory +test-integration-compose: test-integration-compose-internal test-integration-compose-legacy ## run go test on the tests/integration/compose directory for both internal and legacy auth modes + +test-integration-compose-internal: dependencies-integration-compose ## run go test on the tests/integration/compose directory for internal auth mode @cd tests/integration/compose; \ - go test -count=1 -timeout=10m -v -tags integration + go test -count=1 -timeout=10m -v -tags integrationComposeInternalAuth -test-integration-compose-interleaved: install-terratest-log-parser dependencies-integration-compose clean-integration-compose ## run go test on the tests/integration/compose directory and run the terratest_log_parser for reports +test-integration-compose-legacy: dependencies-integration-compose ## run go test on the tests/integration/compose directory for legacy auth mode @cd tests/integration/compose; \ - rm -rf test_reports; \ - mkdir test_reports; \ - go test -count=1 -timeout=10m -v -tags integration | tee test_output.log; \ - ${ASDF}/packages/bin/terratest_log_parser -testlog test_output.log -outputdir test_output + go test -count=1 -timeout=10m -v -tags integrationComposeLegacyAuth + +test-integration-compose-interleaved: test-integration-compose-interleaved-internal test-integration-compose-interleaved-legacy ## run go test on the tests/integration/compose directory and run the terratest_log_parser for reports + +test-integration-compose-interleaved-internal: install-terratest-log-parser dependencies-integration-compose clean-integration-compose ## run go test on the tests/integration/compose directory for internal auth mode and run the terratest_log_parser for reports + @cd tests/integration/compose; \ + rm -rf test_output_internal/*; \ + go test -count=1 -timeout=10m -v -tags integrationComposeInternalAuth | tee test_output_internal.log; \ + ${ASDF}/packages/bin/terratest_log_parser -testlog test_output_internal.log -outputdir test_output_internal + +test-integration-compose-interleaved-legacy: install-terratest-log-parser dependencies-integration-compose clean-integration-compose ## run go test on the tests/integration/compose directory for legacy auth mode and run the terratest_log_parser for reports + @cd tests/integration/compose; \ + rm -rf test_output_legacy/*; \ + go test -count=1 -timeout=10m -v -tags integrationComposeLegacyAuth | tee test_output_legacy.log; \ + ${ASDF}/packages/bin/terratest_log_parser -testlog test_output_legacy.log -outputdir test_output_legacy install-terratest-log-parser: ## install terratest_log_parser go install github.com/gruntwork-io/terratest/cmd/terratest_log_parser@latest diff --git a/tests/integration/compose/common_test.go b/tests/integration/compose/common_test.go index ed0819e3..30b8a9a6 100644 --- a/tests/integration/compose/common_test.go +++ b/tests/integration/compose/common_test.go @@ -14,8 +14,21 @@ import ( const ( legacyAuthEnvFixtureFilePath = "../../fixtures/docker/integration_legacy_auth.env" internalAuthFixtureEnvFilePath = "../../fixtures/docker/integration_internal_auth.env" + // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) + overrideFile = "../../tests/fixtures/docker/compose.override.yaml" + // To run the containers on macOS arm64, we need to set the platform + darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" + mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" ) +type serviceValidations struct { + name string + url string + responsePayload string + httpResponseCode int + log string +} + func validate_endpoint(t *testing.T, url string, expectedBody string, expectedStatus int) { maxRetries := 10 timeBetweenRetries := 3 * time.Second diff --git a/tests/integration/compose/docker-compose-internal-auth_test.go b/tests/integration/compose/docker-compose-internal-auth_test.go index fecebbda..0415dedd 100644 --- a/tests/integration/compose/docker-compose-internal-auth_test.go +++ b/tests/integration/compose/docker-compose-internal-auth_test.go @@ -23,11 +23,6 @@ import ( const ( dockerInternalAuthDir = "../../../docker/internal-auth" - // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) - overrideFile = "../../tests/fixtures/docker/compose.override.yaml" - // To run the containers on macOS arm64, we need to set the platform - darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" - mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" ) var internalAuthComposeFile = "compose.yaml" @@ -70,14 +65,6 @@ func TestDockerComposeUpInternalAuth(t *testing.T) { }) } -type serviceValidations struct { - name string - url string - responsePayload string - httpResponseCode int - log string -} - func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { testCases := []struct { name string diff --git a/tests/integration/compose/docker-compose-legacy-auth_test.go b/tests/integration/compose/docker-compose-legacy-auth_test.go index 258871b6..4ff3dd8b 100644 --- a/tests/integration/compose/docker-compose-legacy-auth_test.go +++ b/tests/integration/compose/docker-compose-legacy-auth_test.go @@ -23,11 +23,6 @@ import ( const ( dockerLegacyAuthDir = "../../../docker/legacy-auth" - // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) - overrideFile = "../../tests/fixtures/docker/compose.override.yaml" - // To run the containers on macOS arm64, we need to set the platform - darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" - mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" ) var legacyAuthComposeFile = "compose.yaml" @@ -70,14 +65,6 @@ func TestDockerComposeUpLegacyAuth(t *testing.T) { }) } -type serviceValidations struct { - name string - url string - responsePayload string - httpResponseCode int - log string -} - func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { testCases := []struct { name string From a232a0f8971b09aa74c260a84327ffd08ae6b14b Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Fri, 12 Apr 2024 11:20:31 -0700 Subject: [PATCH 30/42] chore: update .gitignore with new pattern for integration test logs and reports. --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 22161100..6c13249c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ compose.override*yaml helm/gke-example/values-*.yaml helm/gke-example/voxel51-docker.json -tests/integration/*/test_output.log -tests/integration/*/test_output/* +tests/integration/*/test_output*.log +tests/integration/*/test_output*/* tests/integration/*/test_reports/* tests/unit/*/test_output.log tests/unit/*/test_output/* From fae3067c651490e5f2c06bf0194ccf5f35603975 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Fri, 12 Apr 2024 14:25:32 -0700 Subject: [PATCH 31/42] docs: editing for v1.6.0 related docs --- docker/README.md | 99 ++++++++++---------- helm/fiftyone-teams-app/README.md | 109 ++++++++++++----------- helm/fiftyone-teams-app/README.md.gotmpl | 109 ++++++++++++----------- 3 files changed, 165 insertions(+), 152 deletions(-) diff --git a/docker/README.md b/docker/README.md index 348be36a..a05cc562 100644 --- a/docker/README.md +++ b/docker/README.md @@ -104,9 +104,9 @@ quickstart 0.21.2 ### Central Authentication Service -FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which -requires additional configurations and consumes additional resources. Please -review these notes, and the +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +CAS requires additional configurations and consumes additional resources. +Please review these notes, and the [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) documentation before completing your upgrade. @@ -118,12 +118,11 @@ to [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) after confirming your initial upgrade was successful. -The CAS service requires changes to your `.env` files - a brief summary of those -changes include: +The CAS service requires changes to your `.env` files. +A brief summary of those changes include -- adding `FIFTYONE_AUTH_SECRET` variable which populates to each of the - defined services. -- adding the following CAS Service configuration variables: +- Add the `FIFTYONE_AUTH_SECRET` variable used in every service +- Add the following CAS Service configuration variables - `CAS_BASE_URL` - `CAS_BIND_ADDRESS` - `CAS_BIND_PORT` @@ -132,17 +131,17 @@ changes include: - `CAS_DEFAULT_USER_ROLE` Please review these changes in the -[legacy-auth/env.template](legacy-auth/env.template) and in the appropriate -`legacy-auth/compose*` files. +[legacy-auth/env.template](legacy-auth/env.template) +and in the appropriate `legacy-auth/compose*` files. -To upgrade from versions prior to FiftyOne Teams v1.6: +To upgrade from versions prior to FiftyOne Teams v1.6 -- copy your `.env` file into the `legacy-auth` directory -- copy your `compose.override.yaml` file into the `legacy-auth` directory +- Copy your `.env` file into the `legacy-auth` directory +- Copy your `compose.override.yaml` file into the `legacy-auth` directory - `cd` into the `legacy-auth` directory -- update your `.env` file to add the variables listed above -- update your `compose.override.yaml` with `teams-cas` changes (if necessary) -- run `docker compose` commands from the `legacy-auth` directory +- Update your `.env` file, adding the variables listed above +- Update your `compose.override.yaml` with `teams-cas` changes (if necessary) +- Run `docker compose` commands from the `legacy-auth` directory ### Snapshot Archival @@ -252,8 +251,9 @@ use [Custom Plugins Images](https://github.com/voxel51/fiftyone-teams-app/blob/main/docs/custom-plugins.md) Use the FiftyOne Teams UI to deploy plugins by navigating to -`https:///settings/plugins`. Early-adopter plugins installed -manually must be redeployed using the FiftyOne Teams UI. +`https:///settings/plugins`. +Early-adopter plugins installed manually must +be redeployed using the FiftyOne Teams UI. ### Storage Credentials and `FIFTYONE_ENCRYPTION_KEY` @@ -261,8 +261,8 @@ As of FiftyOne Teams 1.1, containers based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -To generate `FIFTYONE_ENCRYPTION_KEY`, run this Python code and add the -output to your `.env` file: +To generate a value for `FIFTYONE_ENCRYPTION_KEY`, run this +Python code and add the output to your `.env` file: ```python from cryptography.fernet import Fernet @@ -374,15 +374,14 @@ create a new IdP or modify your existing configuration. ### From Before FiftyOne Teams Version 1.1.0 > **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires -> upgrading the database and will interrupt all SDK connections. You should -> coordinate this upgrade carefully with your end-users. +> upgrading the database and will interrupt all SDK connections. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) > documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) @@ -390,10 +389,10 @@ create a new IdP or modify your existing configuration. --- -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> You should coordinate this upgrade carefully with your end-users. 1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` directory @@ -428,15 +427,14 @@ create a new IdP or modify your existing configuration. ### From FiftyOne Teams Version 1.1.0 and later > **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires -> upgrading the database and will interrupt all SDK connections. You should -> coordinate this upgrade carefully with your end-users. +> upgrading the database and will interrupt all SDK connections. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) > documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) @@ -444,10 +442,10 @@ create a new IdP or modify your existing configuration. --- -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> you should coordinate this upgrade carefully with your end-users. 1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` directory @@ -533,20 +531,29 @@ create a new IdP or modify your existing configuration. # FIFTYONE_DATABASE_ADMIN: false ``` -The FiftyOne Teams App is now exposed on port `3000` and the FiftyOne Teams CAS -is now exposed on port `3030`. +The FiftyOne Teams App is now exposed on port `3000` and +the FiftyOne Teams CAS is now exposed on port `3030`. An SSL endpoint (Load Balancer or Nginx Proxy or something similar) will need to be configured to route traffic from the SSL endpoint to port `3000` on the host running the FiftyOne Teams App and to use path-based routing to route `/cas` traffic to port `3030` on the host running the FiftyOne Teams CAS. -An example nginx site configuration that forwards http traffic to -https, https traffic for `your.server.name/cas` to port `3030`, and https -traffic for `your.server.name` to port `3000` +Configure an SSL endpoint (like a Load Balancer, Nginx Proxy, or similar) +to route traffic + +- From the SSL endpoint to port `3000` + on the host running the FiftyOne Teams App +- From the path-based route `/cas` to port `3030` + on the host running the FiftyOne Teams CAS See -[./example-nginx-site.conf](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/example-nginx-site.conf). +[./example-nginx-site.conf](https://github.com/voxel51/fiftyone-teams-app-deploy/blob/main/docker/example-nginx-site.conf) +for an example Nginx site configuration that forwards + +- http traffic to https +- https traffic for `your.server.name/cas` to port `3030` +- https traffic for `your.server.name` to port `3000` --- diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index ef61399c..7e936989 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -85,9 +85,9 @@ Consider if you will require these settings for your deployment. ### Central Authentication Service -FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which -requires additional configurations and consumes additional resources. Please -review these notes, and the +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +CAS requires additional configurations and consumes additional resources. +Please review these notes, and the [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) documentation before completing your upgrade. @@ -99,11 +99,12 @@ to [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) after confirming your initial upgrade was successful. -The CAS service requires changes to your `values.yaml` files - a brief summary -of those changes include: +The CAS service requires changes to your `values.yaml` files. +A brief summary of those changes include -- adding a `fiftyoneAuthSecret` secret to `secret.fiftyone` or the secret - defined by `secret.name` +- Add the `fiftyoneAuthSecret` secret to either + - `secret.fiftyone` + - secret specified in `secret.name` ### Snapshot Archival @@ -205,8 +206,9 @@ Pods based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -To generate a `secret.fiftyone.encryptionKey`, run this Python code and add the -output to your `values.yaml` override file, or to your deployment's secret: +To generate a value for `secret.fiftyone.encryptionKey`, run this +Python code and add the output to your `values.yaml` override file, +or to your deployment's secret ```python from cryptography.fernet import Fernet @@ -514,51 +516,51 @@ appSettings: ## Upgrading From Previous Versions -Voxel51 assumes you are using the published Helm Chart to deploy your FiftyOne -Teams environment. If you are using a custom deployment mechanism you will -want to carefully review the changes in the +Voxel51 assumes you are using the published +Helm Chart to deploy your FiftyOne Teams environment. +If you are using a custom deployment mechanism, carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. ### From Early Adopter Versions (Versions less than 1.0) -Please contact your Voxel51 Customer Success team member to coordinate this -upgrade. -You will need to either create a new Identity Provider (IdP) or modify your -existing configuration to migrate to a new Auth0 Tenant. +Please contact your Voxel51 Customer Success +team member to coordinate this upgrade. +You will need to either create a new Identity Provider (IdP) +or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 > **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires -> upgrading the database and will interrupt all SDK connections. You should -> coordinate this upgrade carefully with your end-users. +> upgrading the database and will interrupt all SDK connections. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) -> notes and the +> documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. --- -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. - -1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods -1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods -1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. - This is not the default value in the Helm Chart and must be overridden. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> You should coordinate this upgrade carefully with your end-users. + +1. In your `values.yaml`, set the required values + 1. `secret.fiftyone.encryptionKey` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_ENCRYPTION_KEY` environment variable + in the appropriate service pods + 1. `secret.fiftyone.fiftyoneAuthSecret` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_AUTH_SECRET` environment variable + in the appropriate service pods + 1. `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` + 1. This is not the default value in the Helm Chart and must be overridden 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` @@ -580,19 +582,18 @@ existing configuration to migrate to a new Auth0 Tenant. ### From FiftyOne Teams Version 1.1.0 and later -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) -> notes and the +> documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. @@ -600,12 +601,13 @@ existing configuration to migrate to a new Auth0 Tenant. - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods -1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods +1. In your `values.yaml`, set the required values + 1. `secret.fiftyone.encryptionKey` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_ENCRYPTION_KEY` environment variable + in the appropriate service pods + 1. `secret.fiftyone.fiftyoneAuthSecret` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_AUTH_SECRET` environment variable + in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI @@ -617,8 +619,9 @@ existing configuration to migrate to a new Auth0 Tenant. FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose connectivity at this - > point. Upgrading to `fiftyone==0.16.0` is required. + > **NOTE** Any FiftyOne SDK less than 0.16.0 + > will lose connectivity at this point. + > Upgrading to `fiftyone==0.16.0` is required. 1. Validate that all datasets are now at version 0.23.5 diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index bfac0611..754555ac 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -87,9 +87,9 @@ Consider if you will require these settings for your deployment. ### Central Authentication Service -FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS), which -requires additional configurations and consumes additional resources. Please -review these notes, and the +FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +CAS requires additional configurations and consumes additional resources. +Please review these notes, and the [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) documentation before completing your upgrade. @@ -101,11 +101,12 @@ to [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) after confirming your initial upgrade was successful. -The CAS service requires changes to your `values.yaml` files - a brief summary -of those changes include: +The CAS service requires changes to your `values.yaml` files. +A brief summary of those changes include -- adding a `fiftyoneAuthSecret` secret to `secret.fiftyone` or the secret - defined by `secret.name` +- Add the `fiftyoneAuthSecret` secret to either + - `secret.fiftyone` + - secret specified in `secret.name` ### Snapshot Archival @@ -207,8 +208,9 @@ Pods based on the `fiftyone-teams-api` and `fiftyone-app` images must include the `FIFTYONE_ENCRYPTION_KEY` variable. This key is used to encrypt storage credentials in the MongoDB database. -To generate a `secret.fiftyone.encryptionKey`, run this Python code and add the -output to your `values.yaml` override file, or to your deployment's secret: +To generate a value for `secret.fiftyone.encryptionKey`, run this +Python code and add the output to your `values.yaml` override file, +or to your deployment's secret ```python from cryptography.fernet import Fernet @@ -339,51 +341,51 @@ appSettings: ## Upgrading From Previous Versions -Voxel51 assumes you are using the published Helm Chart to deploy your FiftyOne -Teams environment. If you are using a custom deployment mechanism you will -want to carefully review the changes in the +Voxel51 assumes you are using the published +Helm Chart to deploy your FiftyOne Teams environment. +If you are using a custom deployment mechanism, carefully review the changes in the [Helm Chart](https://github.com/voxel51/fiftyone-teams-app-deploy) and update your deployment accordingly. ### From Early Adopter Versions (Versions less than 1.0) -Please contact your Voxel51 Customer Success team member to coordinate this -upgrade. -You will need to either create a new Identity Provider (IdP) or modify your -existing configuration to migrate to a new Auth0 Tenant. +Please contact your Voxel51 Customer Success +team member to coordinate this upgrade. +You will need to either create a new Identity Provider (IdP) +or modify your existing configuration to migrate to a new Auth0 Tenant. ### From Before FiftyOne Teams Version 1.1.0 > **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires -> upgrading the database and will interrupt all SDK connections. You should -> coordinate this upgrade carefully with your end-users. +> upgrading the database and will interrupt all SDK connections. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) -> notes and the +> documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. --- -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. - -1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods -1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods -1. In your `values.yaml`, set `appSettings.env.FIFTYONE_DATABASE_ADMIN: true`. - This is not the default value in the Helm Chart and must be overridden. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> You should coordinate this upgrade carefully with your end-users. + +1. In your `values.yaml`, set the required values + 1. `secret.fiftyone.encryptionKey` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_ENCRYPTION_KEY` environment variable + in the appropriate service pods + 1. `secret.fiftyone.fiftyoneAuthSecret` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_AUTH_SECRET` environment variable + in the appropriate service pods + 1. `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` + 1. This is not the default value in the Helm Chart and must be overridden 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` @@ -405,19 +407,18 @@ existing configuration to migrate to a new Auth0 Tenant. ### From FiftyOne Teams Version 1.1.0 and later -> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ your users to log in -> after the upgrade is complete. This will interrupt active workflows in the -> FiftyOne Teams Hosted Web App; you should coordinate this upgrade carefully -> with your end-users. +> **NOTE**: Upgrading to FiftyOne Teams v1.6.0 _requires_ +> your users to log in after the upgrade is complete. +> This will interrupt active workflows in the FiftyOne Teams Hosted Web App. +> You should coordinate this upgrade carefully with your end-users. --- -> **NOTE**: FiftyOne Teams v1.6 introduces the -> Central Authentication Service (CAS) which requires additional configurations -> and consumes additional resources. Please review the upgrade instructions -> , the +> **NOTE**: FiftyOne Teams v1.6 introduces the Central Authentication Service (CAS). +> CAS requires additional configurations and consumes additional resources. +> Please review the upgrade instructions, the > [Central Authentication Service](#central-authentication-service) -> notes and the +> documentation and the > [Pluggable Authentication](https://docs.voxel51.com/teams/pluggable_auth.html) > documentation before completing your upgrade. @@ -425,12 +426,13 @@ existing configuration to migrate to a new Auth0 Tenant. - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default -1. In your `values.yaml`, make sure the required `secret.fiftyone.encryptionKey` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_ENCRYPTION_KEY` environment variable in the appropriate service pods -1. In your `values.yaml`, make sure the required `secret.fiftyone.fiftyoneAuthSecert` - (or your deployment's equivalent) is set, which will configure the - `FIFTYONE_AUTH_SECRET` environment variable in the appropriate service pods +1. In your `values.yaml`, set the required values + 1. `secret.fiftyone.encryptionKey` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_ENCRYPTION_KEY` environment variable + in the appropriate service pods + 1. `secret.fiftyone.fiftyoneAuthSecret` (or your deployment's equivalent) + 1. This sets the `FIFTYONE_AUTH_SECRET` environment variable + in the appropriate service pods 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) 1. Upgrade FiftyOne Teams SDK users to FiftyOne Teams version 0.16.0 - Login to the FiftyOne Teams UI @@ -442,8 +444,9 @@ existing configuration to migrate to a new Auth0 Tenant. FIFTYONE_DATABASE_ADMIN=true fiftyone migrate --all ``` - > **NOTE** Any FiftyOne SDK less than 0.16.0 will lose connectivity at this - > point. Upgrading to `fiftyone==0.16.0` is required. + > **NOTE** Any FiftyOne SDK less than 0.16.0 + > will lose connectivity at this point. + > Upgrading to `fiftyone==0.16.0` is required. 1. Validate that all datasets are now at version 0.23.5 From 2b112eeee64bab66b0f78bd16ddbc64e0dafa0ef Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Tue, 23 Apr 2024 11:46:22 -0700 Subject: [PATCH 32/42] docs: minor edits (deduplication, white space, fix example) --- docker/README.md | 9 ++++----- helm/fiftyone-teams-app/README.md | 8 ++++---- helm/fiftyone-teams-app/README.md.gotmpl | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docker/README.md b/docker/README.md index a05cc562..3083189e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -450,10 +450,9 @@ create a new IdP or modify your existing configuration. 1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` directory 1. `cd` into the `legacy-auth` directory -1. Make sure your `.env` file includes the required `FIFTYONE_API_URI` - environment variable -1. Make sure your `.env` file includes the required `FIFTYONE_AUTH_SECRET` - environment variable +1. In the `.env` file, set the required environment variables + - `FIFTYONE_API_URI` + - `FIFTYONE_AUTH_SECRET` 1. Ensure all FiftyOne SDK users either - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` @@ -528,7 +527,7 @@ create a new IdP or modify your existing configuration. services: fiftyone-app-common: environment: - # FIFTYONE_DATABASE_ADMIN: false + FIFTYONE_DATABASE_ADMIN: false ``` The FiftyOne Teams App is now exposed on port `3000` and diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 7e936989..4f4f6d68 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -656,11 +656,11 @@ A minimal example `values.yaml` may be found > [helm diff](https://github.com/databus23/helm-diff). > Voxel51 is not affiliated with the author of this plugin. > - > For example: + > For example: > - > ```shell - > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml - > ``` + > ```shell + > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml + > ``` [affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 754555ac..9b4910c2 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -481,11 +481,11 @@ A minimal example `values.yaml` may be found > [helm diff](https://github.com/databus23/helm-diff). > Voxel51 is not affiliated with the author of this plugin. > - > For example: + > For example: > - > ```shell - > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml - > ``` + > ```shell + > helm diff -C1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml + > ``` [affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ From 49d1665b6c7e38cce97f04a725bd99b06712175d Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Wed, 24 Apr 2024 17:36:02 -0600 Subject: [PATCH 33/42] docs: v1.6.0 new cas path-based route --- docker/README.md | 9 ++------- helm/README.md | 16 +++++++++------- helm/docs/expose-teams-api.md | 4 ++++ helm/fiftyone-teams-app/README.md | 21 +++++++++++++++++++++ helm/fiftyone-teams-app/README.md.gotmpl | 21 +++++++++++++++++++++ 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/docker/README.md b/docker/README.md index 3083189e..ec0591b9 100644 --- a/docker/README.md +++ b/docker/README.md @@ -530,13 +530,8 @@ create a new IdP or modify your existing configuration. FIFTYONE_DATABASE_ADMIN: false ``` -The FiftyOne Teams App is now exposed on port `3000` and -the FiftyOne Teams CAS is now exposed on port `3030`. -An SSL endpoint (Load Balancer or Nginx Proxy or something similar) -will need to be configured to route traffic from the SSL endpoint -to port `3000` on the host running the FiftyOne Teams App and to use path-based -routing to route `/cas` traffic to port `3030` on the host running the FiftyOne -Teams CAS. +The FiftyOne Teams App is exposed on port `3000` and +the FiftyOne Teams CAS is exposed on port `3030`. Configure an SSL endpoint (like a Load Balancer, Nginx Proxy, or similar) to route traffic diff --git a/helm/README.md b/helm/README.md index c5d16d1c..a3a83aba 100644 --- a/helm/README.md +++ b/helm/README.md @@ -43,7 +43,7 @@ This directory contains resources and information related to Helm deployments - `gke-example` contains additional kubernetes resources to install FiftyOne Teams on Google Kubernetes Engine (GKE). See - [A Full Deployment Example on GKE](#a-full-deployment-example-on-gke). + [A Full Deployment Example on GKE](#a-full-deployment-example-on-gke). - Files - `values.yaml` is example of overrides for the chart's defaults for a deployment @@ -79,11 +79,11 @@ in this directory. > helm plugin. > Voxel51 is not affiliated with the author of this plugin. > - > For example: + > For example: > - > ```shell - > helm diff --context 1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml - > ``` + > ```shell + > helm diff --context 1 upgrade fiftyone-teams-app voxel51/fiftyone-teams-app -f values.yaml + > ``` ### A Full Deployment Example on GKE @@ -117,7 +117,7 @@ Download the example configuration files from the [voxel51/fiftyone-teams-app-deploy](https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/gke-example) GitHub repository. -One way to do this might be: +For example ```shell curl -o values.yaml \ @@ -135,10 +135,12 @@ Update the `values.yaml` file with - Set `mongodbConnectionString` containing your MongoDB username and password - Set `cookieSecret` - Set `encryptionKey` + - Set `fiftyoneAuthSecret` - In `teamsAppSettings.dnsName` - Set ingress `host` values -Assuming you follow these directions your MongoDB host will be `fiftyone-mongodb.fiftyone-mongodb.svc.cluster.local`. +Assuming you follow these directions your MongoDB host will be +`fiftyone-mongodb.fiftyone-mongodb.svc.cluster.local`. #### Create the Necessary Helm Repos diff --git a/helm/docs/expose-teams-api.md b/helm/docs/expose-teams-api.md index d20148ad..d6dc3ea7 100644 --- a/helm/docs/expose-teams-api.md +++ b/helm/docs/expose-teams-api.md @@ -94,6 +94,10 @@ To use this chart's ingress object pathType: Prefix serviceName: teams-api servicePort: 80 + - path: /cas + pathType: Prefix + serviceName: teams-cas + servicePort: 80 - path: / pathType: Prefix serviceName: teams-app diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 4f4f6d68..3a32ea9c 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -106,6 +106,16 @@ A brief summary of those changes include - `secret.fiftyone` - secret specified in `secret.name` +For customers using path-based routing, +update your `values.yaml` to include the route + +```yaml +- path: /cas + pathType: Prefix + serviceName: teams-cas + servicePort: 80 +``` + ### Snapshot Archival Since version v1.5, FiftyOne Teams supports @@ -561,6 +571,17 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. in the appropriate service pods 1. `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` 1. This is not the default value in the Helm Chart and must be overridden + 1. If you use path based routing, update your ingress with the rule + + ```yaml + ingress: + paths: + - path: /cas + pathType: Prefix + serviceName: teams-cas + servicePort: 80 + ``` + 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 9b4910c2..4e026dd2 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -108,6 +108,16 @@ A brief summary of those changes include - `secret.fiftyone` - secret specified in `secret.name` +For customers using path-based routing, +update your `values.yaml` to include the route + +```yaml +- path: /cas + pathType: Prefix + serviceName: teams-cas + servicePort: 80 +``` + ### Snapshot Archival Since version v1.5, FiftyOne Teams supports @@ -386,6 +396,17 @@ or modify your existing configuration to migrate to a new Auth0 Tenant. in the appropriate service pods 1. `appSettings.env.FIFTYONE_DATABASE_ADMIN: true` 1. This is not the default value in the Helm Chart and must be overridden + 1. If you use path based routing, update your ingress with the rule + + ```yaml + ingress: + paths: + - path: /cas + pathType: Prefix + serviceName: teams-cas + servicePort: 80 + ``` + 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) > **NOTE:** At this step, FiftyOne SDK users will lose access to the > FiftyOne Teams Database until they upgrade to `fiftyone==0.16.0` From 55b2e687bce4594660dae4be802d695c1ea88a88 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:39:29 -0600 Subject: [PATCH 34/42] chore: add command to enable pulling container images from internal artifact registry. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index b0d807ce..34adc0d0 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ asdf: ## Update plugins, add plugins, install plugins, set local, reshim auth: gcloud auth application-default login --project computer-vision-team + gcloud auth configure-docker us-central1-docker.pkg.dev hooks: ## Install git hooks (pre-commit) @pre-commit install From a28bf1852968b6e3e1cb67e489643d83b3a5c564 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:41:18 -0600 Subject: [PATCH 35/42] docs: cleanup for release --- docs/custom-plugins.md | 25 ++++++++-------- helm/README.md | 6 ++-- helm/fiftyone-teams-app/README.md | 2 +- helm/fiftyone-teams-app/README.md.gotmpl | 2 +- helm/gke-example/values.yaml | 38 ++++++++++++++++++++---- helm/values.yaml | 37 ++++++++++++----------- 6 files changed, 69 insertions(+), 41 deletions(-) diff --git a/docs/custom-plugins.md b/docs/custom-plugins.md index dae246d9..cdb181f3 100644 --- a/docs/custom-plugins.md +++ b/docs/custom-plugins.md @@ -14,9 +14,10 @@ # Custom Plugins Images -Some plugins have custom python dependencies, which requires the -creation of a new plugins image. This document outlines the steps -Voxel51 recommends for creating those custom plugins containers. +Some plugins have custom python dependencies, +which requires the creation of a new plugins image. +This document outlines the steps Voxel51 recommends +for creating those custom plugins containers. ## Create a New Image From an Existing Voxel51 Image @@ -45,10 +46,10 @@ With a Dockerfile like this, you could use the following commands to build, and publish, your image to your internal registry ```shell -$ TEAMS_VERSION=v1.6.0 -$ docker buildx build --push \ - --build-arg TEAMS_IMAGE_NAME='voxel51/fiftyone-app:${TEAMS_VERSION}' \ - -t your-internal-registry/fiftyone-app-internal:${TEAMS_VERSION} . +TEAMS_VERSION=v1.6.0 +docker buildx build --push \ + --build-arg TEAMS_IMAGE_NAME='voxel51/fiftyone-app:${TEAMS_VERSION}' \ + -t your-internal-registry/fiftyone-app-internal:${TEAMS_VERSION} . ``` You should upgrade your custom plugins image using the `TEAMS_VERSION` @@ -56,8 +57,8 @@ you plan to use in your FiftyOne Teams Deployment. ## Using Your Custom Plugins Image in Docker Compose -Once you have built a custom plugins image, you can add it to your -`compose.override.yaml` using something similar to the following: +After your custom plugins image is built, you can add it to your +`compose.override.yaml` file like ```yaml services: @@ -72,8 +73,8 @@ deployment. ## Using Your Custom Plugins Image in Helm Deployments -Once you have build a custom plugins image, you can add it to your -`values.yaml` using something similar to the following: +After your custom plugins image is built, you can add it to your +`values.yaml` file like ```yaml pluginsSettings: @@ -81,7 +82,7 @@ pluginsSettings: repository: your-internal-registry/fiftyone-app-internal ``` -Assuming you have built your custom container with the same version +Assuming you tagged your custom container with the same version number as the FiftyOne Teams release, the Helm chart will automatically use the chart version to pull your image. diff --git a/helm/README.md b/helm/README.md index a3a83aba..adacf781 100644 --- a/helm/README.md +++ b/helm/README.md @@ -39,10 +39,10 @@ This directory contains resources and information related to Helm deployments - Exposing the teams-api - Plugin storage - `fiftyone-teams-app` contains the helm chart voxel51/fiftyone-teams-app. - For the chart documentation, see the fiftyone-teams-app/README.md file. + For the chart documentation, see the fiftyone-teams-app/README.md file. - `gke-example` contains additional kubernetes resources - to install FiftyOne Teams on Google Kubernetes Engine (GKE). - See + to install FiftyOne Teams on Google Kubernetes Engine (GKE). + See [A Full Deployment Example on GKE](#a-full-deployment-example-on-gke). - Files - `values.yaml` is example of overrides for the chart's defaults for a deployment diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index fc5a19f3..1bf97420 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -106,7 +106,7 @@ A brief summary of those changes include - `secret.fiftyone` - secret specified in `secret.name` -For customers using path-based routing, +When using path-based routing, update your `values.yaml` to include the route ```yaml diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index 4072b531..e1383b5a 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -108,7 +108,7 @@ A brief summary of those changes include - `secret.fiftyone` - secret specified in `secret.name` -For customers using path-based routing, +When using path-based routing, update your `values.yaml` to include the route ```yaml diff --git a/helm/gke-example/values.yaml b/helm/gke-example/values.yaml index 1e9a1506..6c67ab24 100644 --- a/helm/gke-example/values.yaml +++ b/helm/gke-example/values.yaml @@ -1,34 +1,60 @@ --- # Voxel51 provided you with a voxel51-docker.json file, you can use the following command to create a Pull Secret: +# +# ```shell # kubectl --namespace your-namespace-here create secret generic regcred \ # --from-file=.dockerconfigjson=./voxel51-docker.json --type kubernetes.io/dockerconfigjson +# ``` +# # If you use the Voxel51 command above your imagePullSecrets would look like: imagePullSecrets: - name: regcred secret: fiftyone: - # These secrets come from Voxel51 + # Voxel51 provides you these secrets. + # They should be omitted when `casSettings.env.FIFTYONE_AUTH_MODE` is set to `internal` apiClientId: apiClientSecret: auth0Domain: clientId: clientSecret: organizationId: + # These secrets come from your MongoDB implementation fiftyoneDatabaseName: fiftyone mongodbConnectionString: mongodb://admin:REPLACEME@fiftyone-mongodb.fiftyone-mongodb.svc.cluster.local/?authSource=admin - # This secret is a required random string used to encrypt session cookies - # Use something like `openssl rand -hex 32` to generate this string + + # This secret is a required random string used to encrypt session cookies. + # To generate this string, run + # + # ```shell + # openssl rand -hex 32 + # ```` + # cookieSecret: - # This key is required and is used to encrypt storage credentials in the MongoDB - # do NOT lose this key! - # generate keys by executing the following in python: + + # This required key is used to encrypt storage credentials in the database. + # Do NOT lose this key! + # To generate this key, run (in python) # + # ```python # from cryptography.fernet import Fernet # print(Fernet.generate_key().decode()) + # ``` + # encryptionKey: + # This secret is a random string used to authenticate to the CAS service. + # This can be any string you care to use generated by any mechanism you + # prefer. + # You could use something like: + # `cat /dev/urandom | LC_CTYPE=C tr -cd '[:graph:]' | head -c 32` + # to generate this string. + # This is used for inter-service authentication and for the SuperUser to + # authenticate at the CAS UI to configure the Central Authentication Service. + fiftyoneAuthSecret: + # apiSettings: # appSettings: diff --git a/helm/values.yaml b/helm/values.yaml index 0057e1b6..0bc60bf5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -12,8 +12,8 @@ secret: fiftyone: - # These secrets come from Voxel51 and should be omitted when - # `casSettings.env.FIFTYONE_AUTH_MODE` is set to `internal` + # Voxel51 provides you these secrets. + # They should be omitted when `casSettings.env.FIFTYONE_AUTH_MODE` is set to `internal` apiClientId: apiClientSecret: auth0Domain: @@ -44,6 +44,7 @@ secret: # ``` # encryptionKey: + # This secret is a random string used to authenticate to the CAS service. # This can be any string you care to use generated by any mechanism you # prefer. @@ -59,27 +60,27 @@ secret: # # See https://helm.fiftyone.ai/docs/expose-teams-api.html for more information. # dnsName: your-api.hostname.here # env: -# Set FIFTYONE_PLUGINS_DIR if you are enabling plugins in a dedicated +# # Set FIFTYONE_PLUGINS_DIR if you are enabling plugins in a dedicated # `teams-plugins` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins +# # See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins -# Set FIFTYONE_TEAMS_VERSION_OVERRIDE to override the `Install FiftyOne` -# bash command in the `Settings > Install FiftyOne` modal +# # Set FIFTYONE_TEAMS_VERSION_OVERRIDE to override the `Install FiftyOne` +# # bash command in the `Settings > Install FiftyOne` modal # FIFTYONE_TEAMS_VERSION_OVERRIDE: pip install --index-url https://privatepypi.internal.org fiftyone==0.16.0 appSettings: env: -# FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 -# If you are performing a new install or an upgrade from v1.0 or earlier you may want to set -# this value to `true`. -# Please see https://helm.fiftyone.ai/#initial-installation-vs-upgrades for details + # FIFTYONE_DATABASE_ADMIN is set to `false` by default for v1.6.0 + # If you are performing a new install or an upgrade from v1.0 or earlier you may want to set + # this value to `true`. + # Please see https://helm.fiftyone.ai/#initial-installation-vs-upgrades for details FIFTYONE_DATABASE_ADMIN: true -# Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are -# enabling plugins in the `fiftyone-app` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins -# FIFTYONE_PLUGINS_DIR: /opt/plugins -# FIFTYONE_PLUGINS_CACHE_ENABLED: true + # Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are + # enabling plugins in the `fiftyone-app` deployment + # See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins + # FIFTYONE_PLUGINS_DIR: /opt/plugins + # FIFTYONE_PLUGINS_CACHE_ENABLED: true casSettings: env: @@ -88,9 +89,9 @@ casSettings: # pluginsSettings: # enabled: true # env: -# Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are -# enabling plugins in a dedicated `teams-plugins` deployment -# See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins +# # Set FIFTYONE_PLUGINS_DIR and FIFTYONE_PLUGINS_CACHE_ENABLED if you are +# # enabling plugins in a dedicated `teams-plugins` deployment +# # See https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/helm/fiftyone-teams-app#fiftyone-teams-plugins # FIFTYONE_PLUGINS_DIR: /opt/plugins # FIFTYONE_PLUGINS_CACHE_ENABLED: true From d434f24cb28feb45732e47cb42f5822f35661fb8 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:41:49 -0600 Subject: [PATCH 36/42] chore: set version for skaffold from 1.5.8 to 1.5.10 --- skaffold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skaffold.yaml b/skaffold.yaml index cf20b756..aa1a9792 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -36,7 +36,7 @@ deploy: releases: - name: fiftyone-teams chartPath: helm/fiftyone-teams-app - version: 1.5.8 + version: 1.5.10 createNamespace: true namespace: fiftyone-teams overrides: From 4c9a50714aca935bd989dad4a82dc479a27729de Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:43:21 -0600 Subject: [PATCH 37/42] test: fix unit and integration tests. Remove unnecessary test no longer needed after CAS. --- .../docker/integration_internal_auth.env | 14 +- .../docker/integration_legacy_auth.env | 16 +- tests/integration/compose/common_test.go | 6 +- .../docker-compose-internal-auth_test.go | 45 +- .../docker-compose-legacy-auth_test.go | 45 +- .../docker-compose-internal-auth_test.go | 6 +- .../docker-compose-legacy-auth_test.go | 9 +- tests/unit/compose/docker-compose_test.go | 846 ------------------ 8 files changed, 91 insertions(+), 896 deletions(-) delete mode 100644 tests/unit/compose/docker-compose_test.go diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index 2943d37d..6b9041f8 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -1,14 +1,14 @@ # Data for Integration Tests for Internal Auth Mode # FiftyOne -FIFTYONE_DATABASE_URI="mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin" # This is a randomly generated password +FIFTYONE_DATABASE_URI='mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin' # This is a randomly generated password FIFTYONE_ENCRYPTION_KEY="btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=" # This is a randomly generated string # Internal Auth FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" # MongoDB -MONGODB_PASSWORD="3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji" # This is a randomly generated string # pragma: allowlist secret +MONGODB_PASSWORD='3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji' # This is a randomly generated string # pragma: allowlist secret MONGODB_USERNAME=root MONGODB_DIR=/tmp/mongodb MONGODB_BIND_ADDRESS=127.0.0.1 @@ -22,9 +22,9 @@ API_LOGGING_LEVEL=DEBUG FIFTYONE_ENV=development CAS_DEBUG="cas:*" -# Image tags +# Image tags for existing artifacts VERSION=v1.6.0 -FIFTYONE_APP_RC=${VERSION}rc14 -FIFTYONE_TEAMS_API_RC=${VERSION}rc13 -FIFTYONE_TEAMS_APP_RC=${VERSION}-rc.11 -FIFTYONE_TEAMS_CAS_RC=${VERSION}-rc.11 +FIFTYONE_APP_VERSION="${VERSION}rc14" +FIFTYONE_TEAMS_API_VERSION="${VERSION}rc13" +FIFTYONE_TEAMS_APP_VERSION="${VERSION}-rc.11" +FIFTYONE_TEAMS_CAS_VERSION="${VERSION}-rc.11" diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index 3eb50699..e65b4769 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -19,16 +19,16 @@ AUTH0_SECRET=5b32118032bfd50b64b3cc7c0e0821f4e84f63ad517a9687ac2b6ce6ab261976 AUTH0_BASE_URL=http://local.fiftyone.ai # FiftyOne -FIFTYONE_DATABASE_URI="mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin" # This is a randomly generated password +FIFTYONE_DATABASE_URI='mongodb://root:3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji@mongodb/?authSource=admin' # This is a randomly generated password FIFTYONE_ENCRYPTION_KEY="btv8BiFCaPIayWU3IU3a_Lm_EMIIk-t6H_yN1ORV45o=" # This is a randomly generated string # Internal Auth FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" # MongoDB -MONGODB_PASSWORD="3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji" # This is a randomly generated string # pragma: allowlist secret +MONGODB_PASSWORD='3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji' # This is a randomly generated string # pragma: allowlist secret MONGODB_USERNAME=root -MONGODB_DIR=/tmp/mongodb +MONGODB_DIR=/tmp/mongodbhelm/gke-example/values.yaml MONGODB_BIND_ADDRESS=127.0.0.1 APP_USE_HTTPS=false @@ -40,9 +40,9 @@ API_LOGGING_LEVEL=DEBUG FIFTYONE_ENV=development CAS_DEBUG="cas:*" -# Image tags +# Image tags for existing artifacts VERSION=v1.6.0 -FIFTYONE_APP_RC=${VERSION}rc14 -FIFTYONE_TEAMS_API_RC=${VERSION}rc13 -FIFTYONE_TEAMS_APP_RC=${VERSION}-rc.11 -FIFTYONE_TEAMS_CAS_RC=${VERSION}-rc.11 +FIFTYONE_APP_VERSION="${VERSION}rc14" +FIFTYONE_TEAMS_API_VERSION="${VERSION}rc13" +FIFTYONE_TEAMS_APP_VERSION="${VERSION}-rc.11" +FIFTYONE_TEAMS_CAS_VERSION="${VERSION}-rc.11" diff --git a/tests/integration/compose/common_test.go b/tests/integration/compose/common_test.go index 30b8a9a6..a224d6e7 100644 --- a/tests/integration/compose/common_test.go +++ b/tests/integration/compose/common_test.go @@ -17,8 +17,10 @@ const ( // Override FIFTYONE_DATABASE_ADMIN to true (until we can override via environment variable) overrideFile = "../../tests/fixtures/docker/compose.override.yaml" // To run the containers on macOS arm64, we need to set the platform - darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" - mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" + darwinOverrideFile = "../../tests/fixtures/docker/compose.override.darwin.yaml" + darwinOverrideFilePlugins = "../../tests/fixtures/docker/compose.override.darwin_plugins.yaml" + mongodbComposeFile = "../../tests/fixtures/docker/compose.override.mongodb.yaml" + mongodbComposeFilePlugins = "../../tests/fixtures/docker/compose.override.mongodb_plugins.yaml" ) type serviceValidations struct { diff --git a/tests/integration/compose/docker-compose-internal-auth_test.go b/tests/integration/compose/docker-compose-internal-auth_test.go index 0415dedd..302efb05 100644 --- a/tests/integration/compose/docker-compose-internal-auth_test.go +++ b/tests/integration/compose/docker-compose-internal-auth_test.go @@ -32,9 +32,10 @@ var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env. type commonServicesInternalAuthDockerComposeUpTest struct { suite.Suite - composeFilePath string - dotEnvFiles []string - overrideFiles []string + composeFilePath string + dotEnvFiles []string + overrideFiles []string + overrideFilesPlugins []string } func TestDockerComposeUpInternalAuth(t *testing.T) { @@ -49,9 +50,16 @@ func TestDockerComposeUpInternalAuth(t *testing.T) { mongodbComposeFile, } + // Plugins require overriding the teams-plugin image + var overrideFilesPlugins []string + overrideFilesPlugins = append(overrideFilesPlugins, overrideFiles...) + overrideFilesPlugins = append(overrideFilesPlugins, mongodbComposeFilePlugins) + // To run the containers on macOS arm64, we need to set the platform if runtime.GOOS == "darwin" { overrideFiles = append(overrideFiles, darwinOverrideFile) + overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFile, darwinOverrideFilePlugins) + // overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFilePlugins) } suite.Run(t, &commonServicesInternalAuthDockerComposeUpTest{ @@ -61,7 +69,8 @@ func TestDockerComposeUpInternalAuth(t *testing.T) { internalAuthEnvTemplateFilePath, internalAuthFixtureEnvFilePath, }, - overrideFiles: overrideFiles, + overrideFiles: overrideFiles, + overrideFilesPlugins: overrideFilesPlugins, }) } @@ -150,7 +159,7 @@ func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { { "composeDedicatedPlugins", internalAuthComposeDedicatedPluginsFile, - s.overrideFiles, + s.overrideFilesPlugins, s.dotEnvFiles, []serviceValidations{ { @@ -215,7 +224,7 @@ func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { // ``` // // Use existing function to get map[string]string of environment variables from .env file(s) - environmentVariables, err := dotenv.Read(s.dotEnvFiles...) + environmentVariables, err := dotenv.Read(testCase.envFiles...) s.NoError(err) dockerOptions := &docker.Options{ @@ -227,23 +236,28 @@ func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { // In golang, we cannot mix strings with string slice unpacking. // Let's create a slice that will later be unpacked and used as an argument // to the variadic function `docker.RunDockerCompose` parameter `args`. + argsConfig := []string{} argsUp := []string{} argsDown := []string{} args := []string{"-f", testCase.composeFile} - for _, overrideFile := range s.overrideFiles { + for _, overrideFile := range testCase.overrideFiles { args = append(args, "-f", overrideFile) } - argsUp = append(args, "up", "--detach") - argsDown = append(args, "down", "--remove-orphans", "--timeout", "2") + argsConfig = append(args, "config") + argsUp = append(argsUp, args...) + argsUp = append(argsUp, "up", "--detach") + argsDown = append(argsDown, args...) + argsDown = append(argsDown, "down", "--remove-orphans", "--timeout", "2") - // Run containers - output := docker.RunDockerCompose( + // Config + docker.RunDockerCompose( subT, dockerOptions, - argsUp..., + argsConfig..., ) + // Delete containers after tests complete defer docker.RunDockerCompose( subT, @@ -251,6 +265,13 @@ func (s *commonServicesInternalAuthDockerComposeUpTest) TestDockerComposeUp() { argsDown..., ) + // Run containers + output := docker.RunDockerCompose( + subT, + dockerOptions, + argsUp..., + ) + // Validate system health for _, expected := range testCase.expected { logger.Log(subT, fmt.Sprintf("Validating service %s...", expected.name)) diff --git a/tests/integration/compose/docker-compose-legacy-auth_test.go b/tests/integration/compose/docker-compose-legacy-auth_test.go index 4ff3dd8b..ee1be302 100644 --- a/tests/integration/compose/docker-compose-legacy-auth_test.go +++ b/tests/integration/compose/docker-compose-legacy-auth_test.go @@ -32,9 +32,10 @@ var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.temp type commonServicesLegacyAuthDockerComposeUpTest struct { suite.Suite - composeFilePath string - dotEnvFiles []string - overrideFiles []string + composeFilePath string + dotEnvFiles []string + overrideFiles []string + overrideFilesPlugins []string } func TestDockerComposeUpLegacyAuth(t *testing.T) { @@ -49,9 +50,16 @@ func TestDockerComposeUpLegacyAuth(t *testing.T) { mongodbComposeFile, } + // Plugins require overriding the teams-plugin image + var overrideFilesPlugins []string + overrideFilesPlugins = append(overrideFilesPlugins, overrideFiles...) + overrideFilesPlugins = append(overrideFilesPlugins, mongodbComposeFilePlugins) + // To run the containers on macOS arm64, we need to set the platform if runtime.GOOS == "darwin" { overrideFiles = append(overrideFiles, darwinOverrideFile) + overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFile, darwinOverrideFilePlugins) + // overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFilePlugins) } suite.Run(t, &commonServicesLegacyAuthDockerComposeUpTest{ @@ -61,7 +69,8 @@ func TestDockerComposeUpLegacyAuth(t *testing.T) { legacyAuthEnvTemplateFilePath, legacyAuthEnvFixtureFilePath, }, - overrideFiles: overrideFiles, + overrideFiles: overrideFiles, + overrideFilesPlugins: overrideFilesPlugins, }) } @@ -150,7 +159,7 @@ func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { { "composeDedicatedPlugins", legacyAuthComposeDedicatedPluginsFile, - s.overrideFiles, + s.overrideFilesPlugins, s.dotEnvFiles, []serviceValidations{ { @@ -215,7 +224,7 @@ func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { // ``` // // Use existing function to get map[string]string of environment variables from .env file(s) - environmentVariables, err := dotenv.Read(s.dotEnvFiles...) + environmentVariables, err := dotenv.Read(testCase.envFiles...) s.NoError(err) dockerOptions := &docker.Options{ @@ -227,23 +236,28 @@ func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { // In golang, we cannot mix strings with string slice unpacking. // Let's create a slice that will later be unpacked and used as an argument // to the variadic function `docker.RunDockerCompose` parameter `args`. + argsConfig := []string{} argsUp := []string{} argsDown := []string{} args := []string{"-f", testCase.composeFile} - for _, overrideFile := range s.overrideFiles { + for _, overrideFile := range testCase.overrideFiles { args = append(args, "-f", overrideFile) } - argsUp = append(args, "up", "--detach") - argsDown = append(args, "down", "--remove-orphans", "--timeout", "2") + argsConfig = append(args, "config") + argsUp = append(argsUp, args...) + argsUp = append(argsUp, "up", "--detach") + argsDown = append(argsDown, args...) + argsDown = append(argsDown, "down", "--remove-orphans", "--timeout", "2") - // Run containers - output := docker.RunDockerCompose( + // Config + docker.RunDockerCompose( subT, dockerOptions, - argsUp..., + argsConfig..., ) + // Delete containers after tests complete defer docker.RunDockerCompose( subT, @@ -251,6 +265,13 @@ func (s *commonServicesLegacyAuthDockerComposeUpTest) TestDockerComposeUp() { argsDown..., ) + // Run containers + output := docker.RunDockerCompose( + subT, + dockerOptions, + argsUp..., + ) + // Validate system health for _, expected := range testCase.expected { logger.Log(subT, fmt.Sprintf("Validating service %s...", expected.name)) diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 9315095b..52681c78 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -263,7 +263,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", @@ -341,7 +341,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", @@ -417,7 +417,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index b2c95c99..59f210f1 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -240,7 +240,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "CAS_BASE_URL=http://teams-cas:3000/cas/api", - "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -263,7 +262,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", @@ -324,7 +323,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "CAS_BASE_URL=http://teams-cas:3000/cas/api", - "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -348,7 +346,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", @@ -407,7 +405,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { s.dotEnvFiles, []string{ "CAS_BASE_URL=http://teams-cas:3000/cas/api", - "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_DATABASE_NAME=fiftyone", "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", @@ -431,7 +428,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FEATURE_FLAG_ENABLE_INVITATIONS=false", "FIFTYONE_API_URI=https://example-api.fiftyone.ai", "FIFTYONE_APP_ALLOW_MEDIA_EXPORT=true", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", + "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION=0.16.0", "FIFTYONE_AUTH_SECRET=test-fiftyone-auth-secret", "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", diff --git a/tests/unit/compose/docker-compose_test.go b/tests/unit/compose/docker-compose_test.go deleted file mode 100644 index dc608271..00000000 --- a/tests/unit/compose/docker-compose_test.go +++ /dev/null @@ -1,846 +0,0 @@ -//go:build docker || compose || unit || unitComposeCommonServices -// +build docker compose unit unitComposeCommonServices - -package unit - -import ( - "context" - "fmt" - "strings" - - "path/filepath" - - "testing" - - "github.com/compose-spec/compose-go/v2/cli" - "github.com/compose-spec/compose-go/v2/types" - - "github.com/gruntwork-io/terratest/modules/logger" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -// TODO: Move to `common_test.go`? -const ( - dockerDir = "../../../docker/" - composeFile = "../../../docker/compose.yaml" - composePluginsFile = "../../../docker/compose.plugins.yaml" - composeDedicatedPluginsFile = "../../../docker/compose.dedicated-plugins.yaml" - envTemplateFilePath = "../../../docker/env.template" - envFixtureFilePath = "../../fixtures/docker/.env" -) - -type commonServicesDockerComposeTest struct { - suite.Suite - composeFilePath string - projectName string - dotEnvFiles []string -} - -func TestDockerCompose(t *testing.T) { - t.Parallel() - - _, err := filepath.Abs(dockerDir) - require.NoError(t, err) - - suite.Run(t, &commonServicesDockerComposeTest{ - Suite: suite.Suite{}, - composeFilePath: dockerDir, - projectName: "fiftyone-compose-test", - dotEnvFiles: []string{ - filepath.Join(dockerDir, "env.template"), - "../../fixtures/docker/.env", - }, - }) -} - -func (s *commonServicesDockerComposeTest) TestServicesNames() { - testCases := []struct { - name string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected []string - }{ - { - "compose", - []string{composeFile}, - s.dotEnvFiles, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - }, - }, - { - "composePlugins", - []string{composePluginsFile}, - s.dotEnvFiles, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - }, - }, - { - "composeDedicatedPlugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - "teams-plugins", - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.ServiceNames(), "Service Names should be equal") - }) - } -} - -func (s *commonServicesDockerComposeTest) TestServiceImage() { - testCases := []struct { - name string - serviceName string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected string - }{ - { - "defaultTeamsApi", - "teams-api", - []string{composeFile}, - s.dotEnvFiles, - "voxel51/fiftyone-teams-api:v1.5.10", - }, - { - "defaultTeamsApp", - "teams-app", - []string{composeFile}, - s.dotEnvFiles, - "voxel51/fiftyone-teams-app:v1.5.10", - }, - { - "defaultFiftyoneApp", - "fiftyone-app", - []string{composeFile}, - s.dotEnvFiles, - "voxel51/fiftyone-app:v1.5.10", - }, - { - "dedicatedPluginsTeamsPlugins", - "teams-plugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - "voxel51/fiftyone-app:v1.5.10", - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.Services[testCase.serviceName].Image, fmt.Sprintf("%s - Image should be equal", testCase.name)) - }) - } -} - -func (s *commonServicesDockerComposeTest) TestServiceEnvironment() { - testCases := []struct { - name string - serviceName string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected []string - }{ - { - "defaultTeamsApi", - "teams-api", - []string{composeFile}, - s.dotEnvFiles, - []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_ENV=production", - "FIFTYONE_INTERNAL_SERVICE=true", - "GRAPHQL_DEFAULT_LIMIT=10", - "LOGGING_LEVEL=INFO", - "MONGO_DEFAULT_DB=fiftyone", - }, - }, - { - "defaultTeamsApp", - "teams-app", - []string{composeFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", - "FIFTYONE_API_URI=https://example-api.fiftyone.ai", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", - "FIFTYONE_SERVER_ADDRESS=", - "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", - "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", - "NODE_ENV=production", - "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", - }, - }, - { - "defaultFiftyoneApp", - "fiftyone-app", - []string{composeFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "FIFTYONE_DATABASE_ADMIN=false", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", - "FIFTYONE_DEFAULT_APP_PORT=5151", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_INTERNAL_SERVICE=true", - "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", - "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", - }, - }, - { - "pluginsTeamsApi", - "teams-api", - []string{composePluginsFile}, - s.dotEnvFiles, - []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_ENV=production", - "FIFTYONE_INTERNAL_SERVICE=true", - "FIFTYONE_PLUGINS_DIR=/opt/plugins", - "GRAPHQL_DEFAULT_LIMIT=10", - "LOGGING_LEVEL=INFO", - "MONGO_DEFAULT_DB=fiftyone", - }, - }, - { - "pluginsTeamsApp", - "teams-app", - []string{composePluginsFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", - "FIFTYONE_API_URI=https://example-api.fiftyone.ai", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", - "FIFTYONE_SERVER_ADDRESS=", - "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", - "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", - "NODE_ENV=production", - "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", - }, - }, - { - "pluginsFiftyoneApp", - "fiftyone-app", - []string{composePluginsFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "FIFTYONE_DATABASE_ADMIN=false", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", - "FIFTYONE_DEFAULT_APP_PORT=5151", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_INTERNAL_SERVICE=true", - "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", - "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_PLUGINS_CACHE_ENABLED=true", - "FIFTYONE_PLUGINS_DIR=/opt/plugins", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", - }, - }, - { - "dedicatedPluginsTeamsApi", - "teams-api", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []string{ - "AUTH0_API_CLIENT_ID=test-auth0-api-client-id", - "AUTH0_API_CLIENT_SECRET=test-auth0-api-client-secret", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_DOMAIN=test-auth0-domain", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_ENV=production", - "FIFTYONE_INTERNAL_SERVICE=true", - "GRAPHQL_DEFAULT_LIMIT=10", - "LOGGING_LEVEL=INFO", - "MONGO_DEFAULT_DB=fiftyone", - "FIFTYONE_PLUGINS_DIR=/opt/plugins", - }, - }, - { - "dedicatedPluginsTeamsApp", - "teams-app", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "APP_USE_HTTPS=true", - "AUTH0_AUDIENCE=test-auth0-audience", - "AUTH0_BASE_URL=https://example.fiftyone.ai", - "AUTH0_CLIENT_ID=test-auth0-client-id", - "AUTH0_CLIENT_SECRET=test-auth0-client-secret", - "AUTH0_ISSUER_BASE_URL=test-auth0-issuer-base-url", - "AUTH0_ORGANIZATION=test-auth0-organization", - "AUTH0_SECRET=test-auth0-secret", - "FIFTYONE_API_URI=https://example-api.fiftyone.ai", - "FIFTYONE_APP_TEAMS_SDK_RECOMMENDED_VERSION0.16.0", - "FIFTYONE_SERVER_ADDRESS=", - "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", - "FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151", - "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", - "NODE_ENV=production", - "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", - }, - }, - { - "dedicatedPluginsFiftyoneApp", - "fiftyone-app", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []string{ - "API_URL=http://teams-api:8000", - "FIFTYONE_DATABASE_ADMIN=false", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", - "FIFTYONE_DEFAULT_APP_PORT=5151", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_INTERNAL_SERVICE=true", - "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", - "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", - }, - }, - { - "dedicatedPluginsTeamsPlugins", - "teams-plugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []string{ - "FIFTYONE_PLUGINS_CACHE_ENABLED=true", - "API_URL=http://teams-api:8000", - "FIFTYONE_DATABASE_ADMIN=false", - "FIFTYONE_DATABASE_NAME=fiftyone", - "FIFTYONE_DATABASE_URI=mongodb://root:test-secret@mongodb.local/?authSource=admin", - "FIFTYONE_DEFAULT_APP_ADDRESS=0.0.0.0", - "FIFTYONE_DEFAULT_APP_PORT=5151", - "FIFTYONE_ENCRYPTION_KEY=test-fiftyone-encryption-key", - "FIFTYONE_INTERNAL_SERVICE=true", - "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", - "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", - "FIFTYONE_PLUGINS_DIR=/opt/plugins", - "FIFTYONE_TEAMS_AUDIENCE=test-auth0-audience", - "FIFTYONE_TEAMS_CLIENT_ID=test-auth0-client-id", - "FIFTYONE_TEAMS_DOMAIN=test-auth0-domain", - "FIFTYONE_TEAMS_ORGANIZATION=test-auth0-organization", - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(types.NewMappingWithEquals(testCase.expected), project.Services[testCase.serviceName].Environment, fmt.Sprintf("%s - Environment should be equal", testCase.name)) - }) - } -} - -func (s *commonServicesDockerComposeTest) TestServicePorts() { - testCases := []struct { - name string - serviceName string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected []types.ServicePortConfig - }{ - { - "defaultTeamsApi", - "teams-api", - []string{composeFile}, - s.dotEnvFiles, - []types.ServicePortConfig{ - { - Mode: "ingress", - HostIP: "127.0.0.1", - Target: 8000, - Published: "8000", - Protocol: "tcp", - Extensions: nil, - }, - }, - }, - { - "defaultTeamsApp", - "teams-app", - []string{composeFile}, - s.dotEnvFiles, - []types.ServicePortConfig{ - { - Mode: "ingress", - HostIP: "127.0.0.1", - Target: 3000, - Published: "3000", - Protocol: "tcp", - Extensions: nil, - }, - }, - }, - { - "defaultFiftyoneApp", - "fiftyone-app", - []string{composeFile}, - s.dotEnvFiles, - []types.ServicePortConfig{ - { - Mode: "ingress", - HostIP: "127.0.0.1", - Target: 5151, - Published: "5151", - Protocol: "tcp", - Extensions: nil, - }, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.Services[testCase.serviceName].Ports, fmt.Sprintf("%s - Ports should be equal", testCase.name)) - }) - } -} - -func (s *commonServicesDockerComposeTest) TestServiceRestart() { - testCases := []struct { - name string - serviceName string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected string - }{ - { - "defaultTeamsApi", - "teams-api", - []string{composeFile}, - s.dotEnvFiles, - types.RestartPolicyAlways, - }, - { - "defaultTeamsApp", - "teams-app", - []string{composeFile}, - s.dotEnvFiles, - types.RestartPolicyAlways, - }, - { - "defaultFiftyoneApp", - "fiftyone-app", - []string{composeFile}, - s.dotEnvFiles, - types.RestartPolicyAlways, - }, - { - "dedicatedPluginsTeamsPlugins", - "teams-plugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - types.RestartPolicyAlways, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.Services[testCase.serviceName].Restart, fmt.Sprintf("%s - Restart should be equal", testCase.name)) - }) - } -} - -func (s *commonServicesDockerComposeTest) TestServiceVolumes() { - testCases := []struct { - name string - serviceName string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected []types.ServiceVolumeConfig - }{ - { - "defaultTeamsApi", - "teams-api", - []string{composeFile}, - s.dotEnvFiles, - nil, - }, - { - "defaultTeamsApp", - "teams-app", - []string{composeFile}, - s.dotEnvFiles, - nil, - }, - { - "defaultFiftyoneApp", - "fiftyone-app", - []string{composeFile}, - s.dotEnvFiles, - nil, - }, - { - "pluginsTeamsApi", - "teams-api", - []string{composePluginsFile}, - s.dotEnvFiles, - []types.ServiceVolumeConfig{ - { - Type: "volume", - Source: "plugins-vol", - Target: "/opt/plugins", - ReadOnly: false, - Volume: &types.ServiceVolumeVolume{}, - }, - }, - }, - { - "pluginsTeamsApp", - "teams-app", - []string{composePluginsFile}, - s.dotEnvFiles, - nil, - }, - { - "pluginsFiftyoneApp", - "fiftyone-app", - []string{composePluginsFile}, - s.dotEnvFiles, - []types.ServiceVolumeConfig{ - { - Type: "volume", - Source: "plugins-vol", - Target: "/opt/plugins", - ReadOnly: true, - Volume: &types.ServiceVolumeVolume{}, - }, - }, - }, - { - "dedicatedPluginsTeamsApi", - "teams-api", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []types.ServiceVolumeConfig{ - { - Type: "volume", - Source: "plugins-vol", - Target: "/opt/plugins", - ReadOnly: false, - Volume: &types.ServiceVolumeVolume{}, - }, - }, - }, - { - "dedicatedPluginsTeamsApp", - "teams-app", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - nil, - }, - { - "dedicatedPluginsFiftyoneApp", - "fiftyone-app", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - // []types.ServiceVolumeConfig{}, - nil, - }, - { - "dedicatedPluginsTeamsPlugins", - "teams-plugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - []types.ServiceVolumeConfig{ - { - Type: "volume", - Source: "plugins-vol", - Target: "/opt/plugins", - ReadOnly: true, - Volume: &types.ServiceVolumeVolume{}, - }, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.Services[testCase.serviceName].Volumes, fmt.Sprintf("%s - Service Volumes should be equal", testCase.name)) - }) - } -} -func (s *commonServicesDockerComposeTest) TestVolumes() { - testCases := []struct { - name string - configPaths []string // file paths to one or more Compose files. - envFiles []string // file paths to ".env" files with additional environment variable data - expected types.Volumes - }{ - { - "default", - []string{composeFile}, - s.dotEnvFiles, - nil, - }, - { - "plugins", - []string{composePluginsFile}, - s.dotEnvFiles, - types.Volumes{ - "plugins-vol": { - Name: "fiftyone-compose-test_plugins-vol", - }, - }, - }, - { - "dedicatedPlugins", - []string{composeDedicatedPluginsFile}, - s.dotEnvFiles, - types.Volumes{ - "plugins-vol": { - Name: "fiftyone-compose-test_plugins-vol", - }, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - projectOptions, err := cli.NewProjectOptions( - testCase.configPaths, - cli.WithWorkingDirectory(dockerDir), - cli.WithName(s.projectName), - cli.WithEnvFiles(testCase.envFiles...), - cli.WithDotEnv, - ) - s.NoError(err) - - project, err := cli.ProjectFromOptions(context.TODO(), projectOptions) - s.NoError(err) - - // Log Output - projectYAML, err := project.MarshalYAML() - s.NoError(err) - // The only next line only prints timestamp on the first line of the yaml file - // logger.Log(s.T(), string(projectYAML)) - for _, line := range strings.Split(string(projectYAML), "\n") { - logger.Log(s.T(), line) - } - - s.Equal(testCase.expected, project.Volumes, fmt.Sprintf("%s - Volumes should be equal", testCase.name)) - }) - } -} From 63b7ac3dcf4176528958b1f9fee74d9a3edd8c92 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:46:09 -0600 Subject: [PATCH 38/42] chore: fix bad paste --- tests/fixtures/docker/integration_legacy_auth.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index e65b4769..c9057064 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -28,7 +28,7 @@ FIFTYONE_AUTH_SECRET="test-fiftyone-auth-secret" # MongoDB MONGODB_PASSWORD='3-9XjJ-gUV?vp^e(WUk>LD&lAjh7yEji' # This is a randomly generated string # pragma: allowlist secret MONGODB_USERNAME=root -MONGODB_DIR=/tmp/mongodbhelm/gke-example/values.yaml +MONGODB_DIR=/tmp/mongodb MONGODB_BIND_ADDRESS=127.0.0.1 APP_USE_HTTPS=false From 9e187d7ec9b05ba1812a77dbd75306085c8b5e88 Mon Sep 17 00:00:00 2001 From: Kevin DiMichel Date: Thu, 25 Apr 2024 18:55:34 -0600 Subject: [PATCH 39/42] chore: remove unnecessary comment --- tests/integration/compose/docker-compose-internal-auth_test.go | 1 - tests/integration/compose/docker-compose-legacy-auth_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/integration/compose/docker-compose-internal-auth_test.go b/tests/integration/compose/docker-compose-internal-auth_test.go index 302efb05..3ac84aa6 100644 --- a/tests/integration/compose/docker-compose-internal-auth_test.go +++ b/tests/integration/compose/docker-compose-internal-auth_test.go @@ -59,7 +59,6 @@ func TestDockerComposeUpInternalAuth(t *testing.T) { if runtime.GOOS == "darwin" { overrideFiles = append(overrideFiles, darwinOverrideFile) overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFile, darwinOverrideFilePlugins) - // overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFilePlugins) } suite.Run(t, &commonServicesInternalAuthDockerComposeUpTest{ diff --git a/tests/integration/compose/docker-compose-legacy-auth_test.go b/tests/integration/compose/docker-compose-legacy-auth_test.go index ee1be302..acd6699c 100644 --- a/tests/integration/compose/docker-compose-legacy-auth_test.go +++ b/tests/integration/compose/docker-compose-legacy-auth_test.go @@ -59,7 +59,6 @@ func TestDockerComposeUpLegacyAuth(t *testing.T) { if runtime.GOOS == "darwin" { overrideFiles = append(overrideFiles, darwinOverrideFile) overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFile, darwinOverrideFilePlugins) - // overrideFilesPlugins = append(overrideFilesPlugins, darwinOverrideFilePlugins) } suite.Run(t, &commonServicesLegacyAuthDockerComposeUpTest{ From f3d74d5ede8e7c711666e531da9d84557d6d0a39 Mon Sep 17 00:00:00 2001 From: Eric Hofesmann Date: Mon, 29 Apr 2024 12:23:53 -0400 Subject: [PATCH 40/42] Clarify Docker CAS changes (#129) * Update docker README.md to clarify CAS changes Signed-off-by: Eric Hofesmann * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: Eric Hofesmann * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: Eric Hofesmann --------- Signed-off-by: Eric Hofesmann Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> --- docker/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker/README.md b/docker/README.md index 4d70a82b..d3178f36 100644 --- a/docker/README.md +++ b/docker/README.md @@ -142,6 +142,7 @@ To upgrade from versions prior to FiftyOne Teams v1.6 - Update your `.env` file, adding the variables listed above - Update your `compose.override.yaml` with `teams-cas` changes (if necessary) - Run `docker compose` commands from the `legacy-auth` directory +- When using path-based routing, configure a `/cas` route to value of the `CAS_BIND_PORT` ### Snapshot Archival @@ -453,6 +454,16 @@ create a new IdP or modify your existing configuration. 1. In the `.env` file, set the required environment variables - `FIFTYONE_API_URI` - `FIFTYONE_AUTH_SECRET` + - `CAS_BASE_URL` + - `CAS_BIND_ADDRESS` + - `CAS_BIND_PORT` + - `CAS_DATABASE_NAME` + - `CAS_DEBUG` + - `CAS_DEFAULT_USER_ROLE` + + > **Note**: For the `CAS_*` variables, consider using + > the seed values from the `.env.template` file + 1. Ensure all FiftyOne SDK users either - set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` From 3d96c7d51405512622d8ed530ebd3e9670e55af1 Mon Sep 17 00:00:00 2001 From: Eric Hofesmann Date: Mon, 29 Apr 2024 12:24:09 -0400 Subject: [PATCH 41/42] Remove broken link to internal mode migration (#130) * Update docker README.md Signed-off-by: Eric Hofesmann * Update helm README.md Signed-off-by: Eric Hofesmann * Update helm/fiftyone-teams-app/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: Eric Hofesmann * Update docker/README.md Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> Signed-off-by: Eric Hofesmann * Update README.md Signed-off-by: Eric Hofesmann * Update README.md Signed-off-by: Eric Hofesmann --------- Signed-off-by: Eric Hofesmann Co-authored-by: Kevin DiMichel <56850465+kevin-dimichel@users.noreply.github.com> --- docker/README.md | 7 ++++--- helm/fiftyone-teams-app/README.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docker/README.md b/docker/README.md index d3178f36..22a239fb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -112,12 +112,13 @@ documentation before completing your upgrade. Voxel51 recommends upgrading your deployment using [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -and -[migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -to +and migrating to [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) after confirming your initial upgrade was successful. +Please contact your Voxel51 customer success +representative for assistance in migrating to internal mode. + The CAS service requires changes to your `.env` files. A brief summary of those changes include diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 1bf97420..cfd11518 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -93,12 +93,13 @@ documentation before completing your upgrade. Voxel51 recommends upgrading your deployment using [`legacy` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#legacy-mode) -and -[migrating](https://docs.voxel51.com/teams/pluggable_auth.html#migrating-from-legacy-to-internal-mode) -to +and migrating to [`internal` authentication mode](https://docs.voxel51.com/teams/pluggable_auth.html#internal-mode) after confirming your initial upgrade was successful. +Please contact your Voxel51 customer success +representative for assistance in migrating to internal mode. + The CAS service requires changes to your `values.yaml` files. A brief summary of those changes include From cf6819aa1c921d89ad329bc29b32dd0385f8b7a8 Mon Sep 17 00:00:00 2001 From: Allen Lee Date: Mon, 29 Apr 2024 15:22:14 -0400 Subject: [PATCH 42/42] Minor updates v1.6 docker-compose deploy docs (#132) * Update README.md * docs: cleanup docs suggestions by Allen --------- Signed-off-by: Allen Lee Co-authored-by: Kevin DiMichel --- docker/README.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/docker/README.md b/docker/README.md index 22a239fb..fabbc7e2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -141,10 +141,15 @@ To upgrade from versions prior to FiftyOne Teams v1.6 - Copy your `compose.override.yaml` file into the `legacy-auth` directory - `cd` into the `legacy-auth` directory - Update your `.env` file, adding the variables listed above + - For seed values, see + [legacy-auth/env.template](legacy-auth/env.template) - Update your `compose.override.yaml` with `teams-cas` changes (if necessary) - Run `docker compose` commands from the `legacy-auth` directory - When using path-based routing, configure a `/cas` route to value of the `CAS_BIND_PORT` +> **NOTE**: See +> [Upgrade Process Recommendations](#upgrade-process-recommendations) + ### Snapshot Archival Since version v1.5, FiftyOne Teams supports @@ -399,12 +404,10 @@ create a new IdP or modify your existing configuration. 1. Copy your `compose.override.yaml` and `.env` files into the `legacy-auth` directory 1. `cd` into the `legacy-auth` directory -1. Make sure your `.env` file includes the required - `FIFTYONE_ENCRYPTION_KEY` environment variable -1. Make sure your `.env` file includes the required `FIFTYONE_API_URI` - environment variable -1. Make sure your `.env` file includes the required `FIFTYONE_AUTH_SECRET` - environment variable +1. In your `.env` file, set the required environment variables + - `FIFTYONE_ENCRYPTION_KEY` + - `FIFTYONE_API_URI` + - `FIFTYONE_AUTH_SECRET` 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) with `FIFTYONE_DATABASE_ADMIN=true` (this is not the default for this release). @@ -428,7 +431,7 @@ create a new IdP or modify your existing configuration. ### From FiftyOne Teams Version 1.1.0 and later -> **NOTE**: Upgrading from versions of FiftyOne Teams prior to v1.1.0 requires +> **NOTE**: Upgrading from versions of FiftyOne Teams v1.1.0 and later requires > upgrading the database and will interrupt all SDK connections. > You should coordinate this upgrade carefully with your end-users. @@ -463,10 +466,12 @@ create a new IdP or modify your existing configuration. - `CAS_DEFAULT_USER_ROLE` > **Note**: For the `CAS_*` variables, consider using - > the seed values from the `.env.template` file + > the seed values from the `.env.template` file. + > See + > [Central Authentication Service](#central-authentication-service) 1. Ensure all FiftyOne SDK users either - - set `FIFTYONE_DATABASE_ADMIN=false` + - Set `FIFTYONE_DATABASE_ADMIN=false` - `unset FIFTYONE_DATABASE_ADMIN` - This should generally be your default 1. [Upgrade to FiftyOne Teams version 1.6.0](#deploying-fiftyone-teams) @@ -503,7 +508,7 @@ create a new IdP or modify your existing configuration. [FiftyOne Teams Environment Variables](#fiftyone-teams-environment-variables) table. 1. Create a `compose.override.yaml` with any configuration overrides for - this deployment. + this deployment 1. For the first installation, set ```yaml @@ -521,7 +526,7 @@ create a new IdP or modify your existing configuration. ``` 1. After the successful installation, and logging into Fiftyone Teams - 1. In `compose.override.yaml` remove the `FIFTYONE_DATABASE_ADMIN` override + 1. In `compose.override.yaml`, remove the `FIFTYONE_DATABASE_ADMIN` override ```yaml services: @@ -530,10 +535,10 @@ create a new IdP or modify your existing configuration. # FIFTYONE_DATABASE_ADMIN: true ``` - > **Note**: This example shows commenting this line, + > **NOTE**: This example shows commenting this line, > however you may remove the line. - or set it to `false` like in + or set it to `false` like in ```yaml services: