diff --git a/.github/workflows/build-and-publish-images.yaml b/.github/workflows/build-and-publish-images.yaml index 111b03fbc..e392c39f4 100644 --- a/.github/workflows/build-and-publish-images.yaml +++ b/.github/workflows/build-and-publish-images.yaml @@ -77,7 +77,7 @@ jobs: - name: Restore Docker cache (amd64) if: ${{ (matrix.image == 'secrets-manager') || (matrix.image == 'session-manager') || (matrix.image == 'training-portal') || (matrix.image == 'tunnel-manager') }} - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-${{matrix.image}}-${{github.sha}} @@ -86,7 +86,7 @@ jobs: - name: Restore Docker cache (arm64) if: ${{ (matrix.image == 'secrets-manager') || (matrix.image == 'session-manager') || (matrix.image == 'training-portal') || (matrix.image == 'tunnel-manager') }} - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-${{matrix.image}}-${{github.sha}} @@ -155,14 +155,14 @@ jobs: - name: Save Docker cache (amd64) if: ${{ (matrix.image == 'secrets-manager') || (matrix.image == 'session-manager') || (matrix.image == 'training-portal') || (matrix.image == 'tunnel-manager') }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-${{matrix.image}}-${{github.sha}} - name: Save Docker cache (arm64) if: ${{ (matrix.image == 'secrets-manager') || (matrix.image == 'session-manager') || (matrix.image == 'training-portal') || (matrix.image == 'tunnel-manager') }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-${{matrix.image}}-${{github.sha}} @@ -224,7 +224,7 @@ jobs: docker images prune - name: Restore Docker cache (amd64) - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-base-environment-${{github.sha}} @@ -232,7 +232,7 @@ jobs: ${{runner.os}}-buildx-cache-amd64-base-environment- - name: Restore Docker cache (arm64) - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-base-environment-${{github.sha}} @@ -304,13 +304,13 @@ jobs: du -ks /tmp/.buildx-cache-* || true - name: Save Docker cache (amd64) - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-base-environment-${{github.sha}} - name: Save Docker cache (arm64) - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-base-environment-${{github.sha}} @@ -386,7 +386,7 @@ jobs: - name: Restore Docker cache (amd64) if: ${{ (matrix.image == 'conda-environment') }} - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-${{matrix.image}}-${{github.sha}} @@ -395,7 +395,7 @@ jobs: - name: Restore Docker cache (arm64) if: ${{ (matrix.image == 'conda-environment') }} - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-${{matrix.image}}-${{github.sha}} @@ -480,14 +480,14 @@ jobs: - name: Save Docker cache (amd64) if: ${{ (matrix.image == 'conda-environment') }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-amd64-new key: ${{runner.os}}-buildx-cache-amd64-${{matrix.image}}-${{github.sha}} - name: Save Docker cache (arm64) if: ${{ (matrix.image == 'conda-environment') }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: /tmp/.buildx-cache-arm64-new key: ${{runner.os}}-buildx-cache-arm64-${{matrix.image}}-${{github.sha}} @@ -536,7 +536,7 @@ jobs: -v imageRegistry.namespace=${{env.REPOSITORY_OWNER}} > package-repository/packages/cluster-essentials.educates.dev/educates-cluster-essentials-${{env.REPOSITORY_TAG}}.yaml - name: Save educates-cluster-essentials.yaml - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: educates-cluster-essentials-${{env.REPOSITORY_TAG}}.yaml path: package-repository/packages/cluster-essentials.educates.dev/educates-cluster-essentials-${{env.REPOSITORY_TAG}}.yaml @@ -568,7 +568,7 @@ jobs: -v imageRegistry.namespace=${{env.REPOSITORY_OWNER}} > package-repository/packages/training-platform.educates.dev/educates-training-platform-${{env.REPOSITORY_TAG}}.yaml - name: Save educates-training-platform.yaml - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: educates-training-platform-${{env.REPOSITORY_TAG}}.yaml path: package-repository/packages/training-platform.educates.dev/educates-training-platform-${{env.REPOSITORY_TAG}}.yaml @@ -581,13 +581,13 @@ jobs: ytt -f carvel-packages/repository.yaml -v packageRepository=ghcr.io/${{env.REPOSITORY_OWNER}} -v repositoryVersion=${{env.REPOSITORY_TAG}} > educates-packagerepository.yaml - name: Save educates-training-platform repository.yaml - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: educates-packagerepository.yaml path: educates-packagerepository.yaml - name: Save educates-training-platform package repository - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: package-repository path: package-repository @@ -607,7 +607,7 @@ jobs: echo "REPOSITORY_OWNER=${REPOSITORY_OWNER,,}" >>${GITHUB_ENV} - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21.4" cache-dependency-path: | @@ -624,7 +624,7 @@ jobs: IMAGE_REPOSITORY=ghcr.io/${{env.REPOSITORY_OWNER}} go build -o educates-linux-amd64 -ldflags "-X 'main.projectVersion=$REPOSITORY_TAG' -X 'main.imageRepository=$IMAGE_REPOSITORY'" cmd/educates/main.go - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: educates-linux-amd64 path: client-programs/educates-linux-amd64 @@ -644,7 +644,7 @@ jobs: echo "REPOSITORY_OWNER=${REPOSITORY_OWNER,,}" >>${GITHUB_ENV} - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21.4" cache-dependency-path: | @@ -661,7 +661,7 @@ jobs: IMAGE_REPOSITORY=ghcr.io/${{env.REPOSITORY_OWNER}} GOOS=linux GOARCH=arm64 go build -o educates-linux-arm64 -ldflags "-X 'main.projectVersion=$REPOSITORY_TAG' -X 'main.imageRepository=$IMAGE_REPOSITORY'" cmd/educates/main.go - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: educates-linux-arm64 path: client-programs/educates-linux-arm64 @@ -681,7 +681,7 @@ jobs: echo "REPOSITORY_OWNER=$(echo "$REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')" >>${GITHUB_ENV} - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21.4" cache-dependency-path: | @@ -703,7 +703,7 @@ jobs: # GOOS=darwin GOARCH=amd64 go build -o educates-darwin-amd64 -ldflags "-X 'main.projectVersion=$REPOSITORY_TAG' -X 'main.imageRepository=$IMAGE_REPOSITORY'" cmd/educates/main.go go build -o educates-darwin-amd64 -ldflags "-X 'main.projectVersion=$REPOSITORY_TAG' -X 'main.imageRepository=$IMAGE_REPOSITORY'" cmd/educates/main.go - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: educates-darwin-amd64 path: client-programs/educates-darwin-amd64 @@ -723,7 +723,7 @@ jobs: echo "REPOSITORY_OWNER=$(echo "$REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')" >>${GITHUB_ENV} - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21.4" cache-dependency-path: | @@ -740,7 +740,7 @@ jobs: IMAGE_REPOSITORY=ghcr.io/${{env.REPOSITORY_OWNER}} GOOS=darwin GOARCH=arm64 go build -o educates-darwin-arm64 -ldflags "-X 'main.projectVersion=$REPOSITORY_TAG' -X 'main.imageRepository=$IMAGE_REPOSITORY'" cmd/educates/main.go - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: educates-darwin-arm64 path: client-programs/educates-darwin-arm64 @@ -756,25 +756,25 @@ jobs: steps: - name: Restore educates-linux-amd64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-linux-amd64 path: client-programs - name: Restore educates-linux-arm64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-linux-arm64 path: client-programs - name: Restore educates-darwin-amd64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-darwin-amd64 path: client-programs - name: Restore educates-darwin-arm64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-darwin-arm64 path: client-programs @@ -889,37 +889,37 @@ jobs: echo "PRERELEASE=${PRERELEASE}" >>${GITHUB_ENV} - name: Restore packagerepository.yaml - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-packagerepository.yaml - name: Restore educates-cluster-essentials.yaml - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-cluster-essentials-${{env.REPOSITORY_TAG}}.yaml - name: Restore educates-training-platform.yaml - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-training-platform-${{env.REPOSITORY_TAG}}.yaml - name: Restore educates-linux-amd64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-linux-amd64 - name: Restore educates-linux-arm64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-linux-arm64 - name: Restore educates-darwin-amd64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-darwin-amd64 - name: Restore educates-darwin-arm64 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-darwin-arm64 @@ -939,12 +939,12 @@ jobs: cat checksums.txt >> release-notes.md echo '```' >> release-notes.md - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: checksums.txt path: checksums.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: release-notes.md path: release-notes.md @@ -996,13 +996,13 @@ jobs: echo "REPOSITORY_TAG=${REPOSITORY_TAG}" >>${GITHUB_ENV} - name: Restore educates-cluster-essentials.yaml - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-cluster-essentials-${{env.REPOSITORY_TAG}}.yaml path: package-repository/packages/cluster-essentials.educates.dev/ - name: Restore educates-training-platform.yaml - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: educates-training-platform-${{env.REPOSITORY_TAG}}.yaml path: package-repository/packages/training-platform.educates.dev/ diff --git a/carvel-packages/training-platform/bundle/config/00-values.yaml b/carvel-packages/training-platform/bundle/config/00-values.yaml index 3977109e3..6383d6243 100644 --- a/carvel-packages/training-platform/bundle/config/00-values.yaml +++ b/carvel-packages/training-platform/bundle/config/00-values.yaml @@ -6,16 +6,16 @@ imageVersions: image: "debian:sid-20230502-slim" - name: docker-in-docker image: "docker:20.10.18-dind" -- name: rancher-k3s-v1.22 - image: "rancher/k3s:v1.22.16-k3s1" -- name: rancher-k3s-v1.23 - image: "rancher/k3s:v1.23.14-k3s1" -- name: rancher-k3s-v1.24 - image: "rancher/k3s:v1.24.8-k3s1" - name: rancher-k3s-v1.25 - image: "rancher/k3s:v1.25.3-k3s1" + image: "rancher/k3s:v1.25.16-k3s4" +- name: rancher-k3s-v1.26 + image: "rancher/k3s:v1.26.12-k3s1" +- name: rancher-k3s-v1.27 + image: "rancher/k3s:v1.27.9-k3s1" +- name: rancher-k3s-v1.28 + image: "rancher/k3s:v1.28.5-k3s1" - name: loftsh-vcluster - image: "loftsh/vcluster:0.13.0" + image: "loftsh/vcluster:0.18.1" - name: contour-bundle #! contour.community.tanzu.vmware.com.1.22.0 image: "projects.registry.vmware.com/tce/contour@sha256:b68ad8ec3012db7d2a2e84f8544685012e2dca09d28d54dce8735fb60f0d05bf" diff --git a/carvel-packages/training-platform/bundle/config/06-secrets.yaml b/carvel-packages/training-platform/bundle/config/06-secrets.yaml index 1c293ff83..4460eea16 100644 --- a/carvel-packages/training-platform/bundle/config/06-secrets.yaml +++ b/carvel-packages/training-platform/bundle/config/06-secrets.yaml @@ -52,7 +52,6 @@ kind: Secret metadata: name: #@ "{}-ca".format(data.values.clusterIngress.domain) namespace: #@ data.values.operator.namespace -type: kubernetes.io/tls data: ca.crt: #@ base64.encode(ingress_ca_certificate) #@ end diff --git a/carvel-packages/training-platform/bundle/config/11-session-manager/01-crds-workshop.yaml b/carvel-packages/training-platform/bundle/config/11-session-manager/01-crds-workshop.yaml index 5f33cfa31..2e0cd24b0 100644 --- a/carvel-packages/training-platform/bundle/config/11-session-manager/01-crds-workshop.yaml +++ b/carvel-packages/training-platform/bundle/config/11-session-manager/01-crds-workshop.yaml @@ -994,6 +994,33 @@ spec: type: string storage: type: string + services: + type: object + properties: + fromHost: + type: array + items: + type: object + required: + - from + - to + properties: + from: + type: string + to: + type: string + fromVirtual: + type: array + items: + type: object + required: + - from + - to + properties: + from: + type: string + to: + type: string ingress: type: object required: @@ -1061,6 +1088,24 @@ spec: type: string port: type: integer + path: + type: string + pathRewrite: + type: array + items: + type: object + required: + - pattern + - replacement + properties: + pattern: + type: string + replacement: + type: string + changeOrigin: + type: boolean + secure: + type: boolean headers: type: array items: diff --git a/carvel-packages/training-platform/config/images.yaml b/carvel-packages/training-platform/config/images.yaml index 688dd875a..caa9adc4c 100644 --- a/carvel-packages/training-platform/config/images.yaml +++ b/carvel-packages/training-platform/config/images.yaml @@ -45,16 +45,16 @@ imageVersions: image: "debian:sid-20230502-slim" - name: docker-in-docker image: "docker:20.10.18-dind" -- name: rancher-k3s-v1.22 - image: "rancher/k3s:v1.22.16-k3s1" -- name: rancher-k3s-v1.23 - image: "rancher/k3s:v1.23.14-k3s1" -- name: rancher-k3s-v1.24 - image: "rancher/k3s:v1.24.8-k3s1" - name: rancher-k3s-v1.25 - image: "rancher/k3s:v1.25.3-k3s1" + image: "rancher/k3s:v1.25.16-k3s4" +- name: rancher-k3s-v1.26 + image: "rancher/k3s:v1.26.12-k3s1" +- name: rancher-k3s-v1.27 + image: "rancher/k3s:v1.27.9-k3s1" +- name: rancher-k3s-v1.28 + image: "rancher/k3s:v1.28.5-k3s1" - name: loftsh-vcluster - image: "loftsh/vcluster:0.13.0" + image: "loftsh/vcluster:0.18.1" - name: contour-bundle #! contour.community.tanzu.vmware.com.1.22.0 image: "projects.registry.vmware.com/tce/contour@sha256:b68ad8ec3012db7d2a2e84f8544685012e2dca09d28d54dce8735fb60f0d05bf" diff --git a/developer-docs/release-procedures.md b/developer-docs/release-procedures.md index fd62adbad..b4cf20404 100644 --- a/developer-docs/release-procedures.md +++ b/developer-docs/release-procedures.md @@ -43,13 +43,13 @@ Development builds created by manually invoking the GitHub actions workflow are The format of the tags you can use for pre-release builds are: -* `X.Y.Z.alpha-N` -* `X.Y.Z.beta-N` -* `X.Y.Z.rc-N` +* `X.Y.Z-alpha.N` +* `X.Y.Z-beta.N` +* `X.Y.Z-rc.N` These can be created against a branch of a fork created from the main GitHub repository, in which case the release will be added against the fork and not the main GitHub repository. -Because the same tag might be used in the main GitHub repository, which would be propagated to the fork when the repositories are synchronized, use of these tags is discouraged in forks except for testing release procedures. If done for this purpose, it is suggest that a tag of the form `0.0.1.???-N` be used, and that after testing both the tag and GitHub release be deleted once no longer required, so that the same tag can be used again in such future testing. +Because the same tag might be used in the main GitHub repository, which would be propagated to the fork when the repositories are synchronized, use of these tags is discouraged in forks except for testing release procedures. If done for this purpose, it is suggest that a tag of the form `0.0.1-???.N` be used, and that after testing both the tag and GitHub release be deleted once no longer required, so that the same tag can be used again in such future testing. Note that pre-release versions created in a fork will only include container images built for the `linux/amd64` platform. If you need for a pre-release version created in a fork to include support for the `linux/arm64` platform, you will need to create a GitHub secret in the repository fork called `TARGET_PLATFORMS` with a value of `linux/arm64` or `linux/amd64,linux/arm64`. Only `linux/amd64` platform support is included by default when builds are done in a fork due to the significantly longer build times required for `linux/arm64`. diff --git a/docker-extension/ui/package-lock.json b/docker-extension/ui/package-lock.json index 171b8da33..f4d574dde 100644 --- a/docker-extension/ui/package-lock.json +++ b/docker-extension/ui/package-lock.json @@ -27,7 +27,7 @@ "@vitejs/plugin-react": "^4.1.0", "jest": "^29.7.0", "typescript": "^5.2.2", - "vite": "^4.4.12" + "vite": "^4.5.2" } }, "node_modules/@ampproject/remapping": { @@ -6212,9 +6212,9 @@ "dev": true }, "node_modules/vite": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", - "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/docker-extension/ui/package.json b/docker-extension/ui/package.json index 07e371879..c1740ddee 100644 --- a/docker-extension/ui/package.json +++ b/docker-extension/ui/package.json @@ -28,6 +28,6 @@ "@vitejs/plugin-react": "^4.1.0", "jest": "^29.7.0", "typescript": "^5.2.2", - "vite": "^4.4.12" + "vite": "^4.5.2" } } diff --git a/project-docs/custom-resources/training-portal.md b/project-docs/custom-resources/training-portal.md index f6d06f0fc..17431e8af 100644 --- a/project-docs/custom-resources/training-portal.md +++ b/project-docs/custom-resources/training-portal.md @@ -413,7 +413,7 @@ spec: Setting extra environment variables ----------------------------------- -If you want to override any environment variables for workshop instances created for a specific work, you can provide the environment variables in the ``env`` field of that workshop. +If you want to override any environment variables for workshop instances created for a specific workshop, you can provide the environment variables in the ``env`` field of that workshop. ```yaml spec: diff --git a/project-docs/custom-resources/workshop-definition.md b/project-docs/custom-resources/workshop-definition.md index 329a8198d..968de5886 100644 --- a/project-docs/custom-resources/workshop-definition.md +++ b/project-docs/custom-resources/workshop-definition.md @@ -1601,6 +1601,57 @@ spec: If the service uses standard ``http`` or ``https`` ports, you can leave out the ``port`` property and the port will be set based on the value of ``protocol``. +When ``protocol`` is ``https`` the target must use a valid signed SSL certificate that can be verified. If SSL certificate verification should be skipped, the ``secure`` property can be set to ``false``. + +```yaml +spec: + session: + ingresses: + - name: application + protocol: https + host: service.$(session_namespace).svc.$(cluster_domain) + secure: false +``` + +If you have mulitple backend services which need to be exposed under a single ingress host you can specify multiple ingress points for the same name and direct requests to different backends based on URL path prefix. + +```yaml +spec: + session: + ingresses: + - name: application + protocol: http + host: service-2.$(session_namespace).svc.$(cluster_domain) + port: 8080 + path: /api/ + - name: application + protocol: http + host: service-1.$(session_namespace).svc.$(cluster_domain) + port: 8080 +``` + +When multiple entries exist for the same ingress name, they are processed in order and as such the more deeply nested URL path should be listed before others. + +If the URL path needs to be rewritten when it passes through the proxy you can use the ``pathRewrite`` property. + +```yaml +spec: + session: + ingresses: + - name: application + protocol: http + host: service-2.$(session_namespace).svc.$(cluster_domain) + port: 8080 + path: /api/ + pathRewrite: + - pattern: "^/api/" + replacement: "/" + - name: application + protocol: http + host: service-1.$(session_namespace).svc.$(cluster_domain) + port: 8080 +``` + When a request is being proxied, you can specify additional request headers that should be passed to the service. ```yaml @@ -2000,7 +2051,7 @@ spec: applications: vcluster: enabled: true - version: "1.23" + version: "1.27" ``` When a virtual cluster is used the workshop session user only has access to the virtual cluster, there is no direct access to the underlying host Kubernetes cluster REST API. The ``kubeconfig`` file provided to the workshop user will be preconfigured to point at the virtual cluster and the workshop user will have cluster admin access to the virtual cluster. @@ -2115,7 +2166,7 @@ spec: kind: App metadata: name: kapp-controller.0.44.9 - namespace: $(session_namespace)-vc + namespace: $(vcluster_namespace) spec: noopDelete: true syncPeriod: 24h @@ -2133,7 +2184,7 @@ spec: - kapp: {} ``` -The namespace on the ``App`` resource must be ``$(session_namespace)-vc``. This is the namespace where the virtual cluster control plane processes run. In order for ``kapp-controller`` to know to install the packages into the virtual cluster, the ``App`` definition must include ``spec.cluster`` section defined as: +The namespace on the ``App`` resource must be ``$(vcluster_namespace)``. This variable references the namespace where the virtual cluster control plane processes run. In order for ``kapp-controller`` to know to install the packages into the virtual cluster, the ``App`` definition must include ``spec.cluster`` section defined as: ```yaml spec: @@ -2212,6 +2263,38 @@ done Note that you do not need to install ``kapp-controller`` into the virtual cluster if using ``kapp-controller`` in the host Kubernetes as the means to install packages, and nothing in the workshop setup scripts or instructions tries to install additional packages. +When deploying an application in the virtual cluster which includes a Kubernetes `ClusterIP` service, and you need to expose that service to other users in the underlying host Kubernetes cluster using a predictable name, you can declare a mapping for the Kubernetes service. + +```yaml +spec: + session: + applications: + vcluster: + enabled: true + services: + fromVirtual: + - from: my-virtual-namespace/my-virtual-service + to: my-host-service +``` + +The ``my-host-service`` created in the underlying host Kubernetes cluster will always be created in the namespace set aside for a workshop session for the virtual cluster. The name of this namespace is available as a data variable ``vcluster_namespace`` in the workshop definition and workshop instructions. + +To inject a Kubernetes service from the host Kubernetes cluster into the virtual cluster instance for a workshop, a mapping in the other direction can be specified. + +```yaml +spec: + session: + applications: + vcluster: + enabled: true + services: + fromHost: + - from: $(workshop_namespace)/my-host-service + to: my-virtual-namespace/my-virtual-service +``` + +The Kubernetes service being mapped can be from any namespace in the host Kubernetes cluster, but for workshops this would usually be ``$(workshop_namespace)`` if a shared service created from ``environment.objects``, or ``$(session_namespace)`` where created for each workshop session from ``session.objects``. + (enabling-the-local-git-server)= Enabling the local Git server ----------------------------- diff --git a/project-docs/getting-started/creating-a-workshop.md b/project-docs/getting-started/creating-a-workshop.md index 9209f4e3b..587f3b8b6 100644 --- a/project-docs/getting-started/creating-a-workshop.md +++ b/project-docs/getting-started/creating-a-workshop.md @@ -115,7 +115,7 @@ When such an ``exercises`` sub directory exists, the initial working directory f Note that the ``exercises`` directory isn't set as the home directory of the user. This means that if a user inadvertently runs ``cd`` with no arguments from the terminal, they will end up back in the home directory. -To try and avoid confusion and provide a means for a user to easily get back to where they need to be, it is recommended if instructing users to change directories, to always provide a full path relative to the home directory. Thus use a path of the form ``~/exercises/example-1`` rather than ``example-1``, to the ``cd`` command if changing directories. By using a full path, they can execute the command again and know they will end up back in the required location. +To try and avoid confusion and provide a means for a user to easily get back to where they need to be, it is recommended if instructing users to change directories, to always provide a full path relative to the home directory. Thus use a path of the form ``~/exercises/example-1`` or ``$HOME/exercises/example-1`` rather than ``example-1``, to the ``cd`` command if changing directories. By using a full path, they can execute the command again and know they will end up back in the required location. (modifying-workshop-content)= Modifying workshop content diff --git a/project-docs/release-notes/version-2.7.0.md b/project-docs/release-notes/version-2.7.0.md index 2f4aa8f0c..12792f529 100644 --- a/project-docs/release-notes/version-2.7.0.md +++ b/project-docs/release-notes/version-2.7.0.md @@ -51,6 +51,28 @@ New Features * Added `jdk21-environment` workshop base image. +* Added ability to map services from/to a virtual cluster. To support this, also + added a `$(vcluster_namespace)` variable that can be used in the workshop + definition to refer to the namespace used by virtual cluster control plane. + For more details see [Provisioning a virtual + cluster](provisioning-a-virtual-cluster). + +* A default command can now be specified for all terminal sessions by supplying + a ``terminal.sh`` file in the ``workshop`` directory. Where a terminal script + exists for a specific session at the same time, it will take precedence. + +* When adding an additional ingress points for a workshop session and the target + uses the ``https`` protocol, it is now possible to supply the ``secure`` + property set to ``false`` to indicate that certification verification should + be skipped. For more details see [Defining additional ingress + points](defining-additional-ingress-points). + +* Path based prefix routing can now be used in conjunction with additional + workshop ingress points. This allows one to combine multiple backend services + under one ingress hostname at different URL paths. If necessary the path can + be rewritten as it passes through the proxy. For more details see [Defining + additional ingress points](defining-additional-ingress-points). + Features Changed ---------------- @@ -124,6 +146,17 @@ Features Changed interface was previously addressed, but the option to disable bracketed paste mode in `bash` wasn't removed at the time when it should have. +* When using a editor clickable action, you could use a target path starting + with `~/` to denote a file relative to the home directory of the workshop + user. You can now also use the prefix `$HOME/`. Both are to avoid hard coding + the `/home/eduk8s` path, which could in the future change if the name of the + workshop user were ever changed. + +* Updated version of vcluster package used. Virtual clusters will now default to + being created using Kubernetes 1.27. Kubernetes versions 1.22 through 1.24 are + no longer supported and if selecting Kubernetes versions must be in range 1.25 + through to 1.28. + Bugs Fixed ---------- @@ -142,3 +175,33 @@ Bugs Fixed * Filtering workshop environments based on state when requesting catalog of workshops via the training portal API wasn't working and only workshop environments in running state were ever returned. + +* When using ``env`` defaults for all workshops, or specific workshops in the + training portal definition, any changes to these in the training portal + definition after the initial workshop environment had been created for a + workshop, were not being reflected in subsequent workshop sessions created + after that point. + +* Processing of Kyverno rules was not correctly injecting a namespace selector + targeting workshop session namespaces when the Kyverno rule used a `match.any` + or `match.all` condition. Consequence was that these rules were being applied + to all namespaces and thus could have affected other applications deployed to + the Kubernetes cluster besides Educates. The affected rules were + `disallow-ingress-nginx-custom-snippets`, `restrict-annotations` + `restrict-ingress-paths` and `prevent-cr8escape`. + +* Including TLS wildcard certificates embedded in the data values file was not + working as there was a typo when looking up the data value. This meant that + a secret was not created for the TLS wildcard certificate when embedded in + data values file and Educates was only configured for plain HTTP and not HTTPS. + This issue was inadvertantly added when support was added for supplying the + TLS wildcard certificate and CA secrets as actual secrets rather than + embedded in the data values file. + +* The generated CA secret was incorrectly setting the secret type to + `kubernetes.io/tls` which resulted in Kubernetes rejecting it as it didn't + contain `tls.crt` and `tls.key` data attributes as required by Kubernetes + for that type of secret. Secret type should have been left as default generic + opaque data secret. This issue was inadvertantly introduced when support was + added for providing the CA secret as an actual secret rather than being + enmbedded in the data values file when deploying Educates. diff --git a/project-docs/workshop-content/workshop-instructions.md b/project-docs/workshop-content/workshop-instructions.md index 175c2518d..69a8c312e 100644 --- a/project-docs/workshop-content/workshop-instructions.md +++ b/project-docs/workshop-content/workshop-instructions.md @@ -355,7 +355,7 @@ file: ~/exercises/sample.txt ``` ~~~ -You can use ``~/`` prefix to indicate the path relative to the home directory of the session. On opening the file, if you want the insertion point left on a specific line, provide the ``line`` property. Lines numbers start at ``1``. +You can use ``~/`` or ``$HOME/`` prefix to indicate the path relative to the home directory of the session. On opening the file, if you want the insertion point left on a specific line, provide the ``line`` property. Lines numbers start at ``1``. ~~~text ```editor:open-file diff --git a/project-docs/workshop-content/workshop-runtime.md b/project-docs/workshop-content/workshop-runtime.md index b3ce74262..4664ae787 100644 --- a/project-docs/workshop-content/workshop-runtime.md +++ b/project-docs/workshop-content/workshop-runtime.md @@ -157,3 +157,5 @@ echo exec bash ``` + +In the case where you need to provide a default command for all terminal sessions regardless of name, such as to redirect all sessions to a virtual machine or container, you can do so by providing an executable shell script ``terminal.sh`` in the ``workshop`` directory. Note that a terminal script for a specific named terminal session, if it also exists, will take precedence over this default terminal script. diff --git a/secrets-manager/requirements.txt b/secrets-manager/requirements.txt index 97f7730d3..be92c0762 100644 --- a/secrets-manager/requirements.txt +++ b/secrets-manager/requirements.txt @@ -1,5 +1,5 @@ kopf[full-auth]==1.36.2 bcrypt==4.1.2 -aiohttp==3.9.1 +aiohttp==3.9.2 PyYAML==6.0.1 pykube-ng==23.6.0 diff --git a/session-manager/handlers/application_vcluster.py b/session-manager/handlers/application_vcluster.py index fa3a0d5ac..8c33e5b16 100644 --- a/session-manager/handlers/application_vcluster.py +++ b/session-manager/handlers/application_vcluster.py @@ -5,22 +5,22 @@ from .operator_config import ( OPERATOR_API_GROUP, CLUSTER_STORAGE_GROUP, - RANCHER_K3S_V1_22_IMAGE, - RANCHER_K3S_V1_23_IMAGE, - RANCHER_K3S_V1_24_IMAGE, RANCHER_K3S_V1_25_IMAGE, + RANCHER_K3S_V1_26_IMAGE, + RANCHER_K3S_V1_27_IMAGE, + RANCHER_K3S_V1_28_IMAGE, LOFTSH_VCLUSTER_IMAGE, CONTOUR_BUNDLE_IMAGE, ) -K8S_DEFAULT_VERSION = "1.25" +K8S_DEFAULT_VERSION = "1.27" K3S_VERSIONS = { - "1.22": RANCHER_K3S_V1_22_IMAGE, - "1.23": RANCHER_K3S_V1_23_IMAGE, - "1.24": RANCHER_K3S_V1_24_IMAGE, "1.25": RANCHER_K3S_V1_25_IMAGE, + "1.26": RANCHER_K3S_V1_26_IMAGE, + "1.27": RANCHER_K3S_V1_27_IMAGE, + "1.28": RANCHER_K3S_V1_28_IMAGE, } @@ -39,6 +39,10 @@ def vcluster_workshop_spec_patches(workshop_spec, application_properties): "name": "vcluster_secret", "value": "$(session_namespace)-vc-kubeconfig", }, + { + "name": "vcluster_namespace", + "value": "$(session_namespace)-vc", + }, ], } } @@ -293,6 +297,24 @@ def vcluster_session_objects_list(workshop_spec, application_properties): vcluster_objects = xget(application_properties, "objects", []) + syncer_args = [] + + syncer_args.append(f"--sync={sync_resources}") + + map_services_from_virtual = xget(application_properties, "services.fromVirtual", []) + + for mapping in map_services_from_virtual: + from_virtual = mapping["from"] + to_host = mapping["to"] + syncer_args.append(f"--map-virtual-service={from_virtual}={to_host}") + + map_services_from_host = xget(application_properties, "services.fromHost", []) + + for mapping in map_services_from_host: + from_host = mapping["from"] + to_virtual = mapping["to"] + syncer_args.append(f"--map-host-service={from_host}={to_virtual}") + objects = [ { "apiVersion": "v1", @@ -360,6 +382,11 @@ def vcluster_session_objects_list(workshop_spec, application_properties): "resources": ["storageclasses"], "verbs": ["get", "list", "watch"], }, + { + "apiGroups": [""], + "resources": ["services"], + "verbs": ["get", "list", "watch"], + }, ], }, { @@ -693,8 +720,7 @@ def vcluster_session_objects_list(workshop_spec, application_properties): "--out-kube-config-secret=$(session_namespace)-vc-kubeconfig", "--kube-config-context-name=my-vcluster", "--leader-elect=false", - f"--sync={sync_resources}", - ], + ] + syncer_args, "livenessProbe": { "httpGet": { "path": "/healthz", diff --git a/session-manager/handlers/kyverno_rules.py b/session-manager/handlers/kyverno_rules.py index d621a7922..4c5b96528 100644 --- a/session-manager/handlers/kyverno_rules.py +++ b/session-manager/handlers/kyverno_rules.py @@ -34,10 +34,14 @@ def kyverno_environment_rules(workshop_spec, environment_name): else: rule["name"] = policy_name - resources = xget(rule, "match.all", []) + # Add a namespace selector to each resource which is a target of the + # rule. This is to ensure that the rule is only applied to the + # namespaces which are created for a workshop session. + + resources = [condition.get("resources", {}) for condition in xget(rule, "match.all", [])] if not resources: - resources = xget(rule, "match.any", []) + resources = [condition.get("resources", {}) for condition in xget(rule, "match.any", [])] if not resources: resources = [xget(rule, "match.resources", {})] diff --git a/session-manager/handlers/operator_config.py b/session-manager/handlers/operator_config.py index 2385b1b45..a330ce701 100644 --- a/session-manager/handlers/operator_config.py +++ b/session-manager/handlers/operator_config.py @@ -50,7 +50,7 @@ INGRESS_SECRET = xget(config_values, "clusterIngress.tlsCertificateRef.name") if not INGRESS_SECRET: - tls_certficate = xget(config_values, "clusterIngres.tlsCertificate", {}) + tls_certficate = xget(config_values, "clusterIngress.tlsCertificate", {}) if ( tls_certficate and tls_certficate.get("tls.crt") @@ -61,7 +61,7 @@ INGRESS_CA_SECRET = xget(config_values, "clusterIngress.caCertificateRef.name") if not INGRESS_CA_SECRET: - ca_certficate = xget(config_values, "clusterIngres.caCertificate", {}) + ca_certficate = xget(config_values, "clusterIngress.caCertificate", {}) if ca_certficate and ca_certficate.get("ca.crt"): INGRESS_CA_SECRET = f"{INGRESS_DOMAIN}-ca" @@ -159,10 +159,10 @@ def image_reference(name): "conda-environment:*": CONDA_ENVIRONMENT_IMAGE, } -RANCHER_K3S_V1_22_IMAGE = image_reference("rancher-k3s-v1.22") -RANCHER_K3S_V1_23_IMAGE = image_reference("rancher-k3s-v1.23") -RANCHER_K3S_V1_24_IMAGE = image_reference("rancher-k3s-v1.24") RANCHER_K3S_V1_25_IMAGE = image_reference("rancher-k3s-v1.25") +RANCHER_K3S_V1_26_IMAGE = image_reference("rancher-k3s-v1.26") +RANCHER_K3S_V1_27_IMAGE = image_reference("rancher-k3s-v1.27") +RANCHER_K3S_V1_28_IMAGE = image_reference("rancher-k3s-v1.28") LOFTSH_VCLUSTER_IMAGE = image_reference("loftsh-vcluster") diff --git a/session-manager/requirements.txt b/session-manager/requirements.txt index 824988646..b50837064 100644 --- a/session-manager/requirements.txt +++ b/session-manager/requirements.txt @@ -1,7 +1,7 @@ kopf[full-auth]==1.36.2 bcrypt==4.1.2 -aiohttp==3.9.1 +aiohttp==3.9.2 PyYAML==6.0.1 pykube-ng==23.6.0 wrapt==1.15.0 -cryptography==41.0.7 +cryptography==42.0.2 diff --git a/training-portal/requirements.txt b/training-portal/requirements.txt index dfa34c544..0466ce2eb 100644 --- a/training-portal/requirements.txt +++ b/training-portal/requirements.txt @@ -1,5 +1,5 @@ mod_wsgi==5.0.0 -Django==4.2.8 +Django==4.2.10 django-registration==3.4 django-crispy-forms==2.1 crispy-bootstrap5==2023.10 diff --git a/training-portal/src/project/apps/workshops/manager/environments.py b/training-portal/src/project/apps/workshops/manager/environments.py index 8f8944d8a..cfa024907 100644 --- a/training-portal/src/project/apps/workshops/manager/environments.py +++ b/training-portal/src/project/apps/workshops/manager/environments.py @@ -368,6 +368,8 @@ def update_workshop_environments(training_portal, workshops): environment.labels = labels + environment.env = workshop["env"] + environment.capacity = workshop["capacity"] environment.reserved = workshop["reserved"] @@ -484,7 +486,6 @@ def process_workshop_environment(portal, workshop, position): "secret": settings.INGRESS_SECRET, "class": settings.INGRESS_CLASS, }, - "env": environment.env, }, "environment": {"objects": [], "secrets": []}, "registry": environment.registry or None, diff --git a/training-portal/src/project/apps/workshops/templates/workshops/catalog.html b/training-portal/src/project/apps/workshops/templates/workshops/catalog.html index 84e972196..ccdff3fb8 100644 --- a/training-portal/src/project/apps/workshops/templates/workshops/catalog.html +++ b/training-portal/src/project/apps/workshops/templates/workshops/catalog.html @@ -70,8 +70,8 @@
{% endfor %} {% else %} -
-

No workshops available...

+
+

No workshops available...

{% endif %}
diff --git a/tunnel-manager/requirements.txt b/tunnel-manager/requirements.txt index deb51a79e..94c9977a5 100644 --- a/tunnel-manager/requirements.txt +++ b/tunnel-manager/requirements.txt @@ -1,5 +1,5 @@ kopf[full-auth]==1.36.1 -aiohttp==3.9.0 +aiohttp==3.9.2 PyYAML==6.0.1 pykube-ng==22.9.0 websockets==10.4 diff --git a/workshop-images/base-environment/opt/gateway/src/backend/modules/access.ts b/workshop-images/base-environment/opt/gateway/src/backend/modules/access.ts index deb7fc2ba..5a6b543e5 100644 --- a/workshop-images/base-environment/opt/gateway/src/backend/modules/access.ts +++ b/workshop-images/base-environment/opt/gateway/src/backend/modules/access.ts @@ -151,7 +151,7 @@ function register_oauth_callback(app: express.Application, oauth2_config: any, o return res.redirect(next_url) } catch (error) { - logger.error('Unexpected error occurred', error.message) + logger.error('Unexpected error occurred', { error: error.message }) return res.status(500).json("Authentication failed") } @@ -284,18 +284,18 @@ export async function setup_access(app: express.Application): Promise { // we just log it and return without failing. This will result in higher // level function needing the access token to fail instead. -const EXPIRATION_WINDOW_IN_SECONDS = 15*60 +const EXPIRATION_WINDOW_IN_SECONDS = 15 * 60 export async function check_for_access_token_expiry(session: any, oauth2_client: any) { let access_token = oauth2_client.createToken(JSON.parse(session.token)) - function expiring() : boolean { + function expiring(): boolean { return access_token.token.expires_at - (Date.now() + EXPIRATION_WINDOW_IN_SECONDS * 1000) <= 0 } if (expiring()) { try { - logger.debug("Refreshing accessing token", {token: access_token}) + logger.debug("Refreshing accessing token", { token: access_token }) let refresh_params = { scope: "user:info" @@ -303,11 +303,11 @@ export async function check_for_access_token_expiry(session: any, oauth2_client: access_token = await access_token.refresh(refresh_params) - logger.debug("Refreshed access token", {token: access_token}) + logger.debug("Refreshed access token", { token: access_token }) session.token = JSON.stringify(access_token) } catch (error) { - logger.error("Error refreshing access token", { message: error.message }) + logger.error("Error refreshing access token", { error: error.message }) } } } diff --git a/workshop-images/base-environment/opt/gateway/src/backend/modules/config.ts b/workshop-images/base-environment/opt/gateway/src/backend/modules/config.ts index 5e8a3de09..8aeefd3da 100644 --- a/workshop-images/base-environment/opt/gateway/src/backend/modules/config.ts +++ b/workshop-images/base-environment/opt/gateway/src/backend/modules/config.ts @@ -444,7 +444,11 @@ function calculate_ingresses() { "authentication": ingresses[i]["authentication"] || { "type": "session" }, "host": substitute_session_params(ingresses[i]["host"] || ""), "port": ingresses[i]["port"], + "path": ingresses[i]["path"] || "/", + "pathRewrite": ingresses[i]["pathRewrite"] || [], + "changeOrigin": ingresses[i]["changeOrigin"] === undefined ? true : ingresses[i]["changeOrigin"], "protocol": ingresses[i]["protocol"], + "secure": ingresses[i]["secure"] === undefined ? true : ingresses[i]["secure"], "headers": ingresses[i]["headers"] || [] }) } diff --git a/workshop-images/base-environment/opt/gateway/src/backend/modules/proxy.ts b/workshop-images/base-environment/opt/gateway/src/backend/modules/proxy.ts index 350a65adb..684174a09 100644 --- a/workshop-images/base-environment/opt/gateway/src/backend/modules/proxy.ts +++ b/workshop-images/base-environment/opt/gateway/src/backend/modules/proxy.ts @@ -6,47 +6,74 @@ import { config } from "./config" // Setup intercepts for proxying to internal application ports. export function setup_proxy(app: express.Application, auth: string) { - function filter(pathname, req) { - let host = req.headers.host + if (config.ingresses) { + // For each ingress, create a proxy to the internal application port, + // Kubernetes service, or external host. - if (!host) - return false + for (let i = 0; i < config.ingresses.length; i++) { + let ingress = config.ingresses[i] + let name = ingress["name"] - let node = host.split(".")[0] - let ingresses = config.ingresses + let secure = ingress["secure"] - for (let i = 0; i < ingresses.length; i++) { - let ingress = ingresses[i] - // Note that suffix use is deprecated, use prefix instead. - if (node.startsWith(ingress["name"] + "-") || node.endsWith("-" + ingress["name"])) { - let ingress_auth_type = ingress?.authentication?.type || "session" - if (ingress_auth_type != auth) - return false - return true + if (secure === undefined) + secure = true + + let path_rewrite = ingress["pathRewrite"] || [] + + let path_rewrite_map = {} + + for (let item of path_rewrite) { + path_rewrite_map[item["pattern"]] = item["replacement"] } - } - return false - } + // For a specific ingress, we need to match a host name where the + // session name is either prefixed or suffixed with the name of the + // ingress. Note that suffix use is deprecated but need to support + // it for backwards compatibility. + + let hosts = [] + + hosts.push(`${name}-${config.session_name}.${config.ingress_domain}`) + hosts.push(`${config.session_name}-${name}.${config.ingress_domain}`) + + // The filter/router function should only match and return a target + // for requests that are actually for the calculated host names. + + function filter(pathname, req) { + let host = req.headers.host + + if (!host) + return false + + if (hosts.includes(host)) { + // If the ingress has an authentication type, then we need + // to check that the request has the correct authentication + // type. Otherwise, we just need to check that the request + // is for the correct path. + + let ingress_auth_type = ingress?.authentication?.type || "session" + + if (ingress_auth_type != auth) + return false - function router(req) { - let host = req.headers.host - let node = host.split(".")[0] - let ingresses = config.ingresses + let ingress_path = ingress?.path || "/" - for (let i = 0; i < ingresses.length; i++) { - let ingress = ingresses[i] - // Note that suffix use is deprecated, use prefix instead. - if (node.startsWith(ingress["name"] + "-") || node.endsWith("-" + ingress["name"])) { + if (ingress_path.endsWith("/")) { + return pathname.startsWith(ingress_path) + } else { + return pathname == ingress_path || pathname.startsWith(ingress_path + "/") + } + } + + return false + } + + function router(req) { let protocol = ingress["protocol"] || "http" let host = ingress["host"] let port = ingress["port"] - // Replaced this with forwarding to localhost below. - // - // if (!host) - // host = `${ingress.name}-${config.session_namespace}` - if (!port || port == "0") port = protocol == "https" ? 443 : 80 @@ -56,81 +83,69 @@ export function setup_proxy(app: express.Application, auth: string) { port: port } } - } - } - if (config.ingresses) { - app.use(createProxyMiddleware(filter, { - target: "http://localhost", - router: router, - changeOrigin: true, - ws: true, - onProxyReq: (proxyReq, req, res) => { - let host = req.headers.host - let node = host.split(".")[0] - let ingresses = config.ingresses - - for (let i = 0; i < ingresses.length; i++) { - let ingress = ingresses[i] - // Note that suffix use is deprecated, use prefix instead. - if (node.startsWith(ingress["name"] + "-") || node.endsWith("-" + ingress["name"])) { - if (ingress["headers"]) { - for (let j = 0; j < ingress["headers"].length; j++) { - let header = ingress["headers"][j] - let name = header["name"] - let value = header["value"] || "" - value = value.split("$(kubernetes_token)").join(config.kubernetes_token || "") - proxyReq.setHeader(name, value) - } + app.use(createProxyMiddleware(filter, { + target: "http://localhost", + router: router, + changeOrigin: ingress["changeOrigin"] === undefined ? true : ingress["changeOrigin"], + pathRewrite: path_rewrite_map, + secure: secure, + ws: true, + onProxyReq: (proxyReq, req, res) => { + let host = req.headers.host + + if (ingress["headers"]) { + for (let j = 0; j < ingress["headers"].length; j++) { + let header = ingress["headers"][j] + let name = header["name"] + let value = header["value"] || "" + value = value.split("$(kubernetes_token)").join(config.kubernetes_token || "") + proxyReq.setHeader(name, value) } - let target_host = ingress["host"] || "localhost" - if (target_host == "localhost") - proxyReq.setHeader("host", host) } - } - }, - onProxyReqWs: (proxyReq, req, socket, options, head) => { - let host = req.headers.host - let node = host.split(".")[0] - let ingresses = config.ingresses - - for (let i = 0; i < ingresses.length; i++) { - let ingress = ingresses[i] - // Note that suffix use is deprecated, use prefix instead. - if (node.startsWith(ingress["name"] + "-") || node.endsWith("-" + ingress["name"])) { - if (ingress["headers"]) { - for (let j = 0; j < ingress["headers"].length; j++) { - let header = ingress["headers"][j] - let name = header["name"] - let value = header["value"] || "" - value = value.split("$(kubernetes_token)").join(config.kubernetes_token || "") - proxyReq.setHeader(name, value) - } + + let target_host = ingress["host"] || "localhost" + + if (target_host == "localhost") + proxyReq.setHeader("host", host) + }, + onProxyReqWs: (proxyReq, req, socket, options, head) => { + let host = req.headers.host + + if (ingress["headers"]) { + for (let j = 0; j < ingress["headers"].length; j++) { + let header = ingress["headers"][j] + let name = header["name"] + let value = header["value"] || "" + value = value.split("$(kubernetes_token)").join(config.kubernetes_token || "") + proxyReq.setHeader(name, value) } - let target_host = ingress["host"] || "localhost" - if (target_host == "localhost") - proxyReq.setHeader("host", host) } + + let target_host = ingress["host"] || "localhost" + + if (target_host == "localhost") + proxyReq.setHeader("host", host) + }, + onProxyRes: (proxyRes, req, res) => { + delete proxyRes.headers["x-frame-options"] + delete proxyRes.headers["content-security-policy"] + res.append("Access-Control-Allow-Origin", ["*"]) + res.append("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,HEAD") + res.append("Access-Control-Allow-Headers", "Content-Type") + }, + onError: (err, req, res) => { + // The error handler can be called for either HTTP requests + // or a web socket connection. Check whether have writeHead + // method, indicating it is a HTTP request. Otherwise it is + // actually a socket object and shouldn't do anything. + + console.log("Proxy", err) + + if (res.writeHead) + res.status(503).render("proxy-error-page") } - }, - onProxyRes: (proxyRes, req, res) => { - delete proxyRes.headers["x-frame-options"] - delete proxyRes.headers["content-security-policy"] - res.append("Access-Control-Allow-Origin", ["*"]) - res.append("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,HEAD") - res.append("Access-Control-Allow-Headers", "Content-Type") - }, - onError: (err, req, res) => { - // The error handler can be called for either HTTP requests - // or a web socket connection. Check whether have writeHead - // method, indicating it is a HTTP request. Otherwise it is - // actually a socket object and shouldn't do anything. - - console.log("Proxy", err) - - if (res.writeHead) - res.status(503).render("proxy-error-page") - } - })) + })) + } } } diff --git a/workshop-images/base-environment/opt/gateway/src/backend/scripts/start-terminal b/workshop-images/base-environment/opt/gateway/src/backend/scripts/start-terminal index 69569a485..9f5584dcc 100755 --- a/workshop-images/base-environment/opt/gateway/src/backend/scripts/start-terminal +++ b/workshop-images/base-environment/opt/gateway/src/backend/scripts/start-terminal @@ -23,21 +23,32 @@ args = ["terminal", "-il"] session = os.environ.get("TERMINAL_SESSION_ID", "") -script_dirs = [ - "/home/eduk8s/workshop/terminal", - "/opt/workshop/terminal", - "/opt/eduk8s/workshop/terminal" +workshop_dirs = [ + "/home/eduk8s/workshop", + "/opt/workshop", + "/opt/eduk8s/workshop" ] def is_executable(path): return os.path.isfile(path) and os.access(path, os.X_OK) +script = None + if session: - for dir in script_dirs: - if is_executable(f"{dir}/{session}.sh"): - args.extend(["-c", f"{dir}/{session}.sh"]) + for dir in workshop_dirs: + if is_executable(f"{dir}/terminal/{session}.sh"): + script = f"{dir}/terminal/{session}.sh" + break + +if script is None: + for dir in workshop_dirs: + if is_executable(f"{dir}/terminal.sh"): + script = f"{dir}/terminal.sh" break +if script is not None: + args.extend(["-c", script]) + # Execute the shell process in place of this process. os.execv("/bin/bash", args) diff --git a/workshop-images/base-environment/opt/renderer/src/frontend/scripts/educates.ts b/workshop-images/base-environment/opt/renderer/src/frontend/scripts/educates.ts index 7336d769f..60a71e494 100644 --- a/workshop-images/base-environment/opt/renderer/src/frontend/scripts/educates.ts +++ b/workshop-images/base-environment/opt/renderer/src/frontend/scripts/educates.ts @@ -228,6 +228,8 @@ class Editor { private fixup_path(file: string) { if (file.startsWith("~/")) file = file.replace("~/", "/home/eduk8s/") + else if (file.startsWith("$HOME/")) + file = file.replace("$HOME/", "/home/eduk8s/") else if (!file.startsWith("/")) file = path.join("/home/eduk8s", file) return file @@ -1813,7 +1815,7 @@ $(document).ready(async () => { setup: (args, element) => { let parent_element = element let title_element = parent_element.prev() - let form_element = $(`
`) + let form_element = $(`
`) let div_element = $("
") div_element.prepend(form_element) form_element.on("keydown", ":input:not(textarea)", function (event) { @@ -1890,7 +1892,7 @@ $(document).ready(async () => { setup: (args, element) => { let parent_element = element let title_element = parent_element.prev() - let form_element = $(`
`) + let form_element = $(`
`) let div_element = $("
") div_element.prepend(form_element) form_element.on("keydown", ":input:not(textarea)", function (event) { diff --git a/workshop-images/base-environment/opt/renderer/src/frontend/styles/educates.css b/workshop-images/base-environment/opt/renderer/src/frontend/styles/educates.css index 9361a6a87..fe9938d61 100644 --- a/workshop-images/base-environment/opt/renderer/src/frontend/styles/educates.css +++ b/workshop-images/base-environment/opt/renderer/src/frontend/styles/educates.css @@ -324,3 +324,7 @@ span.magic-code-block-glyph { .admonition>p:last-child { margin-bottom: 0; } + +form.jsonform-hasrequired div.form-group { + margin-bottom: 1rem; +}