Repository: natrontech/kubelab Branch: main Commit: 5708ca17b031 Files: 240 Total size: 463.1 KB Directory structure: gitextract_b2npil12/ ├── .dockerignore ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── docker-release.yml │ └── pr-labels.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── assets/ │ └── drawio/ │ └── architecture.drawio ├── deploy/ │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── configmap.yaml │ ├── ingress.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ ├── persistentvolume.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── statefulset.yaml ├── docker-compose.yaml ├── kubelab-backend/ │ ├── .gitignore │ ├── README.md │ ├── entrypoint.sh │ ├── example-hook-script.sh │ ├── go.mod │ ├── go.sum │ ├── hooks/ │ │ └── hooks.go │ ├── main.go │ ├── modd.conf │ ├── pb_migrations/ │ │ ├── 1671835039_created_hooks.js │ │ ├── 1671926343_updated_hooks.js │ │ ├── 1685691966_created_sessions.js │ │ ├── 1686128320_updated_users.js │ │ ├── 1686128357_created_labs.js │ │ ├── 1686128492_updated_sessions.js │ │ ├── 1686128857_updated_sessions.js │ │ ├── 1686129123_created_exercises.js │ │ ├── 1686129154_updated_labs.js │ │ ├── 1686129166_updated_sessions.js │ │ ├── 1686144343_updated_labs.js │ │ ├── 1686311410_updated_sessions.js │ │ ├── 1686311431_updated_sessions.js │ │ ├── 1686311476_created_exercise_sessions.js │ │ ├── 1686311483_updated_exercises.js │ │ ├── 1686311638_updated_exercise_sessions.js │ │ ├── 1686311654_updated_lab_sessions.js │ │ ├── 1686311927_created_user_exercise_score.js │ │ ├── 1686312000_updated_user_exercise_score.js │ │ ├── 1686312029_updated_user_exercise_score.js │ │ ├── 1686312069_updated_user_exercise_score.js │ │ ├── 1686312095_updated_user_exercise_score.js │ │ ├── 1686312146_updated_user_exercise_score.js │ │ ├── 1686312270_deleted_user_exercise_score.js │ │ ├── 1686312735_updated_labs.js │ │ ├── 1686312758_updated_exercises.js │ │ ├── 1686485128_updated_labs.js │ │ ├── 1686485135_updated_lab_sessions.js │ │ ├── 1686485142_updated_exercises.js │ │ ├── 1686485148_updated_exercise_sessions.js │ │ ├── 1686568901_updated_exercise_sessions.js │ │ ├── 1686568909_updated_lab_sessions.js │ │ ├── 1686569025_updated_lab_sessions.js │ │ ├── 1686569075_updated_lab_sessions.js │ │ ├── 1686569086_updated_lab_sessions.js │ │ ├── 1686569099_updated_lab_sessions.js │ │ ├── 1686569141_updated_exercise_sessions.js │ │ ├── 1686600246_updated_exercise_sessions.js │ │ ├── 1686600263_updated_exercise_sessions.js │ │ ├── 1686990480_updated_exercise_sessions.js │ │ ├── 1686990524_updated_lab_sessions.js │ │ ├── 1687092568_updated_labs.js │ │ ├── 1687092574_updated_exercises.js │ │ ├── 1687092614_updated_exercises.js │ │ ├── 1687092623_updated_labs.js │ │ ├── 1687116582_updated_exercises.js │ │ ├── 1687116588_updated_labs.js │ │ ├── 1687200869_updated_users.js │ │ ├── 1687200885_updated_exercises.js │ │ ├── 1687201122_updated_users.js │ │ ├── 1687201342_updated_exercises.js │ │ ├── 1687201352_updated_labs.js │ │ ├── 1687208987_created_material.js │ │ ├── 1687209270_updated_material.js │ │ ├── 1687209711_deleted_material.js │ │ ├── 1692004564_created_plans.js │ │ ├── 1692004578_created_features.js │ │ ├── 1692004634_updated_plans.js │ │ ├── 1692004755_updated_plans.js │ │ ├── 1692006976_updated_users.js │ │ ├── 1692013150_updated_users.js │ │ ├── 1692013264_updated_plans.js │ │ ├── 1692013276_updated_plans.js │ │ ├── 1692013682_updated_features.js │ │ ├── 1692029251_created_faqs.js │ │ ├── 1692030049_created_companies.js │ │ ├── 1692030210_updated_companies.js │ │ ├── 1697824916_updated_users.js │ │ ├── 1697824932_updated_users.js │ │ ├── 1697982589_created_exercise_logs.js │ │ ├── 1697982994_updated_exercise_logs.js │ │ ├── 1697983253_updated_exercise_logs.js │ │ ├── 1697983426_updated_exercise_session_logs.js │ │ ├── 1697983434_updated_exercise_session_logs.js │ │ ├── 1697983470_updated_users.js │ │ ├── 1697983479_updated_exercise_sessions.js │ │ ├── 1697983500_updated_lab_sessions.js │ │ ├── 1697983560_updated_exercise_session_logs.js │ │ ├── 1697984665_updated_exercise_session_logs.js │ │ ├── 1697984707_updated_exercise_session_logs.js │ │ ├── 1697986578_created_notifications.js │ │ ├── 1697987216_updated_notifications.js │ │ ├── 1697988059_updated_users.js │ │ ├── 1697992209_updated_exercise_session_logs.js │ │ ├── 1697992245_updated_notifications.js │ │ ├── 1697992793_updated_notifications.js │ │ └── 1697995266_updated_users.js │ ├── pkg/ │ │ ├── controller/ │ │ │ ├── exercise.go │ │ │ ├── handler.go │ │ │ ├── lab.go │ │ │ ├── sessions.go │ │ │ └── util.go │ │ ├── env/ │ │ │ └── env.go │ │ ├── helm/ │ │ │ └── helm.go │ │ ├── k8s/ │ │ │ ├── config.go │ │ │ ├── deployment.go │ │ │ ├── ingress.go │ │ │ ├── namespace.go │ │ │ ├── pod.go │ │ │ ├── resourcequota.go │ │ │ ├── secret.go │ │ │ └── service.go │ │ └── util/ │ │ └── helpers.go │ └── vcluster-values.yaml ├── kubelab-fill/ │ ├── example_users.csv │ ├── upload.py │ └── users_import.py ├── kubelab-score/ │ ├── score.csv │ └── score_calculation.py └── kubelab-ui/ ├── .dockerignore ├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode/ │ └── settings.json ├── Dockerfile ├── README.md ├── next-env.d.ts ├── package.json ├── playwright.config.ts ├── postcss.config.cjs ├── src/ │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── app.postcss │ ├── hooks.client.ts │ ├── lib/ │ │ ├── components/ │ │ │ ├── Console.svelte │ │ │ ├── base/ │ │ │ │ ├── BackButton.svelte │ │ │ │ ├── Badges.svelte │ │ │ │ ├── Card.svelte │ │ │ │ ├── Desktop.svelte │ │ │ │ ├── Nav.svelte │ │ │ │ ├── PlaceholderComponent.svelte │ │ │ │ ├── SideOver.svelte │ │ │ │ └── ToggleConfetti.svelte │ │ │ ├── dashboard/ │ │ │ │ ├── Chart.svelte │ │ │ │ ├── RunningExercises.svelte │ │ │ │ └── data.ts │ │ │ ├── labs/ │ │ │ │ ├── Exercise.svelte │ │ │ │ └── Lab.svelte │ │ │ ├── landingpage/ │ │ │ │ ├── Companies.svelte │ │ │ │ ├── Cta.svelte │ │ │ │ ├── Faq.svelte │ │ │ │ ├── Features.svelte │ │ │ │ ├── Footer.svelte │ │ │ │ ├── Header.svelte │ │ │ │ └── Hero.svelte │ │ │ └── markdown/ │ │ │ ├── CodeComponent.svelte │ │ │ ├── CodeSpanComponent.svelte │ │ │ ├── LinkComponent.svelte │ │ │ └── ListComponent.svelte │ │ ├── config.ts │ │ ├── mock-data.ts │ │ ├── pocketbase/ │ │ │ ├── generated-types.ts │ │ │ ├── index.ts │ │ │ └── ui.ts │ │ ├── stores/ │ │ │ ├── codeView.ts │ │ │ ├── data.ts │ │ │ ├── layout_store.ts │ │ │ ├── loading.ts │ │ │ ├── metadata.ts │ │ │ ├── sidebar.ts │ │ │ ├── tableView.ts │ │ │ ├── terminal.ts │ │ │ └── theme.ts │ │ ├── terminal.ts │ │ ├── types.ts │ │ ├── utils/ │ │ │ ├── clickOutside.ts │ │ │ ├── enums.ts │ │ │ ├── interfaces.ts │ │ │ └── time.ts │ │ └── xterm.css │ ├── routes/ │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── +page.ts │ │ ├── admin/ │ │ │ └── [id]/ │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ ├── app/ │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── profile/ │ │ │ └── +page.svelte │ │ ├── labs/ │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── [id]/ │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ └── [id]/ │ │ │ ├── +layout.svelte │ │ │ ├── +layout.ts │ │ │ └── +page.svelte │ │ ├── login/ │ │ │ └── +page.svelte │ │ └── material/ │ │ ├── +layout.svelte │ │ └── +page.svelte │ └── styles/ │ ├── prism.css │ └── xterm.css ├── svelte.config.js ├── tailwind.config.cjs ├── tsconfig.json └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ assets/ README.md SECURITY.md CODE_OF_CONDUCT.md CODEOWNERS LICENSE ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [{*.go,Makefile,.gitmodules,go.mod,go.sum}] indent_style = tab [*.md] indent_style = tab trim_trailing_whitespace = false [*.{yml,yaml,json}] indent_style = space indent_size = 2 [*.{js,jsx,ts,tsx,css,less,sass,scss,vue,py}] indent_style = space indent_size = 4 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "npm" # See documentation for possible values directory: "kubelab-ui/" # Location of package manifests schedule: interval: "weekly" commit-message: prefix: ":robot:" - package-ecosystem: "gomod" # See documentation for possible values directory: "kubelab-backend/" # Location of package manifests schedule: interval: "weekly" commit-message: prefix: ":robot:" # GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" commit-message: prefix: ":seedling:" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ '*' ] jobs: go-build: name: Backend Build runs-on: ubuntu-latest defaults: run: working-directory: kubelab-backend strategy: matrix: goVer: [1.22] steps: - name: Set up Go ${{ matrix.goVer }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.goVer }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Get dependencies run: | go get -v -t -d ./... go mod vendor - name: Test run: | go test -v ./... - name: Build run: | go build -v ./... ui-build: name: Frontend Build runs-on: ubuntu-latest defaults: run: working-directory: kubelab-ui steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - name: Install dependencies run: npm install --legacy-peer-deps - name: Build run: npm run build ================================================ FILE: .github/workflows/docker-release.yml ================================================ name: Docker Image Build & Push on: release: types: [created] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push-image: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to the Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@60a0d343a0d8a18aedee9d34e62251f752153bdb with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push Docker image uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/pr-labels.yml ================================================ name: Size Label on: pull_request jobs: size-label: runs-on: ubuntu-latest if: github.actor != 'dependabot[bot]' steps: - name: size-label uses: "pascalgn/size-label-action@v0.5.4" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" ================================================ FILE: .gitignore ================================================ # Mac OS X files .DS_Store # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ # Dependency directories (remove the comment below to include it) vendor/ pb_data/ temp/ tmp/ dist/ .builds/ .cache .local .npm .env .ash_history /docker-compose.override.yml _temp/ local_userlist_november.csv venv/ ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace exclude: README.md - repo: https://github.com/psf/black rev: 22.8.0 hooks: - id: black - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.0 hooks: - id: go-fmt - id: no-go-testing # - id: golangci-lint # exclude: '^tmp/' # - id: go-unit-tests - repo: https://github.com/pre-commit/mirrors-eslint rev: "v8.25.0" hooks: - id: eslint additional_dependencies: - eslint-config-next@12.1.6 files: ^ui/ types_or: [ts, tsx] - repo: https://github.com/pre-commit/mirrors-prettier rev: "v2.7.1" hooks: - id: prettier files: ^ui/ types_or: [javascript, jsx, ts, tsx, json, css, scss, markdown] additional_dependencies: - prettier - prettier-plugin-svelte - svelte ================================================ FILE: CODEOWNERS ================================================ * @janlauber ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at info@natron.io. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at . Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. Please note we have a code of conduct, please follow it in all your interactions with the project. ## Pull Request Process 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: Dockerfile ================================================ FROM golang:1.22-alpine AS backend-builder WORKDIR /build COPY kubelab-backend/go.mod kubelab-backend/go.sum kubelab-backend/main.go ./ COPY kubelab-backend/hooks ./hooks COPY kubelab-backend/pkg ./pkg COPY kubelab-backend/vcluster-values.yaml ./vcluster-values.yaml RUN apk --no-cache add upx make git gcc libtool musl-dev ca-certificates dumb-init \ && go mod tidy \ && CGO_ENABLED=0 go build \ && upx kubelab FROM node:lts-slim as ui-builder WORKDIR /build COPY ./kubelab-ui/package*.json ./ RUN rm -rf ./node_modules RUN rm -rf ./build COPY ./kubelab-ui . RUN npm install --legacy-peer-deps RUN npm run build FROM alpine as runtime WORKDIR /app/kubelab COPY --from=backend-builder /build/kubelab /app/kubelab/kubelab COPY --from=backend-builder /build/vcluster-values.yaml /app/kubelab/vcluster-values.yaml COPY ./kubelab-backend/pb_migrations ./pb_migrations COPY --from=ui-builder /build/build /app/kubelab/pb_public EXPOSE 8090 CMD ["/app/kubelab/kubelab","serve", "--http", "0.0.0.0:8090"] ================================================ FILE: LICENSE ================================================ Apache License ============== _Version 2.0, January 2004_ _<>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright 2023 Natron Tech GmbH / Natron Tech AG Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # KubeLab: The Ultimate Kubernetes Learning Platform

KubeLab
Embark on your Kubernetes Journey through Hands-on Practice

Build License GitHub go.mod Go version GitHub Workflow Status GitHub Workflow Status GitHub Workflow Status

