diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c10b5f33 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.env +.env.example +.next +auth.log +.yamllint.yaml +SECURITY.md +README.md +Makefile +MAINTAINERS.md +LICENSE +CONTRIBUTING.md +CODE_OF_CONDUCT.md +.markdownlint-cli2.json +deploy/ +docs/ +certificates/ +server/ +.github/ diff --git a/.env.example b/.env.example index c79f9f05..9abe7573 100644 --- a/.env.example +++ b/.env.example @@ -2,13 +2,15 @@ IL_UI_ADMIN_USERNAME=admin IL_UI_ADMIN_PASSWORD=password OAUTH_GITHUB_ID= OAUTH_GITHUB_SECRET= +GITHUB_TOKEN= NEXTAUTH_SECRET=your_super_secret_random_string NEXTAUTH_URL=http://localhost:3000 + +TAXONOMY_DOCUMENTS_REPO=github.com// +NEXT_PUBLIC_TAXONOMY_REPO_OWNER= +NEXT_PUBLIC_TAXONOMY_REPO= + IL_GRANITE_API= IL_GRANITE_MODEL_NAME= IL_MERLINITE_API= IL_MERLINITE_MODEL_NAME= -GITHUB_TOKEN= -TAXONOMY_DOCUMENTS_REPO=github.com// -NEXT_PUBLIC_TAXONOMY_REPO_OWNER= -NEXT_PUBLIC_TAXONOMY_REPO= diff --git a/Containerfile b/Containerfile index 75be26e1..0a37e976 100644 --- a/Containerfile +++ b/Containerfile @@ -3,7 +3,7 @@ FROM node:22-alpine WORKDIR /app COPY package*.json ./ -RUN npm install +RUN npm install --production COPY ./ . RUN npm run build diff --git a/Makefile b/Makefile index d7e5ba9d..ff2cade2 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,15 @@ else PIPE_DEV_NULL= endif +ILAB_KUBE_CONTEXT?=kind-instructlab-ui +ILAB_KUBE_NAMESPACE?=instructlab +ILAB_KUBE_CLUSTER_NAME?=instructlab-ui +TAG=$(shell git rev-parse HEAD) +##@ Development - Helper commands for development .PHONY: md-lint md-lint: ## Lint markdown files $(ECHO_PREFIX) printf " %-12s ./...\n" "[MD LINT]" - $(CMD_PREFIX) podman run --rm -v $(CURDIR):/workdir docker.io/davidanson/markdownlint-cli2:v0.6.0 > /dev/null + $(CMD_PREFIX) docker run --rm -v $(CURDIR):/workdir docker.io/davidanson/markdownlint-cli2:v0.6.0 > /dev/null .PHONY: action-lint action-lint: ## Lint GitHub Action workflows @@ -31,6 +36,91 @@ action-lint: ## Lint GitHub Action workflows fi $(CMD_PREFIX) actionlint -color +##@ Artifacts - Command to build and publish artifacts ui-image: Containerfile ## Build continaer image for the InstructLab UI - $(ECHO_PREFIX) printf " %-12s Containerfile\n" "[PODMAN]" - $(CMD_PREFIX) podman build -f Containerfile -t ghcr.io/instructlab/ui/ui:main . + $(ECHO_PREFIX) printf " %-12s Containerfile\n" "[docker]" + $(CMD_PREFIX) docker build -f Containerfile -t ghcr.io/instructlab/ui/ui:$(TAG) . + $(CMD_PREFIX) docker tag ghcr.io/instructlab/ui/ui:$(TAG) ghcr.io/instructlab/ui/ui:main + + +##@ Kubernetes - kind dev environment +.PHONY: check-kind +check-kind: + $(CMD_PREFIX) if [ -z "$(shell which kind)" ]; then \ + echo "Please install kind and then start the kind dev environment." ; \ + echo "https://kind.sigs.k8s.io/" ; \ + exit 1 ; \ + fi + +.PHONY: check-kubectl +check-kubectl: + $(CMD_PREFIX) if [ -z "$(shell which kubectl)" ]; then \ + echo "Please install kubectl" ; \ + echo "https://kubernetes.io/docs/tasks/tools/#kubectl" ; \ + exit 1 ; \ + fi + +.PHONY: load-images +load-images: ## Load images onto kind + $(CMD_PREFIX) kind load --name $(ILAB_KUBE_CLUSTER_NAME) docker-image ghcr.io/instructlab/ui/ui:main + +.PHONY: stop-dev +stop-dev: check-kind ## Stop the kind cluster to destroy the development environment + $(CMD_PREFIX) kind delete cluster --name $(ILAB_KUBE_CLUSTER_NAME) + +.PHONY: setup-kind +setup-kind: check-kind check-kubectl stop-dev ## Create a kind cluster with ingress enabled + $(CMD_PREFIX) kind create cluster --config ./deploy/k8s/kind.yaml + $(CMD_PREFIX) kubectl cluster-info + $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -f ./deploy/k8s/kind-ingress.yaml + +.PHONY: wait-for-readiness +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=5m + +.PHONY: deploy +deploy: 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 ; \ + fi + $(CMD_PREFIX) yes | cp -rf .env ./deploy/k8s/overlays/kind/.env + $(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 + $(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) -n $(ILAB_KUBE_NAMESPACE) rollout restart deploy/ui + +.PHONY: undeploy +undeploy: ## 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 ## Run the development environment on kind +start-dev: setup-kind deploy ## Setup a kind cluster and deploy InstructLab UI on it + +##@ OpenShift - UI deployment in OpenShift +.PHONY: deploy-openshift +deploy-openshift: ## Deploy the InstructLab UI on OpenShift + $(CMD_PREFIX) if [ ! -f .env ]; then \ + 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/.env + $(CMD_PREFIX) oc apply -k ./deploy/k8s/overlays/openshift + $(CMD_PREFIX) oc wait --for=condition=Ready pods -n $(ILAB_KUBE_NAMESPACE) --all -l app.kubernetes.io/part-of=ui --timeout=15m + +.PHONY: redeploy-openshift +redeploy-openshift: deploy-openshift ## Redeploy the InstructLab UI on OpenShift + +.PHONY: undeploy-openshift +undeploy-openshift: ## Undeploy the InstructLab UI on OpenShift + $(CMD_PREFIX) oc delete -k ./deploy/k8s/overlays/openshift + $(CMD_PREFIX) if [ -f ./deploy/k8s/overlays/openshift/.env ]; then \ + rm ./deploy/k8s/overlays/openshift/.env ; \ + fi diff --git a/deploy/k8s/base/kustomization.yaml b/deploy/k8s/base/kustomization.yaml new file mode 100644 index 00000000..e2fbe498 --- /dev/null +++ b/deploy/k8s/base/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: instructlab +resources: + - namespace.yaml + - ui +labels: + - includeSelectors: true + pairs: + app.kubernetes.io/part-of: ui diff --git a/deploy/k8s/base/namespace.yaml b/deploy/k8s/base/namespace.yaml new file mode 100644 index 00000000..eea620c2 --- /dev/null +++ b/deploy/k8s/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: instructlab diff --git a/deploy/k8s/base/ui/deployment.yaml b/deploy/k8s/base/ui/deployment.yaml new file mode 100644 index 00000000..b055a8f7 --- /dev/null +++ b/deploy/k8s/base/ui/deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui +spec: + replicas: 1 + strategy: + type: RollingUpdate + template: + spec: + containers: + - name: ui + image: ghcr.io/instructlab/ui/ui:main + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + cpu: 100m + memory: 200Mi + ports: + - name: http + protocol: TCP + containerPort: 3000 + envFrom: + - secretRef: + name: ui-config + restartPolicy: Always diff --git a/deploy/k8s/base/ui/ingress-ui.yaml b/deploy/k8s/base/ui/ingress-ui.yaml new file mode 100644 index 00000000..a539100d --- /dev/null +++ b/deploy/k8s/base/ui/ingress-ui.yaml @@ -0,0 +1,20 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ui +spec: + tls: + - hosts: + - localhost + secretName: letsencrypt-ui + rules: + - host: localhost + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: ui + port: + number: 8080 diff --git a/deploy/k8s/base/ui/kustomization.yaml b/deploy/k8s/base/ui/kustomization.yaml new file mode 100644 index 00000000..b0232287 --- /dev/null +++ b/deploy/k8s/base/ui/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - deployment.yaml + - service.yaml + - ingress-ui.yaml +labels: + - includeSelectors: true + pairs: + app.kubernetes.io/component: ui + app.kubernetes.io/instance: ui + app.kubernetes.io/name: ui diff --git a/deploy/k8s/base/ui/service.yaml b/deploy/k8s/base/ui/service.yaml new file mode 100644 index 00000000..ab02bc2c --- /dev/null +++ b/deploy/k8s/base/ui/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: ui +spec: + clusterIP: None + selector: + app.kubernetes.io/component: ui + app.kubernetes.io/instance: ui + app.kubernetes.io/name: ui + ports: + - port: 8080 + targetPort: 3000 diff --git a/deploy/k8s/kind-ingress.yaml b/deploy/k8s/kind-ingress.yaml new file mode 100644 index 00000000..6ab7fd20 --- /dev/null +++ b/deploy/k8s/kind-ingress.yaml @@ -0,0 +1,673 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx +--- +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resourceNames: + - ingress-nginx-leader + resources: + - configmaps + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - coordination.k8s.io + resourceNames: + - ingress-nginx-leader + resources: + - leases + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +rules: + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: + - kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission +subjects: + - kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: + - kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission +subjects: + - kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: v1 +data: + allow-snippet-annotations: "true" +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller + namespace: ingress-nginx +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: NodePort +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller-admission + namespace: ingress-nginx +spec: + ports: + - appProtocol: https + name: https-webhook + port: 443 + targetPort: webhook + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + minReadySeconds: 0 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + strategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + spec: + containers: + - args: + - /nginx-ingress-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + - --watch-ingress-without-class=true + - --publish-status-address=localhost + - --enable-ssl-passthrough + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + image: registry.k8s.io/ingress-nginx/controller:v1.10.0@sha256:42b3f0e5d0846876b1791cd3afeb5f1cbbe4259d6f35651dcc1b5c980925379c + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 80 + hostPort: 80 + name: http + protocol: TCP + - containerPort: 443 + hostPort: 443 + name: https + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 90Mi + securityContext: + allowPrivilegeEscalation: true + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 + volumeMounts: + - mountPath: /usr/local/certificates/ + name: webhook-cert + readOnly: true + dnsPolicy: ClusterFirst + nodeSelector: + ingress-ready: "true" + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 0 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Equal + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Equal + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-create + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-create + spec: + containers: + - args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230404-helm-chart-4.6.0-11-gc76179c04@sha256:46a9364c2907dbde8a9fd01e75db44d4e0ba2ff9d7ab2ec9fe63e86a31cf8164 + imagePullPolicy: IfNotPresent + name: create + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-patch + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission-patch + spec: + containers: + - args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230404-helm-chart-4.6.0-11-gc76179c04@sha256:46a9364c2907dbde8a9fd01e75db44d4e0ba2ff9d7ab2ec9fe63e86a31cf8164 + imagePullPolicy: IfNotPresent + name: patch + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: nginx +spec: + controller: k8s.io/ingress-nginx +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.5.1 + name: ingress-nginx-admission +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: ingress-nginx-controller-admission + namespace: ingress-nginx + path: /networking/v1/ingresses + failurePolicy: Fail + matchPolicy: Equivalent + name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None diff --git a/deploy/k8s/kind.yaml b/deploy/k8s/kind.yaml new file mode 100644 index 00000000..7afee7f2 --- /dev/null +++ b/deploy/k8s/kind.yaml @@ -0,0 +1,26 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: instructlab-ui +nodes: + - role: control-plane + image: kindest/node:v1.30.0 + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 8080 + hostPort: 8080 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP + - role: worker + image: kindest/node:v1.30.0 + - role: worker + image: kindest/node:v1.30.0 diff --git a/deploy/k8s/overlays/kind/kustomization.yaml b/deploy/k8s/overlays/kind/kustomization.yaml new file mode 100644 index 00000000..db6625da --- /dev/null +++ b/deploy/k8s/overlays/kind/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: instructlab +resources: + - ../../base + +secretGenerator: +- name: ui-config + envs: + - .env + +# Override the image of the ui deployment for Kind deployment +patches: + - target: + kind: Deployment + name: ui + patch: |- + - op: replace + path: /spec/template/spec/containers/0/image + value: gchr.io/instructlab/ui/ui:main # Override this image if you want to use a different UI image diff --git a/deploy/k8s/overlays/openshift/certificate.yaml b/deploy/k8s/overlays/openshift/certificate.yaml new file mode 100644 index 00000000..3d1d1256 --- /dev/null +++ b/deploy/k8s/overlays/openshift/certificate.yaml @@ -0,0 +1,23 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: letsencrypt-ui +spec: + secretName: letsencrypt-ui + duration: 2160h0m0s + renewBefore: 360h0m0s + subject: + organizations: + - nexodus + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + usages: + - server auth + - client auth + dnsNames: + - qa.ui.instructlab.ai + issuerRef: + name: letsencrypt-ui + kind: Issuer diff --git a/deploy/k8s/overlays/openshift/issuer.yaml b/deploy/k8s/overlays/openshift/issuer.yaml new file mode 100644 index 00000000..18d17312 --- /dev/null +++ b/deploy/k8s/overlays/openshift/issuer.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt-ui +spec: + acme: + email: avishnoi@redhat.com + privateKeySecretRef: + name: letsencrypt-ui-key + server: 'https://acme-v02.api.letsencrypt.org/directory' + solvers: + - http01: + ingress: + serviceType: ClusterIP diff --git a/deploy/k8s/overlays/openshift/kustomization.yaml b/deploy/k8s/overlays/openshift/kustomization.yaml new file mode 100644 index 00000000..d05e7b5c --- /dev/null +++ b/deploy/k8s/overlays/openshift/kustomization.yaml @@ -0,0 +1,39 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: instructlab +resources: + - ../../base + - issuer.yaml + - certificate.yaml + +secretGenerator: +- name: ui-config + envs: + - .env + +patches: + - target: + kind: Ingress + name: ui + patch: |- + - op: replace + path: /spec/rules/0/http/paths/0/pathType + value: ImplementationSpecific + - op: replace + path: /spec/rules/0/http/paths/0/path + value: '' + - op: replace + path: /spec/rules/0/host + value: qa.ui.instructlab.ai + - op: replace + path: /spec/tls/0/hosts/0 + value: qa.ui.instructlab.ai + +# Override the image of the ui deployment for Openshift deployment + - target: + kind: Deployment + name: ui + patch: |- + - op: replace + path: /spec/template/spec/containers/0/image + value: ghcr.io/instructlab/ui/ui:main # Override this image if you want to use a different UI image diff --git a/docs/design_principles.md b/docs/design_principles.md new file mode 100644 index 00000000..df08fd63 --- /dev/null +++ b/docs/design_principles.md @@ -0,0 +1,96 @@ +# Design Principles + +## Summary of Server-Side Rendering and Client-Side Data Handling for Jobs and Chat Routes + +We are leveraging Next.js's app router to handle +[server-side rendering](https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering) +(SSR) and client-side data interactions for both jobs and chat functionalities. +Below is a summary of how we manage server-side rendering and client-side data +handling for these routes. + +### Server-Side Rendering (SSR) + +**API Routes**: + +- We have dedicated API routes for both jobs and chat functionalities that + handle data fetching from the backend. These routes ensure that the data is + sourced from the server. +- The API routes use environment variables to authenticate and interact with the + backend services securely. + +**Server Components**: + +- Components within the `app` directory are treated as server components by + default. These components handle the initial rendering on the server side. +- For pages like jobs and chat, the main page components are designed to be + server components, ensuring that the initial data rendering is performed on + the server. + +#### Client-Side Data Handling + +**Client Components**: + +- Components that utilize client-side hooks (`useState`, `useEffect`, `useRef`, + etc.) are explicitly marked with `'use client'` to indicate they should be + executed on the client side. +- These components are responsible for interacting with the API routes to fetch + and update data dynamically after the initial server-side rendering. + +**Custom Hooks**: + +- Custom hooks, such as `useFetchJobs` and `usePostChat`, encapsulate the logic + for API interactions. These hooks handle the state management and side effects + associated with fetching or posting data. +- By using these hooks, we maintain a clean separation of concerns, keeping the + components focused on rendering and user interaction. + +#### Jobs Functionality + +- **API Route**: The jobs API route fetches job data from the backend and + provides it to the front-end components. +- **Server Component**: The jobs page component fetches the job data server-side + during the initial render. +- **Client Component**: The jobs list component, marked as a client component, + uses the `useFetchJobs` hook to handle dynamic data fetching and updating in + real-time. + +#### Chat Functionality + +- **API Route**: The chat API route handles posting chat messages to the backend + and retrieving responses. +- **Server Component**: The chat page component sets up the overall layout and + structure, rendered server-side initially. +- **Client Component**: The chat form component, marked as a client component, + uses the `usePostChat` hook to handle user interactions, sending messages to + the API, and displaying responses dynamically. + +### Key Points + +- **Separation of Concerns**: By distinguishing between server and client + components, we ensure that server-side rendering is leveraged for the initial + load, while client-side components manage dynamic data interactions. +- **API Integration**: The use of API routes ensures secure and efficient + communication between the front-end and back-end services. +- **Custom Hooks**: Encapsulating data fetching and state management logic in + custom hooks promotes code reusability and maintainability. +- **Explicit Client Components**: Marking components with `'use client'` where + necessary clarifies their role and ensures correct execution context, avoiding + common pitfalls in SSR and CSR (client-side rendering) integration. + +This setup ensures that our application benefits from the performance advantages +of server-side rendering, while still providing a responsive and dynamic user +experience through client-side interactions. + +1. **API Route**: The API route fetches job data from the backend and provides it + to the client. This is already handled correctly with server-side logic. + +2. **Hook for Fetching Jobs**: The `useFetchJobs` hook fetches data from the API. + This is used within a client component since it utilizes React hooks like + `useState` and `useEffect`. + +3. **Jobs Component**: The `AllJobs` component fetches job data using the + `useFetchJobs` hook. This component is a client component. + +4. **Jobs Page Component**: The `AllJobsPage` component renders the `AllJobs` + component within the `AppLayout`. This component is a server component to + leverage SSR. diff --git a/docs/development.md b/docs/development.md index b4a6b348..8f7daa1e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -2,10 +2,6 @@ This is a [NextJS](https://nextjs.org) framework with [Patternfly](https://www.patternfly.org/get-started/develop/) UI library components. -## Quickstart - -- Run the [compose.ui](compose.ui). - ## Manually Running the React UI Set the .env in the ui directory and run the following: @@ -19,37 +15,9 @@ npm run build npm run start ``` -## OAuth COnfiguration - -You can either set up the Oauth app in your -[GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) -account or use the user/pass defined in `.env`. To change those defaults, create -the `/ui/.env` file and fill in the account user/pass with the following. - -Example [.env](.env.example) file. - -```text -IL_UI_ADMIN_USERNAME=admin -IL_UI_ADMIN_PASSWORD=password -IL_UI_API_SERVER_USERNAME= -IL_UI_API_SERVER_PASSWORD= -IL_UI_API_SERVER_URL=http://: -IL_UI_API_CHAT_URL=http:///: -GITHUB_ID= -GITHUB_SECRET= -NEXTAUTH_SECRET= -NEXTAUTH_URL=http://: -``` - -## Development Scripts +## Other helpful NPM Commands ```bash -# Install development/build dependencies -npm install - -# Start the development server -npm run dev - # Run a production build (outputs to ".next" dir) npm run build @@ -69,6 +37,33 @@ npm run pretty npm run type-check ``` +## Deploying the UI stack in KIND cluster + +Set the .env in the ui directory and run the following: + +```bash +make start-dev +``` + +This will start the kind cluster and deploy the ui stack related manifest files in the cluster. + +To stop the kind cluster and delete the ui stack related resources, run the following: + +```bash +make stop-dev +``` + +Use `make help` to see all the available commands. + +## OAuth Configuration + +You can either set up the Oauth app in your +[GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) +account or use the user/pass defined in `.env`. To change those defaults, create +the `/ui/.env` file and fill in the account user/pass with the following. + +Example [.env](../.env.example) file. + ## Local Dev Chat Environment ### 1) Using the ilab command line tool @@ -86,7 +81,7 @@ After you use the `ilab serve` command you should have, by default, a chat serve #### Current issues - The docker image that runs the server does not utilise Mac Metal GPU and therefore is very slow when answering prompts -- The docker image is very large as it contains the model itself. Potential to have the model incoperated via a docker volume to reduce the size of the actual image. +- The docker image is very large as it contains the model itself. Potential to have the model incorporated via a docker volume to reduce the size of the actual image. `docker run -p 8000:8000 aevo987654/instructlab_chat_8000:v2` @@ -115,98 +110,3 @@ Go to the chat interface [http://localhost:3000/playground/chat](http://localhos The chat interface should now use the server. ![enter image description here](../public/dev-local-chat-server/successful-chat.png) - -## Summary of Server-Side Rendering and Client-Side Data Handling for Jobs and Chat Routes - -We are leveraging Next.js's app router to handle -[server-side rendering](https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering) -(SSR) and client-side data interactions for both jobs and chat functionalities. -Below is a summary of how we manage server-side rendering and client-side data -handling for these routes. - -### Server-Side Rendering (SSR) - -**API Routes**: - -- We have dedicated API routes for both jobs and chat functionalities that - handle data fetching from the backend. These routes ensure that the data is - sourced from the server. -- The API routes use environment variables to authenticate and interact with the - backend services securely. - -**Server Components**: - -- Components within the `app` directory are treated as server components by - default. These components handle the initial rendering on the server side. -- For pages like jobs and chat, the main page components are designed to be - server components, ensuring that the initial data rendering is performed on - the server. - -#### Client-Side Data Handling - -**Client Components**: - -- Components that utilize client-side hooks (`useState`, `useEffect`, `useRef`, - etc.) are explicitly marked with `'use client'` to indicate they should be - executed on the client side. -- These components are responsible for interacting with the API routes to fetch - and update data dynamically after the initial server-side rendering. - -**Custom Hooks**: - -- Custom hooks, such as `useFetchJobs` and `usePostChat`, encapsulate the logic - for API interactions. These hooks handle the state management and side effects - associated with fetching or posting data. -- By using these hooks, we maintain a clean separation of concerns, keeping the - components focused on rendering and user interaction. - -#### Jobs Functionality - -- **API Route**: The jobs API route fetches job data from the backend and - provides it to the front-end components. -- **Server Component**: The jobs page component fetches the job data server-side - during the initial render. -- **Client Component**: The jobs list component, marked as a client component, - uses the `useFetchJobs` hook to handle dynamic data fetching and updating in - real-time. - -#### Chat Functionality - -- **API Route**: The chat API route handles posting chat messages to the backend - and retrieving responses. -- **Server Component**: The chat page component sets up the overall layout and - structure, rendered server-side initially. -- **Client Component**: The chat form component, marked as a client component, - uses the `usePostChat` hook to handle user interactions, sending messages to - the API, and displaying responses dynamically. - -### Key Points - -- **Separation of Concerns**: By distinguishing between server and client - components, we ensure that server-side rendering is leveraged for the initial - load, while client-side components manage dynamic data interactions. -- **API Integration**: The use of API routes ensures secure and efficient - communication between the front-end and back-end services. -- **Custom Hooks**: Encapsulating data fetching and state management logic in - custom hooks promotes code reusability and maintainability. -- **Explicit Client Components**: Marking components with `'use client'` where - necessary clarifies their role and ensures correct execution context, avoiding - common pitfalls in SSR and CSR (client-side rendering) integration. - -This setup ensures that our application benefits from the performance advantages -of server-side rendering, while still providing a responsive and dynamic user -experience through client-side interactions. - -1. **API Route**: The API route fetches job data from the backend and provides it - to the client. This is already handled correctly with server-side logic. - -2. **Hook for Fetching Jobs**: The `useFetchJobs` hook fetches data from the API. - This is used within a client component since it utilizes React hooks like - `useState` and `useEffect`. - -3. **Jobs Component**: The `AllJobs` component fetches job data using the - `useFetchJobs` hook. This component is a client component. - -4. **Jobs Page Component**: The `AllJobsPage` component renders the `AllJobs` - component within the `AppLayout`. This component is a server component to - leverage SSR. diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 12145e60..5acf0846 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -37,7 +37,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Console(), new winston.transports.File({ filename: path.join(process.cwd(), 'auth.log') })] }); -const ORG = 'instructlab'; +const ORG = process.env.NEXT_PUBLIC_TAXONOMY_REPO_OWNER!; const authOptions: NextAuthOptions = { providers: [ diff --git a/src/utils/github.ts b/src/utils/github.ts index c90ac52c..aa6265e6 100644 --- a/src/utils/github.ts +++ b/src/utils/github.ts @@ -5,7 +5,7 @@ import { PullRequestUpdateData } from '@/types'; const UPSTREAM_REPO_OWNER = process.env.NEXT_PUBLIC_TAXONOMY_REPO_OWNER!; const UPSTREAM_REPO_NAME = process.env.NEXT_PUBLIC_TAXONOMY_REPO!; -export const fetchPullRequests = async (token: string) => { +export async function fetchPullRequests(token: string) { try { console.log('Refreshing PR Listing'); const response = await axios.get(`https://api.github.com/repos/${UPSTREAM_REPO_OWNER}/${UPSTREAM_REPO_NAME}/pulls?state=all`, { @@ -24,7 +24,7 @@ export const fetchPullRequests = async (token: string) => { } throw error; } -}; +} export const fetchPullRequest = async (token: string, prNumber: number) => { try {