diff --git a/.devcontainer/Containerfile b/.devcontainer/Containerfile index b5d208d0..6bc07912 100644 --- a/.devcontainer/Containerfile +++ b/.devcontainer/Containerfile @@ -1,5 +1,7 @@ FROM registry.access.redhat.com/ubi9/nodejs-22:9.5-1730543890 +WORKDIR /opt/app-root/src + ARG USERNAME=default ARG NPM_GLOBAL=/usr/local/share/npm-global @@ -10,35 +12,82 @@ USER root RUN umask 0002 +# DEVX: install zsh, oh-my-zsh, vim, jq +RUN dnf install -y zsh && \ + curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash && \ + sed -i '/^default:/s|:/bin/bash|:/bin/zsh|' /etc/passwd && \ + dnf install -y vim jq + +ENV ZSH_DISABLE_COMPFIX="true" + +# npm configs + perms RUN groupadd npm && \ usermod -a -G npm ${USERNAME} && \ - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) && \ + usermod -a -G npm root && \ chown -R ${USERNAME}:npm /usr/local/ && \ - mkdir -p /opt/app-root/src/.npm && chown -R ${USERNAME}:npm /opt/app-root/src/ && \ - dnf install -y vim + mkdir -p /opt/app-root/src/.npm && chown -R ${USERNAME}:npm /opt/app-root/src/ +# install kubectl +ADD install-kubectl.sh /tmp +RUN /tmp/install-kubectl.sh; \ + rm /tmp/install-kubectl.sh + +# # install kubseal +# ADD install-kubeseal.sh /tmp +# RUN /tmp/install-kubeseal.sh; \ +# rm /tmp/install-kubeseal.sh -USER default +# # # install docker as a dependency of kind +# # # only need the CLI and runtime, binding to host docker socket for access to host docker context +# RUN dnf -y install dnf-plugins-core; \ +# dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo; \ +# dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -RUN umask 0002 && \ - mkdir -p ${NPM_GLOBAL} && \ - touch /usr/local/etc/npmrc +# # install kind +# ADD install-kind.sh /tmp +# RUN /tmp/install-kind.sh; \ +# rm /tmp/install-kind.sh -USER root +# # symlink oc because cannot install stable stream without RH auth +# RUN ln -sf /usr/local/bin/kubectl /usr/local/bin/oc + +# USER default + +# ADD shell-completions.sh /tmp +# RUN /tmp/shell-completions.sh && \ +# zsh -c "source ~/.zshrc" + +# RUN umask 0002 && \ +# mkdir -p ${NPM_GLOBAL} && \ +# touch /usr/local/etc/npmrc + +# USER root + +# RUN chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc && \ +# chmod g+s ${NPM_GLOBAL} && \ +# npm config -g set prefix ${NPM_GLOBAL} + +# # install linting tools +# RUN dnf install -y python3-pip + +# RUN bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) && \ +# mv /opt/app-root/src/actionlint /usr/local/bin -RUN chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc && \ - chmod g+s ${NPM_GLOBAL} && \ - npm config -g set prefix ${NPM_GLOBAL} +# USER default -USER default +# RUN pip install yamllint -ARG NODE_MODULES="tslint-to-eslint-config typescript" -RUN npm install -g eslint && \ - npm install -g ${NODE_MODULES} && \ - npm cache clean --force > /dev/null 2>&1 +# ARG NODE_MODULES="tslint-to-eslint-config typescript" +# RUN npm install -g eslint && \ +# npm install -g markdownlint-cli2 && \ +# npm install -g ${NODE_MODULES} && \ +# npm cache clean --force > /dev/null 2>&1 -WORKDIR /workspaces/ui +# WORKDIR /workspaces/ui -ENTRYPOINT ["npm", "install"] +# # ENTRYPOINT ["/tmp/shell-completions.sh", "&&", "source /opt/app-root/.zshrc", "&&", "npm", "install"] +# # ENTRYPOINT ["source /opt/app-root/.zshrc", "&&", "npm", "install"] +# ENTRYPOINT ["npm", "install"] -EXPOSE 3000/tcp +# EXPOSE 3000/tcp +# EXPOSE 6443/tcp diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b0d2203e..e8e3463a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,16 +11,20 @@ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "DavidAnson.vscode-markdownlint", - "ms-vscode-remote.remote-containers" + "ms-vscode-remote.remote-containers", + "foxundermoon.shell-format", + "timonwong.shellcheck" ], "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "terminal.integrated.shell.linux": "/bin/zsh" } } }, - "forwardPorts": [3000], + "forwardPorts": [3000, 6443], "mounts": [ - "type=bind,source=${localWorkspaceFolder}/.env,target=/workspace/ui/.env,consistency=cached" + "type=bind,source=${localWorkspaceFolder}/.env,target=/workspace/ui/.env,consistency=cached", + "source=${env:HOME}/.kube,target=/opt/app-root/src/.kube,type=bind,consistency=cached", + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], "runArgs": ["-p", "3000:3000"] } diff --git a/.devcontainer/install-kind.sh b/.devcontainer/install-kind.sh new file mode 100755 index 00000000..372ae7cb --- /dev/null +++ b/.devcontainer/install-kind.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# -*- indent-tabs-mode: nil; tab-width: 2; sh-indentation: 2; -*- + +# Install the kind binary + +[ "$(uname -m)" = "x86_64" ] && curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v0.25.0/kind-linux-amd64 +[ "$(uname -m)" = "aarch64" ] && curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v0.25.0/kind-linux-arm64 +chmod +x /tmp/kind +mv /tmp/kind /usr/local/bin/kind +kind completion zsh > "${HOME}/.oh-my-zsh/cache/completions/_kind" +chown -R ${USERNAME}:npm ${HOME}/.oh-my-zsh/cache/completions/_kind diff --git a/.devcontainer/install-kubectl.sh b/.devcontainer/install-kubectl.sh new file mode 100755 index 00000000..474bc78a --- /dev/null +++ b/.devcontainer/install-kubectl.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# -*- indent-tabs-mode: nil; tab-width: 2; sh-indentation: 2; -*- + +# Install the kubectl binary + +ARCH=$(uname -m) +if [ "$ARCH" == "x86_64" ] || [ "$ARCH" == "amd64" ]; then + ARCH="amd64" +elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then + ARCH="arm64" +else + echo "Unsupported architecture: $ARCH" + exit 1 +fi + +KUBECTL_VERSION=$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt) +echo "Installing kubectl version $KUBECTL_VERSION for $ARCH..." +curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl" +chmod +x kubectl +mv kubectl /usr/local/bin/ +kubectl completion zsh > /opt/app-root/src/.oh-my-zsh/cache/completions/_kubectl diff --git a/.devcontainer/install-kubeseal.sh b/.devcontainer/install-kubeseal.sh new file mode 100755 index 00000000..cc2ef7cf --- /dev/null +++ b/.devcontainer/install-kubeseal.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# -*- indent-tabs-mode: nil; tab-width: 2; sh-indentation: 2; -*- + +# Install the kubeseal binary + +set -x +set -e +set -o pipefail + +# Determine architecture +ARCH=$(uname -m) +if [ "$ARCH" == "x86_64" ] || [ "$ARCH" == "amd64" ]; then + ARCH="amd64" +elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then + ARCH="arm64" +else + echo "Unsupported architecture: $ARCH" + exit 1 +fi + +KUBESEAL_VERSION=$(curl -s https://api.github.com/repos/bitnami-labs/sealed-secrets/tags | jq -r '.[0].name' | cut -c 2-) +if [ -z "$KUBESEAL_VERSION" ]; then + echo "Failed to fetch the latest KUBESEAL_VERSION" + exit 1 +fi + +curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-${ARCH}.tar.gz" +tar -xvzf "kubeseal-${KUBESEAL_VERSION}-linux-${ARCH}.tar.gz" kubeseal +chmod 755 kubeseal +mv kubeseal /usr/local/bin/kubeseal +kubeseal completion zsh > ~/.oh-my-zsh/cache/completions/_kubeseal +rm "kubeseal-${KUBESEAL_VERSION}-linux-${ARCH}.tar.gz" diff --git a/.devcontainer/shell-completions.sh b/.devcontainer/shell-completions.sh new file mode 100755 index 00000000..33b601d2 --- /dev/null +++ b/.devcontainer/shell-completions.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# -*- indent-tabs-mode: nil; tab-width: 2; sh-indentation: 2; -*- + +# Misc shell completions +personal_completions_dir=/opt/app-root/src/completions +mkdir -p ${personal_completions_dir} + +kubectl completion zsh > ${personal_completions_dir}/_kubectl +chmod 644 ${personal_completions_dir}/_kubectl + +kind completion zsh > ${personal_completions_dir}/_kind +chmod 644 ${personal_completions_dir}/_kind + +chown -R default:npm ${personal_completions_dir} + +echo "fpath+=${personal_completions_dir}" >> $HOME/.zshrc diff --git a/Makefile b/Makefile index d812a972..db13b1e2 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ ILAB_KUBE_CONTEXT?=kind-instructlab-ui ILAB_KUBE_NAMESPACE?=instructlab ILAB_KUBE_CLUSTER_NAME?=instructlab-ui CONTAINER_ENGINE?=docker -DEVCONTAINER_BINARY_EXISTS ?= $(shell command -v devcontainer) +DEVCONTAINER_BINARY_EXISTS?=$(shell command -v devcontainer) +DEVCONTAINER_DEFAULT_SHELL?=zsh TAG=$(shell git rev-parse HEAD) ##@ Development - Helper commands for development .PHONY: md-lint @@ -31,7 +32,7 @@ md-lint: ## Lint markdown files .PHONY: action-lint action-lint: ## Lint GitHub Action workflows $(ECHO_PREFIX) printf " %-12s .github/...\n" "[ACTION LINT]" - $(CMD_PREFIX) if ! which actionlint $(PIPE_DEV_NULL) ; then \ + $(CMD_PREFIX) if ! command -v actionlint $(PIPE_DEV_NULL) ; then \ echo "Please install actionlint." ; \ echo "go install github.com/rhysd/actionlint/cmd/actionlint@latest" ; \ exit 1 ; \ @@ -40,7 +41,7 @@ action-lint: ## Lint GitHub Action workflows .PHONY: yaml-lint yaml-lint: ## Lint yaml files - $(CMD_PREFIX) if ! which yamllint >/dev/null 2>&1; then \ + $(CMD_PREFIX) if ! command -v yamllint >/dev/null 2>&1; then \ echo "Please install yamllint." ; \ echo "See: https://yamllint.readthedocs.io/en/stable/quickstart.html" ; \ exit 1 ; \ @@ -97,7 +98,7 @@ start-dev-podman: ## Start UI development stack in podman ##@ Kubernetes - Kind cluster based dev environment .PHONY: check-kind check-kind: - $(CMD_PREFIX) if [ -z "$(shell which kind)" ]; then \ + $(CMD_PREFIX) if [ -z "$(shell command -v kind)" ]; then \ echo "Please install kind and then start the kind dev environment." ; \ echo "https://kind.sigs.k8s.io/" ; \ exit 1 ; \ @@ -105,12 +106,20 @@ check-kind: .PHONY: check-kubectl check-kubectl: - $(CMD_PREFIX) if [ -z "$(shell which kubectl)" ]; then \ + $(CMD_PREFIX) if [ -z "$(shell command -v kubectl)" ]; then \ echo "Please install kubectl" ; \ echo "https://kubernetes.io/docs/tasks/tools/#kubectl" ; \ exit 1 ; \ fi +.PHONY: check-kubeseal +check-kubeseal: + $(CMD_PREFIX) if [ -z "$(shell command -v kubeseal)" ]; then \ + echo "Please install kubeseal" ; \ + echo "https://github.com/bitnami-labs/sealed-secrets?tab=readme-ov-file#kubeseal" ; \ + exit 1 ; \ + fi + .PHONY: load-images load-images: ## Load images onto Kind cluster $(CMD_PREFIX) kind load --name $(ILAB_KUBE_CLUSTER_NAME) docker-image ghcr.io/instructlab/ui/ui:main @@ -130,8 +139,8 @@ wait-for-readiness: # Wait for operators to be ready $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) -n ingress-nginx rollout restart deployment ingress-nginx-controller $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) -n ingress-nginx rollout status deployment ingress-nginx-controller --timeout=10m -.PHONY: deploy -deploy: wait-for-readiness ## Deploy a InstructLab UI development stack onto a kubernetes cluster +.PHONY: deploy-kind +deploy-kind: wait-for-readiness ## Deploy a InstructLab UI development stack onto a kubernetes cluster $(CMD_PREFIX) if [ ! -f .env ]; then \ echo "Please create a .env file in the root of the project." ; \ exit 1 ; \ @@ -140,20 +149,20 @@ deploy: wait-for-readiness ## Deploy a InstructLab UI development stack onto a k $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -k ./deploy/k8s/overlays/kind $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) wait --for=condition=Ready pods -n $(ILAB_KUBE_NAMESPACE) --all -l app.kubernetes.io/part-of=ui --timeout=15m -.PHONY: redeploy -redeploy: ui-image load-images ## Redeploy the InstructLab UI stack onto a kubernetes cluster +.PHONY: redeploy-kind +redeploy-kind: ui-image load-images ## Redeploy the InstructLab UI stack onto a kubernetes cluster $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) -n $(ILAB_KUBE_NAMESPACE) rollout restart deploy/ui $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) -n $(ILAB_KUBE_NAMESPACE) rollout restart deploy/pathservice -.PHONY: undeploy -undeploy: ## Undeploy the InstructLab UI stack from a kubernetes cluster +.PHONY: undeploy-kind +undeploy-kind: ## Undeploy the InstructLab UI stack from a kubernetes cluster $(CMD_PREFIX) if [ -f ./deploy/k8s/overlays/kind/.env ]; then \ rm ./deploy/k8s/overlays/kind/.env ; \ fi $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) delete namespace $(ILAB_KUBE_NAMESPACE) .PHONY: start-dev-kind ## Run the development environment on Kind cluster -start-dev-kind: setup-kind deploy ## Setup a Kind cluster and deploy InstructLab UI on it +start-dev-kind: setup-kind load-images deploy-kind ## Setup a Kind cluster and deploy InstructLab UI on it ##@ OpenShift - UI prod and qa deployment on OpenShift .PHONY: deploy-qa-openshift @@ -162,7 +171,6 @@ deploy-qa-openshift: ## Deploy QA stack of the InstructLab UI on OpenShift echo "Please create a .env file in the root of the project." ; \ exit 1 ; \ fi - $(CMD_PREFIX) yes | cp -rf .env ./deploy/k8s/overlays/openshift/qa/.env $(CMD_PREFIX) oc apply -k ./deploy/k8s/overlays/openshift/qa $(CMD_PREFIX) oc wait --for=condition=Ready pods -n $(ILAB_KUBE_NAMESPACE) --all -l app.kubernetes.io/part-of=ui --timeout=15m @@ -172,7 +180,6 @@ redeploy-qa-openshift: ## Redeploy QA stack of the InstructLab UI on OpenShift $(CMD_PREFIX) oc -n $(ILAB_KUBE_NAMESPACE) rollout restart deploy/ui $(CMD_PREFIX) oc -n $(ILAB_KUBE_NAMESPACE) rollout restart deploy/pathservice - .PHONY: undeploy-qa-openshift undeploy-qa-openshift: ## Undeploy QA stack of the InstructLab UI on OpenShift $(CMD_PREFIX) oc delete -k ./deploy/k8s/overlays/openshift/qa @@ -224,7 +231,7 @@ start-dev-container: .PHONY: enter-dev-container enter-dev-container: $(MAKE) check-dev-container-installed - devcontainer exec --workspace-folder=./ --docker-path=${CONTAINER_ENGINE} bash + devcontainer exec --workspace-folder=./ --docker-path=${CONTAINER_ENGINE} ${DEVCONTAINER_DEFAULT_SHELL} .PHONY: cycle-dev-container cycle-dev-container: @@ -240,5 +247,4 @@ cycle-dev-container: echo "removing image with id $$image_id and all containers using that image ..."; \ ${CONTAINER_ENGINE} rmi $$image_id -f; \ fi; - $(MAKE) build-dev-container $(MAKE) start-dev-container diff --git a/deploy/k8s/overlays/kind/kind.yaml b/deploy/k8s/overlays/kind/kind.yaml index 7afee7f2..4580a1d6 100644 --- a/deploy/k8s/overlays/kind/kind.yaml +++ b/deploy/k8s/overlays/kind/kind.yaml @@ -24,3 +24,5 @@ nodes: image: kindest/node:v1.30.0 - role: worker image: kindest/node:v1.30.0 +networking: + apiServerPort: 6443 diff --git a/package-lock.json b/package-lock.json index db43e954..838c2d8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-next": "^14.2.3", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.3.3", @@ -456,9 +456,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { @@ -512,14 +512,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -2721,17 +2721,18 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", diff --git a/package.json b/package.json index e37b846b..02fdb5be 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/parser": "^7.13.0", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-next": "^14.2.3", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.3.3",