Welcome to KubeLab! Our advanced web-based platform offers a rich set of interactive labs, specifically crafted for Kubernetes workshops. We aim to revolutionize your learning experience by making it more interactive, engaging, and practical. Our labs will help you grasp and apply complex Kubernetes concepts in a real-world context. KubeLab is a proud offering by [Natron Tech](https://natron.io), and if you're interested in a tailor-made Kubernetes services or workshops for your company, do not hesitate to reach out to us! KubeLab is built using: - [kubelab-agent](https://github.com/natrontech/kubelab-agent) - [xterm.js](https://xtermjs.org/) - [code-server](https://docs.linuxserver.io/images/docker-code-server/) - [pocketbase](https://pocketbase.io) - [vcluster](https://vcluster.com)

**Please note:** This project is still in its early stages, and we're diligently working to enhance your experience. However, bugs might appear, and your patience and feedback will be greatly appreciated. --- ## Cloud Version You can access the cloud version of KubeLab at [kubelab.ch](https://kubelab.ch). This version is hosted by us. As of security reasons, we do not let you to sign up for the cloud version. If you want to use KubeLab for your company, please contact us at [info@natron.io](mailto:info@natron.io). Also, when you want to host a KubeLab instance for your company, we can provide you with a hosted version of KubeLab. ## Features ### Web Terminal KubeLab features a smooth in-browser terminal, letting you execute commands and interact with your Kubernetes cluster in real-time, without needing any additional setup or software. ### Code Editor KubeLab comes with a vscode-based code editor, allowing you to edit and run code directly from your browser. The editor supports syntax highlighting, code completion, and more. Watch out for the code editor button in the bottom left corner of your screen. ### Dedicated Cluster Per Session Every learning session on KubeLab has its own isolated Kubernetes cluster. This design ensures a secure and dedicated learning environment, enabling you to experiment with Kubernetes without impacting others. ### Custom Kubernetes Labs With KubeLab, you can define your own labs and exercises. Check out our workshops [here](https://github.com/natrontech/kubelab-workshops). All the labs and exercises follow the same structure and can be easily created and shared. The structure of a lab with an exercise is as follows: ```bash kubelab-workshops/kubernetes-basics # Example workshop └── 01_introduction # Name of the lab ├── 01_get_kubectl_version # Name of the exercise │ ├── bootstrap.sh # The script that will be executed when the exercise starts │ ├── check.sh # The script that will be executed to check if the exercise is solved │ ├── docs.md # The text that will be displayed to the user │ ├── hint.md # The hint that will be displayed to the user │ └── solution.md # The solution that will be displayed to the user └── docs.md # The text that will be displayed to the user for the lab ``` ### Workshop Mode KubeLab offers a workshop mode, allowing you to hold your own Kubernetes workshops. Each user can have the `workshop` set to `true` and a `company` assigned. This will allow you to filter the users by company and see their progress. Then you can create a user with the role `admin` and when you log in with this user you can see the dashboards of all the companies. The users then also have an addional `request for help` button in the UI which will send a real-time notification to the dashboard. --- ## Development Interested in contributing to KubeLab? Please make sure you have the following prerequisites: - [Docker](https://docs.docker.com/get-docker/) - [Docker Compose](https://docs.docker.com/compose/install/) - [Node.js](https://nodejs.org/en/download/) (v18+) - [Go](https://golang.org/doc/install) (v1.22+) - [modd](https://github.com/cortesi/modd/releases) Please refer to our detailed development guides for the [backend](./kubelab-backend/README.md) and [frontend](./kubelab-ui/README.md) to get started. For contributing, please read our [CONTRIBUTING.md](CONTRIBUTING.md) for information on code conduct and the process for submitting pull requests. ## Deployment To get started, ensure that you have a Kubernetes cluster (v1.27+). 1. Take a look at the [deployment](./deployment) folder for an example deployment. 2. Create a wildcard certificate and store it in a secret. (You can use [cert-manager](https://cert-manager.io/docs/) for this) ```yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: kubelab-ch-wildcard-cert namespace: cert-manager spec: secretName: kubelab-ch-wildcard-cert issuerRef: name: letsencrypt kind: ClusterIssuer dnsNames: - "*.kubelab.ch" secretTemplate: annotations: reflector.v1.k8s.emberstack.com/reflection-allowed: "true" reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "" reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "" ``` 3. Deploy the [reflector](https://github.com/emberstack/kubernetes-reflector/tree/main/src/helm/reflector) to sync the TLS secret with each namespace. 4. Deploy the KubeLab as described in the [deployment](./deployment) folder. 5. Access the Pocketbase UI and create a admin user. (https:///_) 6. Disable the `create` authentication for the `labs` and `exercises` collections in Pocketbase. (This is because you wan't to use the `kubelab-fill` script to fill the labs and exercises collections.) ![disable-creation-rule](./assets/disable-creation-rule.png) 7. Run the [kubelab-fill](./kubelab-fill) script to fill the labs and exercises collections in Pocketbase. (Make sure to set the environment variables in the script `.env.example`) 8. Now disable the `create` authentication for the `labs` and `exercises` collections in Pocketbase again. 9. Create a user in the Pocketbase UI in the `users` collection and set the `role` to `user`. 10. Access the KubeLab UI and login with the user you created in step 9. (https:///) ### Environment Variables The following environment variables are required for KubeLab to function properly: | Variable Name | Default | Description | | --------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `LOCAL` | `false` | Set to `true` if you're running KubeLab locally. It will take your local kubeconfig under .kube/config | | `KUBELAB_AGENT_IMAGE` | `ghcr.io/natrontech/kubelab-agent:latest` | The image for the agent | | `CODE_SERVER_IMAGE` | `ghcr.io/natrontech/kubelab-code-server:latest` | The image for the code-server | | `ALLOWED_HOSTS` | `*` | The allowed hosts for the backend | | `RESOURCE_NAME` | `kubelab` | The name of the resource | | `AGENT_INGRESS_CLASS` | `nginx` | The ingress class for the agent | | `PODS_LIMIT` | `70` | The maximum number of pods allowed per session | | `STORAGE_LIMIT` | `50Gi` | The maximum storage allowed per session | | `VCLUSTER_CHART_VERSION` | `0.16.4` | The version of the vcluster chart | | `VCLUSTER_VALUES_FILE_PATH` | `./vcluster-values.yaml` | The path to the vcluster values file | | `CronTick` | `* * * * *` | The cron tick which creates user sessions for each lab and exercise | | `TlsSecretName` | `kubelab-tls` | The name of the TLS secret which will be for each agent ingress instance (use a wildcard certificate) | ## Known Issues - Either you need to create a wildcard Certificate and use it as the default TLS secret or you need to use something like [reflector](https://github.com/emberstack/kubernetes-reflector/tree/main/src/helm/reflector) to sync the TLS secret with each namespace. - The labs need to be manually created via an upload script in [./kubelab-fill](./kubelab-fill). This will be automated in the future. - Signup is not yet implemented. We're working on it and want to make a free signup with a limited number of sessions available soon. - The frontend is not yet optimized for mobile devices. This is **not a priority** for us at the moment, but we'll get to it eventually. --- Begin your Kubernetes journey with KubeLab today! ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | latest | :white_check_mark: | ## Reporting a Vulnerability Open up an issue :) ================================================ FILE: assets/drawio/architecture.drawio ================================================ ================================================ FILE: deploy/clusterrole.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-admin rules: - apiGroups: ["*"] resources: ["*"] verbs: ["*"] ================================================ FILE: deploy/clusterrolebinding.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubelab-admin subjects: - kind: ServiceAccount name: kubelab-admin namespace: kubelab roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io ================================================ FILE: deploy/configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: kubelab-config data: KUBELAB_AGENT_IMAGE: ghcr.io/natrontech/kubelab-agent:latest ALLOWED_HOSTS: kubelab.natr-demo.k8s.natron.cloud PODS_LIMIT: "70" STORAGE_LIMIT: "50Gi" AGENT_INGRESS_CLASS: nginx-external ================================================ FILE: deploy/ingress.yaml ================================================ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: kubelab-ingress namespace: kubelab annotations: # cert-manager.io/cluster-issuer: letsencrypt-prod-natron-cloud cert-manager.io/private-key-rotation-policy: Always ingress.kubernetes.io/force-ssl-redirect: "true" spec: ingressClassName: nginx-external rules: - host: kubelab.natr-demo.k8s.natron.cloud http: paths: - pathType: Prefix path: "/" backend: service: name: kubelab-service port: number: 8090 # tls: # - hosts: # - kubelab.natr-demo.k8s.natron.cloud # secretName: kubelab-tls ================================================ FILE: deploy/kustomization.yaml ================================================ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: kubelab resources: - ns.yaml - service.yaml - statefulset.yaml - ingress.yaml - serviceaccount.yaml - clusterrole.yaml - clusterrolebinding.yaml - configmap.yaml ================================================ FILE: deploy/ns.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: kubelab ================================================ FILE: deploy/persistentvolume.yaml ================================================ apiVersion: v1 kind: PersistentVolume metadata: name: hostpath-pv namespace: kubelab labels: type: local spec: storageClassName: manual capacity: storage: 10Gi # Adjust size as needed accessModes: - ReadWriteOnce hostPath: path: "/data/kubelab" # Directory on the host ================================================ FILE: deploy/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: kubelab-service namespace: kubelab spec: selector: app: kubelab ports: - protocol: TCP port: 8090 ================================================ FILE: deploy/serviceaccount.yaml ================================================ apiVersion: v1 kind: ServiceAccount metadata: name: kubelab-admin namespace: kubelab ================================================ FILE: deploy/statefulset.yaml ================================================ apiVersion: apps/v1 kind: StatefulSet metadata: name: kubelab-statefulset namespace: kubelab spec: serviceName: "kubelab-service" replicas: 1 selector: matchLabels: app: kubelab template: metadata: labels: app: kubelab spec: serviceAccountName: kubelab-admin containers: - name: kubelab image: ghcr.io/natrontech/kubelab:latest # image: kubelab:local ports: - containerPort: 8090 volumeMounts: - name: pb-data-hostpath mountPath: /app/kubelab/pb_data envFrom: - configMapRef: name: kubelab-config resources: requests: memory: "16Gi" cpu: "4" limits: memory: "16Gi" cpu: "8" nodeSelector: hostpath-node: "true" volumes: - name: pb-data-hostpath persistentVolumeClaim: claimName: hostpath-pvc volumeClaimTemplates: - metadata: name: pb-data-hostpath spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi storageClassName: manual volumeMode: Filesystem volumeName: hostpath-pv ================================================ FILE: docker-compose.yaml ================================================ version: '3.5' services: kubelab: build: . # image: ghcr.io/natrontech/kubelab:latest ports: - "8090:8090" volumes: # * This is the path to the data folder on your local machine - $PWD/kubelab-backend/pb_data:/app/kubelab/pb_data ================================================ FILE: kubelab-backend/.gitignore ================================================ /.cache /pocketbase /pocketbase*.zip /pb_data /pb_data_old /tmp /bin ./pocketbase kubelab ================================================ FILE: kubelab-backend/README.md ================================================ # KubeLab Backend There are two flavors of the backend: 1. standard release downloaded from https://github.com/pocketbase/pocketbase/releases. This one is a good start, but most real-world applications would require more (see next). 2. custom compiled (`go build`), possibly with my customizations and perhaps yours too. Out of the box, the project assumes #2 (custom compiled with my customizations). ## standard (official) release of pocketbase Download from release archive from https://github.com/pocketbase/pocketbase/releases/latest, unzip it and place the `pocketbase` binary in this folder, and you're done. ## custom build If you would like to extend PocketBase and use it as a framework then there is a `main.go` in this folder that you can customize and build using `go build` or do live development using `modd`. See https://pocketbase.io/docs/use-as-framework/ for details. # Setup ## Architecture > **Note:** For optimal set up, ensure you are using a standard distribution of Linux. For other operating systems, you may run into issues, or need additional configuration. > A docker-compose setup is included with the project, which can be used on any OS. ## Build Assuming you have Go language tools installed ... `go build` If you don't have Go and don't want to install it, you can use docker-compose setup. Otherwise, your only choice is to download the binary from https://github.com/pocketbase/pocketbase/releases/latest, and placing it in this folder. But then you will not be able to use any of the custom code (such as "config-driven hooks") ## Run migrations Before you can run the actual backend, you must run the migrations using `./pocketbase migrate up` in the current directory. It will create appropriate schema tables/collections. ## Run the backend You can run the PocketBase backend direct with `./pocketbase serve` or using `npm run backend` in the `sk` directory. Note that if you want the backend to also serve the frontend assets, then you must add the `--publicDir ../frontend/build` option. ## Docker A highly recommended option is to run it inside a Docker container. A `Dockerfile` is included that builds a production Docker image. Also, a `docker-compose.yml` along with an _override_ file example are included, which should be used during development. ## Active development with `modd` Finally, if you are going to actively develop using Go using PocketBase as a framework, then you probably want to use [modd](https://github.com/cortesi/modd), a development tool that rebuilds and restarts your Go binary everytime a source file changes (live reload on change). An basic `modd.conf` config file is included in this setup. You can run it by installing `modd` (`go install github.com/cortesi/modd/cmd/modd@latest`) and then running `modd`. All this is done automatically for you if you are using Docker. # Schema (Collections) With the 0.9 version of PocketBase, JavaScript auto-migrations as implemented. The JS files in `pb_migrations` can create/drop/modify collections and data. These are executed automatically by PocketBase on startup. Not only that, they are also generated automatically whenever you change the schema! So go ahead and make changes to the schema and watch new JS files generated in the `pb_migrations` folder. Just remember to commit them to version control. ## Generated Types The file `generated-types.ts` contains TypeScript definitions of `Record` types mirroring the fields in your database collections. But it needs to be regenerated every time you modify the schema. This can be done by simply running the `typegen` script in the frontend's `package.json`. So remember to do that. # Hooks PocketBase provides API's like .OnModelBefore* and .OnModelAfter* to run callbacks when records change. This app builds on top of that by providing a "hooks" table that drives those hooks using configuration. It has the following fields: - collection: name of the collection that triggers an action - event: insert/update/delete event that triggers the action - action_type: "command" if you want to run a program/script or "post" if you want to POST to a webhook endpoint. The record will be marshaled to JSON and passed to the command as STDIN or to the webhook POST as request body (with header 'content-type: application/json') - action: path to the command/script or URL of the webhook to POST to - action_params: a string that will be passed as argument to the action So now by configuring the above table, you can execute external commands/scripts and POST data to external webhooks in reaction to insert/update/delete of records. Most web services these days provide webhook endpoints (e.g. sendgrid, mailchimp, stripe, etc) which you can POST directly to. But if you need special processing then you can write a script that receives changed data as JSON, parses and manipulates it using [`jq`](https://github.com/stedolan/jq) before sending it on its way. See `example-hook-script.sh` for a demonstration. Possible use cases: - Clone git repo when a record is inserted to "repositories" table - Execute a terraform script when a new cluster is inserted to "clusters" table - Send an acknowledgement email when a "contact" form table is inserted to. - Charge a credit card when payment_token table is inserted to and then send email upon success/failure - Recalculate inventory levels as "orders" table is inserted to, and then send notifications when inventory becomes low. ================================================ FILE: kubelab-backend/entrypoint.sh ================================================ #!/bin/sh set -e # exit on any non-zero status (error) # this entrypoint script checks that all required setup is done # if not done, does it # and then proceeds to execute the main "command" for this container # build if needed go mod tidy go build if [ ! -x "$(which modd)" ]; then echo "go install modd" go install github.com/cortesi/modd/cmd/modd@latest fi exec "$@" ================================================ FILE: kubelab-backend/example-hook-script.sh ================================================ #!/usr/bin/env bash # This example script, along with hooks.go, shows how to trigger a command # when a record changes in PocketBase and how to feed the changed record to this # script. params=$1 # `action_params` field passed from the "hooks" table echo "PARAMS=$params" # The body of the record (as JSON) is fed to this script as stdin. # The following just reformats it and pretty-prints it. cat | jq -C ================================================ FILE: kubelab-backend/go.mod ================================================ module github.com/natrontech/kubelab go 1.22 toolchain go1.22.5 require ( github.com/caarlos0/env/v8 v8.0.0 github.com/pocketbase/dbx v1.10.1 github.com/pocketbase/pocketbase v0.22.18 helm.sh/helm/v3 v3.15.4 k8s.io/api v0.30.3 k8s.io/apimachinery v0.30.3 k8s.io/utils v0.0.0-20240102154912-e7106e64919e ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v25.0.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v25.0.6+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.1 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.7 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc6 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rubenv/sql-migrate v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect golang.org/x/sync v0.7.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a // indirect gopkg.in/evanphx/json-patch.v5 v5.7.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.3 // indirect k8s.io/apiserver v0.30.3 // indirect k8s.io/cli-runtime v0.30.3 // indirect k8s.io/component-base v0.30.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kubectl v0.30.3 // indirect modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.16.0 // indirect sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) require ( github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/aws/smithy-go v1.20.3 // indirect github.com/disintegration/imaging v1.6.2 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/dop251/goja v0.0.0-20240627195025-eb1f15ee67d2 // indirect github.com/dop251/goja_nodejs v0.0.0-20240418154818-2aae10d4cbcf // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/ganigeorgiev/fexpr v0.4.1 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mittwald/go-helm-client v0.12.13 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opencensus.io v0.24.0 // indirect gocloud.dev v0.37.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/api v0.189.0 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect k8s.io/client-go v0.30.3 modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/sqlite v1.31.1 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) ================================================ FILE: kubelab-backend/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s= github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 h1:u1KOU1S15ufyZqmH/rA3POkiRH6EcDANHj2xHRzq+zc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8/go.mod h1:WPv2FRnkIOoDv/8j2gSUsI4qDc7392w5anFB/I89GZ8= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/dop251/goja v0.0.0-20240627195025-eb1f15ee67d2 h1:4Ew88p5s9dwIk5/woUyqI9BD89NgZoUNH4/rM/h2UDg= github.com/dop251/goja v0.0.0-20240627195025-eb1f15ee67d2/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw= github.com/dop251/goja_nodejs v0.0.0-20240418154818-2aae10d4cbcf h1:2JoVYP9iko8uuIW33BQafzaylDixXbdXCRw/vCoxL+s= github.com/dop251/goja_nodejs v0.0.0-20240418154818-2aae10d4cbcf/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.1 h1:S+9bSbua1z3FgCnV0KKOSSZ3mDthb5NyEPL5gEpCvyk= github.com/emicklei/go-restful/v3 v3.11.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k= github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg= github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4= github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mittwald/go-helm-client v0.12.13 h1:TzoHH3NmlUdgy4cbo2tAuGQTcXkUKdORhZSE/Cq72bA= github.com/mittwald/go-helm-client v0.12.13/go.mod h1:BMoJyfs5n2MTe1RmWjTHuRl7b5wXfe6l7Eik1ZaZ0JU= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA= github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= github.com/pocketbase/pocketbase v0.22.18 h1:yVckUhi5GDORqCb0BbtlvRB1CVxHY9HO9btEaeZHVJU= github.com/pocketbase/pocketbase v0.22.18/go.mod h1:0QFvDOOW7ANId78ChZSagyHbmP6CgMxDQrQFXzeaDpA= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5Il8= github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.starlark.net v0.0.0-20231121155337-90ade8b19d09 h1:hzy3LFnSN8kuQK8h9tHl4ndF6UruMj47OqwqsS+/Ai4= go.starlark.net v0.0.0-20231121155337-90ade8b19d09/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro= gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI= google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg= google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a h1:hqK4+jJZXCU4pW7jsAdGOVFIfLHQeV7LaizZKnZ84HI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v5 v5.7.0 h1:dGKGylPlZ/jus2g1YqhhyzfH0gPy2R8/MYUpW/OslTY= gopkg.in/evanphx/json-patch.v5 v5.7.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= helm.sh/helm/v3 v3.15.4 h1:UFHd6oZ1IN3FsUZ7XNhOQDyQ2QYknBNWRHH57e9cbHY= helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= k8s.io/apiextensions-apiserver v0.30.3 h1:oChu5li2vsZHx2IvnGP3ah8Nj3KyqG3kRSaKmijhB9U= k8s.io/apiextensions-apiserver v0.30.3/go.mod h1:uhXxYDkMAvl6CJw4lrDN4CPbONkF3+XL9cacCT44kV4= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apiserver v0.30.3 h1:QZJndA9k2MjFqpnyYv/PH+9PE0SHhx3hBho4X0vE65g= k8s.io/apiserver v0.30.3/go.mod h1:6Oa88y1CZqnzetd2JdepO0UXzQX4ZnOekx2/PtEjrOg= k8s.io/cli-runtime v0.30.3 h1:aG69oRzJuP2Q4o8dm+f5WJIX4ZBEwrvdID0+MXyUY6k= k8s.io/cli-runtime v0.30.3/go.mod h1:hwrrRdd9P84CXSKzhHxrOivAR9BRnkMt0OeP5mj7X30= k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s= k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.30.3 h1:YIBBvMdTW0xcDpmrOBzcpUVsn+zOgjMYIu7kAq+yqiI= k8s.io/kubectl v0.30.3/go.mod h1:IcR0I9RN2+zzTRUa1BzZCm4oM0NLOawE6RzlDvd1Fpo= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e h1:WPC4v0rNIFb2PY+nBBEEKyugPPRHPzUgyN3xZPpGK58= modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c= sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= ================================================ FILE: kubelab-backend/hooks/hooks.go ================================================ package hooks import ( "encoding/json" "errors" "fmt" "io" "log" "net/http" "os" "os/exec" "strings" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" ) func PocketBaseInit(app *pocketbase.PocketBase) error { modelHandler := func(event string) func(e *core.ModelEvent) error { return func(e *core.ModelEvent) error { table := e.Model.TableName() // we don't want to executeEventActions if the event is a system event (e.g. "_collections" changes) if record, ok := e.Model.(*models.Record); ok { executeEventActions(app, event, table, record) } else { log.Println("Skipping executeEventActions for table:", table) } return nil } } app.OnBeforeServe().Add(func(e *core.ServeEvent) error { app.OnModelAfterCreate().Add(modelHandler("insert")) app.OnModelAfterUpdate().Add(modelHandler("update")) app.OnModelAfterDelete().Add(modelHandler("delete")) return nil }) return nil } func executeEventActions(app *pocketbase.PocketBase, event string, table string, record *models.Record) { // TODO: Load and cache this. Reload only on changes to "hooks" table rows := []dbx.NullStringMap{} app.DB().Select("action_type", "action", "action_params", "expands"). From("hooks"). Where(dbx.HashExp{"collection": table, "event": event, "disabled": false}). All(&rows) for _, row := range rows { action_type := row["action_type"].String action := row["action"].String action_params := row["action_params"].String expands := strings.Split(row["expands"].String, ",") app.Dao().ExpandRecord(record, expands, func(c *models.Collection, ids []string) ([]*models.Record, error) { return app.Dao().FindRecordsByIds(c.Name, ids, nil) }) if err := executeEventAction(event, table, action_type, action, action_params, record); err != nil { log.Println("ERROR", err) } } } func executeEventAction(event, table, action_type, action, action_params string, record *models.Record) error { log.Printf("event:%s, table: %s, action: %s\n", event, table, action) switch action_type { case "command": return doCommand(action, action_params, record) case "post": return doPost(action, action_params, record) default: return errors.New(fmt.Sprintf("Unknown action_type: %s", action_type)) } } func doCommand(action, action_params string, record *models.Record) error { cmd := exec.Command(action, action_params) if w, err := cmd.StdinPipe(); err != nil { return err } else { if r, err := cmd.StdoutPipe(); err != nil { return err } else { go func() { defer w.Close() defer r.Close() log.Println("-------------------------------") defer log.Println("-------------------------------") if err := cmd.Start(); err != nil { log.Printf("command start failed: %s %+v\n", action, err) } else { // write JSON into the pipe and close json.NewEncoder(w).Encode(record) w.Close() if err := cmd.Wait(); err != nil { log.Printf("command wait failed: %s %+v\n", action, err) } } }() // read pipe's stdout and copy to ours (in parallel to the above goroutine) io.Copy(os.Stdout, r) } } return nil } func doPost(action, action_params string, record *models.Record) error { r, w := io.Pipe() defer w.Close() go func() { defer r.Close() if resp, err := http.Post(action, "application/json", r); err != nil { log.Println("POST failed", action, err) } else { io.Copy(os.Stdout, resp.Body) } }() if err := json.NewEncoder(w).Encode(record); err != nil { log.Println("ERROR writing to pipe", err) } return nil } ================================================ FILE: kubelab-backend/main.go ================================================ package main import ( "log" "os" "path/filepath" "strings" "github.com/natrontech/kubelab/hooks" "github.com/natrontech/kubelab/pkg/controller" "github.com/natrontech/kubelab/pkg/env" "github.com/natrontech/kubelab/pkg/k8s" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/plugins/jsvm" "github.com/pocketbase/pocketbase/plugins/migratecmd" "github.com/pocketbase/pocketbase/tools/cron" ) func defaultPublicDir() string { if strings.HasPrefix(os.Args[0], os.TempDir()) { // most likely ran with go run return "./pb_public" } return filepath.Join(os.Args[0], "../pb_public") } func init() { // set the default public dir env.Init() k8s.Init() } func main() { app := pocketbase.New() var publicDirFlag string // add "--publicDir" option flag app.RootCmd.PersistentFlags().StringVar( &publicDirFlag, "publicDir", defaultPublicDir(), "the directory to serve static files", ) migrationsDir := "" // default to "pb_migrations" (for js) and "migrations" (for go) // load js files to allow loading external JavaScript migrations jsvm.MustRegister(app, jsvm.Config{ // Dir: migrationsDir, MigrationsDir: migrationsDir, }) // register the `migrate` command migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{ TemplateLang: migratecmd.TemplateLangJS, // or migratecmd.TemplateLangGo (default) Dir: migrationsDir, Automigrate: true, }) // call this only if you want to use the configurable "hooks" functionality hooks.PocketBaseInit(app) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { // serves static files from the provided public dir (if exists) e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(publicDirFlag), true)) return nil }) app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { switch e.Collection.Name { case "lab_sessions": return controller.HandleLabSessions(e, app) case "exercise_sessions": return controller.HandleExerciseSessions(e, app) } return nil }) // scheduler for syncing lab and exercise sessions app.OnBeforeBootstrap().Add(func(e *core.BootstrapEvent) error { scheduler := cron.New() // Run sync every minute scheduler.MustAdd("sessions_syncer", env.Config.CronTick, func() { err := controller.AutoSessionSyncController(app) if err != nil { log.Printf("Error syncing sessions: %v\n", err) } }) scheduler.Start() return nil }) if err := app.Start(); err != nil { log.Fatal(err) } } ================================================ FILE: kubelab-backend/modd.conf ================================================ # Run go test on ALL modules on startup, and subsequently only on modules # containing changes. **/*.go { prep: go build # prep: go test @dirmods daemon +sigterm: ./kubelab serve --http 0.0.0.0:8090 --publicDir ../kubelab-ui/build } ================================================ FILE: kubelab-backend/pb_migrations/1671835039_created_hooks.js ================================================ migrate( (db) => { const collection = new Collection({ id: "3fhw2mfr9zrgodj", created: "2022-12-23 22:30:35.443Z", updated: "2022-12-23 22:30:35.443Z", name: "hooks", type: "base", system: false, schema: [ { system: false, id: "j8mewfur", name: "collection", type: "text", required: true, unique: false, options: { min: null, max: null, pattern: "", }, }, { system: false, id: "4xcxcfuv", name: "event", type: "select", required: true, unique: false, options: { maxSelect: 1, values: ["insert", "update", "delete"], }, }, { system: false, id: "u3bmgjpb", name: "action_type", type: "select", required: true, unique: false, options: { maxSelect: 1, values: ["command", "post"], }, }, { system: false, id: "kayyu1l3", name: "action", type: "text", required: true, unique: false, options: { min: null, max: null, pattern: "", }, }, { system: false, id: "zkengev8", name: "action_params", type: "text", required: false, unique: false, options: { min: null, max: null, pattern: "", }, }, ], listRule: null, viewRule: null, createRule: null, updateRule: null, deleteRule: null, options: {}, }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("3fhw2mfr9zrgodj"); return dao.deleteCollection(collection); } ); ================================================ FILE: kubelab-backend/pb_migrations/1671926343_updated_hooks.js ================================================ migrate( (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("3fhw2mfr9zrgodj"); // add collection.schema.addField( new SchemaField({ system: false, id: "balsaeka", name: "expands", type: "text", required: false, unique: false, options: { min: null, max: null, pattern: "", }, }) ); // add collection.schema.addField( new SchemaField({ system: false, id: "emgxgcok", name: "disabled", type: "bool", required: false, unique: false, options: {}, }) ); return dao.saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("3fhw2mfr9zrgodj"); // remove collection.schema.removeField("balsaeka"); // remove collection.schema.removeField("emgxgcok"); return dao.saveCollection(collection); } ); ================================================ FILE: kubelab-backend/pb_migrations/1685691966_created_sessions.js ================================================ migrate( (db) => { const collection = new Collection({ id: "n7tne53bbobf2bt", created: "2023-06-02 07:46:06.017Z", updated: "2023-06-02 07:46:06.017Z", name: "sessions", type: "base", system: false, schema: [ { system: false, id: "rse3zear", name: "user", type: "relation", required: false, unique: false, options: { collectionId: "_pb_users_auth_", cascadeDelete: false, minSelect: null, maxSelect: 1, displayFields: [], }, }, ], indexes: [], listRule: null, viewRule: null, createRule: null, updateRule: null, deleteRule: null, options: {}, }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt"); return dao.deleteCollection(collection); } ); ================================================ FILE: kubelab-backend/pb_migrations/1686128320_updated_users.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "1hbozave", "name": "totalScore", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "q6psj4sr", "name": "avgMinutesToSolution", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("1hbozave") // remove collection.schema.removeField("q6psj4sr") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686128357_created_labs.js ================================================ migrate((db) => { const collection = new Collection({ "id": "a1s1vqlm7141lcr", "created": "2023-06-07 08:59:17.658Z", "updated": "2023-06-07 08:59:17.658Z", "name": "labs", "type": "base", "system": false, "schema": [ { "system": false, "id": "00op3jnz", "name": "title", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "abvux9fb", "name": "description", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1686128492_updated_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // add collection.schema.addField(new SchemaField({ "system": false, "id": "ituarijg", "name": "title", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "thwhdlu1", "name": "startTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "mapevfhq", "name": "endTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "l3csgcmv", "name": "lab", "type": "relation", "required": false, "unique": false, "options": { "collectionId": "a1s1vqlm7141lcr", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // remove collection.schema.removeField("ituarijg") // remove collection.schema.removeField("thwhdlu1") // remove collection.schema.removeField("mapevfhq") // remove collection.schema.removeField("l3csgcmv") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686128857_updated_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // add collection.schema.addField(new SchemaField({ "system": false, "id": "6zraywm2", "name": "score", "type": "number", "required": false, "unique": false, "options": { "min": 0, "max": null } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "tr8kf1qh", "name": "clusterRunning", "type": "bool", "required": false, "unique": false, "options": {} })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // remove collection.schema.removeField("6zraywm2") // remove collection.schema.removeField("tr8kf1qh") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686129123_created_exercises.js ================================================ migrate((db) => { const collection = new Collection({ "id": "s4f0lpy3ibkgfqp", "created": "2023-06-07 09:12:03.931Z", "updated": "2023-06-07 09:12:03.931Z", "name": "exercises", "type": "base", "system": false, "schema": [ { "system": false, "id": "ow94rlnx", "name": "title", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "ukrbpgyw", "name": "description", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "qpleqnzf", "name": "docs", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } }, { "system": false, "id": "lnv6grdi", "name": "hint", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } }, { "system": false, "id": "lslokljx", "name": "solution", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } }, { "system": false, "id": "1opfp3c3", "name": "check", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } }, { "system": false, "id": "oud4eihs", "name": "bootstrap", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } }, { "system": false, "id": "uao3gxwr", "name": "agentRunning", "type": "bool", "required": false, "unique": false, "options": {} } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1686129154_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // add collection.schema.addField(new SchemaField({ "system": false, "id": "pnavjyzl", "name": "exercises", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // remove collection.schema.removeField("pnavjyzl") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686129166_updated_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // update collection.schema.addField(new SchemaField({ "system": false, "id": "l3csgcmv", "name": "lab", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "a1s1vqlm7141lcr", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // update collection.schema.addField(new SchemaField({ "system": false, "id": "l3csgcmv", "name": "lab", "type": "relation", "required": false, "unique": false, "options": { "collectionId": "a1s1vqlm7141lcr", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686144343_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // add collection.schema.addField(new SchemaField({ "system": false, "id": "qe7ybzdk", "name": "docs", "type": "url", "required": false, "unique": false, "options": { "exceptDomains": null, "onlyDomains": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // remove collection.schema.removeField("qe7ybzdk") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311410_updated_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // remove collection.schema.removeField("ituarijg") return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // add collection.schema.addField(new SchemaField({ "system": false, "id": "ituarijg", "name": "title", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311431_updated_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.name = "lab_sessions" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.name = "sessions" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311476_created_exercise_sessions.js ================================================ migrate((db) => { const collection = new Collection({ "id": "qj6ssich32lcxru", "created": "2023-06-09 11:51:16.171Z", "updated": "2023-06-09 11:51:16.171Z", "name": "exercise_sessions", "type": "base", "system": false, "schema": [ { "system": false, "id": "nc43tbmx", "name": "agentRunning", "type": "bool", "required": false, "unique": false, "options": {} } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1686311483_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") // remove collection.schema.removeField("uao3gxwr") return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") // add collection.schema.addField(new SchemaField({ "system": false, "id": "uao3gxwr", "name": "agentRunning", "type": "bool", "required": false, "unique": false, "options": {} })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311638_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") // add collection.schema.addField(new SchemaField({ "system": false, "id": "iousqpjz", "name": "user", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "email" ] } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "g8s5seza", "name": "startTime", "type": "date", "required": true, "unique": false, "options": { "min": "", "max": "" } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "a3yguusb", "name": "endTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "9oftn8f7", "name": "exercise", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") // remove collection.schema.removeField("iousqpjz") // remove collection.schema.removeField("g8s5seza") // remove collection.schema.removeField("a3yguusb") // remove collection.schema.removeField("9oftn8f7") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311654_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // remove collection.schema.removeField("6zraywm2") // update collection.schema.addField(new SchemaField({ "system": false, "id": "rse3zear", "name": "user", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [] } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "thwhdlu1", "name": "startTime", "type": "date", "required": true, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // add collection.schema.addField(new SchemaField({ "system": false, "id": "6zraywm2", "name": "score", "type": "number", "required": false, "unique": false, "options": { "min": 0, "max": null } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "rse3zear", "name": "user", "type": "relation", "required": false, "unique": false, "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [] } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "thwhdlu1", "name": "startTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686311927_created_user_exercise_score.js ================================================ migrate((db) => { const collection = new Collection({ "id": "c0gc1ph97tdim19", "created": "2023-06-09 11:58:47.771Z", "updated": "2023-06-09 11:58:47.771Z", "name": "user_exercise_score", "type": "view", "system": false, "schema": [], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": { "query": "SELECT lab_sessions.id\nFROM lab_sessions" } }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1686312000_updated_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT lab_sessions.id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions" } // add collection.schema.addField(new SchemaField({ "system": false, "id": "wmj3s2se", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT lab_sessions.id\nFROM lab_sessions" } // remove collection.schema.removeField("wmj3s2se") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312029_updated_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions" } // remove collection.schema.removeField("wmj3s2se") // add collection.schema.addField(new SchemaField({ "system": false, "id": "jgwml4ai", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT lab_sessions.id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions" } // add collection.schema.addField(new SchemaField({ "system": false, "id": "wmj3s2se", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // remove collection.schema.removeField("jgwml4ai") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312069_updated_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions WHERE lab_sessions.endTime NOTNULL;" } // remove collection.schema.removeField("jgwml4ai") // add collection.schema.addField(new SchemaField({ "system": false, "id": "4nfqwv0a", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions" } // add collection.schema.addField(new SchemaField({ "system": false, "id": "jgwml4ai", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // remove collection.schema.removeField("4nfqwv0a") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312095_updated_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(exercise_sessions.id) as sum\nFROM exercise_sessions WHERE exercise_sessions.endTime NOTNULL;" } // remove collection.schema.removeField("4nfqwv0a") // add collection.schema.addField(new SchemaField({ "system": false, "id": "swlqrmos", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(lab_sessions.id) as sum\nFROM lab_sessions WHERE lab_sessions.endTime NOTNULL;" } // add collection.schema.addField(new SchemaField({ "system": false, "id": "4nfqwv0a", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // remove collection.schema.removeField("swlqrmos") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312146_updated_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT exercise_sessions.user as id, COUNT(exercise_sessions.id) as sum\nFROM exercise_sessions WHERE exercise_sessions.endTime NOTNULL;" } // remove collection.schema.removeField("swlqrmos") // add collection.schema.addField(new SchemaField({ "system": false, "id": "dvvwd0w4", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19") collection.options = { "query": "SELECT (ROW_NUMBER() OVER()) as id, COUNT(exercise_sessions.id) as sum\nFROM exercise_sessions WHERE exercise_sessions.endTime NOTNULL;" } // add collection.schema.addField(new SchemaField({ "system": false, "id": "swlqrmos", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // remove collection.schema.removeField("dvvwd0w4") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312270_deleted_user_exercise_score.js ================================================ migrate((db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("c0gc1ph97tdim19"); return dao.deleteCollection(collection); }, (db) => { const collection = new Collection({ "id": "c0gc1ph97tdim19", "created": "2023-06-09 11:58:47.771Z", "updated": "2023-06-09 12:02:26.128Z", "name": "user_exercise_score", "type": "view", "system": false, "schema": [ { "system": false, "id": "dvvwd0w4", "name": "sum", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": { "query": "SELECT exercise_sessions.user as id, COUNT(exercise_sessions.id) as sum\nFROM exercise_sessions WHERE exercise_sessions.endTime NOTNULL;" } }); return Dao(db).saveCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1686312735_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // remove collection.schema.removeField("pnavjyzl") return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") // add collection.schema.addField(new SchemaField({ "system": false, "id": "pnavjyzl", "name": "exercises", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686312758_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") // add collection.schema.addField(new SchemaField({ "system": false, "id": "fqnmihgd", "name": "lab", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "a1s1vqlm7141lcr", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "title" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") // remove collection.schema.removeField("fqnmihgd") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686485128_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686485135_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686485142_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686485148_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686568901_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "@request.auth.id != user.id" collection.viewRule = "@request.auth.id != user.id" collection.createRule = "@request.auth.id != user.id" collection.updateRule = "@request.auth.id != user.id" collection.deleteRule = "@request.auth.id != user.id" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686568909_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "@request.auth.id != user.id" collection.viewRule = "@request.auth.id != user.id" collection.createRule = "@request.auth.id != user.id" collection.updateRule = "@request.auth.id != user.id" collection.deleteRule = "@request.auth.id != user.id" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686569025_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686569075_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "@request.auth.id = user.id " return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686569086_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.viewRule = "@request.auth.id = user.id " return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.viewRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686569099_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.createRule = "@request.auth.id = user.id " collection.updateRule = "@request.auth.id = user.id " collection.deleteRule = "@request.auth.id = user.id " return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686569141_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "@request.auth.id = user.id " collection.viewRule = "@request.auth.id = user.id " collection.createRule = "@request.auth.id = user.id " collection.updateRule = "@request.auth.id = user.id " collection.deleteRule = "@request.auth.id = user.id " return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686600246_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686600263_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "@request.auth.id = user.id" collection.viewRule = "@request.auth.id = user.id" collection.createRule = "@request.auth.id = user.id" collection.updateRule = "@request.auth.id = user.id" collection.deleteRule = "@request.auth.id = user.id" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686990480_updated_exercise_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") // update collection.schema.addField(new SchemaField({ "system": false, "id": "g8s5seza", "name": "startTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") // update collection.schema.addField(new SchemaField({ "system": false, "id": "g8s5seza", "name": "startTime", "type": "date", "required": true, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1686990524_updated_lab_sessions.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // update collection.schema.addField(new SchemaField({ "system": false, "id": "thwhdlu1", "name": "startTime", "type": "date", "required": false, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") // update collection.schema.addField(new SchemaField({ "system": false, "id": "thwhdlu1", "name": "startTime", "type": "date", "required": true, "unique": false, "options": { "min": "", "max": "" } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687092568_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = "" collection.viewRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687092574_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = "" collection.viewRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687092614_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = "" collection.viewRule = "" collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = null collection.viewRule = null collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687092623_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = "" collection.viewRule = "" collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.listRule = null collection.viewRule = null collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687116582_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687116588_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687200869_updated_users.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.createRule = "id = @request.auth.id" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.createRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687200885_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = "" collection.viewRule = "" collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687201122_updated_users.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.createRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.createRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687201342_updated_exercises.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = "" collection.viewRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("s4f0lpy3ibkgfqp") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687201352_updated_labs.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("a1s1vqlm7141lcr") collection.createRule = "" collection.updateRule = "" collection.deleteRule = "" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687208987_created_material.js ================================================ migrate((db) => { const collection = new Collection({ "id": "19zg2e2qeca2b7d", "created": "2023-06-19 21:09:47.996Z", "updated": "2023-06-19 21:09:47.996Z", "name": "material", "type": "base", "system": false, "schema": [ { "system": false, "id": "bql7kw5k", "name": "name", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "tkpuopqe", "name": "file", "type": "file", "required": false, "unique": false, "options": { "maxSelect": 1, "maxSize": 5242880, "mimeTypes": [], "thumbs": [], "protected": false } } ], "indexes": [], "listRule": "", "viewRule": "", "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("19zg2e2qeca2b7d"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1687209270_updated_material.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("19zg2e2qeca2b7d") // update collection.schema.addField(new SchemaField({ "system": false, "id": "tkpuopqe", "name": "file", "type": "file", "required": false, "unique": false, "options": { "maxSelect": 1, "maxSize": 10485760, "mimeTypes": [], "thumbs": [], "protected": false } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("19zg2e2qeca2b7d") // update collection.schema.addField(new SchemaField({ "system": false, "id": "tkpuopqe", "name": "file", "type": "file", "required": false, "unique": false, "options": { "maxSelect": 1, "maxSize": 5242880, "mimeTypes": [], "thumbs": [], "protected": false } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1687209711_deleted_material.js ================================================ migrate((db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("19zg2e2qeca2b7d"); return dao.deleteCollection(collection); }, (db) => { const collection = new Collection({ "id": "19zg2e2qeca2b7d", "created": "2023-06-19 21:09:47.996Z", "updated": "2023-06-19 21:14:30.500Z", "name": "material", "type": "base", "system": false, "schema": [ { "system": false, "id": "bql7kw5k", "name": "name", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "tkpuopqe", "name": "file", "type": "file", "required": false, "unique": false, "options": { "maxSelect": 1, "maxSize": 10485760, "mimeTypes": [], "thumbs": [], "protected": false } } ], "indexes": [], "listRule": "", "viewRule": "", "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1692004564_created_plans.js ================================================ migrate((db) => { const collection = new Collection({ "id": "twr8eflpoom78k9", "created": "2023-08-14 09:16:04.916Z", "updated": "2023-08-14 09:16:04.916Z", "name": "plans", "type": "base", "system": false, "schema": [ { "system": false, "id": "jhgt65nx", "name": "name", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "mbuxpqro", "name": "description", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "qe5g2utb", "name": "price", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } } ], "indexes": [], "listRule": "", "viewRule": "", "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1692004578_created_features.js ================================================ migrate((db) => { const collection = new Collection({ "id": "vknl4jpc8e5wlbv", "created": "2023-08-14 09:16:18.702Z", "updated": "2023-08-14 09:16:18.702Z", "name": "features", "type": "base", "system": false, "schema": [ { "system": false, "id": "0zkeyduh", "name": "feature", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("vknl4jpc8e5wlbv"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1692004634_updated_plans.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // add collection.schema.addField(new SchemaField({ "system": false, "id": "eldb7j7p", "name": "features", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "feature" ] } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "srzysrtq", "name": "optionalFeatures", "type": "relation", "required": false, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "feature" ] } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "jhgt65nx", "name": "name", "type": "text", "required": true, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "mbuxpqro", "name": "description", "type": "text", "required": true, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "qe5g2utb", "name": "price", "type": "number", "required": true, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // remove collection.schema.removeField("eldb7j7p") // remove collection.schema.removeField("srzysrtq") // update collection.schema.addField(new SchemaField({ "system": false, "id": "jhgt65nx", "name": "name", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "mbuxpqro", "name": "description", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } })) // update collection.schema.addField(new SchemaField({ "system": false, "id": "qe5g2utb", "name": "price", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692004755_updated_plans.js ================================================ migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "qe5g2utb", "name": "price", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "qe5g2utb", "name": "price", "type": "number", "required": true, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692006976_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("1hbozave") // remove collection.schema.removeField("q6psj4sr") return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "1hbozave", "name": "totalScore", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) // add collection.schema.addField(new SchemaField({ "system": false, "id": "q6psj4sr", "name": "avgMinutesToSolution", "type": "number", "required": false, "unique": false, "options": { "min": null, "max": null } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692013150_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "qrhpiuil", "name": "plan", "type": "relation", "required": false, "unique": false, "options": { "collectionId": "twr8eflpoom78k9", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("qrhpiuil") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692013264_updated_plans.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "eldb7j7p", "name": "features", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "feature" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "eldb7j7p", "name": "features", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "feature" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692013276_updated_plans.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "eldb7j7p", "name": "features", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": null, "displayFields": [ "feature" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("twr8eflpoom78k9") // update collection.schema.addField(new SchemaField({ "system": false, "id": "eldb7j7p", "name": "features", "type": "relation", "required": true, "unique": false, "options": { "collectionId": "vknl4jpc8e5wlbv", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": [ "feature" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692013682_updated_features.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("vknl4jpc8e5wlbv") collection.listRule = "" collection.viewRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("vknl4jpc8e5wlbv") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1692029251_created_faqs.js ================================================ /// migrate((db) => { const collection = new Collection({ "id": "jibt2vep48ufd22", "created": "2023-08-14 16:07:31.811Z", "updated": "2023-08-14 16:07:31.811Z", "name": "faqs", "type": "base", "system": false, "schema": [ { "system": false, "id": "e9bldrjo", "name": "question", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "qrlumdkc", "name": "answer", "type": "text", "required": false, "unique": false, "options": { "min": null, "max": null, "pattern": "" } } ], "indexes": [], "listRule": "", "viewRule": "", "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("jibt2vep48ufd22"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1692030049_created_companies.js ================================================ /// migrate((db) => { const collection = new Collection({ "id": "w8voaruazjzwfdy", "created": "2023-08-14 16:20:49.199Z", "updated": "2023-08-14 16:20:49.199Z", "name": "companies", "type": "base", "system": false, "schema": [ { "system": false, "id": "ivpolooz", "name": "name", "type": "text", "required": true, "unique": false, "options": { "min": null, "max": null, "pattern": "" } }, { "system": false, "id": "vl0yntjz", "name": "logo", "type": "file", "required": true, "unique": false, "options": { "maxSelect": 1, "maxSize": 5242880, "mimeTypes": [ "image/jpeg", "image/png", "image/svg+xml", "image/gif", "image/webp" ], "thumbs": [], "protected": false } } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("w8voaruazjzwfdy"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1692030210_updated_companies.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("w8voaruazjzwfdy") collection.listRule = "" collection.viewRule = "" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("w8voaruazjzwfdy") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697824916_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "tep58ydu", "name": "role", "type": "select", "required": false, "presentable": false, "unique": false, "options": { "maxSelect": 1, "values": [ "user", "admin" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("tep58ydu") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697824932_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // update collection.schema.addField(new SchemaField({ "system": false, "id": "tep58ydu", "name": "role", "type": "select", "required": true, "presentable": false, "unique": false, "options": { "maxSelect": 1, "values": [ "user", "admin" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // update collection.schema.addField(new SchemaField({ "system": false, "id": "tep58ydu", "name": "role", "type": "select", "required": false, "presentable": false, "unique": false, "options": { "maxSelect": 1, "values": [ "user", "admin" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697982589_created_exercise_logs.js ================================================ /// migrate((db) => { const collection = new Collection({ "id": "juzwmxmth0oqv89", "created": "2023-10-22 13:49:49.779Z", "updated": "2023-10-22 13:49:49.779Z", "name": "exercise_logs", "type": "base", "system": false, "schema": [ { "system": false, "id": "qy80deun", "name": "user", "type": "relation", "required": true, "presentable": true, "unique": false, "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } }, { "system": false, "id": "c94ndl04", "name": "exercise", "type": "relation", "required": true, "presentable": true, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } }, { "system": false, "id": "40uyszyy", "name": "timestamp", "type": "date", "required": true, "presentable": true, "unique": false, "options": { "min": "", "max": "" } } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1697982994_updated_exercise_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = "@request.auth.id != \"\"" collection.viewRule = "@request.auth.id != \"\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = null collection.viewRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983253_updated_exercise_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.name = "exercise_session_logs" // remove collection.schema.removeField("c94ndl04") // add collection.schema.addField(new SchemaField({ "system": false, "id": "flngas50", "name": "exercise_session", "type": "relation", "required": false, "presentable": false, "unique": false, "options": { "collectionId": "qj6ssich32lcxru", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.name = "exercise_logs" // add collection.schema.addField(new SchemaField({ "system": false, "id": "c94ndl04", "name": "exercise", "type": "relation", "required": true, "presentable": true, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) // remove collection.schema.removeField("flngas50") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983426_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = "@request.auth.id != \"\"" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983434_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.viewRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.createRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.updateRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.deleteRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.viewRule = "@request.auth.id != \"\"" collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983470_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.listRule = "id = @request.auth.id || @request.auth.role = \"admin\"" collection.viewRule = "id = @request.auth.id || @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") collection.listRule = "id = @request.auth.id" collection.viewRule = "id = @request.auth.id" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983479_updated_exercise_sessions.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "@request.auth.id = user.id || @request.auth.role = \"admin\"" collection.viewRule = "@request.auth.id = user.id || @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("qj6ssich32lcxru") collection.listRule = "@request.auth.id = user.id" collection.viewRule = "@request.auth.id = user.id" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983500_updated_lab_sessions.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "@request.auth.id = user.id || @request.auth.role = \"admin\"" collection.viewRule = "@request.auth.id = user.id || @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("n7tne53bbobf2bt") collection.listRule = "@request.auth.id = user.id " collection.viewRule = "@request.auth.id = user.id " return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697983560_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // update collection.schema.addField(new SchemaField({ "system": false, "id": "flngas50", "name": "exercise_session", "type": "relation", "required": true, "presentable": true, "unique": false, "options": { "collectionId": "qj6ssich32lcxru", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // update collection.schema.addField(new SchemaField({ "system": false, "id": "flngas50", "name": "exercise_session", "type": "relation", "required": false, "presentable": false, "unique": false, "options": { "collectionId": "qj6ssich32lcxru", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697984665_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // add collection.schema.addField(new SchemaField({ "system": false, "id": "2pfxi0ho", "name": "type", "type": "select", "required": false, "presentable": false, "unique": false, "options": { "maxSelect": 1, "values": [ "start", "end" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // remove collection.schema.removeField("2pfxi0ho") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697984707_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // update collection.schema.addField(new SchemaField({ "system": false, "id": "2pfxi0ho", "name": "type", "type": "select", "required": true, "presentable": true, "unique": false, "options": { "maxSelect": 1, "values": [ "start", "end" ] } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") // update collection.schema.addField(new SchemaField({ "system": false, "id": "2pfxi0ho", "name": "type", "type": "select", "required": false, "presentable": false, "unique": false, "options": { "maxSelect": 1, "values": [ "start", "end" ] } })) return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697986578_created_notifications.js ================================================ /// migrate((db) => { const collection = new Collection({ "id": "r2q6rrbyred18kq", "created": "2023-10-22 14:56:18.570Z", "updated": "2023-10-22 14:56:18.570Z", "name": "notifications", "type": "base", "system": false, "schema": [ { "system": false, "id": "1oiscevm", "name": "type", "type": "select", "required": true, "presentable": true, "unique": false, "options": { "maxSelect": 1, "values": [ "help" ] } }, { "system": false, "id": "a3furdsq", "name": "user", "type": "relation", "required": true, "presentable": true, "unique": false, "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } }, { "system": false, "id": "wwksthrr", "name": "done", "type": "bool", "required": false, "presentable": true, "unique": false, "options": {} } ], "indexes": [], "listRule": null, "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} }); return Dao(db).saveCollection(collection); }, (db) => { const dao = new Dao(db); const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq"); return dao.deleteCollection(collection); }) ================================================ FILE: kubelab-backend/pb_migrations/1697987216_updated_notifications.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") collection.listRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.viewRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.createRule = "@request.auth.id != \"\"" collection.updateRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.deleteRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") collection.listRule = null collection.viewRule = null collection.createRule = null collection.updateRule = null collection.deleteRule = null return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697988059_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "arqpjvok", "name": "company", "type": "relation", "required": false, "presentable": true, "unique": false, "options": { "collectionId": "w8voaruazjzwfdy", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("arqpjvok") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697992209_updated_exercise_session_logs.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = "@request.auth.id != \"\"" collection.viewRule = "@request.auth.id != \"\"" collection.createRule = "@request.auth.id != \"\" " collection.updateRule = "@request.auth.id != \"\"" collection.deleteRule = "@request.auth.id != \"\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("juzwmxmth0oqv89") collection.listRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.viewRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.createRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.updateRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.deleteRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697992245_updated_notifications.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") collection.listRule = "@request.auth.id != \"\"" collection.viewRule = "@request.auth.id != \"\"" collection.updateRule = "@request.auth.id != \"\"" return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") collection.listRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.viewRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" collection.updateRule = "@request.auth.id != \"\" && @request.auth.role = \"admin\"" return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697992793_updated_notifications.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") // add collection.schema.addField(new SchemaField({ "system": false, "id": "qoqrd85a", "name": "exercise", "type": "relation", "required": false, "presentable": false, "unique": false, "options": { "collectionId": "s4f0lpy3ibkgfqp", "cascadeDelete": false, "minSelect": null, "maxSelect": 1, "displayFields": null } })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("r2q6rrbyred18kq") // remove collection.schema.removeField("qoqrd85a") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pb_migrations/1697995266_updated_users.js ================================================ /// migrate((db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // add collection.schema.addField(new SchemaField({ "system": false, "id": "3wxrrkpg", "name": "workshop", "type": "bool", "required": false, "presentable": true, "unique": false, "options": {} })) return dao.saveCollection(collection) }, (db) => { const dao = new Dao(db) const collection = dao.findCollectionByNameOrId("_pb_users_auth_") // remove collection.schema.removeField("3wxrrkpg") return dao.saveCollection(collection) }) ================================================ FILE: kubelab-backend/pkg/controller/exercise.go ================================================ package controller import ( "log" "time" "github.com/natrontech/kubelab/pkg/env" "github.com/natrontech/kubelab/pkg/helm" "github.com/natrontech/kubelab/pkg/k8s" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func setupExerciseResources(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { exercise, err := app.Dao().FindRecordById("exercises", e.Record.GetString("exercise")) if err != nil { return logAndReturnErr(err) } user, err := app.Dao().FindRecordById("users", e.Record.GetString("user")) if err != nil { return err } namespaceParams := k8s.NamespaceParams{ Name: namespaceName(e, exercise.GetString("lab")), UserRecord: user, } if err = k8s.CreateNamespace(namespaceParams); err != nil { log.Println(err) } if _, err = k8s.GetPodByName(namespaceName(e, exercise.GetString("lab")), "vcluster-0"); err != nil { return logAndReturnErr(err) } secret, err := k8s.GetSecretByName(namespaceName(e, exercise.GetString("lab")), "vc-vcluster") if err != nil { return logAndReturnErr(err) } bootstrapBody, err := fetchBodyFromURL(exercise.GetString("bootstrap")) if err != nil { return logAndReturnErr(err) } checkBody, err := fetchBodyFromURL(exercise.GetString("check")) if err != nil { return logAndReturnErr(err) } deploymentParams := k8s.DeploymentParams{ Name: "kubelab-agent-" + exercise.Id, Namespace: helm.GetNamespaceName(exercise.GetString("lab"), e.Record.GetString("user")), Image: env.Config.KubelabImage, Replicas: 1, Kubeconfig: string(secret.Data["config"]), Bootstrap: string(bootstrapBody), Check: string(checkBody), Host: env.Config.AllowedHosts, UserRecord: user, CodeServerPath: "kubelab-" + exercise.GetString("lab") + "-" + exercise.Id + "-" + e.Record.GetString("user"), } // create a new deployment _, err = k8s.CreateDeployment(deploymentParams) if err != nil { log.Println(err) } serviceParams := k8s.ServiceParams{ Name: "kubelab-agent-" + exercise.Id, Namespace: helm.GetNamespaceName(exercise.GetString("lab"), e.Record.GetString("user")), Port: 8376, UserRecord: user, } // create a new service _, err = k8s.CreateService(serviceParams) if err != nil { log.Println(err) } ingressParams := k8s.IngressParams{ Name: "kubelab-agent-" + exercise.Id, Namespace: helm.GetNamespaceName(exercise.GetString("lab"), e.Record.GetString("user")), ServiceName: "kubelab-agent-" + exercise.Id, Host: env.Config.AllowedHosts, Path: "kubelab-" + exercise.GetString("lab") + "-" + exercise.Id + "-" + e.Record.GetString("user"), UserRecord: user, } // create a first ingress ingressParams.UseFirstRule = true _, err = k8s.CreateIngress(ingressParams) if err != nil { log.Println(err) } // create a second ingress ingressParams.UseFirstRule = false _, err = k8s.CreateIngress(ingressParams) if err != nil { log.Println(err) } // check if deployment is ready err = k8s.WaitForDeployment(helm.GetNamespaceName(exercise.GetString("lab"), e.Record.GetString("user")), "kubelab-agent-"+exercise.Id) if err != nil { log.Println(err) } // sleep for 5 seconds time.Sleep(5 * time.Second) return nil } func deleteExerciseResources(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { exercise, err := app.Dao().FindRecordById("exercises", e.Record.GetString("exercise")) if err != nil { return logAndReturnErr(err) } deleteFuncs := []func(string, string) error{ k8s.DeleteDeployment, k8s.DeleteService, k8s.DeleteIngress, } for _, fn := range deleteFuncs { if err = fn(namespaceName(e, exercise.GetString("lab")), "kubelab-agent-"+exercise.Id); err != nil { log.Println(err) } } return nil } ================================================ FILE: kubelab-backend/pkg/controller/handler.go ================================================ package controller import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func HandleLabSessions(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { if e.Record.GetBool("clusterRunning") { return deployVCluster(e, app) } return deleteClusterResources(e, app) } func HandleExerciseSessions(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { if e.Record.GetBool("agentRunning") { return setupExerciseResources(e, app) } return deleteExerciseResources(e, app) } ================================================ FILE: kubelab-backend/pkg/controller/lab.go ================================================ package controller import ( "log" "os" "time" "github.com/natrontech/kubelab/pkg/env" "github.com/natrontech/kubelab/pkg/helm" "github.com/natrontech/kubelab/pkg/k8s" "github.com/natrontech/kubelab/pkg/util" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" ) func deployVCluster(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { helmclient, err := helm.CreateHelmClient(e.Record.GetString("lab"), e.Record.GetString("user")) if err != nil { return logAndReturnErr(err) } user, err := app.Dao().FindRecordById("users", e.Record.GetString("user")) if err != nil { return err } if err = helm.AddHelmRepositoryToClient(helmclient, "loft-sh", "https://charts.loft.sh"); err != nil { return logAndReturnErr(err) } yamlValues, err := os.ReadFile(env.Config.VClusterValuesFilePath) if err != nil { return logAndReturnErr(err) } labels := map[string]string{ "kubelab.ch": e.Record.GetString("lab"), "kubelab.ch/userId": e.Record.GetString("user"), "kubelab.ch/username": user.GetString("username"), "kubelab.ch/displayName": util.StringParser(user.GetString("name")), } // add string at the end of yamlValues yamlValues = append(yamlValues, []byte("\nlabels:\n")...) for k, v := range labels { yamlValues = append(yamlValues, []byte(" "+k+": "+v+"\n")...) } // add string at the end of yamlValues yamlValues = append(yamlValues, []byte("\npodLabels:\n")...) for k, v := range labels { yamlValues = append(yamlValues, []byte(" "+k+": "+v+"\n")...) } // add string at the end of yamlValues yamlValues = append(yamlValues, []byte("\ncoredns:\n podLabels:\n")...) for k, v := range labels { yamlValues = append(yamlValues, []byte(" "+k+": "+v+"\n")...) } if _, err = helm.CreateOrUpdateHelmRelease( helmclient, "loft-sh/vcluster", "vcluster", namespaceName(e, e.Record.GetString("lab")), env.Config.VClusterChartVersion, string(yamlValues), ); err != nil { return logAndReturnErr(err) } if err = k8s.CreateResourceQuota(namespaceName(e, e.Record.GetString("lab")), env.Config.ResourceName, env.Config.PodsLimit, env.Config.StorageLimit); err != nil { return logAndReturnErr(err) } time.Sleep(15 * time.Second) return nil } func deleteClusterResources(e *core.RecordUpdateEvent, app *pocketbase.PocketBase) error { if err := k8s.DeleteNamespace(namespaceName(e, e.Record.GetString("lab"))); err != nil { log.Println(err) } time.Sleep(15 * time.Second) return nil } ================================================ FILE: kubelab-backend/pkg/controller/sessions.go ================================================ package controller import ( "fmt" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/models" ) func AutoSessionSyncController(app *pocketbase.PocketBase) error { // get each user with role "user" users, err := app.Dao().FindRecordsByExpr("users", dbx.NewExp("LOWER(role) = {:role}", dbx.Params{"role": "user"})) if err != nil { fmt.Println("Error getting users: ", err) return err } // get each lab labs, err := app.Dao().FindRecordsByExpr("labs") if err != nil { fmt.Println("Error getting labs: ", err) return err } // get each exercise exercises, err := app.Dao().FindRecordsByExpr("exercises") if err != nil { return err } labSessionsCollection, err := app.Dao().FindCollectionByNameOrId("lab_sessions") if err != nil { return err } exerciseSessionsCollection, err := app.Dao().FindCollectionByNameOrId("exercise_sessions") if err != nil { return err } // go through each user for _, user := range users { // get lab_sessions for user labSessions, err := app.Dao().FindRecordsByExpr("lab_sessions", dbx.NewExp("user = {:user}", dbx.Params{"user": user.Id})) if err != nil { fmt.Println("Error getting lab sessions: ", err) return err } // check if there exists for each lab a lab_session for _, lab := range labs { found := false for _, labSession := range labSessions { if labSession.Get("lab") == lab.Id { found = true break } } if !found { record := models.NewRecord(labSessionsCollection) form := forms.NewRecordUpsert(app, record) form.LoadData(map[string]any{ "user": user.Id, "lab": lab.Id, "clusterRunning": false, }) err := form.Validate() if err != nil { return err } err = form.Submit() if err != nil { return err } } } // get exercise_sessions for user exerciseSessions, err := app.Dao().FindRecordsByExpr("exercise_sessions", dbx.NewExp("user = {:user}", dbx.Params{"user": user.Id})) if err != nil { fmt.Println("Error getting exercise sessions: ", err) return err } // check if there exists for each exercise an exercise_session for _, exercise := range exercises { found := false for _, exerciseSession := range exerciseSessions { if exerciseSession.Get("exercise") == exercise.Id { found = true break } } if !found { record := models.NewRecord(exerciseSessionsCollection) form := forms.NewRecordUpsert(app, record) form.LoadData(map[string]any{ "user": user.Id, "exercise": exercise.Id, "agentRunning": false, }) err := form.Validate() if err != nil { return err } err = form.Submit() if err != nil { return err } } } // check if there exist sessions which don't belong to any lab or exercise and delete them for _, labSession := range labSessions { found := false for _, lab := range labs { if labSession.Get("lab") == lab.Id { found = true break } } if !found { err := app.Dao().DeleteRecord(labSession) if err != nil { return err } } } for _, exerciseSession := range exerciseSessions { found := false for _, exercise := range exercises { if exerciseSession.Get("exercise") == exercise.Id { found = true break } } if !found { err := app.Dao().DeleteRecord(exerciseSession) if err != nil { return err } } } } return nil } ================================================ FILE: kubelab-backend/pkg/controller/util.go ================================================ package controller import ( "io" "log" "net/http" "github.com/natrontech/kubelab/pkg/helm" "github.com/pocketbase/pocketbase/core" ) func namespaceName(e *core.RecordUpdateEvent, lab string) string { return helm.GetNamespaceName(lab, e.Record.GetString("user")) } func logAndReturnErr(err error) error { log.Println(err) return err } func fetchBodyFromURL(url string) ([]byte, error) { response, err := http.Get(url) if err != nil { return nil, logAndReturnErr(err) } defer response.Body.Close() return io.ReadAll(response.Body) } ================================================ FILE: kubelab-backend/pkg/env/env.go ================================================ package env import ( "log" "github.com/caarlos0/env/v8" ) type config struct { Local bool `env:"LOCAL"` KubelabImage string `env:"KUBELAB_AGENT_IMAGE" envDefault:"ghcr.io/natrontech/kubelab-agent:latest"` CodeServerImage string `env:"CODE_SERVER_IMAGE" envDefault:"ghcr.io/natrontech/kubelab-code-server:latest"` AllowedHosts string `env:"ALLOWED_HOSTS" envDefault:"*"` ResourceName string `env:"RESOURCE_NAME" envDefault:"kubelab"` IngressClass string `env:"AGENT_INGRESS_CLASS" envDefault:"nginx"` PodsLimit string `env:"PODS_LIMIT" envDefault:"70"` StorageLimit string `env:"STORAGE_LIMIT" envDefault:"50Gi"` VClusterChartVersion string `env:"VCLUSTER_CHART_VERSION" envDefault:"0.16.4"` VClusterValuesFilePath string `env:"VCLUSTER_VALUES_FILE_PATH" envDefault:"./vcluster-values.yaml"` CronTick string `env:"CRON_TICK" envDefault:"* * * * *"` TlsSecretName string `env:"TLS_SECRET_NAME" envDefault:"kubelab-ch-wildcard-cert"` } var Config config func Init() { if err := env.Parse(&Config); err != nil { log.Printf("%+v\n", err) } if Config.Local { log.Println("Running in local mode") } } ================================================ FILE: kubelab-backend/pkg/helm/helm.go ================================================ package helm import ( "context" "strings" "time" helmclient "github.com/mittwald/go-helm-client" "github.com/natrontech/kubelab/pkg/util" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" ) const Prefix = "kubelab" func GetNamespaceName(labName, username string) string { return Prefix + "-" + util.StringParser(labName) + "-" + util.StringParser(username) } func CreateHelmClient(labName, username string) (helmclient.Client, error) { opt := &helmclient.Options{ Namespace: GetNamespaceName(labName, username), Debug: true, Linting: true, DebugLog: func(format string, v ...interface{}) {}, } return helmclient.New(opt) } func AddHelmRepositoryToClient(helmClient helmclient.Client, repositoryName, repositoryURL string) error { chartRepo := repo.Entry{ Name: strings.ToLower(repositoryName), URL: repositoryURL, } return helmClient.AddOrUpdateChartRepo(chartRepo) } func CreateOrUpdateHelmRelease(helmClient helmclient.Client, chartName, releaseName, namespace, version, valuesYaml string) (rel *release.Release, err error) { chartSpec := helmclient.ChartSpec{ ChartName: strings.ToLower(chartName), ReleaseName: strings.ToLower(releaseName), Namespace: namespace, CreateNamespace: true, Timeout: 32 * time.Second, Version: version, ValuesYaml: valuesYaml, } rel, err = helmClient.InstallOrUpgradeChart(context.Background(), &chartSpec, nil) return } func GetHelmRelease(helmClient helmclient.Client, releaseName string) (*release.Release, error) { return helmClient.GetRelease(releaseName) } ================================================ FILE: kubelab-backend/pkg/k8s/config.go ================================================ package k8s import ( "context" "flag" "path/filepath" "github.com/natrontech/kubelab/pkg/env" "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) var ( Clientset *kubernetes.Clientset Kubeconfig *rest.Config DiscoveryClient *discovery.DiscoveryClient Ctx context.Context ) func Init() { var err error if env.Config.Local { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") } else { kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") } flag.Parse() Kubeconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(Kubeconfig) if err != nil { panic(err) } Clientset, err = kubernetes.NewForConfig(Kubeconfig) if err != nil { panic(err) } Ctx = context.Background() } else { Kubeconfig, err = rest.InClusterConfig() if err != nil { panic(err.Error()) } DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(Kubeconfig) if err != nil { panic(err) } Clientset, err = kubernetes.NewForConfig(Kubeconfig) if err != nil { panic(err) } Ctx = context.Background() } } func GetClusterVersion() (string, error) { if DiscoveryClient != nil { clusterVersion, err := DiscoveryClient.ServerVersion() if err != nil { return "", err } return clusterVersion.GitVersion, nil } return "unknown", nil } func GetClusterApi() (string, error) { var clusterName string if Kubeconfig != nil { clusterName = Kubeconfig.Host } else { clusterName = "unknown" } return clusterName, nil } ================================================ FILE: kubelab-backend/pkg/k8s/deployment.go ================================================ package k8s import ( "log" "strings" "time" "github.com/natrontech/kubelab/pkg/env" "github.com/natrontech/kubelab/pkg/util" "github.com/pocketbase/pocketbase/models" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" ) type DeploymentParams struct { Name string Namespace string Image string Replicas int32 Kubeconfig string Bootstrap string Check string Host string UserRecord *models.Record CodeServerPath string } func CreateDeployment(params DeploymentParams) (*appsv1.Deployment, error) { params.Kubeconfig = strings.Replace(params.Kubeconfig, "localhost:8443", "vcluster:443", -1) createConfigMap(params.Namespace, "kubeconfig", map[string]string{"config": params.Kubeconfig}) createConfigMap(params.Namespace, "scripts-"+params.Name, map[string]string{ "check.sh": params.Check, "bootstrap.sh": params.Bootstrap, }) deployment := constructDeployment(params.Name, params.Namespace, params.Image, params.Replicas, params.Host, params.UserRecord, params.CodeServerPath) deployed, err := Clientset.AppsV1().Deployments(params.Namespace).Create(Ctx, deployment, metav1.CreateOptions{}) if err != nil { log.Println(err) } return deployed, nil } func createConfigMap(namespace, name string, data map[string]string) { configMap := &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{Name: name}, Data: data, } if _, err := Clientset.CoreV1().ConfigMaps(namespace).Create(Ctx, configMap, metav1.CreateOptions{}); err != nil { log.Println(err) } } func constructDeployment(name, namespace, image string, replicas int32, host string, userRecord *models.Record, codeServerPath string) *appsv1.Deployment { scriptVolumeName := "scripts-" + name return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "kubelab.ch": name, "kubelab.ch/userId": userRecord.GetString("id"), "kubelab.ch/username": userRecord.GetString("username"), "kubelab.ch/displayName": util.StringParser(userRecord.GetString("name")), }, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "kubelab.ch": name, "kubelab.ch/userId": userRecord.GetString("id"), "kubelab.ch/username": userRecord.GetString("username"), "kubelab.ch/displayName": util.StringParser(userRecord.GetString("name")), "vcluster.loft.sh/managed-by": "vcluster", }, }, Spec: v1.PodSpec{ InitContainers: []v1.Container{ { Name: "init-copy-chmod-chown", Image: "busybox", Command: []string{"sh", "-c", "cp /config/config /config-writable/kubeconfig && chmod 0600 /config-writable/kubeconfig && chown 1001:1001 /config-writable/kubeconfig && cp /scripts-source/* /scripts-writable && chmod 0700 /scripts-writable/* && chown 1001:1001 /scripts-writable/*"}, VolumeMounts: []v1.VolumeMount{ { Name: "kubeconfig", MountPath: "/config", }, { Name: "kubeconfig-writable", MountPath: "/config-writable", }, { Name: scriptVolumeName, // The name of the ConfigMap volume MountPath: "/scripts-source", // Source directory for the scripts }, { Name: scriptVolumeName + "-writable", // The writable directory MountPath: "/scripts-writable", }, }, }, { Name: "init-kubelab-agent-home", Image: image, // using the same image as kubelab-container Command: []string{"sh", "-c", "mkdir -p /shared-kubelab-agent-home && cp -r /home/kubelab-agent/. /shared-kubelab-agent-home/"}, SecurityContext: &v1.SecurityContext{ RunAsUser: pointer.Int64(1001), RunAsGroup: pointer.Int64(1001), }, VolumeMounts: []v1.VolumeMount{ { Name: "kubelab-agent-home", MountPath: "/shared-kubelab-agent-home", }, }, }, }, Containers: []v1.Container{ { Name: "kubelab-container", Image: image, Ports: []v1.ContainerPort{ { ContainerPort: 8376, }, }, // --allowed-hostnames Args: []string{"--allowed-hostnames", "*," + host}, VolumeMounts: []v1.VolumeMount{ { Name: "kubeconfig-writable", MountPath: "/home/kubelab-agent/.kube/config", SubPath: "kubeconfig", }, { Name: scriptVolumeName + "-writable", MountPath: "/scripts", ReadOnly: true, }, { Name: "kubelab-agent-home", MountPath: "/home/kubelab-agent", }, }, }, { Name: "code-server", Image: env.Config.CodeServerImage, Env: []v1.EnvVar{ // { // Name: "PUID", // Value: "1001", // }, // { // Name: "PGID", // Value: "1001", // }, { Name: "DEFAULT_WORKSPACE", Value: "/home/kubelab-agent/exercise", }, // { // Name: "PROXY_DOMAIN", // Value: codeServerPath + "." + host, // }, { Name: "CS_DISABLE_PROXY", Value: "true", }, }, Ports: []v1.ContainerPort{ { ContainerPort: 8443, }, }, VolumeMounts: []v1.VolumeMount{ { Name: "kubeconfig-writable", MountPath: "/config/.kube/config", SubPath: "kubeconfig", }, { Name: scriptVolumeName + "-writable", MountPath: "/scripts", ReadOnly: true, }, { Name: "kubelab-agent-home", MountPath: "/home/kubelab-agent", }, }, }, }, Volumes: []v1.Volume{ { Name: "kubeconfig", VolumeSource: v1.VolumeSource{ ConfigMap: &v1.ConfigMapVolumeSource{ LocalObjectReference: v1.LocalObjectReference{ Name: "kubeconfig", }, }, }, }, { Name: "kubeconfig-writable", VolumeSource: v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }, }, { Name: scriptVolumeName, VolumeSource: v1.VolumeSource{ ConfigMap: &v1.ConfigMapVolumeSource{ LocalObjectReference: v1.LocalObjectReference{ Name: scriptVolumeName, }, }, }, }, { Name: scriptVolumeName + "-writable", VolumeSource: v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }, }, { Name: "kubelab-agent-home", VolumeSource: v1.VolumeSource{ EmptyDir: &v1.EmptyDirVolumeSource{}, }, }, }, }, }, }, } } func DeleteDeployment(namespace, name string) error { deleteConfigMap(namespace, "kubeconfig") deleteConfigMap(namespace, "scripts-"+name) return Clientset.AppsV1().Deployments(namespace).Delete(Ctx, name, metav1.DeleteOptions{}) } func deleteConfigMap(namespace, name string) { if err := Clientset.CoreV1().ConfigMaps(namespace).Delete(Ctx, name, metav1.DeleteOptions{}); err != nil { log.Println(err) } } func WaitForDeployment(namespace, name string) error { for { deployment, err := Clientset.AppsV1().Deployments(namespace).Get(Ctx, name, metav1.GetOptions{}) if err != nil { return err } if deployment.Status.ReadyReplicas == *deployment.Spec.Replicas { return nil } time.Sleep(1 * time.Second) } } ================================================ FILE: kubelab-backend/pkg/k8s/ingress.go ================================================ package k8s import ( "github.com/natrontech/kubelab/pkg/env" "github.com/natrontech/kubelab/pkg/util" "github.com/pocketbase/pocketbase/models" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type IngressParams struct { Namespace string Name string Host string ServiceName string Path string UserRecord *models.Record UseFirstRule bool } // create a ingress with a hostpath with the namespace name pointed to a service func CreateIngress(params IngressParams) (*networkingv1.Ingress, error) { // Annotations common to both rules annotations := map[string]string{ "nginx.ingress.kubernetes.io/affinity": "cookie", "nginx.ingress.kubernetes.io/proxy-connect-timeout": "3600", "nginx.ingress.kubernetes.io/proxy-next-upstream-timeout": "3600", "nginx.ingress.kubernetes.io/proxy-read-timeout": "3600", "nginx.ingress.kubernetes.io/proxy-send-timeout": "3600", "nginx.ingress.kubernetes.io/session-cookie-expires": "172800", "nginx.ingress.kubernetes.io/session-cookie-max-age": "172800", "nginx.ingress.kubernetes.io/session-cookie-name": "route", "nginx.ingress.kubernetes.io/websocket-services": params.ServiceName, "nginx.org/websocket-services": params.ServiceName, } var rules []networkingv1.IngressRule if params.UseFirstRule { annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2" rules = append(rules, networkingv1.IngressRule{ Host: params.Host, IngressRuleValue: networkingv1.IngressRuleValue{ HTTP: &networkingv1.HTTPIngressRuleValue{ Paths: []networkingv1.HTTPIngressPath{ { Path: "/" + params.Path + "(/|$)(.*)", PathType: func() *networkingv1.PathType { p := networkingv1.PathTypeImplementationSpecific; return &p }(), Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: params.ServiceName, Port: networkingv1.ServiceBackendPort{ Number: 8376, }, }, }, }, }, }, }, }) } else { rules = append(rules, networkingv1.IngressRule{ Host: params.Path + "." + params.Host, IngressRuleValue: networkingv1.IngressRuleValue{ HTTP: &networkingv1.HTTPIngressRuleValue{ Paths: []networkingv1.HTTPIngressPath{ { Path: "/", PathType: func() *networkingv1.PathType { p := networkingv1.PathTypePrefix; return &p }(), Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: params.ServiceName, Port: networkingv1.ServiceBackendPort{ Number: 8443, }, }, }, }, }, }, }, }) } ingress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: params.Name + (func() string { if params.UseFirstRule { return "-first-rule" } else { return "-second-rule" } })(), Namespace: params.Namespace, Annotations: annotations, Labels: map[string]string{ "kubelab.ch": params.Name, "kubelab.ch/userId": params.UserRecord.GetString("id"), "kubelab.ch/username": params.UserRecord.GetString("username"), "kubelab.ch/displayName": util.StringParser(params.UserRecord.GetString("name")), }, }, Spec: networkingv1.IngressSpec{ IngressClassName: func() *string { s := env.Config.IngressClass; return &s }(), TLS: []networkingv1.IngressTLS{ { SecretName: env.Config.TlsSecretName, }, }, Rules: rules, }, } return Clientset.NetworkingV1().Ingresses(params.Namespace).Create(Ctx, ingress, metav1.CreateOptions{}) } func DeleteIngress(namespace string, name string) error { // Delete the ingress for the first rule err := Clientset.NetworkingV1().Ingresses(namespace).Delete(Ctx, name+"-first-rule", metav1.DeleteOptions{}) if err != nil { return err } // Delete the ingress for the second rule err = Clientset.NetworkingV1().Ingresses(namespace).Delete(Ctx, name+"-second-rule", metav1.DeleteOptions{}) if err != nil { return err } return nil } ================================================ FILE: kubelab-backend/pkg/k8s/namespace.go ================================================ package k8s import ( "strconv" "strings" "github.com/natrontech/kubelab/pkg/util" "github.com/pocketbase/pocketbase/models" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func GetTotalNamespaces() (string, error) { if Clientset != nil { namespaces, err := Clientset.CoreV1().Namespaces().List(Ctx, metav1.ListOptions{}) if err != nil { return "", err } totalNamespaces := len(namespaces.Items) return strconv.Itoa(totalNamespaces), nil } return "unknown", nil } type NamespaceParams struct { Name string UserRecord *models.Record } func CreateNamespace(params NamespaceParams) error { ns := &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: params.Name, Labels: map[string]string{ "kubelab.ch": params.Name, "kubelab.ch/userId": params.UserRecord.GetString("id"), "kubelab.ch/username": params.UserRecord.GetString("username"), "kubelab.ch/displayName": util.StringParser(params.UserRecord.GetString("name")), }, }, } _, err := Clientset.CoreV1().Namespaces().Create(Ctx, ns, metav1.CreateOptions{}) // if err already exists, update if err != nil && strings.Contains(err.Error(), "already exists") { _, err = Clientset.CoreV1().Namespaces().Update(Ctx, ns, metav1.UpdateOptions{}) } return err } func DeleteNamespace(namespace string) error { return Clientset.CoreV1().Namespaces().Delete(Ctx, namespace, metav1.DeleteOptions{}) } func GetTotalNamespacesByPrefix(prefix string) (int, error) { if Clientset != nil { namespaces, err := Clientset.CoreV1().Namespaces().List(Ctx, metav1.ListOptions{}) if err != nil { return 0, err } var totalNamespaces int // check if prefix of namespace is prefix for _, namespace := range namespaces.Items { // get name of namespace name := namespace.GetName() // check if namespace contains prefix if strings.Contains(name, prefix) { totalNamespaces++ } } return totalNamespaces, nil } return 0, nil } ================================================ FILE: kubelab-backend/pkg/k8s/pod.go ================================================ package k8s import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func GetPodByName(namespace string, name string) (*v1.Pod, error) { if Clientset != nil { pod, err := Clientset.CoreV1().Pods(namespace).Get(Ctx, name, metav1.GetOptions{}) if err != nil { return nil, err } return pod, nil } return nil, nil } ================================================ FILE: kubelab-backend/pkg/k8s/resourcequota.go ================================================ package k8s import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func CreateResourceQuota(namespace string, name string, pods string, storage string) error { resourceQuota := &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: v1.ResourceQuotaSpec{ Hard: v1.ResourceList{ v1.ResourcePods: resource.MustParse(pods), v1.ResourceRequestsStorage: resource.MustParse(storage), }, }, } _, err := Clientset.CoreV1().ResourceQuotas(namespace).Create(Ctx, resourceQuota, metav1.CreateOptions{}) return err } ================================================ FILE: kubelab-backend/pkg/k8s/secret.go ================================================ package k8s import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func GetSecretByName(namespace string, name string) (*v1.Secret, error) { if Clientset != nil { secret, err := Clientset.CoreV1().Secrets(namespace).Get(Ctx, name, metav1.GetOptions{}) if err != nil { return nil, err } return secret, nil } return nil, nil } ================================================ FILE: kubelab-backend/pkg/k8s/service.go ================================================ package k8s import ( "github.com/natrontech/kubelab/pkg/util" "github.com/pocketbase/pocketbase/models" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ServiceParams struct { Namespace string Name string Port int32 UserRecord *models.Record } func CreateService(params ServiceParams) (*v1.Service, error) { service := &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: params.Name, Namespace: params.Namespace, Labels: map[string]string{ "kubelab.ch": params.Name, "kubelab.ch/userId": params.UserRecord.GetString("id"), "kubelab.ch/username": params.UserRecord.GetString("username"), "kubelab.ch/displayName": util.StringParser(params.UserRecord.GetString("name")), }, }, Spec: v1.ServiceSpec{ Selector: map[string]string{ "kubelab.ch": params.Name, }, Ports: []v1.ServicePort{ { Name: "main-port", // You can name this appropriately Port: params.Port, }, { Name: "code-server-port", // Name for the code-server port Port: 8443, // Assuming code-server runs on port 8080 }, }, Type: v1.ServiceTypeClusterIP, }, } return Clientset.CoreV1().Services(params.Namespace).Create(Ctx, service, metav1.CreateOptions{}) } func DeleteService(namespace string, name string) error { return Clientset.CoreV1().Services(namespace).Delete(Ctx, name, metav1.DeleteOptions{}) } ================================================ FILE: kubelab-backend/pkg/util/helpers.go ================================================ package util import "strings" var ( SpecialCharacters string = "!@#$%^&*()_+-=[]{}|;':,./<>?`~" ) func StringParser(s string) string { returnString := strings.ToLower(s) returnString = strings.Replace(returnString, " ", "-", -1) // remove special characters from returnString for _, c := range SpecialCharacters { returnString = strings.Replace(returnString, string(c), "", -1) } return returnString } ================================================ FILE: kubelab-backend/vcluster-values.yaml ================================================ sync: persistentvolumes: enabled: true storageclasses: enabled: false ingresses: enabled: true hoststorageclasses: enabled: true events: enabled: false vcluster: image: rancher/k3s:v1.28.7-k3s1 storage: persistence: false isolation: enabled: true podSecurityStandard: privileged nodeProxyPermission: enabled: false resourceQuota: enabled: false limitRange: enabled: false networkPolicy: enabled: false outgoingConnections: ipBlock: cidr: 8.8.8.8/32 except: [] syncer: kubeConfigContextName: "kubelab-vcluster" ================================================ FILE: kubelab-fill/example_users.csv ================================================ firstname,lastname,email,password ================================================ FILE: kubelab-fill/upload.py ================================================ import json import os import requests class Lab: def __init__(self, title, description, docs): self.title = title self.description = description self.docs = docs class Exercise: def __init__(self, title, description, docs, hint, solution, check, bootstrap, lab): self.title = title self.description = description self.docs = docs self.hint = hint self.solution = solution self.check = check self.bootstrap = bootstrap self.lab = lab def post_request(url, data): try: headers = {'Content-type': 'application/json'} response = requests.post(url, data=json.dumps(data), headers=headers) return response except requests.exceptions.RequestException as e: print(e) def main(): # from .env import labs_upload_url, exercises_upload_url labs_upload_url = os.environ['LABS_UPLOAD_URL'] exercises_upload_url = os.environ['EXERCISES_UPLOAD_URL'] prefix = os.environ['URL_PREFIX'] path = os.environ['WORSHOP_DIR_PATH'] labs = get_labs_from_dir(path) exercises = get_exercises_from_dir(path) # sort labs and exercises labs.sort(key=lambda x: x.title) exercises.sort(key=lambda x: x.title) for lab in labs: data = { 'title': lab.title, 'description': lab.description, 'docs': prefix + lab.docs } response = post_request(labs_upload_url, data) if response.status_code == 200: print("Lab {} uploaded successfully".format(lab.title)) # convert response.content bytes to json labId = json.loads(response.text)['id'] print("Lab {} id: {}".format(lab.title, labId)) for exercise in exercises: if exercise.lab == lab.title: data = { 'title': exercise.title, 'description': exercise.description, 'docs': prefix + exercise.docs, 'hint': prefix + exercise.hint, 'solution': prefix + exercise.solution, 'check': prefix + exercise.check, 'bootstrap': prefix + exercise.bootstrap, 'lab': labId } response = post_request(exercises_upload_url, data) if response.status_code == 200: print("Exercise {} uploaded successfully".format(exercise.title)) else: print("Exercise {} upload failed".format(exercise.title)) else: print("Lab {} upload failed".format(lab.title)) def get_labs_from_dir(path): labs = [] for name in os.listdir(path): if os.path.isdir(os.path.join(path, name)): prename = name number = name.split("_")[0] name = name.split("_")[1:] name = " ".join(name) name = number+" "+name.title() labs.append( Lab( title=name, description=name, docs=prename+"/docs.md" ) ) return labs def get_exercises_from_dir(path, level=1, parent=None): exercises = [] for name in os.listdir(path): full_path = os.path.join(path, name) if os.path.isdir(full_path): if level == 2: prename = name number = name.split("_")[0] name = name.split("_")[1:] name = " ".join(name) name = number+" "+name.title() exercises.append( Exercise( title=name, description=name, docs=parent+"/"+prename+"/docs.md", hint=parent+"/"+prename+"/hint.md", solution=parent+"/"+prename+"/solution.md", check=parent+"/"+prename+"/check.sh", bootstrap=parent+"/"+prename+"/bootstrap.sh", lab=parse_name(parent) ) ) exercises.extend(get_exercises_from_dir(full_path, level + 1, parent=name)) return exercises def parse_name(name): name = name.split("_") number = name[0] name = name[1:] name = " ".join(name) name = number+" "+name.title() return name if __name__ == '__main__': main() ================================================ FILE: kubelab-fill/users_import.py ================================================ import csv import json import os import requests class User: def __init__(self, email, password, passwordConfirm, name): self.email = email self.password = password self.passwordConfirm = passwordConfirm self.name = name def post_request(url, data): try: headers = {'Content-type': 'application/json'} response = requests.post(url, data=json.dumps(data), headers=headers) return response except requests.exceptions.RequestException as e: print(e) def main(): user_upload_url = os.environ['USER_UPLOAD_URL'] path = os.environ['CSV_PATH'] users = get_users_from_csv(path) for user in users: data = vars(user) response = post_request(user_upload_url, data) if response.status_code == 200: print("User {} uploaded successfully".format(user.name)) # convert response.content bytes to json userId = json.loads(response.text)['id'] print("User {} id: {}".format(user.name, userId)) def get_users_from_csv(path): users = [] with open(path, newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: name = row['firstname'] + " " + row['lastname'] user = User(row['email'], row['password'], row['password'], name) users.append(user) return users if __name__ == '__main__': main() ================================================ FILE: kubelab-score/score.csv ================================================ userId,username,email,exercise_session_id,start_time,end_time ================================================ FILE: kubelab-score/score_calculation.py ================================================ import csv import json import os import requests class User: def __init__(self, id, name, email, username): self.id = id self.name = name self.email = email self.username = username class ExerciseSession: def __init__(self, id, userId, startTime, endTime): self.id = id self.userId = userId self.startTime = startTime self.endTime = endTime def get_request(url): try: headers = {'Content-type': 'application/json'} response = requests.get(url, headers=headers) return response except requests.exceptions.RequestException as e: print(e) def main(): exercises_sessions_url = os.environ['EXERCISE_SESSIONS_URL'] users_url = os.environ['USERS_URL'] csv_path = os.environ['CSV_PATH'] exercise_sessions = get_exercise_sessions_from_exercise_sessions_url( exercises_sessions_url) users = get_users_from_users_url(users_url) # parse the data into a csv file with open(csv_path, mode='w') as file: writer = csv.writer(file) writer.writerow(["userId", "username", "email", "exercise_session_id", "start_time", "end_time"]) for user in users: for exercise_session in exercise_sessions: if user.id == exercise_session.userId: writer.writerow([user.id, user.username, user.email, exercise_session.id, exercise_session.startTime, exercise_session.endTime]) def get_users_from_users_url(users_url): users = [] response = get_request(users_url) if response.status_code == 200: users_json = json.loads(response.text)['items'] print(users_json) for user_json in users_json: email = user_json.get('email') username = user_json.get('username') user = User(user_json['id'], user_json['name'], email, username) users.append(user) return users def get_exercise_sessions_from_exercise_sessions_url(exercises_sessions_url): exercise_sessions = [] response = get_request(exercises_sessions_url) if response.status_code == 200: exercise_sessions_json = json.loads(response.text)['items'] print(exercise_sessions_json) for exercise_session_json in exercise_sessions_json: exercise_session = ExerciseSession( exercise_session_json['id'], exercise_session_json['user'], exercise_session_json['startTime'], exercise_session_json['endTime']) exercise_sessions.append(exercise_session) return exercise_sessions if __name__ == '__main__': main() ================================================ FILE: kubelab-ui/.dockerignore ================================================ .git .svelte-kit build node_modules ================================================ FILE: kubelab-ui/.env.example ================================================ export AGENT_INGRESS_CLASS=nginx-external export ALLOWED_HOSTS=kubelab.ch export LOCAL=true ================================================ FILE: kubelab-ui/.eslintignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml package-lock.json yarn.lock ================================================ FILE: kubelab-ui/.eslintrc.cjs ================================================ module.exports = { root: true, parser: "@typescript-eslint/parser", extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], plugins: ["svelte3", "@typescript-eslint"], ignorePatterns: ["*.cjs"], overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }], settings: { "svelte3/typescript": () => require("typescript") }, parserOptions: { sourceType: "module", ecmaVersion: 2020 }, env: { browser: true, es2017: true, node: true } }; ================================================ FILE: kubelab-ui/.gitignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* ================================================ FILE: kubelab-ui/.npmrc ================================================ engine-strict=true ================================================ FILE: kubelab-ui/.prettierignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml package-lock.json yarn.lock ================================================ FILE: kubelab-ui/.prettierrc ================================================ { "useTabs": false, "singleQuote": false, "trailingComma": "none", "printWidth": 100, "plugins": ["prettier-plugin-svelte"], "pluginSearchDirs": ["."], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] } ================================================ FILE: kubelab-ui/.vscode/settings.json ================================================ { "tailwindCSS.classAttributes": [ "class", "accent", "active", "background", "badge", "border", "borderColor", "borderWidth", "button", "buttonBack", "buttonClasses", "buttonComplete", "buttonNext", "buttonTextNext", "buttonTextPrevious", "chips", "color", "cursor", "display", "element", "fill", "flex", "gap", "gridColumns", "height", "hover", "invalid", "justify", "meter", "padding", "regionBody", "regionCaption", "regionCaret", "regionCone", "regionContent", "regionControl", "regionDefault", "regionFoot", "regionHead", "regionHeader", "regionIcon", "regionInterface", "regionInterfaceText", "regionLabel", "regionLead", "regionLegend", "regionList", "regionNavigation", "regionPage", "regionPanel", "regionRowHeadline", "regionRowMain", "regionTrail", "rounded", "select", "shadow", "slotDefault", "slotFooter", "slotHeader", "slotLead", "slotMessage", "slotMeta", "slotPageContent", "slotPageFooter", "slotPageHeader", "slotSidebarLeft", "slotSidebarRight", "slotTrail", "space", "spacing", "text", "track", "width" ] } ================================================ FILE: kubelab-ui/Dockerfile ================================================ FROM node:lts-slim as build WORKDIR /app COPY package*.json ./ RUN rm -rf node_modules RUN rm -rf build COPY . . RUN npm install RUN npm run build FROM node:lts-slim as run WORKDIR /app COPY --from=build /app/package.json ./package.json COPY --from=build /app/build ./build RUN npm install --production EXPOSE 3000 ENTRYPOINT [ "npm", "run", "start" ] ================================================ FILE: kubelab-ui/README.md ================================================ # KubeLab UI ## Setup ```bash npm install # install dependencies npm run build # compile frontend ``` The above produces `build` output directory which is then used by PocketBase to serve the frontend of your app. ## Live Development ```bash # start the backend, if not already running ... source .env npm run dev:backend # and then start the frontend ... npm run dev ``` Now visit http://localhost:5173 (ui) or http://localhost:8090 (pb) ## Generated Types The file `generated-types.ts` contains TypeScript definitions of `Record` types mirroring the fields in your database collections. But it needs to be regenerated every time you modify the schema. This can be done by simply running the `typegen` script in the frontend's `package.json`. So remember to run `npm run typegen` after every schema change. ## Building To create a production version of your app (static HTML/JS app): _NOTE_: The build below will fail unless the backend has at least 1 post created. So please create a "posts" record using the app UI or the admin UI before running build below. ```bash # compile frontend npm run build # and then serve it with pocketbase npm run backend ``` The above generates output in the `build` folder. Now you can serve production compiled version of the frontend using the backend (with `--publicDir ../frontend/build`), any static file web server, or `npm preview`. ================================================ FILE: kubelab-ui/next-env.d.ts ================================================ /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: kubelab-ui/package.json ================================================ { "name": "ui", "version": "1.0.0", "private": true, "scripts": { "dev": "vite dev", "dev:backend": "cd ../kubelab-backend && modd", "backend": "cd ../kubelab-backend && ./pocketbase serve --publicDir=../kubelab-ui/build", "build": "vite build", "build:backend": "cd ../kubelab-backend && go build", "typegen": "pocketbase-typegen --db ../kubelab-backend/pb_data/data.db --out ./src/lib/pocketbase/generated-types.ts", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { "@sveltejs/adapter-auto": "^2.1.1", "@sveltejs/kit": "^1.25.2", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/typography": "^0.5.13", "@types/marked": "^5.0.0", "@types/node": "^20.14.10", "@types/prismjs": "^1.26.4", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "autoprefixer": "^10.4.13", "daisyui": "^4.10.2", "eslint": "^9.8.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.39.3", "eslint-plugin-svelte3": "^4.0.0", "flowbite": "^2.3.0", "flowbite-svelte": "^0.46.0", "flowbite-svelte-icons": "^0.4.4", "mdsvex": "^0.11.0", "pocketbase": "^0.21.2", "postcss": "^8.4.38", "prettier": "^3.3.1", "prettier-plugin-svelte": "^3.2.6", "svelte": "^4.2.18", "svelte-check": "^3.6.9", "svelte-preprocess": "^6.0.2", "svelte-splitpanes": "^0.7.14", "tailwind-scrollbar": "^3.0.4", "tailwindcss": "^3.4.3", "tslib": "^2.6.3", "typescript": "^5.4.5", "vite": "^4.0.0" }, "type": "module", "dependencies": { "@floating-ui/dom": "^1.2.8", "@picocss/pico": "^2.0.6", "@sveltejs/adapter-node": "^1.2.1", "@sveltejs/adapter-static": "^2.0.3", "@webcontainer/api": "^1.1.9", "chart.js": "^4.3.0", "lucide-svelte": "^0.265.0", "marked": "^5.0.4", "pocketbase-typegen": "^1.2.1", "prismjs": "^1.29.0", "svelte-confetti": "^1.4.0", "svelte-french-toast": "^1.2.0", "svelte-icons-pack": "^2.1.0", "svelte-local-storage-store": "^0.6.4", "svelte-markdown": "^0.4.1", "xterm": "^5.3.0", "xterm-addon-fit": "^0.7.0" } } ================================================ FILE: kubelab-ui/playwright.config.ts ================================================ import type { PlaywrightTestConfig } from "@playwright/test"; const config: PlaywrightTestConfig = { webServer: { command: "npm run build && npm run preview", port: 4173 }, testDir: "tests" }; export default config; ================================================ FILE: kubelab-ui/postcss.config.cjs ================================================ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer")] }; ================================================ FILE: kubelab-ui/src/app.css ================================================ a { text-decoration: none !important; } @tailwind base; @tailwind components; @tailwind utilities; h1 { font-size: 2rem; font-weight: 700; line-height: 1.2; margin-bottom: 0.5em; } h2 { font-size: 1.5rem; font-weight: 600; line-height: 1.25; margin-bottom: 0.5em; } h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.25; margin-bottom: 0.5em; } .btn { @apply border-2; } .splitpanes__splitter { background-color: darkgray !important; position: relative; opacity: 0; } .splitpanes--horizontal > .splitpanes__splitter { border-top: none !important; } .splitpanes--vertical > .splitpanes__splitter { border-left: none !important; } ================================================ FILE: kubelab-ui/src/app.d.ts ================================================ // See https://kit.svelte.dev/docs/types#app // for information about these interfaces // and what to do when importing types declare namespace App { // interface Locals {} // interface PageData {} // interface Error {} // interface Platform {} } ================================================ FILE: kubelab-ui/src/app.html ================================================ %sveltekit.head%
%sveltekit.body%
================================================ FILE: kubelab-ui/src/app.postcss ================================================ /*place global styles here */ /* html, body { @apply h-full overflow-hidden; } */ html, body { @apply h-full scrollbar-none; } @font-face { /* Reference name */ font-family: "Inter"; /* For multiple files use commas, ex: url(), url(), ... */ src: url("/fonts/Inter-VariableFont_slnt,wght.ttf"); } ================================================ FILE: kubelab-ui/src/hooks.client.ts ================================================ import { client, currentUser } from "$lib/pocketbase"; client.authStore.loadFromCookie(document.cookie); client.authStore.onChange(() => { currentUser.set(client.authStore.model); document.cookie = client.authStore.exportToCookie({ httpOnly: false }); }); ================================================ FILE: kubelab-ui/src/lib/components/Console.svelte ================================================
================================================ FILE: kubelab-ui/src/lib/components/base/BackButton.svelte ================================================ ================================================ FILE: kubelab-ui/src/lib/components/base/Badges.svelte ================================================
{text}
================================================ FILE: kubelab-ui/src/lib/components/base/Card.svelte ================================================
================================================ FILE: kubelab-ui/src/lib/components/base/Desktop.svelte ================================================ {#key $darkTheme}
{/key} ================================================ FILE: kubelab-ui/src/lib/components/base/Nav.svelte ================================================ ================================================ FILE: kubelab-ui/src/lib/components/base/PlaceholderComponent.svelte ================================================
================================================ FILE: kubelab-ui/src/lib/components/base/SideOver.svelte ================================================ ================================================ FILE: kubelab-ui/src/lib/components/base/ToggleConfetti.svelte ================================================ {#if active}
{/if}
================================================ FILE: kubelab-ui/src/lib/components/dashboard/Chart.svelte ================================================ ================================================ FILE: kubelab-ui/src/lib/components/dashboard/RunningExercises.svelte ================================================
Running Exercises
View all
{#if running_exercises.length === 0}
No running exercises
{/if}
{#each running_exercises as running_exercise}
{running_exercise.expand.exercise.title}
{/each}
================================================ FILE: kubelab-ui/src/lib/components/dashboard/data.ts ================================================ export const data: any = { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [ { label: 'My First dataset', fill: true, lineTension: 0.3, backgroundColor: 'rgba(225, 204,230, .3)', borderColor: 'rgb(205, 130, 158)', borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0.0, borderJoinStyle: 'miter', pointBorderColor: 'rgb(205, 130,1 58)', pointBackgroundColor: 'rgb(255, 255, 255)', pointBorderWidth: 10, pointHoverRadius: 5, pointHoverBackgroundColor: 'rgb(0, 0, 0)', pointHoverBorderColor: 'rgba(220, 220, 220,1)', pointHoverBorderWidth: 2, pointRadius: 1, pointHitRadius: 10, data: [65, 59, 80, 81, 56, 55, 40], }, { label: 'My Second dataset', fill: true, lineTension: 0.3, backgroundColor: 'rgba(184, 185, 210, .3)', borderColor: 'rgb(35, 26, 136)', borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0.0, borderJoinStyle: 'miter', pointBorderColor: 'rgb(35, 26, 136)', pointBackgroundColor: 'rgb(255, 255, 255)', pointBorderWidth: 10, pointHoverRadius: 5, pointHoverBackgroundColor: 'rgb(0, 0, 0)', pointHoverBorderColor: 'rgba(220, 220, 220, 1)', pointHoverBorderWidth: 2, pointRadius: 1, pointHitRadius: 10, data: [28, 48, 40, 19, 86, 27, 90], }, ], }; ================================================ FILE: kubelab-ui/src/lib/components/labs/Exercise.svelte ================================================ {#if this_exercise}
{this_exercise.title}
{#if $sidebar_lab_session.clusterRunning} {:else}

Lab not running

{/if}
Status
{#if this_exercise_session.agentRunning} {:else} {/if} {this_exercise_session.agentRunning ? "Running" : "Stopped"}
Done
{#if this_exercise_session.endTime && !this_exercise_session.agentRunning} {getDeltaTime(this_exercise_session.startTime, this_exercise_session.endTime)} {:else} not yet {/if}
{/if} ================================================ FILE: kubelab-ui/src/lib/components/labs/Lab.svelte ================================================ {#if this_lab_session} {#key this_lab_session}
{this_lab.title}
{#if this_lab_session.clusterRunning} {/if}
Status
{#if this_lab_session.clusterRunning} {:else} {/if} {this_lab_session.clusterRunning ? "Running" : "Stopped"}
Time
{#if this_lab_session.clusterRunning && this_lab_session.startTime} since {getTimeAgo(this_lab_session.startTime)} {:else if this_lab_session.endTime && !this_lab_session.clusterRunning} {getTimeAgo(this_lab_session.endTime)} ago {/if}
Done Exercises
{#if getDoneExercises().length == this_exercises.length}
{:else}
{/if}
{getDoneExercises().length} / {this_exercises.length}
{/key} {/if} ================================================ FILE: kubelab-ui/src/lib/components/landingpage/Companies.svelte ================================================
{#if companies.length > 0} {#each companies as company} {company.name} {/each} {/if}

================================================ FILE: kubelab-ui/src/lib/components/landingpage/Cta.svelte ================================================