diff --git a/.circleci/config.yml b/.circleci/config.yml index b9d61787a8..b7ce92e663 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -528,6 +528,11 @@ jobs: docker: - image: "docker/tilt-releaser@sha256:<< pipeline.parameters.tilt-releaser-version >>" resource_class: large + parameters: + all_architectures: + description: "Whether we should build all architectures or not" + type: boolean + default: false steps: - checkout @@ -552,25 +557,32 @@ jobs: - << pipeline.parameters.cli-build-cache-key-prefix >>-{{ checksum "cli/cli/go.sum" }} - run: | - cli/cli/scripts/build.sh true + cli/cli/scripts/build.sh << parameters.all_architectures >> - - save_cache: - key: << pipeline.parameters.cli-build-cache-key-prefix >>-{{ checksum "cli/cli/go.sum" }} - paths: - - "/go/pkg/mod" # Go module cache for the Goreleaser image, as reported by "go env GOMODCACHE" - - "/root/.cache/go-build" # Go build cache for the Goreleaser image, as reported by "go env GOCACHE" - - persist_to_workspace: - root: . - paths: - - "./<< pipeline.parameters.cli-dist-home-relative-dirpath >>" - - "./<< pipeline.parameters.startosis-test-script-file-relative-path >>" - - "./<< pipeline.parameters.starlark-test-taints-and-tolerations >>" + # only save cache and persist to workspace the CLI built for just the CircleCI architecture + - when: + condition: + equal: [ false, << parameters.all_architectures >> ] + steps: + - save_cache: + key: << pipeline.parameters.cli-build-cache-key-prefix >>-{{ checksum "cli/cli/go.sum" }} + paths: + - "/go/pkg/mod" # Go module cache for the Goreleaser image, as reported by "go env GOMODCACHE" + - "/root/.cache/go-build" # Go build cache for the Goreleaser image, as reported by "go env GOCACHE" + + - persist_to_workspace: + root: . + paths: + - "./<< pipeline.parameters.cli-dist-home-relative-dirpath >>" + - "./<< pipeline.parameters.startosis-test-script-file-relative-path >>" + - "./<< pipeline.parameters.starlark-test-taints-and-tolerations >>" build_golang_testsuite: executor: ubuntu_vm parameters: <<: *param_cli_cluster_backend + parallelism: 4 steps: - checkout @@ -604,7 +616,7 @@ jobs: equal: [ "kubernetes", << parameters.cli-cluster-backend >> ] steps: - run: | - if ! ./internal_testsuites/golang/scripts/test.sh kubernetes; then + if ! ./internal_testsuites/golang/scripts/test.sh kubernetes true; then touch /tmp/testsuite-failed fi @@ -615,7 +627,7 @@ jobs: equal: [ "docker", << parameters.cli-cluster-backend >> ] steps: - run: | - if ! ./internal_testsuites/golang/scripts/test.sh; then + if ! ./internal_testsuites/golang/scripts/test.sh docker true; then touch /tmp/testsuite-failed fi @@ -766,9 +778,9 @@ jobs: false fi - run: - name: "Verify Kurtosis cleaned up all its volumes (except for the log storage volume, so we can persist service logs)" + name: "Verify Kurtosis cleaned up all its volumes (except for the log storage and github auth storage volumes)" command: | - if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l ) -eq 1 ]; then + if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l ) -eq 2 ]; then docker volume ls false fi @@ -881,6 +893,10 @@ jobs: - run: "${KURTOSIS_BINPATH} files storeservice test-enclave test1 /usr/local/apache2/conf/httpd.conf --name stored-file" - run: "${KURTOSIS_BINPATH} files download test-enclave stored-file ." + # Check Dump Contains files + - run: "${KURTOSIS_BINPATH} enclave dump test-enclave test-dump" + - run: "ls test-dump/files/rendered-file" + # Module inside an enclave - run: "${KURTOSIS_BINPATH} enclave ls" - run: "${KURTOSIS_BINPATH} enclave inspect test-enclave" @@ -1001,9 +1017,9 @@ jobs: false fi - run: - name: "Verify Kurtosis cleaned up all its volumes (except for the log storage volume, so we can persist service logs)" + name: "Verify Kurtosis cleaned up all its volumes (except for the log storage and github auth storage volumes)" command: | - if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l) -eq 1 ]; then + if ! [ $(docker volume ls | grep -v kurtosis-logs-collector-vol | grep -v kurtosis-logs-db-vol | tail -n+2 | wc -l) -eq 2 ]; then docker volume ls false fi @@ -1470,6 +1486,11 @@ workflows: - build_cli: <<: *filters_ignore_main + - build_cli: + all_architectures: true + name: "Check if CLI builds for all os and arch pairs" + <<: *filters_ignore_main + - test_enclave_manager_web_ui: name: "Test Basic Web UI Functionality in Docker" context: diff --git a/.github/ISSUE_TEMPLATE/docs-issue.yml b/.github/ISSUE_TEMPLATE/docs-issue.yml index e0009f4924..c3ef7ab069 100644 --- a/.github/ISSUE_TEMPLATE/docs-issue.yml +++ b/.github/ISSUE_TEMPLATE/docs-issue.yml @@ -1,9 +1,7 @@ name: "Docs Issue" description: Report an issue in our documentation labels: ["docs"] -assignees: - - leeederek - - thylocine33 +assignees: [] body: - type: input id: url-link diff --git a/CHANGELOG.md b/CHANGELOG.md index ced0748295..8555e6bcc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,82 @@ # Changelog +## [0.86.21](https://github.com/kurtosis-tech/kurtosis/compare/0.86.20...0.86.21) (2024-02-15) + + +### Bug Fixes + +* Core image builds for arm64 under CI ([#2149](https://github.com/kurtosis-tech/kurtosis/issues/2149)) ([807ddae](https://github.com/kurtosis-tech/kurtosis/commit/807ddae12b9274819f53160a8771da7541f4a4c1)) + +## [0.86.20](https://github.com/kurtosis-tech/kurtosis/compare/0.86.19...0.86.20) (2024-02-14) + + +### Features + +* support `run_sh` and `exec` in enclave builder ([#2158](https://github.com/kurtosis-tech/kurtosis/issues/2158)) ([f784eaf](https://github.com/kurtosis-tech/kurtosis/commit/f784eaf7a24ae282aa470d22e6a9ad721d04cc05)) + +## [0.86.19](https://github.com/kurtosis-tech/kurtosis/compare/0.86.18...0.86.19) (2024-02-09) + + +### Features + +* build nix image ([#2132](https://github.com/kurtosis-tech/kurtosis/issues/2132)) ([0eae9fc](https://github.com/kurtosis-tech/kurtosis/commit/0eae9fc942605f14a04554752a0c0dca7b02b1f7)) +* enclave builder tweaks ([#2142](https://github.com/kurtosis-tech/kurtosis/issues/2142)) ([aaf64ca](https://github.com/kurtosis-tech/kurtosis/commit/aaf64ca6085b13b8d70c15114128455277b98e31)) +* enforce enclave builder validation ([#2144](https://github.com/kurtosis-tech/kurtosis/issues/2144)) ([5dcdd9e](https://github.com/kurtosis-tech/kurtosis/commit/5dcdd9e00c0e4f243d2b2e45e752a8c3483cec97)) + + +### Bug Fixes + +* emui yaml editor populating with values ([#2079](https://github.com/kurtosis-tech/kurtosis/issues/2079)) ([9bd26a8](https://github.com/kurtosis-tech/kurtosis/commit/9bd26a8f855a4c2ac7d5fb2f73c756079f9735a9)) + +## [0.86.18](https://github.com/kurtosis-tech/kurtosis/compare/0.86.17...0.86.18) (2024-02-09) + + +### Features + +* add files in enclave dump ([#2136](https://github.com/kurtosis-tech/kurtosis/issues/2136)) ([0525d9f](https://github.com/kurtosis-tech/kurtosis/commit/0525d9fbb12561cf4b5a83814baf05d8d1682274)) + + +### Bug Fixes + +* node selector validation ([#2141](https://github.com/kurtosis-tech/kurtosis/issues/2141)) ([0fee848](https://github.com/kurtosis-tech/kurtosis/commit/0fee84881f83d0b5a911f511a9fcdd889a0b8784)) + +## [0.86.17](https://github.com/kurtosis-tech/kurtosis/compare/0.86.16...0.86.17) (2024-02-08) + + +### Features + +* experimental enclave building in the EMUI ([#2137](https://github.com/kurtosis-tech/kurtosis/issues/2137)) ([8a26a6c](https://github.com/kurtosis-tech/kurtosis/commit/8a26a6cc38e832f92e552b5f32fd6a5c73cf2869)) +* support setting node selectors for user services ([#2135](https://github.com/kurtosis-tech/kurtosis/issues/2135)) ([e176a3a](https://github.com/kurtosis-tech/kurtosis/commit/e176a3aa23e9070ba52f0e8a965191a0eb3cc6b4)) + + +### Bug Fixes + +* files artifacts expander image debug tag ([#2119](https://github.com/kurtosis-tech/kurtosis/issues/2119)) ([b3f7edd](https://github.com/kurtosis-tech/kurtosis/commit/b3f7eddc9dcb9e02af7c116c5d86dd97dd74f8c2)) + +## [0.86.16](https://github.com/kurtosis-tech/kurtosis/compare/0.86.15...0.86.16) (2024-02-07) + + +### Features + +* kurtosis github login ([#2113](https://github.com/kurtosis-tech/kurtosis/issues/2113)) ([2f0d638](https://github.com/kurtosis-tech/kurtosis/commit/2f0d638c688828de3eb781b11d696883807357ba)) + + +### Bug Fixes + +* Remove Derek and Karla as default assignees for docs issues ([#2129](https://github.com/kurtosis-tech/kurtosis/issues/2129)) ([2f1eecc](https://github.com/kurtosis-tech/kurtosis/commit/2f1eecc9a935ac19cf4de0fbc26ab852a5248b7f)) + +## [0.86.15](https://github.com/kurtosis-tech/kurtosis/compare/0.86.14...0.86.15) (2024-02-07) + + +### Features + +* Enclave connect ([#2117](https://github.com/kurtosis-tech/kurtosis/issues/2117)) ([d33de7b](https://github.com/kurtosis-tech/kurtosis/commit/d33de7bebb0e997c8b2fd93872a1b676dc3519e7)) + + +### Bug Fixes + +* Fix the enclave connect button and modal help string in the EM UI ([#2127](https://github.com/kurtosis-tech/kurtosis/issues/2127)) ([a11fb4c](https://github.com/kurtosis-tech/kurtosis/commit/a11fb4c701e7ed8700c7812559aebb03d5d3846b)) + ## [0.86.14](https://github.com/kurtosis-tech/kurtosis/compare/0.86.13...0.86.14) (2024-02-06) diff --git a/LICENSE.md b/LICENSE.md index ca47ad8f4e..22c5df34cb 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -3,7 +3,7 @@ Business Source License 1.1 Parameters Licensor: Kurtosis Technologies, Inc. -Licensed Work: Kurtosis 0.86.14 +Licensed Work: Kurtosis 0.86.21 The Licensed Work is (c) 2024 Kurtosis Technologies, Inc. Additional Use Grant: You may make use of the Licensed Work, provided that you may not use the Licensed Work for an Environment Orchestration Service. @@ -12,7 +12,7 @@ you may not use the Licensed Work for an Environment Orchestration Service. allows third parties (other than your employees and contractors) to create distributed system environments. -Change Date: 2028-02-06 +Change Date: 2028-02-15 Change License: Apache 2.0 (Apache License, Version 2.0) diff --git a/README.md b/README.md index 7b6da21925..a2a01ce152 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,40 @@ +[![Follow us on X, formerly Twitter](https://img.shields.io/twitter/follow/KurtosisTech?style=social)](https://twitter.com/Kurtosistech) +[![Number of GitHub stars](https://img.shields.io/github/stars/kurtosis-tech/kurtosis)](https://github.com/kurtosis-tech/kurtosis/stargazers) + ---- What is Kurtosis? ================= -[Kurtosis](https://www.kurtosis.com) is a platform for packaging and launching environments of containerized services ("distributed applications") with a focus on approachability for the average developer. What Docker did for shipping binaries, Kurtosis aims to do even better for distributed applications. + +Have you ever tried to build on top of a colleague's work, or contribute to an open source project, just to get stuck on the first steps of spinning up a stack to play with? [Kurtosis](https://www.kurtosis.com) handles the complexity of spinning up ephemeral dev or test stacks so you can focus on developing, not configuring. Kurtosis is formed of: +- A packaging system for distributing backend stack definitions, which can run on docker or on kubernetes +- A runtime with a per-stack file management system for reproducibly initializing the state of your stack +- A set of tools to enable devs to interact with their stacks, like they do on docker or k8s -- A language for declaring a distributed application in Python syntax ([Starlark](https://github.com/google/starlark-go/blob/master/doc/spec.md)) -- A packaging system for sharing and reusing distributed application components -- A runtime that makes a Kurtosis app Just Work, independent of whether it's running on Docker or Kubernetes, local or in the cloud -- A set of tools to ease common distributed app development needs (e.g. a log aggregator to ease log-diving, automatic port-forwarding to ease connectivity, a `kurtosis service shell` command to ease container filesystem exploration, etc.) +Why use Kurtosis? +========================= -Why should I use Kurtosis? -========================== -Kurtosis shines when creating, working with, and destroying self-contained distributed application environments. Currently, our users report this to be most useful when: +Kurtosis is best for: -- You're developing on your application and you need to rapidly iterate on it -- You want to try someone's containerized service or distributed application without setting up an environment, dependencies, etc. -- You want to spin up your distributed application in ephemeral environments as part of your integration tests -- You want to ad-hoc test your application on a big cloud cluster -- You're the author of a containerized service or distributed application and you want to give your users a one-liner to try it -- You want to get an instance of your application running in the cloud without provisioning or administering a Kubernetes cluster +- Reusing the logic in your stack definitions for all of: local dev, scheduled testing in CI, and ad-hoc larger-scale testing on k8s clusters +- Giving other devs a way to spin up your application, and commonly used variations of it, with one-liners, via Kurtosis' packaging and parameterization systems +- Handling complex setup logic in your backend stack, like passing arbitrary data between services as they start up, and enforcing arbitrary wait conditions -If you're in web3, we have even more specific web3 usecases [here](https://web3.kurtosis.com). +How is Kurtosis different than Docker Compose or Helm? +========================== -Check out an introductory demo video here: +Kurtosis operates at a level higher than Docker Compose or Helm, and produces stacks running on either of the underlying engines (the Docker engine, or Kubernetes). +Because of this additional layer of abstraction, we are able to introduce several features to improve the experience of spinning up ephemeral stacks: - +- A per-stack file management system that enables portable state initialization for dev or test stacks +- Stack-level parameterizability; users have a powerful and flexible way (beyond messing with env vars) to affect modifications in their stacks +- First-class plug-and-play composability; it's expected for users to import stack definitions into larger stacks, and this experience is optimized +- The ability to get all of the above, but running over _either_ the docker engine or k8s, at your election How do I get going? =================== @@ -53,11 +58,7 @@ If you have an issue or feature request, we'd love to hear about it through one ### Going further -To try more Kurtosis packages just like this one, check out the [`awesome-kurtosis` repo][awesome-kurtosis] or one of these packages: - -- [Ethereum](https://github.com/kurtosis-tech/ethereum-package): fully functional private Ethereum network in Kurtosis with Flashbots MEV-boost, any EL and CL client combination, and a collection of network monitoring tools. -- [DIVE](https://github.com/HugoByte/DIVE): A CLI + Kurtosis package by [Hugobyte](https://hugobyte.com) for the ICON ecosystem that can spin up EVM, Cosmos, or JVM networks with a bridge between them. -- [NEAR](https://github.com/kurtosis-tech/near-package): A private NEAR network in Kurtosis. +To try more Kurtosis packages just like this one, check out the [`awesome-kurtosis` repo][awesome-kurtosis]! To learn about how to write Kurtosis packages, check out our [quickstart][quickstart-reference]. @@ -67,40 +68,6 @@ To see where we're going with the product, check out the roadmap [here](https:// Got more questions? Drop them in our [Github Discussions](https://github.com/kurtosis-tech/kurtosis/discussions/new?category=q-a) where we, or other community members, can help answer. -Why Kurtosis over Compose, Helm, or Terraform? -============================================== -These tools have been around for over a decade, yet most developers still struggle to build distributed applications. Why? In a sentence: building distributed applications is hard, and these tools still haven't made it easy enough for the average developer. - -Some of our observations: - -- No tool works across the whole software lifecycle: Compose is oriented around quick local environments rather than Prod environments, while Helm and Terraform are the opposite. This often means a dedicated DevOps team handles Prod deployment, leading to the same "throw it across the wall" problem the DevOps movement was founded around. -- Compose, Helm, and Terraform use fully declarative paradigms, making difficult the sequential "first this, then this" logic necessary for many prototyping workflows. -- The inherently declarative nature of all three make [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) difficult, leading to frequent copy-pasting. -- All three tend to leave resources hanging around that the developer needs to manually clean up. -- Compose and Helm favor "run it and see what happens" over validation & error-checking, resulting in debugging time and longer dev cycles. -- A significant percentage of developers don't understand how Docker works, and [most don't understand Kubernetes or Terraform][stackoverflow-2022-developer-survey--other-tools]. - -Here's what our users tell us they like about Kurtosis: - -- **It's understandable:** you write code in Python syntax, and you get your distributed application the other side. Variables and functions keep your code DRY. -- **It's portable:** your application runs with a one-liner independent of where you run it. You can build your application on your local Docker, and in seconds get the same thing on your friend's laptop or a Kubernetes cluster in the cloud. -- **It can handle sequential dependencies:** for example, "first generate these files, then use them when starting a service". -- **It's reliable and reproducible:** Kurtosis started as a testing tool and is built to be safe: deterministic execution order, validation to catch errors before runtime, built-in support for inter-service dependencies and readiness checks, etc. Your distributed app should spin up the same way, every time. -- **It abstracts away complexity while being configurable:** instantiating a distributed application is as simple as calling its function with the parameters you want. For example, instantiating a Postgres server with modified username and password: - - On the CLI... - ```bash - kurtosis run github.com/kurtosis-tech/postgres-package '{"user": "bobmarley", "password": "buffalosoldier"}' - ``` - - Inside an environment definition... - ```python - postgres = import_module("github.com/kurtosis-tech/postgres-package/main.star") - - def run(plan): - postgres.run(plan, user = "bobmarley", password = "buffalosoldier") - ``` - Contributing to Kurtosis ======================== diff --git a/api/golang/kurtosis_version/kurtosis_version.go b/api/golang/kurtosis_version/kurtosis_version.go index 547be4dc6b..39846c1700 100644 --- a/api/golang/kurtosis_version/kurtosis_version.go +++ b/api/golang/kurtosis_version/kurtosis_version.go @@ -9,6 +9,6 @@ const ( // !!!!!!!!!!! DO NOT UPDATE! WILL BE MANUALLY UPDATED DURING THE RELEASE PROCESS !!!!!!!!!!!!!!!!!!!!!! // This is necessary so that Kurt Core consumers will know if they're compatible with the currently-running // API container - KurtosisVersion = "0.86.14" + KurtosisVersion = "0.86.21" // !!!!!!!!!!! DO NOT UPDATE! WILL BE MANUALLY UPDATED DURING THE RELEASE PROCESS !!!!!!!!!!!!!!!!!!!!!! ) diff --git a/api/rust/Cargo.toml b/api/rust/Cargo.toml index 266796ed0f..2bb54ac4f9 100644 --- a/api/rust/Cargo.toml +++ b/api/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kurtosis-sdk" -version = "0.86.14" +version = "0.86.21" license = "BUSL-1.1" description = "Rust SDK for Kurtosis" edition = "2021" diff --git a/api/typescript/package.json b/api/typescript/package.json index 8d4cd51870..48524be958 100644 --- a/api/typescript/package.json +++ b/api/typescript/package.json @@ -1,7 +1,7 @@ { "name": "kurtosis-sdk", "//": "NOTE: DO NOT UPDATE THIS VERSION MANUALLY - IT WILL BE UPDATED DURING THE RELEASE PROCESS!", - "version": "0.86.14", + "version": "0.86.21", "main": "./build/index", "description": "This repo contains a Typescript client for communicating with the Kurtosis Engine server, which is responsible for creating, managing and destroying Kurtosis Enclaves.", "types": "./build/index", diff --git a/api/typescript/src/kurtosis_version/kurtosis_version.ts b/api/typescript/src/kurtosis_version/kurtosis_version.ts index 17585dfeb6..25ea10e87b 100644 --- a/api/typescript/src/kurtosis_version/kurtosis_version.ts +++ b/api/typescript/src/kurtosis_version/kurtosis_version.ts @@ -1,5 +1,5 @@ // !!!!!!!!!!! DO NOT UPDATE! WILL BE MANUALLY UPDATED DURING THE RELEASE PROCESS !!!!!!!!!!!!!!!!!!!!!! // This is necessary so that Kurt Core consumers (e.g. modules) will know if they're compatible with the currently-running // API container -export const KURTOSIS_VERSION: string = "0.86.14" +export const KURTOSIS_VERSION: string = "0.86.21" // !!!!!!!!!!! DO NOT UPDATE! WILL BE MANUALLY UPDATED DURING THE RELEASE PROCESS !!!!!!!!!!!!!!!!!!!!!! diff --git a/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command/engine_consuming_kurtosis_command.go b/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command/engine_consuming_kurtosis_command.go index a909a211e3..b4d3c12b2e 100644 --- a/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command/engine_consuming_kurtosis_command.go +++ b/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command/engine_consuming_kurtosis_command.go @@ -186,7 +186,7 @@ func (cmd *EngineConsumingKurtosisCommand) getSetupFunc() func(context.Context) // commands only access the Kurtosis APIs, we can remove this. kurtosisBackend := engineManager.GetKurtosisBackend() - engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize) + engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating a new Kurtosis engine client") } diff --git a/cli/cli/command_str_consts/command_str_consts.go b/cli/cli/command_str_consts/command_str_consts.go index 2f39bc320d..bf5ec0416e 100644 --- a/cli/cli/command_str_consts/command_str_consts.go +++ b/cli/cli/command_str_consts/command_str_consts.go @@ -76,6 +76,11 @@ const ( PortCmdStr = "port" PortPrintCmdStr = "print" WebCmdStr = "web" + GitHubCmdStr = "github" + GitHubLoginCmdStr = "login" + GitHubLogoutCmdStr = "logout" + GitHubTokenCmdStr = "token" + GitHubStatusCmdStr = "status" ) // TODO: added constant error message here, can we move to another file later. diff --git a/cli/cli/commands/cluster/set/set.go b/cli/cli/commands/cluster/set/set.go index 208b8c6fd5..6600fc1233 100644 --- a/cli/cli/commands/cluster/set/set.go +++ b/cli/cli/commands/cluster/set/set.go @@ -123,7 +123,7 @@ func run(ctx context.Context, flags *flags.ParsedFlags, args *args.ParsedArgs) e // we only start in a stopped state, the idempotent visitor gets stuck with engine_manager.EngineStatus_ContainerRunningButServerNotResponding if the gateway isn't running // TODO - fix the idempotent starter longer term if engineStatus == engine_manager.EngineStatus_Stopped { - _, engineClientCloseFunc, err := engineManagerNewCluster.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize) + _, engineClientCloseFunc, err := engineManagerNewCluster.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride) if err != nil { return stacktrace.Propagate(err, "Engine could not be started after cluster was updated. Its status can be retrieved "+ "running 'kurtosis %s %s' and it can potentially be started running 'kurtosis %s %s'", diff --git a/cli/cli/commands/enclave/add/add.go b/cli/cli/commands/enclave/add/add.go index e9cbb57c0a..9d0e628e5a 100644 --- a/cli/cli/commands/enclave/add/add.go +++ b/cli/cli/commands/enclave/add/add.go @@ -119,8 +119,7 @@ func run( if err != nil { return stacktrace.Propagate(err, "An error occurred creating an engine manager.") } - - engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize) + engineClient, closeClientFunc, err := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, defaults.DefaultEngineLogLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride) if err != nil { return stacktrace.Propagate(err, "An error occurred creating a new Kurtosis engine client") } diff --git a/cli/cli/commands/enclave/dump/dump.go b/cli/cli/commands/enclave/dump/dump.go index bf22d77e03..7e0ba026d5 100644 --- a/cli/cli/commands/enclave/dump/dump.go +++ b/cli/cli/commands/enclave/dump/dump.go @@ -11,11 +11,14 @@ import ( "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/files" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/enclave" "github.com/kurtosis-tech/kurtosis/metrics-library/golang/lib/metrics_client" "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" + "os" + "path" ) const ( @@ -31,6 +34,9 @@ const ( defaultEnclaveDumpDir = "kurtosis-dump" enclaveDumpSeparator = "--" outputDirIsOptional = true + + filesArtifactDestinationDirPermission = 0o777 + filesArtifactFolderName = "files" ) var EnclaveDumpCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisCommand{ @@ -85,16 +91,51 @@ func run( } enclaveUuid := enclaveInfo.GetEnclaveUuid() - if enclaveOutputDirpath == defaultEnclaveDumpDir { enclaveName := enclaveInfo.GetName() enclaveOutputDirpath = fmt.Sprintf("%s%s%s", enclaveName, enclaveDumpSeparator, enclaveUuid) } - if err := kurtosisBackend.DumpEnclave(ctx, enclave.EnclaveUUID(enclaveUuid), enclaveOutputDirpath); err != nil { + if err = kurtosisBackend.DumpEnclave(ctx, enclave.EnclaveUUID(enclaveUuid), enclaveOutputDirpath); err != nil { return stacktrace.Propagate(err, "An error occurred dumping enclave '%v' to '%v'", enclaveIdentifier, enclaveOutputDirpath) } + if enclaveInfo.ApiContainerStatus != kurtosis_engine_rpc_api_bindings.EnclaveAPIContainerStatus_EnclaveAPIContainerStatus_RUNNING { + logrus.Debugf("Couldn't dump file information as the enclave '%v' is not running", enclaveIdentifier) + logrus.Infof("Dumped enclave '%v' to directory '%v'", enclaveIdentifier, enclaveOutputDirpath) + return nil + } + + enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclaveIdentifier) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while retrieving enclave context for enclave with identifier '%v'", enclaveIdentifier) + } + + filesInEnclave, err := enclaveCtx.GetAllFilesArtifactNamesAndUuids(ctx) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while fetching files artifact in enclave '%v'", enclaveIdentifier) + } + + if len(filesInEnclave) == 0 { + logrus.Infof("Dumped enclave '%v' to directory '%v'", enclaveIdentifier, enclaveOutputDirpath) + return nil + } + + filesDownloadFolder := path.Join(enclaveOutputDirpath, filesArtifactFolderName) + if err = os.Mkdir(filesDownloadFolder, filesArtifactDestinationDirPermission); err != nil { + return stacktrace.Propagate(err, "An error occurred while creating a folder '%v' to download files to", filesArtifactFolderName) + } + + for _, fileNameAndUuid := range filesInEnclave { + fileDownloadPath := path.Join(filesDownloadFolder, fileNameAndUuid.GetFileName()) + if err = os.Mkdir(fileDownloadPath, filesArtifactDestinationDirPermission); err != nil { + return stacktrace.Propagate(err, "An error occurred while creating directory '%v' to write files artifact '%v'", fileDownloadPath, fileNameAndUuid.GetFileName()) + } + if err = files.DownloadAndExtractFilesArtifact(ctx, enclaveCtx, fileNameAndUuid.GetFileName(), fileDownloadPath); err != nil { + return stacktrace.Propagate(err, "An error occurred while downloading and extracting file '%v'", fileNameAndUuid.GetFileName()) + } + } + logrus.Infof("Dumped enclave '%v' to directory '%v'", enclaveIdentifier, enclaveOutputDirpath) return nil } diff --git a/cli/cli/commands/engine/restart/restart.go b/cli/cli/commands/engine/restart/restart.go index a45ae86dab..b039800d66 100644 --- a/cli/cli/commands/engine/restart/restart.go +++ b/cli/cli/commands/engine/restart/restart.go @@ -19,9 +19,10 @@ import ( ) const ( - engineVersionFlagKey = "version" - logLevelFlagKey = "log-level" - enclavePoolSizeFlagKey = "enclave-pool-size" + engineVersionFlagKey = "version" + logLevelFlagKey = "log-level" + enclavePoolSizeFlagKey = "enclave-pool-size" + githubAuthTokenOverrideFlagKey = "github-auth-token" defaultEngineVersion = "" restartEngineOnSameVersionIfAnyRunning = false @@ -63,6 +64,13 @@ var RestartCmd = &lowlevel.LowlevelKurtosisCommand{ Type: flags.FlagType_Uint8, Default: strconv.Itoa(int(defaults.DefaultEngineEnclavePoolSize)), }, + { + Key: githubAuthTokenOverrideFlagKey, + Usage: "The GitHub auth token that should be used to authorize git operations such as accessing packages in private repositories. Overrides existing GitHub auth config if a user is logged in.", + Shorthand: "", + Type: flags.FlagType_String, + Default: defaults.DefaultGitHubAuthTokenOverride, + }, }, PreValidationAndRunFunc: nil, RunFunc: run, @@ -93,6 +101,11 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error return stacktrace.Propagate(err, "An error occurred parsing log level string '%v'", logLevelStr) } + githubAuthTokenOverride, err := flags.GetString(githubAuthTokenOverrideFlagKey) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting GitHub auth token override flag with key '%v'. This is a bug in Kurtosis", githubAuthTokenOverrideFlagKey) + } + engineManager, err := engine_manager.NewEngineManager(ctx) if err != nil { return stacktrace.Propagate(err, "An error occurred creating an engine manager.") @@ -116,7 +129,7 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error var engineClientCloseFunc func() error var restartEngineErr error - _, engineClientCloseFunc, restartEngineErr = engineManager.RestartEngineIdempotently(ctx, logLevel, engineVersion, restartEngineOnSameVersionIfAnyRunning, enclavePoolSize, shouldStartInDebugMode) + _, engineClientCloseFunc, restartEngineErr = engineManager.RestartEngineIdempotently(ctx, logLevel, engineVersion, restartEngineOnSameVersionIfAnyRunning, enclavePoolSize, shouldStartInDebugMode, githubAuthTokenOverride) if restartEngineErr != nil { return stacktrace.Propagate(restartEngineErr, "An error occurred restarting the Kurtosis engine") } diff --git a/cli/cli/commands/engine/start/start.go b/cli/cli/commands/engine/start/start.go index 86f93089e2..53802f48bd 100644 --- a/cli/cli/commands/engine/start/start.go +++ b/cli/cli/commands/engine/start/start.go @@ -19,9 +19,10 @@ import ( ) const ( - engineVersionFlagKey = "version" - logLevelFlagKey = "log-level" - enclavePoolSizeFlagKey = "enclave-pool-size" + engineVersionFlagKey = "version" + logLevelFlagKey = "log-level" + enclavePoolSizeFlagKey = "enclave-pool-size" + githubAuthTokenOverrideFlagKey = "github-auth-token" defaultEngineVersion = "" kurtosisTechEngineImagePrefix = "kurtosistech/engine" @@ -64,6 +65,13 @@ var StartCmd = &lowlevel.LowlevelKurtosisCommand{ Type: flags.FlagType_Uint8, Default: strconv.Itoa(int(defaults.DefaultEngineEnclavePoolSize)), }, + { + Key: githubAuthTokenOverrideFlagKey, + Usage: "The github auth token that should be used to authorize git operations such as accessing packages in private repositories. Overrides existing github auth config if a user is logged in.", + Shorthand: "", + Type: flags.FlagType_String, + Default: defaults.DefaultGitHubAuthTokenOverride, + }, }, PreValidationAndRunFunc: nil, RunFunc: run, @@ -92,6 +100,11 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error return stacktrace.Propagate(err, "An error occurred parsing log level string '%v'", logLevelStr) } + githubAuthTokenOverride, err := flags.GetString(githubAuthTokenOverrideFlagKey) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting GitHub auth token override flag with key '%v'. This is a bug in Kurtosis", githubAuthTokenOverrideFlagKey) + } + engineManager, err := engine_manager.NewEngineManager(ctx) if err != nil { return stacktrace.Propagate(err, "An error occurred creating an engine manager") @@ -113,14 +126,13 @@ func run(_ context.Context, flags *flags.ParsedFlags, _ *args.ParsedArgs) error if engineVersion == defaultEngineVersion && isDebugMode { engineDebugVersion := fmt.Sprintf("%s-%s", kurtosis_version.KurtosisVersion, defaults.DefaultKurtosisContainerDebugImageNameSuffix) logrus.Infof("Starting Kurtosis engine in debug mode from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, engineDebugVersion) - _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineDebugVersion, logLevel, enclavePoolSize, true) + _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineDebugVersion, logLevel, enclavePoolSize, true, githubAuthTokenOverride) } else if engineVersion == defaultEngineVersion { - logrus.Infof("Starting Kurtosis engine from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, kurtosis_version.KurtosisVersion) - _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, enclavePoolSize) + _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, enclavePoolSize, githubAuthTokenOverride) } else { logrus.Infof("Starting Kurtosis engine from image '%v%v%v'...", kurtosisTechEngineImagePrefix, imageVersionDelimiter, engineVersion) - _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineVersion, logLevel, enclavePoolSize, defaults.DefaultEnableDebugMode) + _, engineClientCloseFunc, startEngineErr = engineManager.StartEngineIdempotentlyWithCustomVersion(ctx, engineVersion, logLevel, enclavePoolSize, defaults.DefaultEnableDebugMode, githubAuthTokenOverride) } if startEngineErr != nil { return stacktrace.Propagate(startEngineErr, "An error occurred starting the Kurtosis engine") diff --git a/cli/cli/commands/files/download/download.go b/cli/cli/commands/files/download/download.go index 5a79577c51..dc69e9b35f 100644 --- a/cli/cli/commands/files/download/download.go +++ b/cli/cli/commands/files/download/download.go @@ -12,13 +12,12 @@ import ( "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/files" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" "github.com/kurtosis-tech/kurtosis/metrics-library/golang/lib/metrics_client" "github.com/kurtosis-tech/stacktrace" - "github.com/mholt/archiver" "github.com/sirupsen/logrus" "os" - "path" "path/filepath" ) @@ -38,15 +37,10 @@ const ( noExtractFlagKey = "no-extract" noExtractFlagDefaultValue = "false" - filesArtifactExtension = ".tgz" - filesArtifactPermission = 0o744 filesArtifactDestinationDirPermission = 0o777 kurtosisBackendCtxKey = "kurtosis-backend" engineClientCtxKey = "engine-client" - - defaultTmpDir = "" - tmpDirPattern = "tmp-dir-for-download-*" ) var FilesUploadCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisCommand{ @@ -139,45 +133,19 @@ func run( return stacktrace.Propagate(err, "An error occurred getting the enclave context for enclave '%v'", enclaveIdentifier) } - artifactBytes, err := enclaveCtx.DownloadFilesArtifact(ctx, artifactIdentifier) - if err != nil { - return stacktrace.Propagate(err, "An error occurred downloading files with identifier '%v' from enclave '%v'", artifactIdentifier, enclaveIdentifier) - } - - fileNameToWriteTo := fmt.Sprintf("%v%v", artifactIdentifier, filesArtifactExtension) - destinationPathToDownloadFileTo := path.Join(absoluteDestinationPath, fileNameToWriteTo) - // if the user doesn't want to extract, we just download and return if shouldNotExtract { - err = os.WriteFile(destinationPathToDownloadFileTo, artifactBytes, filesArtifactPermission) - if err != nil { - return stacktrace.Propagate(err, "An error occurred while writing bytes to file '%v' with permission '%v'", destinationPathToDownloadFileTo, filesArtifactPermission) + if err = files.DownloadFilesArtifactToLocation(ctx, enclaveCtx, artifactIdentifier, absoluteDestinationPath); err != nil { + return stacktrace.Propagate(err, "An error occurred while downloading file '%v' to '%v' for enclave '%v'", artifactIdentifier, absoluteDestinationPath, enclaveIdentifier) } - logrus.Infof("File package with identifier '%v' downloaded to '%v'", artifactIdentifier, destinationPathToDownloadFileTo) + logrus.Infof("File package with identifier '%v' downloaded to '%v'", artifactIdentifier, absoluteDestinationPath) return nil } - tmpDirPath, err := os.MkdirTemp(defaultTmpDir, tmpDirPattern) - if err != nil { - return stacktrace.Propagate(err, "An error occurred while creating a temporary directory to download the files artifact with identifier '%v' to", artifactIdentifier) - } - shouldCleanupTmpDir := false - defer func() { - if shouldCleanupTmpDir { - os.RemoveAll(tmpDirPath) - } - }() - tmpFileToWriteTo := path.Join(tmpDirPath, fileNameToWriteTo) - err = os.WriteFile(tmpFileToWriteTo, artifactBytes, filesArtifactPermission) - if err != nil { - return stacktrace.Propagate(err, "An error occurred while writing bytes to file '%v' with permission '%v'", tmpDirPath, filesArtifactPermission) - } - err = archiver.Unarchive(tmpFileToWriteTo, absoluteDestinationPath) - if err != nil { - return stacktrace.Propagate(err, "An error occurred while extracting '%v' to '%v'", tmpFileToWriteTo, destinationPathToDownloadFileTo) + if err = files.DownloadAndExtractFilesArtifact(ctx, enclaveCtx, artifactIdentifier, absoluteDestinationPath); err != nil { + return stacktrace.Propagate(err, "An error occurred while downloading and extracting file '%v' to '%v' for enclave '%v'", artifactIdentifier, absoluteDestinationPath, enclaveIdentifier) } logrus.Infof("File package with identifier '%v' extracted to '%v'", artifactIdentifier, absoluteDestinationPath) - shouldCleanupTmpDir = true return nil } diff --git a/cli/cli/commands/github/github.go b/cli/cli/commands/github/github.go new file mode 100644 index 0000000000..de7c451d78 --- /dev/null +++ b/cli/cli/commands/github/github.go @@ -0,0 +1,25 @@ +package github + +import ( + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/login" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/logout" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/status" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/github/token" + "github.com/spf13/cobra" +) + +// GitHubCmd Suppressing exhaustruct requirement because this struct has ~40 properties +// nolint: exhaustruct +var GitHubCmd = &cobra.Command{ + Use: command_str_consts.GitHubCmdStr, + Short: "Manage GitHub login", + RunE: nil, +} + +func init() { + GitHubCmd.AddCommand(login.LoginCmd.MustGetCobraCommand()) + GitHubCmd.AddCommand(logout.LogoutCmd.MustGetCobraCommand()) + GitHubCmd.AddCommand(status.StatusCmd.MustGetCobraCommand()) + GitHubCmd.AddCommand(token.TokenCmd.MustGetCobraCommand()) +} diff --git a/cli/cli/commands/github/login/login.go b/cli/cli/commands/github/login/login.go new file mode 100644 index 0000000000..3702e405a4 --- /dev/null +++ b/cli/cli/commands/github/login/login.go @@ -0,0 +1,50 @@ +package login + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/oauth" + "github.com/kurtosis-tech/kurtosis/cli/cli/out" + "github.com/kurtosis-tech/stacktrace" +) + +var LoginCmd = &lowlevel.LowlevelKurtosisCommand{ + CommandStr: command_str_consts.GitHubLoginCmdStr, + ShortDescription: "Authorizes Kurtosis CLI on behalf of a Github user.", + LongDescription: "Authorizes Kurtosis CLI to perform git operations on behalf of a GitHub user such as retrieving packages in private repositories.", + Args: nil, + Flags: nil, + PreValidationAndRunFunc: nil, + RunFunc: run, + PostValidationAndRunFunc: nil, +} + +func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error { + githubAuthStore, err := github_auth_store.GetGitHubAuthStore() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth store.") + } + username, err := githubAuthStore.GetUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.") + } + if username != "" { + out.PrintOutLn(fmt.Sprintf("Logged in as GitHub user: %v", username)) + return nil + } + authToken, username, err := oauth.AuthFlow() + if err != nil { + return stacktrace.Propagate(err, "An error occurred in the Github OAuth flow.") + } + err = githubAuthStore.SetUser(username, authToken) + if err != nil { + return stacktrace.Propagate(err, "An error occurred setting GitHub user: %v", username) + } + out.PrintOutLn(fmt.Sprintf("Successfully logged in GitHub user: %v", username)) + return nil +} diff --git a/cli/cli/commands/github/logout/logout.go b/cli/cli/commands/github/logout/logout.go new file mode 100644 index 0000000000..a6ba7da499 --- /dev/null +++ b/cli/cli/commands/github/logout/logout.go @@ -0,0 +1,45 @@ +package logout + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store" + "github.com/kurtosis-tech/kurtosis/cli/cli/out" + "github.com/kurtosis-tech/stacktrace" +) + +var LogoutCmd = &lowlevel.LowlevelKurtosisCommand{ + CommandStr: command_str_consts.GitHubLogoutCmdStr, + ShortDescription: "Logs out a GitHub user from Kurtosis CLI", + LongDescription: "Logs out a GitHub user from Kurtosis CLI by removing their GitHub user info and auth token from Kurtosis CLI config", + Args: nil, + Flags: nil, + PreValidationAndRunFunc: nil, + RunFunc: run, + PostValidationAndRunFunc: nil, +} + +func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error { + githubAuthStore, err := github_auth_store.GetGitHubAuthStore() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth configuration.") + } + username, err := githubAuthStore.GetUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.") + } + if username == "" { + out.PrintOutLn("No GitHub user logged into Kurtosis CLI: %v") + return nil + } + err = githubAuthStore.RemoveUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred logging out GitHub user: %v", username) + } + out.PrintOutLn(fmt.Sprintf("Successfully logged GitHub user '%v' out of Kurtosis CLI", username)) + return nil +} diff --git a/cli/cli/commands/github/status/status.go b/cli/cli/commands/github/status/status.go new file mode 100644 index 0000000000..c8fbf3441b --- /dev/null +++ b/cli/cli/commands/github/status/status.go @@ -0,0 +1,41 @@ +package status + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store" + "github.com/kurtosis-tech/kurtosis/cli/cli/out" + "github.com/kurtosis-tech/stacktrace" +) + +var StatusCmd = &lowlevel.LowlevelKurtosisCommand{ + CommandStr: command_str_consts.GitHubStatusCmdStr, + ShortDescription: "Displays GitHub auth info", + LongDescription: "Displays GitHub auth info by showing a logged in user's info or whether no GitHub user is logged into Kurtosis CLI", + Args: nil, + Flags: nil, + PreValidationAndRunFunc: nil, + RunFunc: run, + PostValidationAndRunFunc: nil, +} + +func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error { + githubAuthStore, err := github_auth_store.GetGitHubAuthStore() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth configuration.") + } + username, err := githubAuthStore.GetUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.") + } + if username == "" { + out.PrintOutLn("No GitHub user logged into Kurtosis CLI") + return nil + } + out.PrintOutLn(fmt.Sprintf("Logged in as GitHub user: %v", username)) + return nil +} diff --git a/cli/cli/commands/github/token/token.go b/cli/cli/commands/github/token/token.go new file mode 100644 index 0000000000..fc27a68e53 --- /dev/null +++ b/cli/cli/commands/github/token/token.go @@ -0,0 +1,44 @@ +package token + +import ( + "context" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store" + "github.com/kurtosis-tech/kurtosis/cli/cli/out" + "github.com/kurtosis-tech/stacktrace" +) + +var TokenCmd = &lowlevel.LowlevelKurtosisCommand{ + CommandStr: command_str_consts.GitHubTokenCmdStr, + ShortDescription: "Displays GitHub auth token used if a user is logged in", + LongDescription: "Displays GitHub auth token used if a user is logged in", + Args: nil, + Flags: nil, + PreValidationAndRunFunc: nil, + RunFunc: run, + PostValidationAndRunFunc: nil, +} + +func run(_ context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error { + githubAuthStore, err := github_auth_store.GetGitHubAuthStore() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth store.") + } + username, err := githubAuthStore.GetUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting user to see if user already exists.") + } + if username == "" { + out.PrintOutLn("No GitHub user currently logged in.") + return nil + } + authToken, err := githubAuthStore.GetAuthToken() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth token for user: %v.", username) + } + out.PrintOutLn(authToken) + return nil +} diff --git a/cli/cli/commands/import/import.go b/cli/cli/commands/import/import.go index 13583d192f..6058d95cc5 100644 --- a/cli/cli/commands/import/import.go +++ b/cli/cli/commands/import/import.go @@ -3,6 +3,11 @@ package _import import ( "context" "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/types" "github.com/joho/godotenv" @@ -25,10 +30,6 @@ import ( "github.com/kurtosis-tech/kurtosis/name_generator" "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" - "os" - "path/filepath" - "strconv" - "strings" ) const ( @@ -175,6 +176,7 @@ func run( func convertComposeFileToStarlark(path string, dotEnvMap map[string]string) (string, map[string]string, error) { project, err := loader.Load(types.ConfigDetails{ //nolint:exhaustruct + // nolint: exhaustruct ConfigFiles: []types.ConfigFile{{Filename: path}}, Environment: dotEnvMap, }) diff --git a/cli/cli/commands/kurtosis_context/set/set.go b/cli/cli/commands/kurtosis_context/set/set.go index 6818c111bc..f52d798cf7 100644 --- a/cli/cli/commands/kurtosis_context/set/set.go +++ b/cli/cli/commands/kurtosis_context/set/set.go @@ -147,7 +147,7 @@ func SetContext( return stacktrace.Propagate(err, "An error occurred creating an engine manager for the new context.") } - _, engineClientCloseFunc, startEngineErr := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logrus.InfoLevel, defaults.DefaultEngineEnclavePoolSize) + _, engineClientCloseFunc, startEngineErr := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logrus.InfoLevel, defaults.DefaultEngineEnclavePoolSize, defaults.DefaultGitHubAuthTokenOverride) if startEngineErr != nil { logrus.Warnf("The context was successfully set to '%s' but Kurtosis failed to start an engine in "+ "this new context. A new engine should be started manually with '%s %s %s'. The error was:\n%v", diff --git a/cli/cli/commands/root.go b/cli/cli/commands/root.go index 2aa5c563a9..f81cd735e8 100644 --- a/cli/cli/commands/root.go +++ b/cli/cli/commands/root.go @@ -23,6 +23,7 @@ import ( "github.com/kurtosis-tech/kurtosis/cli/cli/commands/feedback" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/files" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/gateway" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/github" _import "github.com/kurtosis-tech/kurtosis/cli/cli/commands/import" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/kurtosis_context" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/lint" @@ -138,6 +139,7 @@ func init() { RootCmd.AddCommand(version.VersionCmd) RootCmd.AddCommand(web.WebCmd.MustGetCobraCommand()) RootCmd.AddCommand(_package.PackageCmd) + RootCmd.AddCommand(github.GitHubCmd) } // ==================================================================================================== diff --git a/cli/cli/defaults/defaults.go b/cli/cli/defaults/defaults.go index 3f32456b1b..27e8032fd2 100644 --- a/cli/cli/defaults/defaults.go +++ b/cli/cli/defaults/defaults.go @@ -22,6 +22,8 @@ const ( DebugModeFlagKey = "debug-mode" DefaultEnableDebugMode = false DefaultKurtosisContainerDebugImageNameSuffix = "debug" + + DefaultGitHubAuthTokenOverride = "" ) var DefaultApiContainerLogLevel = logrus.DebugLevel diff --git a/cli/cli/go.mod b/cli/cli/go.mod index 9e910a541b..8061858727 100644 --- a/cli/cli/go.mod +++ b/cli/cli/go.mod @@ -29,7 +29,7 @@ require ( github.com/kurtosis-tech/kurtosis/metrics-library/golang v0.0.0 // Local dependency github.com/kurtosis-tech/stacktrace v0.0.0-20211028211901-1c67a77b5409 github.com/manifoldco/promptui v0.9.0 - github.com/mattn/go-isatty v0.0.19 + github.com/mattn/go-isatty v0.0.20 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -45,9 +45,14 @@ require github.com/bazelbuild/buildtools v0.0.0-20221110131218-762712d8ce3f require ( github.com/briandowns/spinner v1.20.0 + github.com/cli/cli/v2 v2.42.1 + github.com/cli/go-gh/v2 v2.4.1-0.20231120145612-d32c104a9a25 + github.com/cli/oauth v1.0.1 github.com/compose-spec/compose-go v1.17.0 github.com/fatih/color v1.13.0 + github.com/go-git/go-git/v5 v5.11.0 github.com/google/go-github/v50 v50.2.0 + github.com/henvic/httpretty v0.1.3 github.com/joho/godotenv v1.5.1 github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2 github.com/kurtosis-tech/kurtosis/cloud/api/golang v0.0.0 @@ -56,6 +61,7 @@ require ( github.com/kurtosis-tech/vscode-kurtosis/starlark-lsp v0.0.0-20230406131103-c466e04f1b89 github.com/mholt/archiver v3.1.1+incompatible github.com/xlab/treeprint v1.2.0 + github.com/zalando/go-keyring v0.2.3 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -66,13 +72,20 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/alessio/shellescape v1.4.1 // indirect + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/bytedance/sonic v1.10.0-rc3 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/cli/browser v1.3.0 // indirect + github.com/cli/safeexec v1.0.1 // indirect + github.com/cli/shurcooL-graphql v0.0.4 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.2 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect @@ -86,34 +99,42 @@ require ( github.com/gammazero/workerpool v1.1.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/go-playground/validator/v10 v10.14.1 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0 // indirect github.com/kurtosis-tech/kurtosis/utils v0.0.0-20240104153602-385833de9d76 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/buildkit v0.12.4 // indirect @@ -122,6 +143,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/termenv v0.13.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -129,14 +151,19 @@ require ( github.com/pascaldekloe/name v1.0.1 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rs/cors v1.9.0 // indirect github.com/segmentio/backo-go v1.0.0 // indirect github.com/segmentio/encoding v0.2.7 // indirect + github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 // indirect + github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/smacker/go-tree-sitter v0.0.0-20230226123037-c459dbde1464 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/stretchr/objx v0.5.0 // indirect + github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -156,21 +183,22 @@ require ( go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.20.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/segmentio/analytics-go.v3 v3.1.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect nhooyr.io/websocket v1.8.7 // indirect diff --git a/cli/cli/go.sum b/cli/cli/go.sum index a0efc36cde..5cb753030d 100644 --- a/cli/cli/go.sum +++ b/cli/cli/go.sum @@ -17,12 +17,13 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 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/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -34,6 +35,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -45,6 +48,8 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/bazelbuild/buildtools v0.0.0-20221110131218-762712d8ce3f h1:pkH5ds19YGNyq6CaDwioradmMA9XCMDhEN2jCgI8OF0= github.com/bazelbuild/buildtools v0.0.0-20221110131218-762712d8ce3f/go.mod h1:689QdV3hBP7Vo9dJMmzhoYIyo/9iMhEmHkJcnaPRCbo= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -59,7 +64,7 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/briandowns/spinner v1.20.0 h1:GQq1Yf1KyzYT8CY19GzWrDKP6hYOFB6J72Ks7d8aO1U= github.com/briandowns/spinner v1.20.0/go.mod h1:TcwZHb7Wb6vn/+bcVv1UXEzaA4pLS7yznHlkY/HzH44= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= @@ -84,8 +89,21 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/cli/cli/v2 v2.42.1 h1:LtgC7N46J9xdRhq9KNUUpowy41H4xjlBCmgcl4pw7H8= +github.com/cli/cli/v2 v2.42.1/go.mod h1:Jtsn9iQxcsIE6T9Aj88xSMFnaZP35rjkD+Cpr1QnbUg= +github.com/cli/go-gh/v2 v2.4.1-0.20231120145612-d32c104a9a25 h1:m2opPgNTaKx1QydI4NfGdZqiYkA/Kl9a7tsDSjHgWWg= +github.com/cli/go-gh/v2 v2.4.1-0.20231120145612-d32c104a9a25/go.mod h1:h3salfqqooVpzKmHp6aUdeNx62UmxQRpLbagFSHTJGQ= +github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA= +github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= +github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= +github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= +github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= +github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= +github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -106,8 +124,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= 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= @@ -138,6 +160,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= @@ -156,7 +179,7 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= @@ -170,6 +193,13 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -210,6 +240,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= @@ -221,6 +253,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -257,8 +291,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk= github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= @@ -272,6 +306,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -294,6 +330,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -314,6 +351,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/henvic/httpretty v0.1.3 h1:4A6vigjz6Q/+yAfTD4wqipCv+Px69C7Th/NhT0ApuU8= +github.com/henvic/httpretty v0.1.3/go.mod h1:UUEv7c2kHZ5SPQ51uS3wBpzPDibg2U3Y+IaXyHy5GBg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -324,6 +363,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -383,6 +424,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -401,12 +444,15 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= @@ -435,6 +481,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= 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= @@ -460,7 +508,7 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -487,6 +535,8 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -522,11 +572,14 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +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/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -544,10 +597,14 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 h1:kdEGVAV4sO46DPtb8k793jiecUEhaX9ixoIBt41HEGU= +github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= @@ -608,6 +665,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o 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/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -638,6 +697,9 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63M 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= @@ -698,6 +760,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U 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.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -720,8 +784,10 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -749,8 +815,12 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -766,8 +836,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -805,16 +877,24 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -822,6 +902,10 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 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.6/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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -849,8 +933,10 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK 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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= @@ -926,12 +1012,14 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/segmentio/analytics-go.v3 v3.1.0 h1:UzxH1uaGZRpMKDhJyBz0pexz6yUoBU3x8bJsRk/HV6U= gopkg.in/segmentio/analytics-go.v3 v3.1.0/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cli/cli/helpers/engine_manager/engine_existence_guarantor.go b/cli/cli/helpers/engine_manager/engine_existence_guarantor.go index a21f5992d4..c522476f86 100644 --- a/cli/cli/helpers/engine_manager/engine_existence_guarantor.go +++ b/cli/cli/helpers/engine_manager/engine_existence_guarantor.go @@ -3,6 +3,7 @@ package engine_manager import ( "context" "fmt" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/github_auth_store" "github.com/Masterminds/semver/v3" "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" @@ -77,6 +78,9 @@ type engineExistenceGuarantor struct { // Whether the engine's should run with the debug server to receive a remote debug connection shouldRunInDebugMode bool + + // token with git auth to override existing GitHub auth if there is any + githubAuthTokenOverride string } func newEngineExistenceGuarantorWithDefaultVersion( @@ -93,6 +97,8 @@ func newEngineExistenceGuarantorWithDefaultVersion( enclaveEnvVars string, allowedCORSOrigins *[]string, shouldRunInDebugMode bool, + githubAuthTokenOverride string, + ) *engineExistenceGuarantor { return newEngineExistenceGuarantorWithCustomVersion( ctx, @@ -109,6 +115,7 @@ func newEngineExistenceGuarantorWithDefaultVersion( enclaveEnvVars, allowedCORSOrigins, shouldRunInDebugMode, + githubAuthTokenOverride, ) } @@ -127,6 +134,7 @@ func newEngineExistenceGuarantorWithCustomVersion( enclaveEnvVars string, allowedCORSOrigins *[]string, shouldRunInDebugMode bool, + githubAuthTokenOverride string, ) *engineExistenceGuarantor { return &engineExistenceGuarantor{ ctx: ctx, @@ -145,6 +153,7 @@ func newEngineExistenceGuarantorWithCustomVersion( enclaveEnvVars: enclaveEnvVars, allowedCORSOrigins: allowedCORSOrigins, shouldRunInDebugMode: shouldRunInDebugMode, + githubAuthTokenOverride: githubAuthTokenOverride, } } @@ -164,6 +173,27 @@ func (guarantor *engineExistenceGuarantor) VisitStopped() error { maybeCloudUserId, maybeCloudInstanceId := metrics_cloud_user_instance_id_helper.GetMaybeCloudUserAndInstanceID() + var githubAuthToken string + // If override was provided, use it, else use existing GitHub auth if it exists + if guarantor.githubAuthTokenOverride != "" { + githubAuthToken = guarantor.githubAuthTokenOverride + } else { + githubAuthStore, err := github_auth_store.GetGitHubAuthStore() + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving GitHub auth store.") + } + username, err := githubAuthStore.GetUser() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting GitHub user.") + } + if username != "" { + githubAuthToken, err = githubAuthStore.GetAuthToken() + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting GitHub auth token for user: %v.", username) + } + } + } + var engineLaunchErr error if guarantor.imageVersionTag == defaultEngineImageVersionTag { _, _, engineLaunchErr = guarantor.engineServerLauncher.LaunchWithDefaultVersion( @@ -181,6 +211,7 @@ func (guarantor *engineExistenceGuarantor) VisitStopped() error { maybeCloudInstanceId, guarantor.allowedCORSOrigins, guarantor.shouldRunInDebugMode, + githubAuthToken, ) } else { _, _, engineLaunchErr = guarantor.engineServerLauncher.LaunchWithCustomVersion( @@ -199,6 +230,7 @@ func (guarantor *engineExistenceGuarantor) VisitStopped() error { maybeCloudInstanceId, guarantor.allowedCORSOrigins, guarantor.shouldRunInDebugMode, + githubAuthToken, ) } if engineLaunchErr != nil { diff --git a/cli/cli/helpers/engine_manager/engine_manager.go b/cli/cli/helpers/engine_manager/engine_manager.go index 7717e88c82..f1638f5bb1 100644 --- a/cli/cli/helpers/engine_manager/engine_manager.go +++ b/cli/cli/helpers/engine_manager/engine_manager.go @@ -176,7 +176,11 @@ func (manager *EngineManager) GetEngineStatus( } // StartEngineIdempotentlyWithDefaultVersion Starts an engine if one doesn't exist already, and returns a client to it -func (manager *EngineManager) StartEngineIdempotentlyWithDefaultVersion(ctx context.Context, logLevel logrus.Level, poolSize uint8) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { +func (manager *EngineManager) StartEngineIdempotentlyWithDefaultVersion( + ctx context.Context, + logLevel logrus.Level, + poolSize uint8, + githubAuthTokenOverride string) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { status, maybeHostMachinePortBinding, engineVersion, err := manager.GetEngineStatus(ctx) if err != nil { return nil, nil, stacktrace.Propagate(err, "An error occurred retrieving the Kurtosis engine status, which is necessary for creating a connection to the engine") @@ -197,6 +201,7 @@ func (manager *EngineManager) StartEngineIdempotentlyWithDefaultVersion(ctx cont manager.enclaveEnvVars, manager.allowedCORSOrigins, doNotStartTheEngineInDebugModeForDefaultVersion, + githubAuthTokenOverride, ) // TODO Need to handle the Kubernetes case, where a gateway needs to be started after the engine is started but // before we can return an EngineClient @@ -207,8 +212,14 @@ func (manager *EngineManager) StartEngineIdempotentlyWithDefaultVersion(ctx cont return engineClient, engineClientCloseFunc, nil } -// StartEngineIdempotentlyWithCustomVersion Starts an engine if one doesn't exist already, and returns a client to it -func (manager *EngineManager) StartEngineIdempotentlyWithCustomVersion(ctx context.Context, engineImageVersionTag string, logLevel logrus.Level, poolSize uint8, shouldStartInDebugMode bool) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { +// StartEngineIdempotentlyWithCustomVersion Starts an engine if one doesn't exist already, and returns a client to it TokenOverride string) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { +func (manager *EngineManager) StartEngineIdempotentlyWithCustomVersion( + ctx context.Context, + engineImageVersionTag string, + logLevel logrus.Level, + poolSize uint8, + shouldStartInDebugMode bool, + githubAuthTokenOverride string) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { status, maybeHostMachinePortBinding, engineVersion, err := manager.GetEngineStatus(ctx) if err != nil { return nil, nil, stacktrace.Propagate(err, "An error occurred retrieving the Kurtosis engine status, which is necessary for creating a connection to the engine") @@ -230,6 +241,7 @@ func (manager *EngineManager) StartEngineIdempotentlyWithCustomVersion(ctx conte manager.enclaveEnvVars, manager.allowedCORSOrigins, shouldStartInDebugMode, + githubAuthTokenOverride, ) engineClient, engineClientCloseFunc, err := manager.startEngineWithGuarantor(ctx, status, engineGuarantor) if err != nil { @@ -321,7 +333,14 @@ func (manager *EngineManager) StopEngineIdempotently(ctx context.Context) error // If no optionalVersionToUse is passed, then the new engine will take the default version, unless // restartEngineOnSameVersionIfAnyRunning is set to true in which case it will take the version of the currently // running engine -func (manager *EngineManager) RestartEngineIdempotently(ctx context.Context, logLevel logrus.Level, optionalVersionToUse string, restartEngineOnSameVersionIfAnyRunning bool, poolSize uint8, shouldStartInDebugMode bool) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { +func (manager *EngineManager) RestartEngineIdempotently( + ctx context.Context, + logLevel logrus.Level, + optionalVersionToUse string, + restartEngineOnSameVersionIfAnyRunning bool, + poolSize uint8, + shouldStartInDebugMode bool, + githubAuthTokenOverride string) (kurtosis_engine_rpc_api_bindings.EngineServiceClient, func() error, error) { var versionOfNewEngine string // We try to do our best to restart an engine on the same version the current on is on _, _, currentEngineVersion, err := manager.GetEngineStatus(ctx) @@ -346,9 +365,9 @@ func (manager *EngineManager) RestartEngineIdempotently(ctx context.Context, log var engineClientCloseFunc func() error var restartEngineErr error if versionOfNewEngine != defaultEngineVersion { - _, engineClientCloseFunc, restartEngineErr = manager.StartEngineIdempotentlyWithCustomVersion(ctx, versionOfNewEngine, logLevel, poolSize, shouldStartInDebugMode) + _, engineClientCloseFunc, restartEngineErr = manager.StartEngineIdempotentlyWithCustomVersion(ctx, versionOfNewEngine, logLevel, poolSize, shouldStartInDebugMode, githubAuthTokenOverride) } else { - _, engineClientCloseFunc, restartEngineErr = manager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, poolSize) + _, engineClientCloseFunc, restartEngineErr = manager.StartEngineIdempotentlyWithDefaultVersion(ctx, logLevel, poolSize, githubAuthTokenOverride) } if restartEngineErr != nil { return nil, nil, stacktrace.Propagate(restartEngineErr, "An error occurred starting a new engine") diff --git a/cli/cli/helpers/files/donwload.go b/cli/cli/helpers/files/donwload.go new file mode 100644 index 0000000000..6f97fa680f --- /dev/null +++ b/cli/cli/helpers/files/donwload.go @@ -0,0 +1,67 @@ +package files + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" + "github.com/kurtosis-tech/stacktrace" + "github.com/mholt/archiver" + "os" + "path" +) + +const ( + filesArtifactExtension = ".tgz" + filesArtifactPermission = 0o744 + + defaultTmpDir = "" + tmpDirPattern = "tmp-dir-for-download-*" +) + +func DownloadFilesArtifactToLocation(ctx context.Context, enclaveCtx *enclaves.EnclaveContext, artifactIdentifier string, absoluteDestinationPath string) error { + + artifactBytes, err := enclaveCtx.DownloadFilesArtifact(ctx, artifactIdentifier) + if err != nil { + return stacktrace.Propagate(err, "An error occurred downloading files with identifier '%v' from enclave '%v'", artifactIdentifier, enclaveCtx.GetEnclaveName()) + } + + fileNameToWriteTo := fmt.Sprintf("%v%v", artifactIdentifier, filesArtifactExtension) + destinationPathToDownloadFileTo := path.Join(absoluteDestinationPath, fileNameToWriteTo) + + err = os.WriteFile(destinationPathToDownloadFileTo, artifactBytes, filesArtifactPermission) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while writing bytes to file '%v' with permission '%v'", destinationPathToDownloadFileTo, filesArtifactPermission) + } + return nil +} + +func DownloadAndExtractFilesArtifact(ctx context.Context, enclaveCtx *enclaves.EnclaveContext, artifactIdentifier string, absoluteDestinationPath string) error { + artifactBytes, err := enclaveCtx.DownloadFilesArtifact(ctx, artifactIdentifier) + if err != nil { + return stacktrace.Propagate(err, "An error occurred downloading files with identifier '%v' from enclave '%v'", artifactIdentifier, enclaveCtx.GetEnclaveName()) + } + fileNameToWriteTo := fmt.Sprintf("%v%v", artifactIdentifier, filesArtifactExtension) + + tmpDirPath, err := os.MkdirTemp(defaultTmpDir, tmpDirPattern) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while creating a temporary directory to download the files artifact with identifier '%v' to", artifactIdentifier) + } + shouldCleanupTmpDir := false + defer func() { + if shouldCleanupTmpDir { + os.RemoveAll(tmpDirPath) + } + }() + tmpFileToWriteTo := path.Join(tmpDirPath, fileNameToWriteTo) + err = os.WriteFile(tmpFileToWriteTo, artifactBytes, filesArtifactPermission) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while writing bytes to file '%v' with permission '%v'", tmpDirPath, filesArtifactPermission) + } + err = archiver.Unarchive(tmpFileToWriteTo, absoluteDestinationPath) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while extracting '%v' to '%v'", tmpFileToWriteTo, absoluteDestinationPath) + } + + shouldCleanupTmpDir = true + return nil +} diff --git a/cli/cli/helpers/github_auth_store/github_auth_store.go b/cli/cli/helpers/github_auth_store/github_auth_store.go new file mode 100644 index 0000000000..51b7686450 --- /dev/null +++ b/cli/cli/helpers/github_auth_store/github_auth_store.go @@ -0,0 +1,361 @@ +package github_auth_store + +import ( + "errors" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/host_machine_directories" + "github.com/kurtosis-tech/stacktrace" + "github.com/sirupsen/logrus" + "github.com/zalando/go-keyring" + "os" + "sync" +) + +const ( + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // DO NOT CHANGE THIS VALUE + // Changing this value could leak tokens in a users keyring/make Kurtosis unable to retrieve/remove them. + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + kurtosisCliKeyringServiceName = "kurtosis-cli" + + githubAuthFilesPerms = 0644 +) + +var ( + // NOTE: This will be initialized exactly once (singleton pattern) + githubAuthStore GitHubAuthStore + once sync.Once + + NoTokenFound = errors.New("no token found for currently logged in user") +) + +// GitHubAuthStore stores information about a GitHub user that has authorized Kurtosis CLI to perform git operations on their behalf +// [username] is their GitHub username +// [authToken] is a scoped token that authorizes Kurtosis CLI on behalf of [username +type GitHubAuthStore interface { + // GetUser returns [username] of current user + // If no user exists, returns empty string + GetUser() (string, error) + + // GetAuthToken returns authToken for the user if they exist + // If [authToken] doesn't exist in system credential storage, attempts to retrieve token from plain text file + // Returns empty string if no user exists + // Returns NoTokenFound err if user exists but no [authToken] was found + GetAuthToken() (string, error) + + // SetUser sets current user to [username] and stores their [authToken] in system credential storage if it exists + // otherwise, stores [authToken] in plain text file + SetUser(username, authToken string) error + + // RemoveUser removes user and user's [authToken] from store, if a user exists + RemoveUser() error +} + +func GetGitHubAuthStore() (GitHubAuthStore, error) { + store, err := NewGitHubAuthStore() + if err != nil { + return nil, err + } + once.Do(func() { + // NOTE: We use a 'once' to initialize the GitHubAuthStore because it contains a mutex to guard + // the files, and we don't ever want multiple GitHubAuthStore instances in existence + githubAuthStore = store + }) + return githubAuthStore, nil +} + +type githubConfigStoreImpl struct { + *sync.RWMutex + + usernameFilePath, authTokenFilePath string +} + +func NewGitHubAuthStore() (GitHubAuthStore, error) { + usernameFilePath, err := host_machine_directories.GetGitHubUsernameFilePath() + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred getting the GitHub username filepath") + } + authTokenFilePath, err := host_machine_directories.GetGitHubAuthTokenFilePath() + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred getting the Github auth token filepath") + } + return &githubConfigStoreImpl{ + RWMutex: &sync.RWMutex{}, + usernameFilePath: usernameFilePath, + authTokenFilePath: authTokenFilePath, + }, nil +} + +func newGitHubAuthStoreForTesting(testUsernameFilePath, testAuthTokenFilePath string) GitHubAuthStore { + return &githubConfigStoreImpl{ + RWMutex: &sync.RWMutex{}, + usernameFilePath: testUsernameFilePath, + authTokenFilePath: testAuthTokenFilePath, + } +} + +func (store *githubConfigStoreImpl) GetUser() (string, error) { + store.RLock() + defer store.RUnlock() + + username, userExists, err := store.getUserAndIfTheyExist() + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred verifying if a user exists.") + } + if !userExists { + return "", nil + } + return username, nil +} + +func (store *githubConfigStoreImpl) GetAuthToken() (string, error) { + store.RLock() + defer store.RUnlock() + + username, userExists, err := store.getUserAndIfTheyExist() + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred verifying if a user exists.") + } + if !userExists { + return "", nil + } + + authToken, err := getAuthTokenFromKeyring(username) + if err == nil { + return authToken, nil + } + if err != nil && !errors.Is(err, keyring.ErrNotFound) { + return "", stacktrace.Propagate(err, "An error getting auth token from keyring for GitHub user: %v.", username) + } + logrus.Debugf("No auth token found in keyring for user '%v'\nFalling back to retrieving auth token from plain text file.", username) + githubAuthTokenFileExists, err := store.doesGitHubAuthTokenFileExist() + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred verifying if GitHub auth token file exists for GitHub user: %v.", username) + } + if !githubAuthTokenFileExists { + return "", NoTokenFound + } + authToken, err = store.getGitHubAuthTokenFromFile() + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred getting auth token from file for GitHub user: %v", username) + } + if authToken == "" { + return "", NoTokenFound + } + return authToken, nil +} + +func (store *githubConfigStoreImpl) SetUser(username, authToken string) error { + store.Lock() + defer store.Unlock() + + err := store.saveGitHubUsernameFile(username) + if err != nil { + return stacktrace.Propagate(err, "An error occurred saving '%v' to store.", username) + } + shouldUnsetGitHubUsername := true + defer func() { + if shouldUnsetGitHubUsername { + if err := store.removeGitHubUsernameFile(); err != nil { + logrus.Errorf("Error occurred removing GitHub username after setting it failed!!! GitHub auth could be in a bad state.") + } + } + }() + + err = store.setAuthToken(username, authToken) + if err != nil { + return stacktrace.Propagate(err, "An error occurred setting auth token in store for user: %v", username) + } + shouldUnsetAuthToken := true + defer func() { + if shouldUnsetAuthToken { + if err = store.removeAuthToken(username); err != nil { + logrus.Errorf("Error occurred removing GitHub auth token after setting it failed!!! GitHub auth could be in a bad state.") + } + } + }() + + shouldUnsetGitHubUsername = false + shouldUnsetAuthToken = false + return nil +} + +func (store *githubConfigStoreImpl) RemoveUser() error { + store.Lock() + defer store.Unlock() + + username, userExists, err := store.getUserAndIfTheyExist() + if err != nil { + return stacktrace.Propagate(err, "An error occurred verifying if a user exists.") + } + if !userExists { + return nil + } + + err = store.removeAuthToken(username) + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing user '%v' token from store.", username) + } + + err = store.removeGitHubUsernameFile() + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing GitHub user '%v' from store", username) + } + + return nil +} + +func (store *githubConfigStoreImpl) getUserAndIfTheyExist() (string, bool, error) { + userExists, err := store.doesGitHubUsernameFileExist() + if err != nil { + return "", false, stacktrace.Propagate(err, "An error occurred discovering if user exists.") + } + if !userExists { + return "", false, nil + } + username, err := store.getGitHubUsernameFromFile() + if err != nil { + return "", false, stacktrace.Propagate(err, "An error occurred getting user from store.") + } + if username == "" { + return "", false, nil + } + return username, true, nil +} + +// setAuthToken attempts to set the git auth token for username +// Will attempt to store in secure system credential storage, but if no secure storage is found will resort to storing in a plain text file +func (store *githubConfigStoreImpl) setAuthToken(username, authToken string) error { + err := setAuthTokenInKeyring(username, authToken) + if err == nil { + return nil + } + logrus.Debugf("An error occurred setting GitHub auth token in keyring: %v\nFalling back to setting token in plain text file.", err) + err = store.saveGitHubAuthTokenFile(authToken) + if err != nil { + return stacktrace.Propagate(err, "An error occurred attempting to store GitHub auth token in plain text file after failing to store in keyring.") + } + return nil +} + +func (store *githubConfigStoreImpl) removeAuthToken(username string) error { + err := removeAuthTokenFromKeyring(username) + if err == nil { + return nil + } + logrus.Debugf("An error occurred removing GitHub auth token in keyring: %v\nAssuming token is in plain text file and removing from there.", err) + err = store.removeGitHubAuthTokenFile() + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing GitHub auth token from plain text file after failing to remove from keyring.") + } + return nil +} + +func (store *githubConfigStoreImpl) doesGitHubUsernameFileExist() (bool, error) { + _, err := os.Stat(store.usernameFilePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, stacktrace.Propagate(err, "An error occurred verifying if filepath '%v' exists", store.usernameFilePath) + } + return true, nil +} + +func (store *githubConfigStoreImpl) getGitHubUsernameFromFile() (string, error) { + logrus.Debugf("Github username filepath: '%v'", store.usernameFilePath) + fileContentBytes, err := os.ReadFile(store.usernameFilePath) + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred reading GitHub username file") + } + fileContentStr := string(fileContentBytes) + return fileContentStr, nil +} + +func (store *githubConfigStoreImpl) saveGitHubUsernameFile(username string) error { + fileContent := []byte(username) + logrus.Debugf("Saving git username in file...") + err := os.WriteFile(store.usernameFilePath, fileContent, githubAuthFilesPerms) + if err != nil { + return stacktrace.Propagate(err, "An error occurred writing GitHub username to file '%v'", store.usernameFilePath) + } + logrus.Debugf("Saved GitHub username file") + return nil +} + +func (store *githubConfigStoreImpl) removeGitHubUsernameFile() error { + logrus.Debugf("Removing git username in file...") + err := os.Remove(store.usernameFilePath) + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing GitHub username file '%v'", store.usernameFilePath) + } + logrus.Debugf("Removed Github username file") + return nil +} + +func (store *githubConfigStoreImpl) doesGitHubAuthTokenFileExist() (bool, error) { + _, err := os.Stat(store.authTokenFilePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, stacktrace.Propagate(err, "An error occurred verifying if filepath '%v' exists", store.authTokenFilePath) + } + return true, nil +} + +func (store *githubConfigStoreImpl) getGitHubAuthTokenFromFile() (string, error) { + fileContentBytes, err := os.ReadFile(store.authTokenFilePath) + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred reading GitHub auth token file") + } + fileContentStr := string(fileContentBytes) + return fileContentStr, nil +} + +func (store *githubConfigStoreImpl) saveGitHubAuthTokenFile(authToken string) error { + fileContent := []byte(authToken) + logrus.Debugf("Saving GitHub auth token in file...") + err := os.WriteFile(store.authTokenFilePath, fileContent, githubAuthFilesPerms) + if err != nil { + return stacktrace.Propagate(err, "An error occurred writing GitHub auth token to file '%v'", store.authTokenFilePath) + } + logrus.Debugf("Saved GitHub auth token") + return nil +} + +func (store *githubConfigStoreImpl) removeGitHubAuthTokenFile() error { + logrus.Debugf("Removing GitHub auth token file...") + err := os.Remove(store.authTokenFilePath) + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing GitHub auth token file '%v'", store.authTokenFilePath) + } + logrus.Debugf("Removed GitHub auth token file") + return nil +} + +func getAuthTokenFromKeyring(username string) (string, error) { + authToken, err := keyring.Get(kurtosisCliKeyringServiceName, username) + if err != nil && errors.Is(err, keyring.ErrNotFound) { + return "", err // don't wrap so this specific err can be detected + } + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred retrieving token for '%v' from keyring", username) + } + return authToken, nil +} + +func setAuthTokenInKeyring(username, authToken string) error { + err := keyring.Set(kurtosisCliKeyringServiceName, username, authToken) + if err != nil { + return stacktrace.Propagate(err, "An error occurred setting GitHub auth token for user '%v' in keyring.", username) + } + return nil +} + +func removeAuthTokenFromKeyring(username string) error { + err := keyring.Delete(kurtosisCliKeyringServiceName, username) + if err != nil { + return stacktrace.Propagate(err, "An error occurred removing GitHub auth token for user '%v' from keyring", username) + } + return nil +} diff --git a/cli/cli/helpers/github_auth_store/github_auth_store_test.go b/cli/cli/helpers/github_auth_store/github_auth_store_test.go new file mode 100644 index 0000000000..9d2ee9d683 --- /dev/null +++ b/cli/cli/helpers/github_auth_store/github_auth_store_test.go @@ -0,0 +1,297 @@ +package github_auth_store + +import ( + "github.com/stretchr/testify/require" + "github.com/zalando/go-keyring" + "os" + "testing" +) + +const ( + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + //DO NOT CHANGE THIS VALUE + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + expectedKeyringServiceName = "kurtosis-cli" + + tempFileDir = "" + tempUsernameFileNamePattern = "github-username" + tempAuthTokenFileNamePattern = "github-token" +) + +// The keyring service name in this package has to be always "kurtosis-cli" +// so we control that it does not change +func TestKeyringServiceNameDoesNotChange(t *testing.T) { + require.Equal(t, expectedKeyringServiceName, kurtosisCliKeyringServiceName) +} + +func TestGetUserReturnsEmptyStringIfNoUserExists(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + actualUsername, err := store.GetUser() + require.NoError(t, err) + require.Empty(t, actualUsername) +} + +func TestGetUserReturnsUser(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + expectedUsername := "john123" + _, err = tempUsernameFile.Write([]byte(expectedUsername)) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + actualUsername, err := store.GetUser() + require.NoError(t, err) + require.Equal(t, expectedUsername, actualUsername) +} + +func TestGetAuthTokenGetsTokenFromKeyring(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + expectedUsername := "john123" + expectedToken := "token" + _, err = tempUsernameFile.Write([]byte(expectedUsername)) + require.NoError(t, err) + keyring.MockInit() // changes the underlying keyring to an in memory keyring for testing + err = keyring.Set(kurtosisCliKeyringServiceName, expectedUsername, expectedToken) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + actualToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Equal(t, expectedToken, actualToken) +} + +func TestGetAuthTokenReturnsEmptyStringIfNoUserExists(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + actualToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Empty(t, actualToken) +} + +func TestGetAuthTokenGetsTokenFromFile(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + username := "john123" + expectedToken := "token" + _, err = tempUsernameFile.Write([]byte(username)) + require.NoError(t, err) + _, err = tempAuthTokenFile.Write([]byte(expectedToken)) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + actualToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Equal(t, expectedToken, actualToken) +} + +func TestGetAuthTokenReturnsNoTokenFoundIfUserExistsWithNoToken(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + _, err = tempUsernameFile.Write([]byte("john123")) + require.NoError(t, err) + keyring.MockInit() + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + _, err = store.GetAuthToken() + require.ErrorIs(t, err, NoTokenFound) +} + +func TestSetUser(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + expectedUsername := "john123" + expectedAuthToken := "password" + keyring.MockInit() + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + currentUser, err := store.GetUser() + require.NoError(t, err) + require.Empty(t, currentUser) + + err = store.SetUser(expectedUsername, expectedAuthToken) + require.NoError(t, err) + + actualUsername, err := store.GetUser() + require.NoError(t, err) + require.Equal(t, expectedUsername, actualUsername) + + actualAuthToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Equal(t, expectedAuthToken, actualAuthToken) +} + +func TestSetUserOverwritesExistingUser(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + oldUser := "john123" + oldToken := "password" + _, err = tempUsernameFile.Write([]byte(oldUser)) + require.NoError(t, err) + keyring.MockInit() + err = keyring.Set(kurtosisCliKeyringServiceName, oldUser, oldToken) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + currentUser, err := store.GetUser() + require.NoError(t, err) + require.Equal(t, oldUser, currentUser) + + newUser := "tim" + newToken := "wordpass" + err = store.SetUser(newUser, newToken) + require.NoError(t, err) + + actualNewUser, err := store.GetUser() + require.NoError(t, err) + require.Equal(t, newUser, actualNewUser) + + actualNewToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Equal(t, newToken, actualNewToken) +} + +func TestRemoveUserIsNoOpIfNoUserExists(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + err = store.RemoveUser() + require.NoError(t, err) +} + +func TestRemoveUserWithTokenInKeyring(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + expectedUsername := "john123" + expectedToken := "token" + _, err = tempUsernameFile.Write([]byte(expectedUsername)) + require.NoError(t, err) + keyring.MockInit() + err = keyring.Set(kurtosisCliKeyringServiceName, expectedUsername, expectedToken) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + err = store.RemoveUser() + require.NoError(t, err) + + username, err := store.GetUser() + require.NoError(t, err) + require.Empty(t, username) + + authToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Empty(t, authToken) +} + +func TestRemoveUserWithTokenInFile(t *testing.T) { + // setup mock GitHub store + tempUsernameFile, err := os.CreateTemp(tempFileDir, tempUsernameFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempUsernameFile.Name()) + tempAuthTokenFile, err := os.CreateTemp(tempFileDir, tempAuthTokenFileNamePattern) + require.NoError(t, err) + defer os.Remove(tempAuthTokenFile.Name()) + + expectedUsername := "john123" + expectedToken := "token" + _, err = tempUsernameFile.Write([]byte(expectedUsername)) + require.NoError(t, err) + _, err = tempAuthTokenFile.Write([]byte(expectedToken)) + require.NoError(t, err) + + // run test + store := newGitHubAuthStoreForTesting(tempUsernameFile.Name(), tempAuthTokenFile.Name()) + + err = store.RemoveUser() + require.NoError(t, err) + + username, err := store.GetUser() + require.NoError(t, err) + require.Empty(t, username) + + authToken, err := store.GetAuthToken() + require.NoError(t, err) + require.Empty(t, authToken) +} diff --git a/cli/cli/helpers/host_machine_directories/host_machine_directories.go b/cli/cli/helpers/host_machine_directories/host_machine_directories.go index 1222b28f51..d78a53dbb9 100644 --- a/cli/cli/helpers/host_machine_directories/host_machine_directories.go +++ b/cli/cli/helpers/host_machine_directories/host_machine_directories.go @@ -17,6 +17,9 @@ const ( metricsUserIDFilename = "metrics-user-id" + githubUsernameFilename = "github-username" + githubAuthTokenFilename = "github-auth-token" + userSendMetricsElection = "user-send-metrics-election" LastPesteredUserAboutOldVersionFilename = "last-pestered-user-about-old-version" @@ -147,6 +150,24 @@ func GetPortalPidFilePath() (string, error) { return portalPidFilePath, nil } +func GetGitHubUsernameFilePath() (string, error) { + xdgRelFilepath := getRelativeFilepathForXDG(githubUsernameFilename) + githubUsernameFilePath, err := xdg.StateFile(xdgRelFilepath) + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred getting Kurtosis GitHub username file path using '%s'", xdgRelFilepath) + } + return githubUsernameFilePath, nil +} + +func GetGitHubAuthTokenFilePath() (string, error) { + xdgRelFilepath := getRelativeFilepathForXDG(githubAuthTokenFilename) + githubAuthTokenFilePath, err := xdg.StateFile(xdgRelFilepath) + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred getting Kurtosis GitHub auth token file path using '%s'", xdgRelFilepath) + } + return githubAuthTokenFilePath, nil +} + // ==================================================================================================== // // Private Helper Functions diff --git a/cli/cli/helpers/metrics_user_id_store/metrics_user_id_store.go b/cli/cli/helpers/metrics_user_id_store/metrics_user_id_store.go index a64d801df8..bfdbc60a6a 100644 --- a/cli/cli/helpers/metrics_user_id_store/metrics_user_id_store.go +++ b/cli/cli/helpers/metrics_user_id_store/metrics_user_id_store.go @@ -58,7 +58,7 @@ func (store *MetricsUserIDStore) GetUserID() (string, error) { } else { userID, err = machineid.ProtectedID(applicationID) if err != nil { - return "", stacktrace.Propagate(err, "An error occurred generating anonimazed user ID") + return "", stacktrace.Propagate(err, "An error occurred generating anonymized user ID") } if err = store.saveMetricsUserIdFile(userID); err != nil { return "", stacktrace.Propagate(err, "An error occurred saving metrics user id in file") diff --git a/cli/cli/helpers/oauth/oauth.go b/cli/cli/helpers/oauth/oauth.go new file mode 100644 index 0000000000..2a4c940a75 --- /dev/null +++ b/cli/cli/helpers/oauth/oauth.go @@ -0,0 +1,168 @@ +package oauth + +import ( + "bufio" + "fmt" + "github.com/cli/cli/v2/api" + gitbrowser "github.com/cli/go-gh/v2/pkg/browser" + "github.com/cli/oauth" + "io" + "net/http" + "net/url" + "os" +) + +var ( + // The "Kurtosis CLI" OAuth app client id and secrets + + // According to GitHub, it's okay to embed the client id and secret as pointed out here: https://github.com/cli/oauth/issues/1#issuecomment-754713746 + oauthClientID = "ff28fd26dcaf1be48c45" + + // secret is actually not needed to retrieve the token, so we leave it empty + oauthClientSecret = "" + + isInteractive = true + oauthHost = "github.com" + emptyNotice = "" + defaultLauncher = "" +) + +var ( + browser = *gitbrowser.New(defaultLauncher, os.Stdout, os.Stderr) +) + +type OAuth interface { + AuthFlow() (string, string, error) +} + +// Retrieves a long-lived OAuth token from a GitHub user that authorizes Kurtosis CLI +// Returns the GitHub username, authToken or an error +func AuthFlow() (string, string, error) { + httpClient := &http.Client{} // nolint: exhaustruct + + minimumScopes := []string{"repo", "read:org", "gist"} + + callbackURI := "http://127.0.0.1/callback" + flow := &oauth.Flow{ // nolint: exhaustruct + Host: oauth.GitHubHost(fmt.Sprintf("https://%s/", oauthHost)), + ClientID: oauthClientID, + ClientSecret: oauthClientSecret, + CallbackURI: callbackURI, + Scopes: minimumScopes, + DisplayCode: func(code, verificationURL string) error { + fmt.Fprintf(os.Stdout, "First copy your one-time code: %s\n", code) + return nil + }, + BrowseURL: func(authURL string) error { + if u, err := url.Parse(authURL); err == nil { + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("invalid URL: %s", authURL) + } + } else { + return err + } + + if !isInteractive { + fmt.Fprintf(os.Stdout, "%s to continue in your web browser: %s\n", "Open this URL", authURL) + return nil + } + + fmt.Fprintf(os.Stdout, "%s to open %s in your browser... ", "Press Enter", oauthHost) + _ = waitForEnter(os.Stdin) + + if err := browser.Browse(authURL); err != nil { + fmt.Fprintf(os.Stdout, "%s Failed opening a web browser at %s\n", "!", authURL) + fmt.Fprintf(os.Stdout, " %s\n", err) + fmt.Fprint(os.Stdout, " Please try entering the URL in your browser manually\n") + } + return nil + }, + WriteSuccessHTML: func(w io.Writer) { + fmt.Fprint(w, oauthSuccessPage) + }, + HTTPClient: httpClient, + Stdin: os.Stdin, + Stdout: os.Stdout, + } + + fmt.Fprintln(os.Stdout, emptyNotice) + + token, err := flow.DetectFlow() + if err != nil { + return "", "", err + } + + userLogin, err := getViewer(oauthHost, token.Token, os.Stderr) + if err != nil { + return "", "", err + } + + return token.Token, userLogin, nil +} + +type cfg struct { + token string +} + +func (c cfg) ActiveToken(hostname string) (string, string) { + return c.token, "oauth_token" +} + +func getViewer(hostname, token string, logWriter io.Writer) (string, error) { + opts := api.HTTPClientOptions{ // nolint: exhaustruct + Config: cfg{token: token}, + Log: logWriter, + } + client, err := api.NewHTTPClient(opts) + if err != nil { + return "", err + } + return api.CurrentLoginName(api.NewClientFromHTTP(client), hostname) +} + +func waitForEnter(r io.Reader) error { + scanner := bufio.NewScanner(r) + scanner.Scan() + return scanner.Err() +} + +const oauthSuccessPage = ` + + +Success: GitHub CLI + + + +
+

Successfully authenticated Kurtosis CLI

+

You may now close this tab and return to the terminal.

+
+ +` diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go index d2ede4273b..a3210874ea 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/consts/consts.go @@ -31,6 +31,8 @@ const ( //The Docker network name where all the containers in the engine and logs service context will be added NameOfNetworkToStartEngineAndLogServiceContainersIn = "bridge" HttpApplicationProtocol = "http" + + GitHubAuthStorageDirPath = "/kurtosis-data/github-auth/" ) // This maps a Docker container's status to a binary "is the container considered running?" determiner diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go index ed9c68ee64..d663de40a9 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go @@ -2,11 +2,13 @@ package docker_kurtosis_backend import ( "context" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" "io" "sync" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + "github.com/sirupsen/logrus" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions" @@ -111,6 +113,7 @@ func (backend *DockerKurtosisBackend) CreateEngine( grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, + gitAuthToken string, ) ( *engine.Engine, error, @@ -121,9 +124,10 @@ func (backend *DockerKurtosisBackend) CreateEngine( imageVersionTag, grpcPortNum, envVars, + shouldStartInDebugMode, + gitAuthToken, backend.dockerManager, backend.objAttrsProvider, - shouldStartInDebugMode, ) } @@ -542,6 +546,10 @@ func (backend *DockerKurtosisBackend) BuildImage(ctx context.Context, imageName return backend.dockerManager.BuildImage(ctx, imageName, imageBuildSpec) } +func (backend *DockerKurtosisBackend) NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) { + return backend.dockerManager.NixBuild(ctx, nixBuildSpec) +} + // ==================================================================================================== // // Private helper functions shared by multiple subfunctions files @@ -591,3 +599,22 @@ func (backend *DockerKurtosisBackend) getEnclaveDataVolumeByEnclaveUuid(ctx cont volume := foundVolumes[0] return volume.Name, nil } + +// Guaranteed to either return a GitHub auth storage volume name or throw an error +func (backend *DockerKurtosisBackend) getGitHubAuthStorageVolume(ctx context.Context) (string, error) { + volumeSearchLabels := map[string]string{ + docker_label_key.VolumeTypeDockerLabelKey.GetString(): label_value_consts.GitHubAuthStorageVolumeTypeDockerLabelValue.GetString(), + } + foundVolumes, err := backend.dockerManager.GetVolumesByLabels(ctx, volumeSearchLabels) + if err != nil { + return "", stacktrace.Propagate(err, "An error occurred getting GitHub auth storage volumes matching labels '%+v'", volumeSearchLabels) + } + if len(foundVolumes) > 1 { + return "", stacktrace.NewError("Found multiple GitHub auth storage volumes. This should never happen") + } + if len(foundVolumes) == 0 { + return "", stacktrace.NewError("No GitHub auth storage volume found.") + } + volume := foundVolumes[0] + return volume.Name, nil +} diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend_api_container_functions.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend_api_container_functions.go index bfbf53d0a2..4f7d670833 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend_api_container_functions.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend_api_container_functions.go @@ -72,6 +72,11 @@ func (backend *DockerKurtosisBackend) CreateAPIContainer( return nil, stacktrace.Propagate(err, "An error occurred getting the enclave data volume for enclave '%v'", enclaveUuid) } + githubAuthStorageVolumeName, err := backend.getGitHubAuthStorageVolume(ctx) + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred getting the GitHub auth storage volume name.") + } + // Get the Docker network ID where we'll start the new API container enclaveNetwork, err := backend.getEnclaveNetworkByEnclaveUuid(ctx, enclaveUuid) if err != nil { @@ -187,7 +192,8 @@ func (backend *DockerKurtosisBackend) CreateAPIContainer( } volumeMounts := map[string]string{ - enclaveDataVolumeName: enclaveDataVolumeDirpath, + enclaveDataVolumeName: enclaveDataVolumeDirpath, + githubAuthStorageVolumeName: consts.GitHubAuthStorageDirPath, } labelStrs := map[string]string{} diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go index b4986f9d69..c3aad229f4 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/create_engine.go @@ -3,6 +3,7 @@ package engine_functions import ( "context" "fmt" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/github_auth_storage_creator" "time" "github.com/docker/go-connections/nat" @@ -30,8 +31,7 @@ const ( engineDebugServerPort = 50102 // in ClI this is 50101 and 50103 for the APIC maxWaitForEngineAvailabilityRetries = 10 timeBetweenWaitForEngineAvailabilityRetries = 1 * time.Second - logsStorageDirpath = "/var/log/kurtosis/" - removeLogsWaitHours = 6 * time.Hour + logsStorageDirPath = "/var/log/kurtosis/" ) func CreateEngine( @@ -40,9 +40,10 @@ func CreateEngine( imageVersionTag string, grpcPortNum uint16, envVars map[string]string, + shouldStartInDebugMode bool, + gitAuthToken string, dockerManager *docker_manager.DockerManager, objAttrsProvider object_attributes_provider.DockerObjectAttributesProvider, - shouldStartInDebugMode bool, ) ( *engine.Engine, error, @@ -239,13 +240,34 @@ func CreateEngine( usedPorts[debugServerDockerPort] = docker_manager.NewManualPublishingSpec(uint16(engineDebugServerPort)) } + // Configure GitHub Auth by writing the provided token to a volume that's accessible by the engine + githubAuthStorageVolObjAttrs, err := objAttrsProvider.ForGitHubAuthStorageVolume() + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred retrieving object attributes for GitHub auth storage.") + } + githubAuthStorageVolNameStr := githubAuthStorageVolObjAttrs.GetName().GetString() + githubAuthStorageVolLabelStrs := map[string]string{} + for labelKey, labelValue := range githubAuthStorageVolObjAttrs.GetLabels() { + githubAuthStorageVolLabelStrs[labelKey.GetString()] = labelValue.GetString() + } + // This volume is created idempotently (like logs storage volume) and just write the token to the file everytime the engine starts + if err = dockerManager.CreateVolume(ctx, githubAuthStorageVolNameStr, githubAuthStorageVolLabelStrs); err != nil { + return nil, stacktrace.Propagate(err, "An error occurred creating GitHub auth storage volume.") + } + githubAuthStorageCreator := github_auth_storage_creator.NewGitHubAuthStorageCreator(gitAuthToken) + err = githubAuthStorageCreator.CreateGitHubAuthStorage(ctx, targetNetworkId, githubAuthStorageVolNameStr, consts.GitHubAuthStorageDirPath, dockerManager) + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred creating GitHub auth storage.") + } + bindMounts := map[string]string{ // Necessary so that the engine server can interact with the Docker engine consts.DockerSocketFilepath: consts.DockerSocketFilepath, } volumeMounts := map[string]string{ - logsStorageVolNameStr: logsStorageDirpath, + logsStorageVolNameStr: logsStorageDirPath, + githubAuthStorageVolNameStr: consts.GitHubAuthStorageDirPath, } if serverArgs.OnBastionHost { diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/github_auth_storage_creator/github_auth_storage_creator.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/github_auth_storage_creator/github_auth_storage_creator.go new file mode 100644 index 0000000000..6fb2290a2a --- /dev/null +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/github_auth_storage_creator/github_auth_storage_creator.go @@ -0,0 +1,153 @@ +package github_auth_storage_creator + +import ( + "bytes" + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_manager" + "github.com/kurtosis-tech/stacktrace" + "github.com/sirupsen/logrus" + "time" +) + +const ( + // We use this image and version because we already are using this in other projects so there is a high probability + // that the image is in the local machine's cache + creatorContainerImage = "alpine:3.17" + creatorContainerName = "kurtosis-github-auth-storage-creator" + + shBinaryFilepath = "/bin/sh" + shCmdFlag = "-c" + printfCmdName = "printf" + + authStorageCreationSuccessExitCode = 0 + + authStorageCreationCmdMaxRetries = 2 + authStorageCreationCmdDelayInRetries = 200 * time.Millisecond + + authTokenFilename = "token.txt" + + sleepSeconds = 1800 +) + +type GitHubAuthStorageCreator struct { + token string +} + +func NewGitHubAuthStorageCreator(token string) *GitHubAuthStorageCreator { + return &GitHubAuthStorageCreator{token: token} +} + +func (creator *GitHubAuthStorageCreator) CreateGitHubAuthStorage( + ctx context.Context, + targetNetworkId string, + volumeName string, + githubAuthStorageDirPath string, + dockerManager *docker_manager.DockerManager, +) error { + entrypointArgs := []string{ + shBinaryFilepath, + shCmdFlag, + fmt.Sprintf("sleep %v", sleepSeconds), + } + + volumeMounts := map[string]string{ + volumeName: githubAuthStorageDirPath, + } + + createAndStartArgs := docker_manager.NewCreateAndStartContainerArgsBuilder( + creatorContainerImage, + creatorContainerName, + targetNetworkId, + ).WithEntrypointArgs( + entrypointArgs, + ).WithVolumeMounts( + volumeMounts, + ).Build() + + containerId, _, err := dockerManager.CreateAndStartContainer(ctx, createAndStartArgs) + if err != nil { + return stacktrace.Propagate(err, "An error occurred starting the GitHub Auth Storage Creator container with these args '%+v'", createAndStartArgs) + } + //The killing step has to be executed always in the success and also in the failed case + defer func() { + if err = dockerManager.RemoveContainer(context.Background(), containerId); err != nil { + logrus.Errorf( + "Launching the GitHub Auth Storage Creator container with container ID '%v' didn't complete successfully so we "+ + "tried to remove the container we started, but doing so exited with an error:\n%v", + containerId, + err) + logrus.Errorf("ACTION REQUIRED: You'll need to manually remove the container with ID '%v'!!!!!!", containerId) + } + }() + + if err := creator.storeTokenInVolume( + ctx, + dockerManager, + containerId, + authStorageCreationCmdMaxRetries, + authStorageCreationCmdDelayInRetries, + githubAuthStorageDirPath, + ); err != nil { + return stacktrace.Propagate(err, "An error occurred creating GitHub auth storage in volume.") + } + + return nil +} + +func (creator *GitHubAuthStorageCreator) storeTokenInVolume( + ctx context.Context, + dockerManager *docker_manager.DockerManager, + containerId string, + maxRetries uint, + timeBetweenRetries time.Duration, + githubAuthStorageDirPath string, +) error { + commandStr := fmt.Sprintf( + "%v '%v' > %v", + printfCmdName, + creator.token, + fmt.Sprintf("%s/%s", githubAuthStorageDirPath, authTokenFilename), + ) + + execCmd := []string{ + shBinaryFilepath, + shCmdFlag, + commandStr, + } + for i := uint(0); i < maxRetries; i++ { + outputBuffer := &bytes.Buffer{} + exitCode, err := dockerManager.RunExecCommand(ctx, containerId, execCmd, outputBuffer) + if err == nil { + if exitCode == authStorageCreationSuccessExitCode { + logrus.Debugf("The GitHub auth token was successfully added into the volume.") + return nil + } + logrus.Debugf( + "GitHub auth storage creation command '%v' returned without a Docker error, but exited with non-%v exit code '%v' and logs:\n%v", + commandStr, + authStorageCreationSuccessExitCode, + exitCode, + outputBuffer.String(), + ) + } else { + logrus.Debugf( + "GitHub auth storage creation command '%v' experienced a Docker error:\n%v", + commandStr, + err, + ) + } + + // Tiny optimization to not sleep if we're not going to run the loop again + if i < maxRetries { + time.Sleep(timeBetweenRetries) + } + } + + return stacktrace.NewError( + "The GitHub auth storage creation didn't return success (as measured by the command '%v') even after retrying %v times with %v between retries", + commandStr, + maxRetries, + timeBetweenRetries, + ) +} diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/stop_engines.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/stop_engines.go index b6050fbc7b..67c0ae8f05 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/stop_engines.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/engine_functions/stop_engines.go @@ -2,7 +2,6 @@ package engine_functions import ( "context" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/logs_aggregator_functions" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/reverse_proxy_functions" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/docker/docker_manager" @@ -14,8 +13,7 @@ import ( func StopEngines( ctx context.Context, filters *engine.EngineFilters, - dockerManager *docker_manager.DockerManager, -) ( + dockerManager *docker_manager.DockerManager) ( resultSuccessfulEngineGuids map[engine.EngineGUID]bool, resultErroredEngineGuids map[engine.EngineGUID]error, resultErr error, diff --git a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go index ad5e386806..cdcf06fac3 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go @@ -9,22 +9,28 @@ import ( "bufio" "bytes" "context" + "crypto/md5" "encoding/json" "fmt" - "github.com/docker/docker/api/types/registry" - "github.com/docker/go-units" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/uuid_generator" - "github.com/kurtosis-tech/kurtosis/utils" "io" "math" "net" + "os" + "os/exec" "regexp" "strings" "sync" "time" + "github.com/docker/docker/api/types/registry" + "github.com/docker/go-units" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/image_utils" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/uuid_generator" + "github.com/kurtosis-tech/kurtosis/utils" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -152,6 +158,8 @@ const ( // Per https://github.com/hashicorp/waypoint/pull/1937/files buildkitSessionSharedKey = "" + + nixCmdPath = "/nix/var/nix/profiles/default/bin/nix" ) type RestartPolicy string @@ -1313,6 +1321,58 @@ func (manager *DockerManager) FetchImage(ctx context.Context, image string, regi return pulledFromRemote, imageArchitecture, nil } +func (manager *DockerManager) NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) { + flakeReference := nixBuildSpec.GetFullFlakeReference() + + // Flake generates a link to the nix store containing the image result, to avoid collision with a possible existing one from the + // build context (from the user env) and which would result when trying to overwrite it, we create a unique one + hasher := md5.New() + hasher.Write([]byte(flakeReference)) + resultLink := fmt.Sprintf("nix-result-%x", hasher.Sum(nil)) + + cmd := exec.Command( + nixCmdPath, "build", flakeReference, + "--print-out-paths", + "--extra-experimental-features", "flakes nix-command", + "--out-link", resultLink) + + var errBuffer strings.Builder + cmd.Stderr = &errBuffer + imageFileRaw, err := cmd.Output() + if err != nil { + errMsg := errBuffer.String() + logrus.WithError(err).Error(errMsg) + return "", stacktrace.Propagate(err, "Failed to build nix image with Nix.") + } + imageFile := strings.TrimSpace(string(imageFileRaw)) + logrus.Debugf("Nix flake image on attribute %s, result on image file %s", flakeReference, imageFile) + + imageTags, err := image_utils.GetRepoTags(imageFile) + if err != nil { + return "", stacktrace.Propagate(err, "Failed to get image tags from Nix image %s", imageFile) + } + + if len(imageTags) == 0 { + return "", stacktrace.NewError("Generated image %s did not have any tags", imageFile) + } else if len(imageTags) > 1 { + logrus.Warnf("Generated image %s had multiple tags: %v. We'll select the first.", imageFile, imageTags) + } + imageTag := imageTags[0] + + image, err := os.Open(imageFile) + if err != nil { + return "", stacktrace.Propagate(err, "Failed to open generated Nix image on %s", imageFile) + } + + _, err = manager.dockerClient.ImageLoad(ctx, image, false) + if err != nil { + return "", stacktrace.Propagate(err, "Failed to load Nix image %s in docker", imageFile) + } + logrus.Debugf("Nix generated image file %s is loaded into docker", imageFile) + + return imageTag, nil +} + func (manager *DockerManager) BuildImage(ctx context.Context, imageName string, imageBuildSpec *image_build_spec.ImageBuildSpec) (string, error) { buildContextDirPath := imageBuildSpec.GetBuildContextDir() buildContextTarReader, err := getBuildContextReader(buildContextDirPath) diff --git a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/label_value_consts/label_value_consts.go b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/label_value_consts/label_value_consts.go index 9b830cf813..c30e9d1a46 100644 --- a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/label_value_consts/label_value_consts.go +++ b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/label_value_consts/label_value_consts.go @@ -27,6 +27,7 @@ const ( persistentDirectoryVolumeTypeLabelValueStr = "persistent-directory" logsStorageVolumeTypeLabelValueStr = "kurtosis-logs-storage" logsCollectorVolumeTypeLabelValueStr = "logs-collector-data" + githubAuthStorageVolumeTypeLabelValueStr = "github-auth-storage" ) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DO NOT CHANGE THESE VALUES !!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -52,3 +53,4 @@ var FilesArtifactExpansionVolumeTypeDockerLabelValue = docker_label_value.MustCr var PersistentDirectoryVolumeTypeDockerLabelValue = docker_label_value.MustCreateNewDockerLabelValue(persistentDirectoryVolumeTypeLabelValueStr) var LogsStorageVolumeTypeDockerLabelValue = docker_label_value.MustCreateNewDockerLabelValue(logsStorageVolumeTypeLabelValueStr) var LogsCollectorVolumeTypeDockerLabelValue = docker_label_value.MustCreateNewDockerLabelValue(logsCollectorVolumeTypeLabelValueStr) +var GitHubAuthStorageVolumeTypeDockerLabelValue = docker_label_value.MustCreateNewDockerLabelValue(githubAuthStorageVolumeTypeLabelValueStr) diff --git a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go index a0691ad4e8..5c9af7fada 100644 --- a/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go +++ b/container-engine-lib/lib/backend_impls/docker/object_attributes_provider/object_attributes_provider.go @@ -17,11 +17,12 @@ import ( ) const ( - engineServerNamePrefix = "kurtosis-engine" - logsAggregatorName = "kurtosis-logs-aggregator" - logsStorageVolumeName = "kurtosis-logs-storage" - engineRESTAPIPortStr = "engine-rest-api" - reverseProxyNamePrefix = "kurtosis-reverse-proxy" + engineServerNamePrefix = "kurtosis-engine" + logsAggregatorName = "kurtosis-logs-aggregator" + logsStorageVolumeName = "kurtosis-logs-storage" + githubAuthStorageVolumeName = "kurtosis-github-auth-storage" + engineRESTAPIPortStr = "engine-rest-api" + reverseProxyNamePrefix = "kurtosis-reverse-proxy" ) type DockerObjectAttributesProvider interface { @@ -36,6 +37,7 @@ type DockerObjectAttributesProvider interface { ForLogsAggregator() (DockerObjectAttributes, error) ForLogsStorageVolume() (DockerObjectAttributes, error) ForReverseProxy(engineGuid engine.EngineGUID) (DockerObjectAttributes, error) + ForGitHubAuthStorageVolume() (DockerObjectAttributes, error) } func GetDockerObjectAttributesProvider() DockerObjectAttributesProvider { @@ -153,6 +155,23 @@ func (provider *dockerObjectAttributesProviderImpl) ForLogsStorageVolume() (Dock return objectAttributes, nil } +func (provider *dockerObjectAttributesProviderImpl) ForGitHubAuthStorageVolume() (DockerObjectAttributes, error) { + name, err := docker_object_name.CreateNewDockerObjectName(githubAuthStorageVolumeName) + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred creating a Docker object name object from string '%v'", githubAuthStorageVolumeName) + } + + labels := map[*docker_label_key.DockerLabelKey]*docker_label_value.DockerLabelValue{ + docker_label_key.VolumeTypeDockerLabelKey: label_value_consts.GitHubAuthStorageVolumeTypeDockerLabelValue, + } + + objectAttributes, err := newDockerObjectAttributesImpl(name, labels) + if err != nil { + return nil, stacktrace.Propagate(err, "An error occurred while creating the ObjectAttributesImpl with the name '%s' and labels '%+v'", name, labels) + } + return objectAttributes, nil +} + func (provider *dockerObjectAttributesProviderImpl) ForReverseProxy(engineGuid engine.EngineGUID) (DockerObjectAttributes, error) { nameStr := strings.Join( diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go index ce6008782b..3c8f9e9207 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions/create_engine.go @@ -34,6 +34,7 @@ var noWait *port_spec.Wait = nil // TODO add support for passing toleration to Engine var noToleration []apiv1.Toleration = nil +var noSelectors map[string]string = nil func CreateEngine( ctx context.Context, @@ -41,9 +42,11 @@ func CreateEngine( imageVersionTag string, grpcPortNum uint16, envVars map[string]string, + _ bool, //It's not required to add extra configuration in K8S for enabling the debug server + githubAuthToken string, kubernetesManager *kubernetes_manager.KubernetesManager, objAttrsProvider object_attributes_provider.KubernetesObjectAttributesProvider, - _ bool, //It's not required to add extra configuration in K8S for enabling the debug server + ) ( *engine.Engine, error, @@ -318,6 +321,7 @@ func createEngineClusterRole( } clusterRoleName := clusterRolesAttributes.GetName().GetString() clusterRoleLabels := shared_helpers.GetStringMapFromLabelMap(clusterRolesAttributes.GetLabels()) + // nolint: exhaustruct clusterRolePolicyRules := []rbacv1.PolicyRule{ { Verbs: []string{ @@ -385,6 +389,7 @@ func createEngineClusterRoleBindings( } clusterRoleBindingsName := clusterRoleBindingsAttributes.GetName().GetString() clusterRoleBindingsLabels := shared_helpers.GetStringMapFromLabelMap(clusterRoleBindingsAttributes.GetLabels()) + // nolint: exhaustruct clusterRoleBindingsSubjects := []rbacv1.Subject{ { Kind: rbacv1.ServiceAccountKind, @@ -449,6 +454,7 @@ func createEnginePod( } engineContainerEnvVars = append(engineContainerEnvVars, envVar) } + // nolint: exhaustruct engineContainers := []apiv1.Container{ { Name: kurtosisEngineContainerName, @@ -475,6 +481,7 @@ func createEnginePod( // Engine doesn't auto restart apiv1.RestartPolicyNever, noToleration, + noSelectors, ) if err != nil { return nil, nil, stacktrace.Propagate(err, "An error occurred while creating the pod with name '%s' in namespace '%s' with image '%s'", enginePodName, namespace, containerImageAndTag) diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go index b9bd72017f..8f835e4e7e 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go @@ -2,9 +2,11 @@ package kubernetes_kurtosis_backend import ( "context" + "io" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" - "io" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" apiv1 "k8s.io/api/core/v1" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/engine_functions" @@ -132,6 +134,7 @@ func (backend *KubernetesKurtosisBackend) CreateEngine( grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, + githubAuthToken string, ) ( *engine.Engine, error, @@ -142,9 +145,10 @@ func (backend *KubernetesKurtosisBackend) CreateEngine( imageVersionTag, grpcPortNum, envVars, + shouldStartInDebugMode, + githubAuthToken, backend.kubernetesManager, backend.objAttrsProvider, - shouldStartInDebugMode, ) if err != nil { return nil, stacktrace.Propagate( @@ -485,6 +489,11 @@ func (backend *KubernetesKurtosisBackend) BuildImage(ctx context.Context, imageN return "", stacktrace.NewError("Building images isn't yet implemented in Kubernetes.") } +func (backend *KubernetesKurtosisBackend) NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) { + // TODO IMPLEMENT + return "", stacktrace.NewError("Nix image building isn't yet implemented in Kubernetes.") +} + // ==================================================================================================== // // Private Helper Functions diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend_api_container_functions.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend_api_container_functions.go index 2d6fafa414..0cfe1986f4 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend_api_container_functions.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend_api_container_functions.go @@ -3,6 +3,9 @@ package kubernetes_kurtosis_backend import ( "context" "fmt" + "net" + "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/consts" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/shared_helpers" kubernetes_manager_consts "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager/consts" @@ -17,8 +20,6 @@ import ( apiv1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" applyconfigurationsv1 "k8s.io/client-go/applyconfigurations/core/v1" - "net" - "time" ) const ( @@ -45,6 +46,7 @@ var noWait *port_spec.Wait = nil // TODO add support for passing toleration to APIC var noTolerations []apiv1.Toleration = nil +var noSelectors map[string]string = nil // TODO: MIGRATE THIS FOLDER TO USE STRUCTURE OF USER_SERVICE_FUNCTIONS MODULE @@ -232,6 +234,7 @@ func (backend *KubernetesKurtosisBackend) CreateAPIContainer( clusterRoleName := clusterRolesAttributes.GetName().GetString() clusterRoleLabels := shared_helpers.GetStringMapFromLabelMap(clusterRolesAttributes.GetLabels()) + // nolint: exhaustruct clusterRolePolicyRules := []rbacv1.PolicyRule{ { Verbs: []string{ @@ -291,6 +294,7 @@ func (backend *KubernetesKurtosisBackend) CreateAPIContainer( clusterRoleBindingName := clusterRoleBindingsAttributes.GetName().GetString() clusterRoleBindingsLabels := shared_helpers.GetStringMapFromLabelMap(clusterRoleBindingsAttributes.GetLabels()) + // nolint: exhaustruct clusterRoleBindingsSubjects := []rbacv1.Subject{ { Kind: rbacv1.ServiceAccountKind, @@ -334,6 +338,7 @@ func (backend *KubernetesKurtosisBackend) CreateAPIContainer( roleName := rolesAttributes.GetName().GetString() roleLabels := shared_helpers.GetStringMapFromLabelMap(rolesAttributes.GetLabels()) + // nolint: exhaustruct rolePolicyRules := []rbacv1.PolicyRule{ { Verbs: []string{ @@ -393,6 +398,7 @@ func (backend *KubernetesKurtosisBackend) CreateAPIContainer( roleBindingName := roleBindingsAttributes.GetName().GetString() roleBindingsLabels := shared_helpers.GetStringMapFromLabelMap(roleBindingsAttributes.GetLabels()) + // nolint: exhaustruct roleBindingsSubjects := []rbacv1.Subject{ { Kind: rbacv1.ServiceAccountKind, @@ -482,6 +488,7 @@ func (backend *KubernetesKurtosisBackend) CreateAPIContainer( apiContainerServiceAccountName, apiContainerRestartPolicy, noTolerations, + noSelectors, ) if err != nil { errMsg := fmt.Sprintf("An error occurred while creating the pod with name '%s' in namespace '%s' with image '%s'", apiContainerPodName, enclaveNamespaceName, image) @@ -1083,6 +1090,7 @@ func getApiContainerContainersAndVolumes( } containerEnvVars = append(containerEnvVars, ownNamespaceEnvVar) + // nolint: exhaustruct containers := []apiv1.Container{ { Name: kurtosisApiContainerContainerName, diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go index 7fdfd4ea44..5c4a2c97a2 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go @@ -3,9 +3,10 @@ package user_services_functions import ( "context" "fmt" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "strings" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/consts" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/shared_helpers" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager" @@ -309,6 +310,7 @@ func createStartServiceOperation( minMemoryAllocationMegabytes := serviceConfig.GetMinMemoryAllocationMegabytes() user := serviceConfig.GetUser() tolerations := serviceConfig.GetTolerations() + nodeSelectors := serviceConfig.GetNodeSelectors() matchingObjectAndResources, found := servicesObjectsAndResources[serviceUuid] if !found { @@ -417,6 +419,7 @@ func createStartServiceOperation( userServiceServiceAccountName, restartPolicy, tolerations, + nodeSelectors, ) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating pod '%v' using image '%v'", podName, containerImageName) @@ -679,7 +682,7 @@ func getUserServicePodContainerSpecs( Limits: resourceLimitsList, Requests: resourceRequestsList, } - + // nolint: exhaustruct containers := []apiv1.Container{ { Name: userServiceContainerName, @@ -869,6 +872,7 @@ func createRegisterUserServiceOperation( // Kubernetes doesn't allow us to create services without any ports, so we need to set this to a notional value // until the user calls StartService + // nolint: exhaustruct notionalServicePorts := []apiv1.ServicePort{ { Name: unboundPortName, diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager/kubernetes_manager.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager/kubernetes_manager.go index 2b37cd6f68..9f45531ccf 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager/kubernetes_manager.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_manager/kubernetes_manager.go @@ -1049,6 +1049,7 @@ func (manager *KubernetesManager) CreatePod( podServiceAccountName string, restartPolicy apiv1.RestartPolicy, tolerations []apiv1.Toleration, + nodeSelectors map[string]string, ) (*apiv1.Pod, error) { podClient := manager.kubernetesClientSet.CoreV1().Pods(namespaceName) @@ -1080,7 +1081,7 @@ func (manager *KubernetesManager) CreatePod( TerminationGracePeriodSeconds: nil, ActiveDeadlineSeconds: nil, DNSPolicy: "", - NodeSelector: nil, + NodeSelector: nodeSelectors, ServiceAccountName: podServiceAccountName, DeprecatedServiceAccount: "", AutomountServiceAccountToken: nil, diff --git a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go index dcdbbf3b74..c96e23449e 100644 --- a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go @@ -2,11 +2,13 @@ package metrics_reporting import ( "context" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" "io" "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/api_container" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/compute_resources" @@ -53,6 +55,7 @@ func (backend *MetricsReportingKurtosisBackend) CreateEngine( grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, + githubAuthToken string, ) (*engine.Engine, error) { result, err := backend.underlying.CreateEngine( ctx, @@ -61,6 +64,7 @@ func (backend *MetricsReportingKurtosisBackend) CreateEngine( grpcPortNum, envVars, shouldStartInDebugMode, + githubAuthToken, ) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating the engine using image '%v' with tag '%v' and debug mode '%v'", imageOrgAndRepo, imageVersionTag, shouldStartInDebugMode) @@ -468,3 +472,7 @@ func (backend *MetricsReportingKurtosisBackend) GetAvailableCPUAndMemory(ctx con func (backend *MetricsReportingKurtosisBackend) BuildImage(ctx context.Context, imageName string, imageBuildSpec *image_build_spec.ImageBuildSpec) (string, error) { return backend.underlying.BuildImage(ctx, imageName, imageBuildSpec) } + +func (backend *MetricsReportingKurtosisBackend) NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) { + return backend.underlying.NixBuild(ctx, nixBuildSpec) +} diff --git a/container-engine-lib/lib/backend_interface/kurtosis_backend.go b/container-engine-lib/lib/backend_interface/kurtosis_backend.go index 0e23bf4f8e..71b3c16394 100644 --- a/container-engine-lib/lib/backend_interface/kurtosis_backend.go +++ b/container-engine-lib/lib/backend_interface/kurtosis_backend.go @@ -2,11 +2,13 @@ package backend_interface import ( "context" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" "io" "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/api_container" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/compute_resources" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/enclave" @@ -47,6 +49,7 @@ type KurtosisBackend interface { grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, + githubAuthToken string, ) ( *engine.Engine, error, @@ -355,4 +358,6 @@ type KurtosisBackend interface { // BuildImage builds a container image based on the [imageBuildSpec] with [imageName] // Returns image architecture and if error occurred BuildImage(ctx context.Context, imageName string, imageBuildSpec *image_build_spec.ImageBuildSpec) (string, error) + + NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) } diff --git a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go index 554300cdae..63260d872b 100644 --- a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.36.0. DO NOT EDIT. package backend_interface @@ -28,6 +28,8 @@ import ( mock "github.com/stretchr/testify/mock" + nix_build_spec "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + reverse_proxy "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/reverse_proxy" service "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -52,10 +54,6 @@ func (_m *MockKurtosisBackend) EXPECT() *MockKurtosisBackend_Expecter { func (_m *MockKurtosisBackend) BuildImage(ctx context.Context, imageName string, imageBuildSpec *image_build_spec.ImageBuildSpec) (string, error) { ret := _m.Called(ctx, imageName, imageBuildSpec) - if len(ret) == 0 { - panic("no return value specified for BuildImage") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, *image_build_spec.ImageBuildSpec) (string, error)); ok { @@ -110,10 +108,6 @@ func (_c *MockKurtosisBackend_BuildImage_Call) RunAndReturn(run func(context.Con func (_m *MockKurtosisBackend) CopyFilesFromUserService(ctx context.Context, enclaveUuid enclave.EnclaveUUID, serviceUuid service.ServiceUUID, srcPathOnService string, output io.Writer) error { ret := _m.Called(ctx, enclaveUuid, serviceUuid, srcPathOnService, output) - if len(ret) == 0 { - panic("no return value specified for CopyFilesFromUserService") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, service.ServiceUUID, string, io.Writer) error); ok { r0 = rf(ctx, enclaveUuid, serviceUuid, srcPathOnService, output) @@ -160,14 +154,6 @@ func (_c *MockKurtosisBackend_CopyFilesFromUserService_Call) RunAndReturn(run fu func (_m *MockKurtosisBackend) CreateAPIContainer(ctx context.Context, image string, enclaveUuid enclave.EnclaveUUID, grpcPortNum uint16, enclaveDataVolumeDirpath string, ownIpAddressEnvVar string, customEnvVars map[string]string, shouldStartInDebugMode bool) (*api_container.APIContainer, error) { ret := _m.Called(ctx, image, enclaveUuid, grpcPortNum, enclaveDataVolumeDirpath, ownIpAddressEnvVar, customEnvVars, shouldStartInDebugMode) - if len(ret) == 0 { - panic("no return value specified for CreateAPIContainer") - } - - if len(ret) == 0 { - panic("no return value specified for CreateAPIContainer") - } - var r0 *api_container.APIContainer var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, enclave.EnclaveUUID, uint16, string, string, map[string]string, bool) (*api_container.APIContainer, error)); ok { @@ -229,10 +215,6 @@ func (_c *MockKurtosisBackend_CreateAPIContainer_Call) RunAndReturn(run func(con func (_m *MockKurtosisBackend) CreateEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID, enclaveName string) (*enclave.Enclave, error) { ret := _m.Called(ctx, enclaveUuid, enclaveName) - if len(ret) == 0 { - panic("no return value specified for CreateEnclave") - } - var r0 *enclave.Enclave var r1 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, string) (*enclave.Enclave, error)); ok { @@ -285,29 +267,25 @@ func (_c *MockKurtosisBackend_CreateEnclave_Call) RunAndReturn(run func(context. return _c } -// CreateEngine provides a mock function with given fields: ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode -func (_m *MockKurtosisBackend) CreateEngine(ctx context.Context, imageOrgAndRepo string, imageVersionTag string, grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool) (*engine.Engine, error) { - ret := _m.Called(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode) - - if len(ret) == 0 { - panic("no return value specified for CreateEngine") - } +// CreateEngine provides a mock function with given fields: ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken +func (_m *MockKurtosisBackend) CreateEngine(ctx context.Context, imageOrgAndRepo string, imageVersionTag string, grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, githubAuthToken string) (*engine.Engine, error) { + ret := _m.Called(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken) var r0 *engine.Engine var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint16, map[string]string, bool) (*engine.Engine, error)); ok { - return rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode) + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint16, map[string]string, bool, string) (*engine.Engine, error)); ok { + return rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken) } - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint16, map[string]string, bool) *engine.Engine); ok { - r0 = rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode) + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint16, map[string]string, bool, string) *engine.Engine); ok { + r0 = rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*engine.Engine) } } - if rf, ok := ret.Get(1).(func(context.Context, string, string, uint16, map[string]string, bool) error); ok { - r1 = rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode) + if rf, ok := ret.Get(1).(func(context.Context, string, string, uint16, map[string]string, bool, string) error); ok { + r1 = rf(ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken) } else { r1 = ret.Error(1) } @@ -327,13 +305,14 @@ type MockKurtosisBackend_CreateEngine_Call struct { // - grpcPortNum uint16 // - envVars map[string]string // - shouldStartInDebugMode bool -func (_e *MockKurtosisBackend_Expecter) CreateEngine(ctx interface{}, imageOrgAndRepo interface{}, imageVersionTag interface{}, grpcPortNum interface{}, envVars interface{}, shouldStartInDebugMode interface{}) *MockKurtosisBackend_CreateEngine_Call { - return &MockKurtosisBackend_CreateEngine_Call{Call: _e.mock.On("CreateEngine", ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode)} +// - githubAuthToken string +func (_e *MockKurtosisBackend_Expecter) CreateEngine(ctx interface{}, imageOrgAndRepo interface{}, imageVersionTag interface{}, grpcPortNum interface{}, envVars interface{}, shouldStartInDebugMode interface{}, githubAuthToken interface{}) *MockKurtosisBackend_CreateEngine_Call { + return &MockKurtosisBackend_CreateEngine_Call{Call: _e.mock.On("CreateEngine", ctx, imageOrgAndRepo, imageVersionTag, grpcPortNum, envVars, shouldStartInDebugMode, githubAuthToken)} } -func (_c *MockKurtosisBackend_CreateEngine_Call) Run(run func(ctx context.Context, imageOrgAndRepo string, imageVersionTag string, grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool)) *MockKurtosisBackend_CreateEngine_Call { +func (_c *MockKurtosisBackend_CreateEngine_Call) Run(run func(ctx context.Context, imageOrgAndRepo string, imageVersionTag string, grpcPortNum uint16, envVars map[string]string, shouldStartInDebugMode bool, githubAuthToken string)) *MockKurtosisBackend_CreateEngine_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(uint16), args[4].(map[string]string), args[5].(bool)) + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(uint16), args[4].(map[string]string), args[5].(bool), args[6].(string)) }) return _c } @@ -343,7 +322,7 @@ func (_c *MockKurtosisBackend_CreateEngine_Call) Return(_a0 *engine.Engine, _a1 return _c } -func (_c *MockKurtosisBackend_CreateEngine_Call) RunAndReturn(run func(context.Context, string, string, uint16, map[string]string, bool) (*engine.Engine, error)) *MockKurtosisBackend_CreateEngine_Call { +func (_c *MockKurtosisBackend_CreateEngine_Call) RunAndReturn(run func(context.Context, string, string, uint16, map[string]string, bool, string) (*engine.Engine, error)) *MockKurtosisBackend_CreateEngine_Call { _c.Call.Return(run) return _c } @@ -352,10 +331,6 @@ func (_c *MockKurtosisBackend_CreateEngine_Call) RunAndReturn(run func(context.C func (_m *MockKurtosisBackend) CreateLogsAggregator(ctx context.Context) (*logs_aggregator.LogsAggregator, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for CreateLogsAggregator") - } - var r0 *logs_aggregator.LogsAggregator var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*logs_aggregator.LogsAggregator, error)); ok { @@ -410,10 +385,6 @@ func (_c *MockKurtosisBackend_CreateLogsAggregator_Call) RunAndReturn(run func(c func (_m *MockKurtosisBackend) CreateLogsCollectorForEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID, logsCollectorHttpPortNumber uint16, logsCollectorTcpPortNumber uint16) (*logs_collector.LogsCollector, error) { ret := _m.Called(ctx, enclaveUuid, logsCollectorHttpPortNumber, logsCollectorTcpPortNumber) - if len(ret) == 0 { - panic("no return value specified for CreateLogsCollectorForEnclave") - } - var r0 *logs_collector.LogsCollector var r1 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, uint16, uint16) (*logs_collector.LogsCollector, error)); ok { @@ -471,10 +442,6 @@ func (_c *MockKurtosisBackend_CreateLogsCollectorForEnclave_Call) RunAndReturn(r func (_m *MockKurtosisBackend) CreateReverseProxy(ctx context.Context, engineGuid engine.EngineGUID) (*reverse_proxy.ReverseProxy, error) { ret := _m.Called(ctx, engineGuid) - if len(ret) == 0 { - panic("no return value specified for CreateReverseProxy") - } - var r0 *reverse_proxy.ReverseProxy var r1 error if rf, ok := ret.Get(0).(func(context.Context, engine.EngineGUID) (*reverse_proxy.ReverseProxy, error)); ok { @@ -530,10 +497,6 @@ func (_c *MockKurtosisBackend_CreateReverseProxy_Call) RunAndReturn(run func(con func (_m *MockKurtosisBackend) DestroyAPIContainers(ctx context.Context, filters *api_container.APIContainerFilters) (map[enclave.EnclaveUUID]bool, map[enclave.EnclaveUUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for DestroyAPIContainers") - } - var r0 map[enclave.EnclaveUUID]bool var r1 map[enclave.EnclaveUUID]error var r2 error @@ -598,10 +561,6 @@ func (_c *MockKurtosisBackend_DestroyAPIContainers_Call) RunAndReturn(run func(c func (_m *MockKurtosisBackend) DestroyEnclaves(ctx context.Context, filters *enclave.EnclaveFilters) (map[enclave.EnclaveUUID]bool, map[enclave.EnclaveUUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for DestroyEnclaves") - } - var r0 map[enclave.EnclaveUUID]bool var r1 map[enclave.EnclaveUUID]error var r2 error @@ -666,10 +625,6 @@ func (_c *MockKurtosisBackend_DestroyEnclaves_Call) RunAndReturn(run func(contex func (_m *MockKurtosisBackend) DestroyEngines(ctx context.Context, filters *engine.EngineFilters) (map[engine.EngineGUID]bool, map[engine.EngineGUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for DestroyEngines") - } - var r0 map[engine.EngineGUID]bool var r1 map[engine.EngineGUID]error var r2 error @@ -734,10 +689,6 @@ func (_c *MockKurtosisBackend_DestroyEngines_Call) RunAndReturn(run func(context func (_m *MockKurtosisBackend) DestroyLogsAggregator(ctx context.Context) error { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for DestroyLogsAggregator") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -780,10 +731,6 @@ func (_c *MockKurtosisBackend_DestroyLogsAggregator_Call) RunAndReturn(run func( func (_m *MockKurtosisBackend) DestroyLogsCollectorForEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID) error { ret := _m.Called(ctx, enclaveUuid) - if len(ret) == 0 { - panic("no return value specified for DestroyLogsCollectorForEnclave") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID) error); ok { r0 = rf(ctx, enclaveUuid) @@ -827,10 +774,6 @@ func (_c *MockKurtosisBackend_DestroyLogsCollectorForEnclave_Call) RunAndReturn( func (_m *MockKurtosisBackend) DestroyReverseProxy(ctx context.Context) error { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for DestroyReverseProxy") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -873,10 +816,6 @@ func (_c *MockKurtosisBackend_DestroyReverseProxy_Call) RunAndReturn(run func(co func (_m *MockKurtosisBackend) DestroyUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, filters *service.ServiceFilters) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, filters) - if len(ret) == 0 { - panic("no return value specified for DestroyUserServices") - } - var r0 map[service.ServiceUUID]bool var r1 map[service.ServiceUUID]error var r2 error @@ -942,10 +881,6 @@ func (_c *MockKurtosisBackend_DestroyUserServices_Call) RunAndReturn(run func(co func (_m *MockKurtosisBackend) DumpEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID, outputDirpath string) error { ret := _m.Called(ctx, enclaveUuid, outputDirpath) - if len(ret) == 0 { - panic("no return value specified for DumpEnclave") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, string) error); ok { r0 = rf(ctx, enclaveUuid, outputDirpath) @@ -990,10 +925,6 @@ func (_c *MockKurtosisBackend_DumpEnclave_Call) RunAndReturn(run func(context.Co func (_m *MockKurtosisBackend) DumpKurtosis(ctx context.Context, outputDirpath string) error { ret := _m.Called(ctx, outputDirpath) - if len(ret) == 0 { - panic("no return value specified for DumpKurtosis") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, outputDirpath) @@ -1037,10 +968,6 @@ func (_c *MockKurtosisBackend_DumpKurtosis_Call) RunAndReturn(run func(context.C func (_m *MockKurtosisBackend) FetchImage(ctx context.Context, image string, registrySpec *image_registry_spec.ImageRegistrySpec, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { ret := _m.Called(ctx, image, registrySpec, downloadMode) - if len(ret) == 0 { - panic("no return value specified for FetchImage") - } - var r0 bool var r1 string var r2 error @@ -1103,10 +1030,6 @@ func (_c *MockKurtosisBackend_FetchImage_Call) RunAndReturn(run func(context.Con func (_m *MockKurtosisBackend) GetAPIContainers(ctx context.Context, filters *api_container.APIContainerFilters) (map[enclave.EnclaveUUID]*api_container.APIContainer, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for GetAPIContainers") - } - var r0 map[enclave.EnclaveUUID]*api_container.APIContainer var r1 error if rf, ok := ret.Get(0).(func(context.Context, *api_container.APIContainerFilters) (map[enclave.EnclaveUUID]*api_container.APIContainer, error)); ok { @@ -1162,10 +1085,6 @@ func (_c *MockKurtosisBackend_GetAPIContainers_Call) RunAndReturn(run func(conte func (_m *MockKurtosisBackend) GetAvailableCPUAndMemory(ctx context.Context) (compute_resources.MemoryInMegaBytes, compute_resources.CpuMilliCores, bool, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for GetAvailableCPUAndMemory") - } - var r0 compute_resources.MemoryInMegaBytes var r1 compute_resources.CpuMilliCores var r2 bool @@ -1232,10 +1151,6 @@ func (_c *MockKurtosisBackend_GetAvailableCPUAndMemory_Call) RunAndReturn(run fu func (_m *MockKurtosisBackend) GetEnclaves(ctx context.Context, filters *enclave.EnclaveFilters) (map[enclave.EnclaveUUID]*enclave.Enclave, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for GetEnclaves") - } - var r0 map[enclave.EnclaveUUID]*enclave.Enclave var r1 error if rf, ok := ret.Get(0).(func(context.Context, *enclave.EnclaveFilters) (map[enclave.EnclaveUUID]*enclave.Enclave, error)); ok { @@ -1291,10 +1206,6 @@ func (_c *MockKurtosisBackend_GetEnclaves_Call) RunAndReturn(run func(context.Co func (_m *MockKurtosisBackend) GetEngineLogs(ctx context.Context, outputDirpath string) error { ret := _m.Called(ctx, outputDirpath) - if len(ret) == 0 { - panic("no return value specified for GetEngineLogs") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, outputDirpath) @@ -1338,10 +1249,6 @@ func (_c *MockKurtosisBackend_GetEngineLogs_Call) RunAndReturn(run func(context. func (_m *MockKurtosisBackend) GetEngines(ctx context.Context, filters *engine.EngineFilters) (map[engine.EngineGUID]*engine.Engine, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for GetEngines") - } - var r0 map[engine.EngineGUID]*engine.Engine var r1 error if rf, ok := ret.Get(0).(func(context.Context, *engine.EngineFilters) (map[engine.EngineGUID]*engine.Engine, error)); ok { @@ -1397,10 +1304,6 @@ func (_c *MockKurtosisBackend_GetEngines_Call) RunAndReturn(run func(context.Con func (_m *MockKurtosisBackend) GetLogsAggregator(ctx context.Context) (*logs_aggregator.LogsAggregator, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for GetLogsAggregator") - } - var r0 *logs_aggregator.LogsAggregator var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*logs_aggregator.LogsAggregator, error)); ok { @@ -1455,10 +1358,6 @@ func (_c *MockKurtosisBackend_GetLogsAggregator_Call) RunAndReturn(run func(cont func (_m *MockKurtosisBackend) GetLogsCollectorForEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID) (*logs_collector.LogsCollector, error) { ret := _m.Called(ctx, enclaveUuid) - if len(ret) == 0 { - panic("no return value specified for GetLogsCollectorForEnclave") - } - var r0 *logs_collector.LogsCollector var r1 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID) (*logs_collector.LogsCollector, error)); ok { @@ -1514,10 +1413,6 @@ func (_c *MockKurtosisBackend_GetLogsCollectorForEnclave_Call) RunAndReturn(run func (_m *MockKurtosisBackend) GetReverseProxy(ctx context.Context) (*reverse_proxy.ReverseProxy, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for GetReverseProxy") - } - var r0 *reverse_proxy.ReverseProxy var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*reverse_proxy.ReverseProxy, error)); ok { @@ -1572,10 +1467,6 @@ func (_c *MockKurtosisBackend_GetReverseProxy_Call) RunAndReturn(run func(contex func (_m *MockKurtosisBackend) GetShellOnUserService(ctx context.Context, enclaveUuid enclave.EnclaveUUID, serviceUuid service.ServiceUUID) error { ret := _m.Called(ctx, enclaveUuid, serviceUuid) - if len(ret) == 0 { - panic("no return value specified for GetShellOnUserService") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, service.ServiceUUID) error); ok { r0 = rf(ctx, enclaveUuid, serviceUuid) @@ -1620,10 +1511,6 @@ func (_c *MockKurtosisBackend_GetShellOnUserService_Call) RunAndReturn(run func( func (_m *MockKurtosisBackend) GetUserServiceLogs(ctx context.Context, enclaveUuid enclave.EnclaveUUID, filters *service.ServiceFilters, shouldFollowLogs bool) (map[service.ServiceUUID]io.ReadCloser, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, filters, shouldFollowLogs) - if len(ret) == 0 { - panic("no return value specified for GetUserServiceLogs") - } - var r0 map[service.ServiceUUID]io.ReadCloser var r1 map[service.ServiceUUID]error var r2 error @@ -1690,10 +1577,6 @@ func (_c *MockKurtosisBackend_GetUserServiceLogs_Call) RunAndReturn(run func(con func (_m *MockKurtosisBackend) GetUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, filters *service.ServiceFilters) (map[service.ServiceUUID]*service.Service, error) { ret := _m.Called(ctx, enclaveUuid, filters) - if len(ret) == 0 { - panic("no return value specified for GetUserServices") - } - var r0 map[service.ServiceUUID]*service.Service var r1 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, *service.ServiceFilters) (map[service.ServiceUUID]*service.Service, error)); ok { @@ -1746,14 +1629,63 @@ func (_c *MockKurtosisBackend_GetUserServices_Call) RunAndReturn(run func(contex return _c } +// NixBuild provides a mock function with given fields: ctx, nixBuildSpec +func (_m *MockKurtosisBackend) NixBuild(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec) (string, error) { + ret := _m.Called(ctx, nixBuildSpec) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *nix_build_spec.NixBuildSpec) (string, error)); ok { + return rf(ctx, nixBuildSpec) + } + if rf, ok := ret.Get(0).(func(context.Context, *nix_build_spec.NixBuildSpec) string); ok { + r0 = rf(ctx, nixBuildSpec) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, *nix_build_spec.NixBuildSpec) error); ok { + r1 = rf(ctx, nixBuildSpec) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockKurtosisBackend_NixBuild_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NixBuild' +type MockKurtosisBackend_NixBuild_Call struct { + *mock.Call +} + +// NixBuild is a helper method to define mock.On call +// - ctx context.Context +// - nixBuildSpec *nix_build_spec.NixBuildSpec +func (_e *MockKurtosisBackend_Expecter) NixBuild(ctx interface{}, nixBuildSpec interface{}) *MockKurtosisBackend_NixBuild_Call { + return &MockKurtosisBackend_NixBuild_Call{Call: _e.mock.On("NixBuild", ctx, nixBuildSpec)} +} + +func (_c *MockKurtosisBackend_NixBuild_Call) Run(run func(ctx context.Context, nixBuildSpec *nix_build_spec.NixBuildSpec)) *MockKurtosisBackend_NixBuild_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*nix_build_spec.NixBuildSpec)) + }) + return _c +} + +func (_c *MockKurtosisBackend_NixBuild_Call) Return(_a0 string, _a1 error) *MockKurtosisBackend_NixBuild_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockKurtosisBackend_NixBuild_Call) RunAndReturn(run func(context.Context, *nix_build_spec.NixBuildSpec) (string, error)) *MockKurtosisBackend_NixBuild_Call { + _c.Call.Return(run) + return _c +} + // PruneUnusedImages provides a mock function with given fields: ctx func (_m *MockKurtosisBackend) PruneUnusedImages(ctx context.Context) ([]string, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for PruneUnusedImages") - } - var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { @@ -1808,10 +1740,6 @@ func (_c *MockKurtosisBackend_PruneUnusedImages_Call) RunAndReturn(run func(cont func (_m *MockKurtosisBackend) RegisterUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceName]bool) (map[service.ServiceName]*service.ServiceRegistration, map[service.ServiceName]error, error) { ret := _m.Called(ctx, enclaveUuid, services) - if len(ret) == 0 { - panic("no return value specified for RegisterUserServices") - } - var r0 map[service.ServiceName]*service.ServiceRegistration var r1 map[service.ServiceName]error var r2 error @@ -1877,10 +1805,6 @@ func (_c *MockKurtosisBackend_RegisterUserServices_Call) RunAndReturn(run func(c func (_m *MockKurtosisBackend) RemoveRegisteredUserServiceProcesses(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, services) - if len(ret) == 0 { - panic("no return value specified for RemoveRegisteredUserServiceProcesses") - } - var r0 map[service.ServiceUUID]bool var r1 map[service.ServiceUUID]error var r2 error @@ -1946,10 +1870,6 @@ func (_c *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call) RunAndR func (_m *MockKurtosisBackend) RunUserServiceExecCommandWithStreamedOutput(ctx context.Context, enclaveUuid enclave.EnclaveUUID, serviceUuid service.ServiceUUID, cmd []string) (chan string, chan *exec_result.ExecResult, error) { ret := _m.Called(ctx, enclaveUuid, serviceUuid, cmd) - if len(ret) == 0 { - panic("no return value specified for RunUserServiceExecCommandWithStreamedOutput") - } - var r0 chan string var r1 chan *exec_result.ExecResult var r2 error @@ -2016,10 +1936,6 @@ func (_c *MockKurtosisBackend_RunUserServiceExecCommandWithStreamedOutput_Call) func (_m *MockKurtosisBackend) RunUserServiceExecCommands(ctx context.Context, enclaveUuid enclave.EnclaveUUID, userServiceCommands map[service.ServiceUUID][]string) (map[service.ServiceUUID]*exec_result.ExecResult, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, userServiceCommands) - if len(ret) == 0 { - panic("no return value specified for RunUserServiceExecCommands") - } - var r0 map[service.ServiceUUID]*exec_result.ExecResult var r1 map[service.ServiceUUID]error var r2 error @@ -2085,10 +2001,6 @@ func (_c *MockKurtosisBackend_RunUserServiceExecCommands_Call) RunAndReturn(run func (_m *MockKurtosisBackend) StartRegisteredUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]*service.ServiceConfig) (map[service.ServiceUUID]*service.Service, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, services) - if len(ret) == 0 { - panic("no return value specified for StartRegisteredUserServices") - } - var r0 map[service.ServiceUUID]*service.Service var r1 map[service.ServiceUUID]error var r2 error @@ -2154,10 +2066,6 @@ func (_c *MockKurtosisBackend_StartRegisteredUserServices_Call) RunAndReturn(run func (_m *MockKurtosisBackend) StopAPIContainers(ctx context.Context, filters *api_container.APIContainerFilters) (map[enclave.EnclaveUUID]bool, map[enclave.EnclaveUUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for StopAPIContainers") - } - var r0 map[enclave.EnclaveUUID]bool var r1 map[enclave.EnclaveUUID]error var r2 error @@ -2222,10 +2130,6 @@ func (_c *MockKurtosisBackend_StopAPIContainers_Call) RunAndReturn(run func(cont func (_m *MockKurtosisBackend) StopEnclaves(ctx context.Context, filters *enclave.EnclaveFilters) (map[enclave.EnclaveUUID]bool, map[enclave.EnclaveUUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for StopEnclaves") - } - var r0 map[enclave.EnclaveUUID]bool var r1 map[enclave.EnclaveUUID]error var r2 error @@ -2290,10 +2194,6 @@ func (_c *MockKurtosisBackend_StopEnclaves_Call) RunAndReturn(run func(context.C func (_m *MockKurtosisBackend) StopEngines(ctx context.Context, filters *engine.EngineFilters) (map[engine.EngineGUID]bool, map[engine.EngineGUID]error, error) { ret := _m.Called(ctx, filters) - if len(ret) == 0 { - panic("no return value specified for StopEngines") - } - var r0 map[engine.EngineGUID]bool var r1 map[engine.EngineGUID]error var r2 error @@ -2358,10 +2258,6 @@ func (_c *MockKurtosisBackend_StopEngines_Call) RunAndReturn(run func(context.Co func (_m *MockKurtosisBackend) StopUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, filters *service.ServiceFilters) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, filters) - if len(ret) == 0 { - panic("no return value specified for StopUserServices") - } - var r0 map[service.ServiceUUID]bool var r1 map[service.ServiceUUID]error var r2 error @@ -2427,10 +2323,6 @@ func (_c *MockKurtosisBackend_StopUserServices_Call) RunAndReturn(run func(conte func (_m *MockKurtosisBackend) UnregisterUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) { ret := _m.Called(ctx, enclaveUuid, services) - if len(ret) == 0 { - panic("no return value specified for UnregisterUserServices") - } - var r0 map[service.ServiceUUID]bool var r1 map[service.ServiceUUID]error var r2 error @@ -2496,10 +2388,6 @@ func (_c *MockKurtosisBackend_UnregisterUserServices_Call) RunAndReturn(run func func (_m *MockKurtosisBackend) UpdateEnclave(ctx context.Context, enclaveUuid enclave.EnclaveUUID, newName string, creationTime *time.Time) error { ret := _m.Called(ctx, enclaveUuid, newName, creationTime) - if len(ret) == 0 { - panic("no return value specified for UpdateEnclave") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, string, *time.Time) error); ok { r0 = rf(ctx, enclaveUuid, newName, creationTime) diff --git a/container-engine-lib/lib/backend_interface/objects/nix_build_spec/nix_build_spec.go b/container-engine-lib/lib/backend_interface/objects/nix_build_spec/nix_build_spec.go new file mode 100644 index 0000000000..5de9eafa0b --- /dev/null +++ b/container-engine-lib/lib/backend_interface/objects/nix_build_spec/nix_build_spec.go @@ -0,0 +1,70 @@ +package nix_build_spec + +import ( + "encoding/json" + "fmt" + + "github.com/kurtosis-tech/stacktrace" +) + +type NixBuildSpec struct { + // we do this way in order to have exported fields which can be marshalled + // and an unexported type for encapsulation + privateNixBuildSpec *privateNixBuildSpec +} + +// NixBuildSpec contains the information need for building a container from nix. +type privateNixBuildSpec struct { + NixFlakeDir string + ContextDirPath string + FlakeOutput string + ImageName string +} + +func NewNixBuildSpec(imageName string, contextDirPath string, nixFlakeDir string, flakeOutput string) *NixBuildSpec { + internalNixBuildSpec := &privateNixBuildSpec{ + NixFlakeDir: nixFlakeDir, + ContextDirPath: contextDirPath, + FlakeOutput: flakeOutput, + ImageName: imageName, + } + return &NixBuildSpec{internalNixBuildSpec} +} + +func (nixBuildSpec *NixBuildSpec) GetImageName() string { + return nixBuildSpec.privateNixBuildSpec.ImageName +} + +func (nixBuildSpec *NixBuildSpec) GetNixFlakeDir() string { + return nixBuildSpec.privateNixBuildSpec.NixFlakeDir +} + +func (nixBuildSpec *NixBuildSpec) GetBuildContextDir() string { + return nixBuildSpec.privateNixBuildSpec.ContextDirPath +} + +func (nixBuildSpec *NixBuildSpec) GetFlakeOutput() string { + return nixBuildSpec.privateNixBuildSpec.FlakeOutput +} + +func (nixBuildSpec *NixBuildSpec) GetFullFlakeReference() string { + return fmt.Sprintf("%s/.#%s", nixBuildSpec.privateNixBuildSpec.NixFlakeDir, nixBuildSpec.privateNixBuildSpec.FlakeOutput) +} + +func (nixBuildSpec *NixBuildSpec) MarshalJSON() ([]byte, error) { + return json.Marshal(nixBuildSpec.privateNixBuildSpec) +} + +func (nixBuildSpec *NixBuildSpec) UnmarshalJSON(data []byte) error { + + // Suppressing exhaustruct requirement because we want an object with zero values + // nolint: exhaustruct + unmarshalledPrivateStructPtr := &privateNixBuildSpec{} + + if err := json.Unmarshal(data, unmarshalledPrivateStructPtr); err != nil { + return stacktrace.Propagate(err, "An error occurred unmarshalling the private struct") + } + + nixBuildSpec.privateNixBuildSpec = unmarshalledPrivateStructPtr + return nil +} diff --git a/container-engine-lib/lib/backend_interface/objects/service/service_config.go b/container-engine-lib/lib/backend_interface/objects/service/service_config.go index 0b7b56b805..000aec8676 100644 --- a/container-engine-lib/lib/backend_interface/objects/service/service_config.go +++ b/container-engine-lib/lib/backend_interface/objects/service/service_config.go @@ -2,8 +2,10 @@ package service import ( "encoding/json" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" @@ -30,6 +32,8 @@ type privateServiceConfig struct { // Mutually exclusive from ImageBuildSpec, ContainerImageName ImagerRegistrySpec *image_registry_spec.ImageRegistrySpec + NixBuildSpec *nix_build_spec.NixBuildSpec + PrivatePorts map[string]*port_spec.PortSpec PublicPorts map[string]*port_spec.PortSpec //TODO this is a huge hack to temporarily enable static ports for NEAR until we have a more productized solution @@ -61,12 +65,15 @@ type privateServiceConfig struct { // TODO replace this with an abstraction that we own Tolerations []v1.Toleration + + NodeSelectors map[string]string } func CreateServiceConfig( containerImageName string, imageBuildSpec *image_build_spec.ImageBuildSpec, imageRegistrySpec *image_registry_spec.ImageRegistrySpec, + nixBuildSpec *nix_build_spec.NixBuildSpec, privatePorts map[string]*port_spec.PortSpec, publicPorts map[string]*port_spec.PortSpec, entrypointArgs []string, @@ -82,6 +89,7 @@ func CreateServiceConfig( labels map[string]string, user *service_user.ServiceUser, tolerations []v1.Toleration, + nodeSelectors map[string]string, ) (*ServiceConfig, error) { if err := ValidateServiceConfigLabels(labels); err != nil { @@ -92,6 +100,7 @@ func CreateServiceConfig( ContainerImageName: containerImageName, ImageBuildSpec: imageBuildSpec, ImagerRegistrySpec: imageRegistrySpec, + NixBuildSpec: nixBuildSpec, PrivatePorts: privatePorts, PublicPorts: publicPorts, EntrypointArgs: entrypointArgs, @@ -108,6 +117,7 @@ func CreateServiceConfig( Labels: labels, User: user, Tolerations: tolerations, + NodeSelectors: nodeSelectors, } return &ServiceConfig{internalServiceConfig}, nil } @@ -124,6 +134,10 @@ func (serviceConfig *ServiceConfig) GetImageRegistrySpec() *image_registry_spec. return serviceConfig.privateServiceConfig.ImagerRegistrySpec } +func (serviceConfig *ServiceConfig) GetNixBuildSpec() *nix_build_spec.NixBuildSpec { + return serviceConfig.privateServiceConfig.NixBuildSpec +} + func (serviceConfig *ServiceConfig) GetPrivatePorts() map[string]*port_spec.PortSpec { return serviceConfig.privateServiceConfig.PrivatePorts } @@ -190,6 +204,10 @@ func (serviceConfig *ServiceConfig) MarshalJSON() ([]byte, error) { return json.Marshal(serviceConfig.privateServiceConfig) } +func (serviceConfig *ServiceConfig) GetNodeSelectors() map[string]string { + return serviceConfig.privateServiceConfig.NodeSelectors +} + func (serviceConfig *ServiceConfig) UnmarshalJSON(data []byte) error { // Suppressing exhaustruct requirement because we want an object with zero values diff --git a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go index 5a2acb97de..3f3df193f8 100644 --- a/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go +++ b/container-engine-lib/lib/backend_interface/objects/service/service_config_test.go @@ -2,15 +2,17 @@ package service import ( "encoding/json" + "testing" + "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_user" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" - "testing" - "time" ) func TestServiceConfigMarshallers(t *testing.T) { @@ -45,6 +47,7 @@ func TestServiceConfigMarshallers(t *testing.T) { require.EqualValues(t, publicPortSpec, originalPublicPortSpec) } + require.Equal(t, originalServiceConfig, newServiceConfig) require.Equal(t, originalServiceConfig.GetEnvVars(), newServiceConfig.GetEnvVars()) require.Equal(t, originalServiceConfig.GetCmdArgs(), newServiceConfig.GetCmdArgs()) require.Equal(t, originalServiceConfig.GetEnvVars(), newServiceConfig.GetEnvVars()) @@ -57,6 +60,7 @@ func TestServiceConfigMarshallers(t *testing.T) { require.Equal(t, originalServiceConfig.GetMinMemoryAllocationMegabytes(), newServiceConfig.GetMinMemoryAllocationMegabytes()) require.Equal(t, originalServiceConfig.GetLabels(), newServiceConfig.GetLabels()) require.Equal(t, originalServiceConfig.GetImageBuildSpec(), newServiceConfig.GetImageBuildSpec()) + require.Equal(t, originalServiceConfig.GetNodeSelectors(), newServiceConfig.GetNodeSelectors()) } func getServiceConfigForTest(t *testing.T, imageName string) *ServiceConfig { @@ -64,6 +68,7 @@ func getServiceConfigForTest(t *testing.T, imageName string) *ServiceConfig { imageName, testImageBuildSpec(), testImageRegistrySpec(), + testNixBuildSpec(), testPrivatePorts(t), testPublicPorts(t), []string{"bin", "bash", "ls"}, @@ -82,6 +87,7 @@ func getServiceConfigForTest(t *testing.T, imageName string) *ServiceConfig { }, testServiceUser(), testToleration(), + testNodeSelectors(), ) require.NoError(t, err) return serviceConfig @@ -184,6 +190,10 @@ func testImageRegistrySpec() *image_registry_spec.ImageRegistrySpec { return image_registry_spec.NewImageRegistrySpec("test-image", "test-userename", "test-password", "test-registry.io") } +func testNixBuildSpec() *nix_build_spec.NixBuildSpec { + return nix_build_spec.NewNixBuildSpec("test-image", "path", "", "") +} + func testServiceUser() *service_user.ServiceUser { su := service_user.NewServiceUser(100) su.SetGID(100) @@ -200,3 +210,9 @@ func testToleration() []v1.Toleration { TolerationSeconds: &tolerationSeconds, }} } + +func testNodeSelectors() map[string]string { + return map[string]string{ + "disktype": "ssd", + } +} diff --git a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go index 85d497dff7..82311e59ce 100644 --- a/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go +++ b/container-engine-lib/lib/database_accessors/enclave_db/service_registration/repository_test.go @@ -2,6 +2,12 @@ package service_registration import ( "fmt" + "math/rand" + "net" + "os" + "testing" + "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/enclave" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -9,11 +15,6 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/database_accessors/enclave_db" "github.com/stretchr/testify/require" bolt "go.etcd.io/bbolt" - "math/rand" - "net" - "os" - "testing" - "time" ) const ( @@ -306,6 +307,7 @@ func getServiceConfigForTest(t *testing.T, imageName string) *service.ServiceCon imageName, nil, nil, + nil, testPrivatePorts(t), testPublicPorts(t), []string{"bin", "bash", "ls"}, @@ -324,6 +326,9 @@ func getServiceConfigForTest(t *testing.T, imageName string) *service.ServiceCon }, nil, nil, + map[string]string{ + "disktype": "ssd", + }, ) require.NoError(t, err) return serviceConfig diff --git a/container-engine-lib/lib/image_utils/image_utils.go b/container-engine-lib/lib/image_utils/image_utils.go new file mode 100644 index 0000000000..5b9224df71 --- /dev/null +++ b/container-engine-lib/lib/image_utils/image_utils.go @@ -0,0 +1,72 @@ +package image_utils + +import ( + "archive/tar" + "compress/gzip" + "encoding/json" + "io" + "os" + + "github.com/kurtosis-tech/stacktrace" +) + +const ( + manifestFileName = "manifest.json" +) + +// ImageManifest represents the structure of the manifest.json file +type ImageManifest struct { + Config string `json:"Config"` + RepoTags []string `json:"RepoTags"` + Layers []string `json:"Layers"` +} + +// GetRepoTags extracts the RepoTags from a Docker image file +func GetRepoTags(imageFilePath string) ([]string, error) { + imageFile, err := os.Open(imageFilePath) + if err != nil { + return nil, stacktrace.Propagate(err, "Fail to open image file %s", imageFilePath) + } + defer imageFile.Close() + + gzipReader, err := gzip.NewReader(imageFile) + if err != nil { + return nil, stacktrace.Propagate(err, "Fail to ungzip image file %s", stacktrace.Propagate(err, "Fail to read the image files")) + } + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + + var found bool = false + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, stacktrace.Propagate(err, "Fail to read the image files") + } + if header.Name == manifestFileName { + found = true + break + } + } + + if !found { + return nil, stacktrace.NewError("manifest.json not found in the image") + } + + var imageManifest []ImageManifest + jsonDecoder := json.NewDecoder(tarReader) + if err := jsonDecoder.Decode(&imageManifest); err != nil { + return nil, stacktrace.Propagate(err, "Could not parse the manifest.json") + } + + if len(imageManifest) > 1 { + return nil, stacktrace.NewError("Image has more than 1 label/tag, don't know which one to pick: %v", imageManifest) + } else if len(imageManifest) < 1 { + return nil, stacktrace.NewError("Image has no label/tag") + } + + return imageManifest[0].RepoTags, nil +} diff --git a/core/files_artifacts_expander/scripts/build.sh b/core/files_artifacts_expander/scripts/build.sh index 7f18864805..fcbb3fc019 100755 --- a/core/files_artifacts_expander/scripts/build.sh +++ b/core/files_artifacts_expander/scripts/build.sh @@ -19,6 +19,10 @@ DEFAULT_SKIP_DOCKER_IMAGE_BUILDING=false DEFAULT_ARCHITECTURE_TO_BUILD="unknown" +DOCKER_DEBUG_IMAGE_NAME_SUFFIX="debug" +DEFAULT_DEBUG_IMAGE=false + + if [ "$uname_arch" == "x86_64" ] || [ "$uname_arch" == "amd64" ]; then DEFAULT_ARCHITECTURE_TO_BUILD="amd64" elif [ "$uname_arch" == "aarch64" ] || [ "$uname_arch" == "arm64" ]; then @@ -29,9 +33,19 @@ MAIN_GO_FILEPATH="${expander_root_dirpath}/main.go" MAIN_BINARY_OUTPUT_FILENAME="files-artifacts-expander" MAIN_BINARY_OUTPUT_FILEPATH="${expander_root_dirpath}/${BUILD_DIRNAME}/${MAIN_BINARY_OUTPUT_FILENAME}" -# ============================================================================= -# Main Code -# ============================================================================= +# ================================================================================================== +# Arg Parsing & Validation +# ================================================================================================== +show_helptext_and_exit() { + echo "Usage: $(basename "${0}") skip_docker_image_building, architecture_to_build, debug_image..." + echo "" + echo " skip_docker_image_building Whether build the Docker image" + echo " architecture_to_build The desired architecture for the project's binaries" + echo " debug_image Whether images should contains the debug server and run in debug mode, this will use the Dockerfile.debug image to build the container" + echo "" + exit 1 # Exit with an error so that if this is accidentally called by CI, the script will fail +} + skip_docker_image_building="${1:-"${DEFAULT_SKIP_DOCKER_IMAGE_BUILDING}"}" if [ "${skip_docker_image_building}" != "true" ] && [ "${skip_docker_image_building}" != "false" ]; then echo "Error: Invalid skip-docker-image-building arg '${skip_docker_image_building}'" >&2 @@ -42,6 +56,15 @@ if [ "${architecture_to_build}" != "amd64" ] && [ "${architecture_to_build}" != echo "Error: Invalid architecture-to-build arg '${architecture_to_build}'" >&2 fi +debug_image="${3:-"${DEFAULT_DEBUG_IMAGE}"}" +if [ "${debug_image}" != "true" ] && [ "${debug_image}" != "false" ]; then + echo "Error: Invalid debug_image arg: '${debug_image}'" >&2 + show_helptext_and_exit +fi + +# ============================================================================= +# Main Code +# ============================================================================= # Checks if dockerignore file is in the root path if ! [ -f "${expander_root_dirpath}"/.dockerignore ]; then echo "Error: No .dockerignore file found in files artifacts expander root '${expander_root_dirpath}'; this is required so Docker caching is enabled and the image builds remain quick" >&2 @@ -86,6 +109,11 @@ fi dockerfile_filepath="${expander_root_dirpath}/Dockerfile" image_name="${IMAGE_ORG_AND_REPO}:${docker_tag}" +# specifying that this is a image for debugging +if "${debug_image}"; then + image_name="${image_name}-${DOCKER_DEBUG_IMAGE_NAME_SUFFIX}" +fi + load_not_push_image=false docker_build_script_cmd="${git_repo_dirpath}/scripts/docker-image-builder.sh ${load_not_push_image} ${dockerfile_filepath} ${image_name}" if ! eval "${docker_build_script_cmd}"; then diff --git a/core/server/Dockerfile b/core/server/Dockerfile index df50957179..83fa2599d1 100644 --- a/core/server/Dockerfile +++ b/core/server/Dockerfile @@ -1,7 +1,12 @@ FROM alpine:3.17 # We need protobut-dev to run protobuf compiler against startosis .proto files -RUN apk update && apk add --no-cache bash protobuf-dev +RUN apk update && apk add --no-cache bash protobuf-dev sudo shadow curl xz + +# Install Nix +# We need to set filter-syscalls to false to allow Nix to work properly inside a container: https://github.com/NixOS/nix/issues/5258 +ENV NIX_CONFIG=$'filter-syscalls = false\nexperimental-features = nix-command flakes' +RUN sh <(curl -L https://nixos.org/nix/install) --daemon --yes ARG TARGETARCH diff --git a/core/server/Dockerfile.debug b/core/server/Dockerfile.debug index a8b268dc55..0061f97af8 100644 --- a/core/server/Dockerfile.debug +++ b/core/server/Dockerfile.debug @@ -1,7 +1,12 @@ FROM alpine:3.19 # We need protobut-dev to run protobuf compiler against startosis .proto files -RUN apk update && apk add --no-cache bash protobuf-dev +RUN apk update && apk add --no-cache bash protobuf-dev sudo shadow curl xz + +# Install Nix +# We need to set filter-syscalls to false to allow Nix to work properly inside a container: https://github.com/NixOS/nix/issues/5258 +ENV NIX_CONFIG=$'filter-syscalls = false\nexperimental-features = nix-command flakes' +RUN sh <(curl -L https://nixos.org/nix/install) --daemon --yes # Make sure that you changed the port inside the APIC's code before changing it here EXPOSE 50103 diff --git a/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go b/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go index 366fbd3eb8..b0cca993e4 100644 --- a/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go +++ b/core/server/api_container/server/docker_compose_transpiler/docker_compose_transpiler.go @@ -3,6 +3,12 @@ package docker_compose_transpiler import ( "errors" "fmt" + "os" + "path" + "sort" + "strconv" + "strings" + "github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/types" "github.com/joho/godotenv" @@ -15,11 +21,6 @@ import ( "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" "go.starlark.net/starlark" - "os" - "path" - "sort" - "strconv" - "strings" ) const ( @@ -122,6 +123,7 @@ func convertComposeToStarlarkScript(composeBytes []byte, envVars map[string]stri func convertComposeBytesToComposeStruct(composeBytes []byte, envVars map[string]string) (*types.Project, error) { composeParseConfig := types.ConfigDetails{ //nolint:exhaustruct // Note that we might be able to use the WorkingDir property instead, to parse the entire directory + // nolint: exhaustruct ConfigFiles: []types.ConfigFile{{ Content: composeBytes, }}, diff --git a/core/server/api_container/server/service_network/default_service_network_test.go b/core/server/api_container/server/service_network/default_service_network_test.go index f97a53bfed..516f3aa651 100644 --- a/core/server/api_container/server/service_network/default_service_network_test.go +++ b/core/server/api_container/server/service_network/default_service_network_test.go @@ -1215,6 +1215,7 @@ func testServiceConfig(t *testing.T, imageName string) *service.ServiceConfig { nil, nil, nil, + nil, 0, 0, "", @@ -1223,6 +1224,7 @@ func testServiceConfig(t *testing.T, imageName string) *service.ServiceConfig { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) return serviceConfig diff --git a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go index e8a4f6a8ac..5aeae0154e 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_builtins.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_builtins.go @@ -112,6 +112,7 @@ func KurtosisTypeConstructors() []*starlark.Builtin { starlark.NewBuiltin(service_config.ServiceConfigTypeName, service_config.NewServiceConfigType().CreateBuiltin()), starlark.NewBuiltin(service_config.ReadyConditionTypeName, service_config.NewReadyConditionType().CreateBuiltin()), starlark.NewBuiltin(service_config.ImageBuildSpecTypeName, service_config.NewImageBuildSpecType().CreateBuiltin()), + starlark.NewBuiltin(service_config.NixBuildSpecTypeName, service_config.NewNixBuildSpecType().CreateBuiltin()), starlark.NewBuiltin(service_config.ImageRegistrySpecTypeName, service_config.NewImageRegistrySpec().CreateBuiltin()), starlark.NewBuiltin(service_config.UserTypeName, service_config.NewUserType().CreateBuiltin()), starlark.NewBuiltin(service_config.TolerationTypeName, service_config.NewTolerationType().CreateBuiltin()), diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go index e75fbd10fd..93c28bfe95 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared.go @@ -3,6 +3,8 @@ package add_service import ( "context" "fmt" + "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" @@ -17,7 +19,6 @@ import ( "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" "go.starlark.net/starlark" - "time" ) const ( @@ -107,6 +108,8 @@ func validateSingleService(validatorEnvironment *startosis_validator.ValidatorEn validatorEnvironment.AppendRequiredImageBuild(serviceConfig.GetContainerImageName(), serviceConfig.GetImageBuildSpec()) } else if serviceConfig.GetImageRegistrySpec() != nil { validatorEnvironment.AppendImageToPullWithAuth(serviceConfig.GetContainerImageName(), serviceConfig.GetImageRegistrySpec()) + } else if serviceConfig.GetNixBuildSpec() != nil { + validatorEnvironment.AppendRequiredNixBuild(serviceConfig.GetContainerImageName(), serviceConfig.GetNixBuildSpec()) } else { validatorEnvironment.AppendRequiredImagePull(serviceConfig.GetContainerImageName()) } @@ -199,6 +202,7 @@ func replaceMagicStrings( serviceConfig.GetContainerImageName(), serviceConfig.GetImageBuildSpec(), serviceConfig.GetImageRegistrySpec(), + serviceConfig.GetNixBuildSpec(), serviceConfig.GetPrivatePorts(), serviceConfig.GetPublicPorts(), entrypoints, @@ -214,6 +218,7 @@ func replaceMagicStrings( serviceConfig.GetLabels(), serviceConfig.GetUser(), serviceConfig.GetTolerations(), + serviceConfig.GetNodeSelectors(), ) if err != nil { return "", nil, stacktrace.Propagate(err, "An error occurred creating a service config") diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go index 430f9701e8..042c0bbe17 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/add_service/add_service_shared_test.go @@ -2,6 +2,9 @@ package add_service import ( "fmt" + "os" + "testing" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/database_accessors/enclave_db" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_instruction/shared_helpers" @@ -10,8 +13,6 @@ import ( "github.com/stretchr/testify/require" bolt "go.etcd.io/bbolt" "go.starlark.net/starlark" - "os" - "testing" ) const ( @@ -42,6 +43,7 @@ func TestAddServiceShared_EntryPointArgsRuntimeValueAreReplaced(t *testing.T) { nil, nil, nil, + nil, []string{"-- " + runtimeValue}, nil, nil, @@ -55,6 +57,7 @@ func TestAddServiceShared_EntryPointArgsRuntimeValueAreReplaced(t *testing.T) { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) @@ -88,6 +91,7 @@ func TestAddServiceShared_CmdArgsRuntimeValueAreReplaced(t *testing.T) { nil, nil, nil, + nil, []string{"bash", "-c", "sleep " + runtimeValue}, nil, nil, @@ -100,6 +104,7 @@ func TestAddServiceShared_CmdArgsRuntimeValueAreReplaced(t *testing.T) { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) @@ -134,6 +139,7 @@ func TestAddServiceShared_EnvVarsWithRuntimeValueAreReplaced(t *testing.T) { nil, nil, nil, + nil, map[string]string{ "PORT": runtimeValue, }, @@ -147,6 +153,7 @@ func TestAddServiceShared_EnvVarsWithRuntimeValueAreReplaced(t *testing.T) { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) @@ -187,6 +194,7 @@ func TestAddServiceShared_ServiceNameWithRuntimeValuesAreReplaced(t *testing.T) nil, nil, nil, + nil, 0, 0, "", @@ -195,6 +203,7 @@ func TestAddServiceShared_ServiceNameWithRuntimeValuesAreReplaced(t *testing.T) map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go index 5e546a51f8..2632a5eb6c 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/tasks/tasks_shared.go @@ -3,6 +3,10 @@ package tasks import ( "context" "fmt" + "reflect" + "strings" + "time" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/exec_result" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" @@ -19,9 +23,6 @@ import ( "github.com/sirupsen/logrus" "go.starlark.net/starlark" "go.starlark.net/starlarkstruct" - "reflect" - "strings" - "time" ) // shared constants @@ -260,6 +261,7 @@ func getServiceConfig( nil, nil, nil, + nil, // This make sure that the container does not stop as soon as it starts // This only is needed for kubernetes at the moment // TODO: Instead of creating a service and running exec commands @@ -278,6 +280,7 @@ func getServiceConfig( map[string]string{}, nil, nil, + map[string]string{}, ) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred creating service config") diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go index 618cc6009b..38ea311887 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument/validators.go @@ -147,32 +147,47 @@ func DurationOrNone(value starlark.Value, attributeName string) *startosis_error return nil } -func ServiceConfigLabels(value starlark.Value, attributeName string) *startosis_errors.InterpretationError { - labelsMap := map[string]string{} - labelsDict, ok := value.(*starlark.Dict) +func StringMappingToString(value starlark.Value, attributeName string) *startosis_errors.InterpretationError { + if _, err := parseMapStringString(value, attributeName); err != nil { + return err + } + return nil +} + +func ServiceLabelsValidator(value starlark.Value, attributeName string) *startosis_errors.InterpretationError { + labelsMap, interpretationErr := parseMapStringString(value, attributeName) + if interpretationErr != nil { + return interpretationErr + } + if err := service.ValidateServiceConfigLabels(labelsMap); err != nil { + return startosis_errors.WrapWithInterpretationError(err, "An error occurred validating service config labels '%+v'", labelsMap) + } + return nil +} + +func parseMapStringString(value starlark.Value, attributeName string) (map[string]string, *startosis_errors.InterpretationError) { + stringMap := map[string]string{} + stringDict, ok := value.(*starlark.Dict) if !ok { - return startosis_errors.NewInterpretationError("Attribute '%s' is expected to be a dictionary of strings, got '%s'", attributeName, reflect.TypeOf(value)) + return nil, startosis_errors.NewInterpretationError("Attribute '%s' is expected to be a dictionary of strings, got '%s'", attributeName, reflect.TypeOf(value)) } - for _, labelKey := range labelsDict.Keys() { - labelValue, found, err := labelsDict.Get(labelKey) + for _, mapKey := range stringDict.Keys() { + mapValue, found, err := stringDict.Get(mapKey) if err != nil { - return startosis_errors.WrapWithInterpretationError(err, "Unexpected error iterating on dictionary. Value associated to key '%v' could not be found", labelKey) + return nil, startosis_errors.WrapWithInterpretationError(err, "Unexpected error iterating on dictionary. Value associated to key '%v' could not be found", mapKey) } else if !found { - return startosis_errors.NewInterpretationError("Unexpected error iterating on dictionary. Value associated to key '%v' could not be found", labelKey) + return nil, startosis_errors.NewInterpretationError("Unexpected error iterating on dictionary. Value associated to key '%v' could not be found", mapKey) } - labelKeyStr, ok := labelKey.(starlark.String) + mapKeyStr, ok := mapKey.(starlark.String) if !ok { - return startosis_errors.NewInterpretationError("Key in '%s' dictionary was expected to be a string, got '%s'", attributeName, reflect.TypeOf(labelKey)) + return nil, startosis_errors.NewInterpretationError("Key in '%s' dictionary was expected to be a string, got '%s'", attributeName, reflect.TypeOf(mapKey)) } - labelValueStr, ok := labelValue.(starlark.String) + mapValueStr, ok := mapValue.(starlark.String) if !ok { - return startosis_errors.NewInterpretationError("Value associated to key '%s' in dictionary '%s' was expected to be a string, got '%s'", labelKeyStr, attributeName, reflect.TypeOf(value)) + return nil, startosis_errors.NewInterpretationError("Value associated to key '%s' in dictionary '%s' was expected to be a string, got '%s'", mapKeyStr, attributeName, reflect.TypeOf(value)) } - labelsMap[labelKeyStr.GoString()] = labelValueStr.GoString() + stringMap[mapKeyStr.GoString()] = mapValueStr.GoString() } - if err := service.ValidateServiceConfigLabels(labelsMap); err != nil { - return startosis_errors.WrapWithInterpretationError(err, "An error occurred validating service config labels '%+v'", labelsMap) - } - return nil + return stringMap, nil } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go index 615790499f..adb5eb05ac 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_service_framework_test.go @@ -2,9 +2,10 @@ package test_engine import ( "fmt" - "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages/mock_package_content_provider" "testing" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages/mock_package_content_provider" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/container" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -36,6 +37,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddService() { testContainerImageName, nil, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -51,6 +53,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddService() { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(suite.T(), err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go index 87a2247e3b..7912c700a0 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/add_services_framework_test.go @@ -2,13 +2,14 @@ package test_engine import ( "fmt" - "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages/mock_package_content_provider" "io" "net/http" "net/url" "strings" "testing" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages/mock_package_content_provider" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/container" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -52,6 +53,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { testContainerImageName, nil, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -67,6 +69,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(suite.T(), err) @@ -77,6 +80,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { testContainerImageName, nil, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -92,6 +96,7 @@ func (suite *KurtosisPlanInstructionTestSuite) TestAddServices() { map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(suite.T(), err) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/nix_build_spec_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/nix_build_spec_framework_test.go new file mode 100644 index 0000000000..f4c5909dd2 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/nix_build_spec_framework_test.go @@ -0,0 +1,67 @@ +package test_engine + +import ( + "fmt" + "testing" + + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" + "github.com/stretchr/testify/require" +) + +type nixBuildSpecTest struct { + *testing.T + + packageContentProvider *startosis_packages.MockPackageContentProvider +} + +func (suite *KurtosisTypeConstructorTestSuite) TestNixBuildSpecTest() { + suite.packageContentProvider.EXPECT(). + GetAbsoluteLocator(testModulePackageId, testModuleMainFileLocator, testBuildContextDir, testNoPackageReplaceOptions). + Times(1). + Return(testBuildContextLocator, nil) + + suite.packageContentProvider.EXPECT(). + GetOnDiskAbsolutePackageFilePath(testNixFlakeLocator). + Times(1). + Return(testOnDiskNixFlakePath, nil) + + suite.packageContentProvider.EXPECT(). + GetOnDiskAbsolutePath(testBuildContextLocator). + Times(1). + Return(testOnDiskContextDirPath, nil) + + suite.run(&nixBuildSpecTest{ + T: suite.T(), + packageContentProvider: suite.packageContentProvider, + }) +} + +func (t *nixBuildSpecTest) GetStarlarkCode() string { + return fmt.Sprintf("%s(%s=%q, %s=%q, %s=%q, %s=%q)", + service_config.NixBuildSpecTypeName, + service_config.FlakeLocationDir, + testNixFlakeLocationDir, + service_config.NixContextAttr, + testNixContextDir, + service_config.NixImageName, + testNixImageName, + service_config.FlakeOutputAttr, + testNixFlakeOutput) +} + +func (t *nixBuildSpecTest) Assert(typeValue builtin_argument.KurtosisValueType) { + nixBuildSpecStarlark, ok := typeValue.(*service_config.NixBuildSpec) + require.True(t, ok) + + nixBuildSpec, err := nixBuildSpecStarlark.ToKurtosisType( + testModuleMainFileLocator, + testModulePackageId, + t.packageContentProvider, + testNoPackageReplaceOptions) + require.Nil(t, err) + require.Equal(t, testOnDiskNixFlakeDir, nixBuildSpec.GetNixFlakeDir()) + require.Equal(t, testOnDiskNixContextDirPath, nixBuildSpec.GetBuildContextDir()) + require.Equal(t, testNixFlakeOutput, nixBuildSpec.GetFlakeOutput()) +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go index 875448e92f..cd10d46f48 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_full_framework_test.go @@ -38,7 +38,7 @@ func (t *serviceConfigFullTestCase) GetStarlarkCode() string { fileArtifact1 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName1) fileArtifact2 := fmt.Sprintf("%s(%s=[%q])", directory.DirectoryTypeName, directory.ArtifactNamesAttr, testFilesArtifactName2) persistentDirectory := fmt.Sprintf("%s(%s=%q)", directory.DirectoryTypeName, directory.PersistentKeyAttr, testPersistentDirectoryKey) - starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%q, %s=%d, %s=%d, %s=%d, %s=%d, %s=%s, %s=%v)", + starlarkCode := fmt.Sprintf("%s(%s=%q, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%q, %s=%d, %s=%d, %s=%d, %s=%d, %s=%s, %s=%v, %s=%v)", service_config.ServiceConfigTypeName, service_config.ImageAttr, testContainerImageName, service_config.PortsAttr, fmt.Sprintf("{%q: PortSpec(number=%d, transport_protocol=%q, application_protocol=%q, wait=%q)}", testPrivatePortId, testPrivatePortNumber, testPrivatePortProtocolStr, testPrivateApplicationProtocol, testWaitConfiguration), @@ -54,7 +54,8 @@ func (t *serviceConfigFullTestCase) GetStarlarkCode() string { service_config.MinMemoryMegaBytesAttr, testMinMemoryMegabytes, service_config.ReadyConditionsAttr, getDefaultReadyConditionsScriptPart(), - service_config.LabelsAttr, fmt.Sprintf("{%q: %q, %q: %q}", testServiceConfigLabelsKey1, testServiceConfigLabelsValue1, testServiceConfigLabelsKey2, testServiceConfigLabelsValue2)) + service_config.LabelsAttr, fmt.Sprintf("{%q: %q, %q: %q}", testServiceConfigLabelsKey1, testServiceConfigLabelsValue1, testServiceConfigLabelsKey2, testServiceConfigLabelsValue2), + service_config.NodeSelectorsAttr, fmt.Sprintf("{%q: %q}", testNodeSelectorKey1, testNodeSelectorValue1)) return starlarkCode } @@ -122,4 +123,5 @@ func (t *serviceConfigFullTestCase) Assert(typeValue builtin_argument.KurtosisVa require.Equal(t, testMinCpuMilliCores, serviceConfig.GetMinCPUAllocationMillicpus()) require.Equal(t, testServiceConfigLabels, serviceConfig.GetLabels()) + require.Equal(t, testNodeSelectors, serviceConfig.GetNodeSelectors()) } diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go index 037e9e441a..64740547f7 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_build_spec_test.go @@ -2,6 +2,8 @@ package test_engine import ( "fmt" + "testing" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -10,7 +12,6 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" "github.com/stretchr/testify/require" - "testing" ) type serviceConfigImageBuildSpecTestCase struct { @@ -71,6 +72,7 @@ func (t *serviceConfigImageBuildSpecTestCase) Assert(typeValue builtin_argument. testContainerImageName, expectedImageBuildSpec, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -86,6 +88,7 @@ func (t *serviceConfigImageBuildSpecTestCase) Assert(typeValue builtin_argument. map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_registry_spec_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_registry_spec_test.go index 25d05586e9..af3fa83ba2 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_registry_spec_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_image_registry_spec_test.go @@ -2,6 +2,8 @@ package test_engine import ( "fmt" + "testing" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" @@ -10,7 +12,6 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" "github.com/stretchr/testify/require" - "testing" ) type serviceConfigImageRegistrySpecTest struct { @@ -61,6 +62,7 @@ func (t *serviceConfigImageRegistrySpecTest) Assert(typeValue builtin_argument.K testContainerImageName, nil, expectedImageRegistrySpec, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -76,6 +78,7 @@ func (t *serviceConfigImageRegistrySpecTest) Assert(typeValue builtin_argument.K map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go index a3b955d12a..1199948dc0 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_minimal_framework_test.go @@ -2,6 +2,8 @@ package test_engine import ( "fmt" + "testing" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" @@ -9,7 +11,6 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_types/service_config" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" "github.com/stretchr/testify/require" - "testing" ) type serviceConfigMinimalTestCase struct { @@ -48,6 +49,7 @@ func (t *serviceConfigMinimalTestCase) Assert(typeValue builtin_argument.Kurtosi testContainerImageName, nil, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -63,6 +65,7 @@ func (t *serviceConfigMinimalTestCase) Assert(typeValue builtin_argument.Kurtosi map[string]string{}, nil, nil, + map[string]string{}, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_toleration_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_toleration_test.go index 6780b2c474..09f32a09a5 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_toleration_test.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/service_config_toleration_test.go @@ -2,6 +2,8 @@ package test_engine import ( "fmt" + "testing" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/service_network" @@ -10,7 +12,6 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" - "testing" ) type serviceConfigTolerationTest struct { @@ -63,6 +64,7 @@ func (t *serviceConfigTolerationTest) Assert(typeValue builtin_argument.Kurtosis testContainerImageName, nil, nil, + nil, map[string]*port_spec.PortSpec{}, map[string]*port_spec.PortSpec{}, nil, @@ -78,6 +80,7 @@ func (t *serviceConfigTolerationTest) Assert(typeValue builtin_argument.Kurtosis map[string]string{}, nil, expectedTolerations, + map[string]string{}, ) require.NoError(t, err) require.Equal(t, expectedServiceConfig, serviceConfig) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/static_constants.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/static_constants.go index 9c3138eea9..2de6f9ee8a 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/static_constants.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/static_constants.go @@ -34,6 +34,15 @@ var ( testOnDiskContextDirPath = "kurtosis-data/test-package" testOnDiskContainerImagePath = "kurtosis-data/test-package/Dockerfile" + testNixContextDir = "./" + testNixImageName = "test-image" + testNixFlakeOutput = "foo" + testNixFlakeLocationDir = "./server/app" + testOnDiskNixContextDirPath = "kurtosis-data/test-package" + testOnDiskNixFlakePath = "kurtosis-data/test-package/server/app/flake.nix" + testOnDiskNixFlakeDir = "kurtosis-data/test-package/server/app" + testNixFlakeLocator = "github.com/kurtosistech/test-package/server/app/flake.nix" + testRegistryAddr = "http://registry.test.io" testRegistryUsername = "kurtosis" testRegistryPassword = "password" @@ -118,6 +127,12 @@ var ( testServiceConfigLabelsKey2: testServiceConfigLabelsValue2, } + testNodeSelectorKey1 = "k3s.io/hostname" + testNodeSelectorValue1 = "asrock-berlin-03" + testNodeSelectors = map[string]string{ + testNodeSelectorKey1: testNodeSelectorValue1, + } + testTolerationKey = "test-key" testTolerationValue = "test-value" testTolerationSeconds = int64(64) diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/nix_build_spec.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/nix_build_spec.go new file mode 100644 index 0000000000..5e82f085e5 --- /dev/null +++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/nix_build_spec.go @@ -0,0 +1,232 @@ +package service_config + +import ( + "fmt" + "path" + "path/filepath" + + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/builtin_argument" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/kurtosis_type_constructor" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_constants" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" + "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" + "go.starlark.net/starlark" +) + +const ( + NixBuildSpecTypeName = "NixBuildSpec" + + FlakeLocationDir = "flake_location_dir" + FlakeOutputAttr = "flake_output" + NixContextAttr = "build_context_dir" + NixImageName = "image_name" + + // Currently only supports container nix flakes + defaultNixFlakeFile = "flake.nix" +) + +func NewNixBuildSpecType() *kurtosis_type_constructor.KurtosisTypeConstructor { + return &kurtosis_type_constructor.KurtosisTypeConstructor{ + KurtosisBaseBuiltin: &kurtosis_starlark_framework.KurtosisBaseBuiltin{ + Name: NixBuildSpecTypeName, + Arguments: []*builtin_argument.BuiltinArgument{ + { + Name: FlakeLocationDir, + IsOptional: false, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.NonEmptyString(value, FlakeLocationDir) + }, + }, + { + Name: NixContextAttr, + IsOptional: false, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.NonEmptyString(value, NixContextAttr) + }, + }, + { + Name: NixImageName, + IsOptional: false, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.NonEmptyString(value, NixImageName) + }, + }, + { + Name: FlakeOutputAttr, + IsOptional: true, + ZeroValueProvider: builtin_argument.ZeroValueProvider[starlark.String], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.NonEmptyString(value, FlakeOutputAttr) + }, + }, + }, + }, + Instantiate: instantiateNixBuildSpec, + } +} + +func instantiateNixBuildSpec(arguments *builtin_argument.ArgumentValuesSet) (builtin_argument.KurtosisValueType, *startosis_errors.InterpretationError) { + kurtosisValueType, err := kurtosis_type_constructor.CreateKurtosisStarlarkTypeDefault(NixBuildSpecTypeName, arguments) + if err != nil { + return nil, err + } + return &NixBuildSpec{ + KurtosisValueTypeDefault: kurtosisValueType, + }, nil +} + +// NixBuildSpec is a starlark.Value that holds all the information for the startosis_engine to initiate an nix build +type NixBuildSpec struct { + *kurtosis_type_constructor.KurtosisValueTypeDefault +} + +func (nixBuildSpec *NixBuildSpec) Copy() (builtin_argument.KurtosisValueType, error) { + copiedValueType, err := nixBuildSpec.KurtosisValueTypeDefault.Copy() + if err != nil { + return nil, err + } + return &NixBuildSpec{ + KurtosisValueTypeDefault: copiedValueType, + }, nil +} + +// Relative locator of build context directory +func (nixBuildSpec *NixBuildSpec) GetBuildContextLocator() (string, *startosis_errors.InterpretationError) { + buildContextLocator, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](nixBuildSpec.KurtosisValueTypeDefault, NixContextAttr) + if interpretationErr != nil { + return "", interpretationErr + } + if !found { + return "", startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", + NixContextAttr, NixBuildSpecTypeName) + } + buildContextLocatorStr := buildContextLocator.GoString() + return buildContextLocatorStr, nil +} + +func (nixBuildSpec *NixBuildSpec) GetFlakeOutput() (string, *startosis_errors.InterpretationError) { + flakeOutput, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](nixBuildSpec.KurtosisValueTypeDefault, FlakeOutputAttr) + if interpretationErr != nil { + return "", interpretationErr + } + if !found { + return "", nil + } + return flakeOutput.GoString(), nil +} + +func (nixBuildSpec *NixBuildSpec) GetImageName() (string, *startosis_errors.InterpretationError) { + imageName, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](nixBuildSpec.KurtosisValueTypeDefault, NixImageName) + if interpretationErr != nil { + return "", interpretationErr + } + if !found { + return "", nil + } + return imageName.GoString(), nil +} + +func (nixBuildSpec *NixBuildSpec) GetFlakeLocationDir() (string, *startosis_errors.InterpretationError) { + flakeDir, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.String](nixBuildSpec.KurtosisValueTypeDefault, FlakeLocationDir) + if interpretationErr != nil { + return "", interpretationErr + } + if !found { + return "", nil + } + return flakeDir.GoString(), nil +} + +func (nixBuildSpec *NixBuildSpec) GetFullFlakeReference() (string, *startosis_errors.InterpretationError) { + flakeDir, err := nixBuildSpec.GetFlakeLocationDir() + if err != nil { + return "", err + } + flakeAttr, err := nixBuildSpec.GetFlakeOutput() + if err != nil { + return "", err + } + fullLocator := fmt.Sprintf("%s/.#%s", flakeDir, flakeAttr) + return fullLocator, nil +} + +func (nixBuildSpec *NixBuildSpec) ToKurtosisType( + locatorOfModuleInWhichThisBuiltInIsBeingCalled string, + packageId string, + packageContentProvider startosis_packages.PackageContentProvider, + packageReplaceOptions map[string]string) (*nix_build_spec.NixBuildSpec, *startosis_errors.InterpretationError) { + // get locator of context directory (relative or absolute) + buildContextLocator, interpretationErr := nixBuildSpec.GetBuildContextLocator() + if interpretationErr != nil { + return nil, interpretationErr + } + + flakeLocationDir, interpretationErr := nixBuildSpec.GetFlakeLocationDir() + if interpretationErr != nil { + return nil, interpretationErr + } + + buildContextDirPathOnDisk, flakeNixFilePathOnDisk, interpretationErr := getOnDiskNixBuildSpecPaths( + buildContextLocator, + flakeLocationDir, + packageId, + locatorOfModuleInWhichThisBuiltInIsBeingCalled, + packageContentProvider, + packageReplaceOptions) + if interpretationErr != nil { + return nil, interpretationErr + } + + imageName, interpretationErr := nixBuildSpec.GetImageName() + if interpretationErr != nil { + return nil, interpretationErr + } + + flakeOutputStr, interpretationErr := nixBuildSpec.GetFlakeOutput() + if interpretationErr != nil { + return nil, interpretationErr + } + + return nix_build_spec.NewNixBuildSpec(imageName, buildContextDirPathOnDisk, flakeNixFilePathOnDisk, flakeOutputStr), nil +} + +// Returns the filepath of the build context directory and flake nix on APIC based on package info +func getOnDiskNixBuildSpecPaths( + buildContextLocator string, + flakeLocationDir string, + packageId string, + locatorOfModuleInWhichThisBuiltInIsBeingCalled string, + packageContentProvider startosis_packages.PackageContentProvider, + packageReplaceOptions map[string]string) (string, string, *startosis_errors.InterpretationError) { + if packageId == startosis_constants.PackageIdPlaceholderForStandaloneScript { + return "", "", startosis_errors.NewInterpretationError("Cannot use NixBuildSpec in a standalone script; create a package and rerun to use NixBuildSpec.") + } + + // get absolute locator of context directory + contextDirAbsoluteLocator, interpretationErr := packageContentProvider.GetAbsoluteLocator(packageId, locatorOfModuleInWhichThisBuiltInIsBeingCalled, buildContextLocator, packageReplaceOptions) + if interpretationErr != nil { + return "", "", interpretationErr + } + + // get on disk directory path of Dockerfile + flakeNixAbsoluteLocator := path.Join(contextDirAbsoluteLocator, flakeLocationDir, defaultNixFlakeFile) + + flakeNixPathOnDisk, interpretationErr := packageContentProvider.GetOnDiskAbsolutePackageFilePath(flakeNixAbsoluteLocator) + if interpretationErr != nil { + return "", "", interpretationErr + } + + contextDirOnDisk, interpretationErr := packageContentProvider.GetOnDiskAbsolutePath(contextDirAbsoluteLocator) + if interpretationErr != nil { + return "", "", interpretationErr + } + // Assume, that flake nix sits at the same level as context directory to get context dir path on disk + flakeDirOnDisk := filepath.Dir(flakeNixPathOnDisk) + + return contextDirOnDisk, flakeDirOnDisk, nil +} diff --git a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go index e043956d98..8e014b3608 100644 --- a/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go +++ b/core/server/api_container/server/startosis_engine/kurtosis_types/service_config/service_config.go @@ -2,8 +2,12 @@ package service_config import ( "fmt" + "math" + "path" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/port_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" @@ -22,8 +26,6 @@ import ( "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_packages" "go.starlark.net/starlark" v1 "k8s.io/api/core/v1" - "math" - "path" ) const ( @@ -47,6 +49,7 @@ const ( LabelsAttr = "labels" UserAttr = "user" TolerationsAttr = "tolerations" + NodeSelectorsAttr = "node_selectors" DefaultPrivateIPAddrPlaceholder = "KURTOSIS_IP_ADDR_PLACEHOLDER" @@ -182,7 +185,8 @@ func NewServiceConfigType() *kurtosis_type_constructor.KurtosisTypeConstructor { IsOptional: true, ZeroValueProvider: builtin_argument.ZeroValueProvider[*starlark.Dict], Validator: func(value starlark.Value) *startosis_errors.InterpretationError { - return builtin_argument.ServiceConfigLabels(value, LabelsAttr) + + return builtin_argument.ServiceLabelsValidator(value, LabelsAttr) }, }, { @@ -199,6 +203,14 @@ func NewServiceConfigType() *kurtosis_type_constructor.KurtosisTypeConstructor { return nil }, }, + { + Name: NodeSelectorsAttr, + IsOptional: true, + ZeroValueProvider: builtin_argument.ZeroValueProvider[*starlark.Dict], + Validator: func(value starlark.Value) *startosis_errors.InterpretationError { + return builtin_argument.StringMappingToString(value, NodeSelectorsAttr) + }, + }, }, }, @@ -241,6 +253,7 @@ func (config *ServiceConfig) ToKurtosisType( var ok bool var imageName string + var maybeNixBuildSpec *nix_build_spec.NixBuildSpec var maybeImageBuildSpec *image_build_spec.ImageBuildSpec var maybeImageRegistrySpec *image_registry_spec.ImageRegistrySpec rawImageAttrValue, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[starlark.Value](config.KurtosisValueTypeDefault, ImageAttr) @@ -250,7 +263,7 @@ func (config *ServiceConfig) ToKurtosisType( if !found { return nil, startosis_errors.NewInterpretationError("Required attribute '%s' could not be found on type '%s'", ImageAttr, ServiceConfigTypeName) } - imageName, maybeImageBuildSpec, maybeImageRegistrySpec, interpretationErr = convertImage( + imageName, maybeImageBuildSpec, maybeImageRegistrySpec, maybeNixBuildSpec, interpretationErr = convertImage( rawImageAttrValue, locatorOfModuleInWhichThisBuiltInIsBeingCalled, packageId, @@ -476,10 +489,23 @@ func (config *ServiceConfig) ToKurtosisType( } } + nodeSelectors := map[string]string{} + nodeSelectorsStarlark, found, interpretationErr := kurtosis_type_constructor.ExtractAttrValue[*starlark.Dict](config.KurtosisValueTypeDefault, NodeSelectorsAttr) + if interpretationErr != nil { + return nil, interpretationErr + } + if found && nodeSelectorsStarlark.Len() > 0 { + nodeSelectors, interpretationErr = kurtosis_types.SafeCastToMapStringString(nodeSelectorsStarlark, NodeSelectorsAttr) + if interpretationErr != nil { + return nil, interpretationErr + } + } + serviceConfig, err := service.CreateServiceConfig( imageName, maybeImageBuildSpec, maybeImageRegistrySpec, + maybeNixBuildSpec, privatePorts, publicPorts, entryPointArgs, @@ -495,6 +521,7 @@ func (config *ServiceConfig) ToKurtosisType( labels, serviceUser, tolerations, + nodeSelectors, ) if err != nil { return nil, startosis_errors.WrapWithInterpretationError(err, "An error occurred creating a service config") @@ -650,31 +677,38 @@ func convertImage( locatorOfModuleInWhichThisBuiltInIsBeingCalled string, packageId string, packageContentProvider startosis_packages.PackageContentProvider, - packageReplaceOptions map[string]string) (string, *image_build_spec.ImageBuildSpec, *image_registry_spec.ImageRegistrySpec, *startosis_errors.InterpretationError) { + packageReplaceOptions map[string]string) (string, *image_build_spec.ImageBuildSpec, *image_registry_spec.ImageRegistrySpec, *nix_build_spec.NixBuildSpec, *startosis_errors.InterpretationError) { imageBuildSpecStarlarkType, isImageBuildSpecStarlarkType := image.(*ImageBuildSpec) imageRegistrySpecStarlarkType, isImageRegistrySpecStarlarkType := image.(*ImageRegistrySpec) + nixBuildSpecStarlarkType, isNixBuildSpecStarlarkType := image.(*NixBuildSpec) if isImageBuildSpecStarlarkType { imageBuildSpec, interpretationErr := imageBuildSpecStarlarkType.ToKurtosisType(locatorOfModuleInWhichThisBuiltInIsBeingCalled, packageId, packageContentProvider, packageReplaceOptions) if interpretationErr != nil { - return "", nil, nil, interpretationErr + return "", nil, nil, nil, interpretationErr } imageName, interpretationErr := imageBuildSpecStarlarkType.GetImageName() if interpretationErr != nil { - return "", nil, nil, interpretationErr + return "", nil, nil, nil, interpretationErr } - return imageName, imageBuildSpec, nil, nil + return imageName, imageBuildSpec, nil, nil, nil } else if isImageRegistrySpecStarlarkType { imageRegistrySpec, interpretationErr := imageRegistrySpecStarlarkType.ToKurtosisType() if interpretationErr != nil { - return "", nil, nil, interpretationErr + return "", nil, nil, nil, interpretationErr + } + return imageRegistrySpec.GetImageName(), nil, imageRegistrySpec, nil, nil + } else if isNixBuildSpecStarlarkType { + nixBuildSpec, interpretationErr := nixBuildSpecStarlarkType.ToKurtosisType(locatorOfModuleInWhichThisBuiltInIsBeingCalled, packageId, packageContentProvider, packageReplaceOptions) + if interpretationErr != nil { + return "", nil, nil, nil, interpretationErr } - return imageRegistrySpec.GetImageName(), nil, imageRegistrySpec, nil + return nixBuildSpec.GetImageName(), nil, nil, nixBuildSpec, nil } else { imageName, interpretationErr := kurtosis_types.SafeCastToString(image, ImageAttr) if interpretationErr != nil { - return "", nil, nil, interpretationErr + return "", nil, nil, nil, interpretationErr } - return imageName, nil, nil, nil + return imageName, nil, nil, nil, nil } } diff --git a/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider.go b/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider.go index 1580f8c93c..a3cf9e0cfc 100644 --- a/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider.go +++ b/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider.go @@ -4,15 +4,19 @@ import ( "errors" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/shared_utils" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/database_accessors/enclave_db" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/user_support_constants" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_constants" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" "github.com/kurtosis-tech/kurtosis/core/server/commons/yaml_parser" + "github.com/kurtosis-tech/stacktrace" "github.com/mholt/archiver" "github.com/sirupsen/logrus" "io" + "io/fs" "os" "path" "strings" @@ -48,12 +52,15 @@ type GitPackageContentProvider struct { repositoriesTmpDir string repositoriesDir string packageReplaceOptionsRepository *packageReplaceOptionsRepository + + githubAuthTokenFile string } -func NewGitPackageContentProvider(repositoriesDir string, tmpDir string, enclaveDb *enclave_db.EnclaveDB) *GitPackageContentProvider { +func NewGitPackageContentProvider(repositoriesDir, tmpDir, githubAuthTokenFile string, enclaveDb *enclave_db.EnclaveDB) *GitPackageContentProvider { return &GitPackageContentProvider{ repositoriesDir: repositoriesDir, repositoriesTmpDir: tmpDir, + githubAuthTokenFile: githubAuthTokenFile, packageReplaceOptionsRepository: newPackageReplaceOptionsRepository(enclaveDb), } } @@ -313,12 +320,24 @@ func (provider *GitPackageContentProvider) atomicClone(parsedURL *shared_utils.P depth = depthAssumingBranchTagsCommitsAreSpecified } + var githubAuth *http.BasicAuth + githubAuthToken, err := provider.getGitHubAuthToken() + if err != nil { + return startosis_errors.WrapWithInterpretationError(err, "An error occurred retrieving GitHub auth token.") + } + if githubAuthToken != "" { + githubAuth = &http.BasicAuth{ + Username: "token", + Password: githubAuthToken, + } + } + //TODO evaluate to use the GitHub client GetContents call instead, because we are cloning the entire repository's workspace with this approach //TODO and the startosis package could be just a small sub-folder inside a giant mono-repository //TODO and even now, in the upload_files instruction, we are allowing to upload files or a folder for any repository, but we are cloning the entire repository for this repo, err := git.PlainClone(gitClonePath, isNotBareClone, &git.CloneOptions{ URL: parsedURL.GetGitURL(), - Auth: nil, + Auth: githubAuth, RemoteName: "", ReferenceName: "", SingleBranch: false, @@ -329,13 +348,20 @@ func (provider *GitPackageContentProvider) atomicClone(parsedURL *shared_utils.P Tags: 0, InsecureSkipTLS: false, CABundle: nil, + Mirror: false, + ShallowSubmodules: false, + ProxyOptions: transport.ProxyOptions{ + URL: "", + Username: "", + Password: "", + }, + Shared: false, }) if err != nil { - // TODO remove public repository from error after we support private repositories // We silent the underlying error here as it can be confusing to the user. For example, when there's a typo in // the repo name, pointing to a non existing repo, the underlying error is: "authentication required" logrus.Errorf("Error cloning git repository: '%s' to '%s'. Error was: \n%s", parsedURL.GetGitURL(), gitClonePath, err.Error()) - return startosis_errors.NewInterpretationError("Error in cloning git repository '%s' to '%s'. Make sure that '%v' exists and is a public repository.", parsedURL.GetGitURL(), gitClonePath, parsedURL.GetGitURL()) + return startosis_errors.NewInterpretationError("Error in cloning git repository '%s' to '%s'. Make sure that '%v' exists or if it's a private repository, that you are logged into GitHub via `kurtosis github login`.", parsedURL.GetGitURL(), gitClonePath, parsedURL.GetGitURL()) } if parsedURL.GetTagBranchOrCommit() != emptyTagBranchOrCommit { @@ -345,11 +371,12 @@ func (provider *GitPackageContentProvider) atomicClone(parsedURL *shared_utils.P } checkoutOptions := &git.CheckoutOptions{ - Hash: plumbing.Hash{}, - Branch: "", - Create: false, - Force: false, - Keep: false, + Hash: plumbing.Hash{}, + Branch: "", + Create: false, + Force: false, + Keep: false, + SparseCheckoutDirectories: []string{}, } if found { // if we have a tag or branch we set it @@ -398,6 +425,18 @@ func (provider *GitPackageContentProvider) atomicClone(parsedURL *shared_utils.P return nil } +// Returns empty string if no token found in [githubAuthTokenFile] or [githubAuthTokenFile] doesn't exist +func (provider *GitPackageContentProvider) getGitHubAuthToken() (string, error) { + tokenBytes, err := os.ReadFile(provider.githubAuthTokenFile) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return "", nil + } + return "", stacktrace.Propagate(err, "An error occurred reading contents at '%v' to retrieve GitHub auth token.", provider.githubAuthTokenFile) + } + return string(tokenBytes), nil +} + // methods checks whether the root of the package is same as repository root // or it is a sub-folder under it func getPathToPackageRoot(parsedPackagePath *shared_utils.ParsedGitURL) string { diff --git a/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider_test.go b/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider_test.go index 33d221124a..9d6e367497 100644 --- a/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider_test.go +++ b/core/server/api_container/server/startosis_engine/startosis_packages/git_package_content_provider/git_package_content_provider_test.go @@ -18,6 +18,8 @@ import ( const ( packagesDirRelPath = "startosis-packages" repositoriesTmpDirRelPath = "tmp-repositories" + githubAuthDirRelPath = "github-auth" + githubAuthTokenFilename = "token.txt" genericRepositoriesDirRelPath = "generic-repositories" packageDescriptionForTest = "package description test" localAbsoluteLocatorNotAllowedMsg = "is referencing a file within the same package using absolute import syntax" @@ -32,8 +34,13 @@ func TestGitPackageProvider_SucceedsForValidPackage(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -48,8 +55,13 @@ func TestGitPackageProvider_SucceedsForValidPackageWithExplicitMasterSet(t *test packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@main" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -64,8 +76,13 @@ func TestGitPackageProvider_SucceedsForValidPackageWithBranch(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@test-branch" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -80,8 +97,13 @@ func TestGitPackageProvider_FailsForInvalidBranch(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@non-existent-branch" _, err = provider.GetModuleContents(sampleStartosisModule) @@ -95,8 +117,13 @@ func TestGitPackageProvider_SucceedsForValidPackageWithTag(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@0.1.1" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -111,8 +138,13 @@ func TestGitPackageProvider_SucceedsForValidPackageWithCommit(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@ec9062828e1a687a5db7dfa750f754f88119e4c0" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -127,8 +159,13 @@ func TestGitPackageProvider_SucceedsForValidPackageWithCommitOnABranch(t *testin packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) sampleStartosisModule := "github.com/kurtosis-tech/sample-startosis-load/sample.star@df88baf51caffbe7e8f66c0e54715f680f4482b2" contents, err := provider.GetModuleContents(sampleStartosisModule) @@ -143,8 +180,13 @@ func TestGitPackageProvider_SucceedsForNonStarlarkFile(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) // TODO replace this with something local or static sampleStarlarkPackage := "github.com/kurtosis-tech/prometheus-package/static-files/prometheus.yml.tmpl" @@ -160,8 +202,13 @@ func TestGitPackageProvider_FailsForNonExistentPackage(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(oackageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(oackageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) nonExistentModulePath := "github.com/kurtosis-tech/non-existent-startosis-load/sample.star" _, err = provider.GetModuleContents(nonExistentModulePath) @@ -175,8 +222,13 @@ func TestGetAbsolutePathOnDisk_WorksForPureDirectories(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) packagePath := "github.com/kurtosis-tech/datastore-army-package/src/helpers.star" pathOnDisk, err := provider.getOnDiskAbsolutePath(packagePath, true) @@ -192,8 +244,13 @@ func TestGetAbsolutePathOnDisk_WorksForNonInMainBranchLocators(t *testing.T) { packageTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, nil) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), nil) absoluteFileLocator := "github.com/kurtosis-tech/sample-dependency-package@test-branch/main.star" pathOnDisk, err := provider.getOnDiskAbsolutePath(absoluteFileLocator, true) @@ -206,12 +263,17 @@ func TestGetAbsolutePathOnDisk_GenericRepositoryDir(t *testing.T) { repositoriesDir, err := os.MkdirTemp("", packagesDirRelPath) require.Nil(t, err) defer os.RemoveAll(repositoriesDir) - repositoriesTmpDir, err := os.MkdirTemp("", repositoriesTmpDirRelPath) require.Nil(t, err) defer os.RemoveAll(repositoriesTmpDir) - provider := NewGitPackageContentProvider(repositoriesDir, repositoriesTmpDir, nil) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) + + provider := NewGitPackageContentProvider(repositoriesDir, repositoriesTmpDir, githubAuthTokenFilePath.Name(), nil) repositoryPathURL := "github.com/kurtosis-tech/minimal-grpc-server/golang/scripts" pathOnDisk, err := provider.GetOnDiskAbsolutePath(repositoryPathURL) @@ -229,7 +291,13 @@ func TestGetAbsolutePathOnDisk_GenericRepositoryFile(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(repositoriesTmpDir) - provider := NewGitPackageContentProvider(repositoriesDir, repositoriesTmpDir, nil) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) + + provider := NewGitPackageContentProvider(repositoriesDir, repositoriesTmpDir, githubAuthTokenFilePath.Name(), nil) repositoryPathURL := "github.com/kurtosis-tech/minimal-grpc-server/golang/scripts/build.sh" pathOnDisk, err := provider.GetOnDiskAbsolutePath(repositoryPathURL) @@ -239,7 +307,7 @@ func TestGetAbsolutePathOnDisk_GenericRepositoryFile(t *testing.T) { } func TestGetAbsoluteLocator_SucceedsForRelativeFile(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/avalanche-package" parentModuleId := "github.com/kurtosis-tech/avalanche-package/src/builder.star" @@ -260,7 +328,7 @@ func TestGetAbsoluteLocator_SucceedsForRelativeFile(t *testing.T) { } func TestGetAbsoluteLocator_RegularReplaceSucceeds(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/sample-startosis-load/sample-package" parentModuleId := "github.com/kurtosis-tech/sample-startosis-load/sample-package/main.star" @@ -277,7 +345,7 @@ func TestGetAbsoluteLocator_RegularReplaceSucceeds(t *testing.T) { } func TestGetAbsoluteLocator_RootPackageReplaceSucceeds(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/sample-startosis-load/sample-package" parentModuleId := "github.com/kurtosis-tech/sample-startosis-load/sample-package/main.star" @@ -295,7 +363,7 @@ func TestGetAbsoluteLocator_RootPackageReplaceSucceeds(t *testing.T) { } func TestGetAbsoluteLocator_SubPackageReplaceSucceeds(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/sample-startosis-load/sample-package" parentModuleId := "github.com/kurtosis-tech/sample-startosis-load/sample-package/main.star" @@ -313,7 +381,7 @@ func TestGetAbsoluteLocator_SubPackageReplaceSucceeds(t *testing.T) { } func TestGetAbsoluteLocator_ReplacePackageInternalModuleSucceeds(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/sample-startosis-load/sample-package" parentModuleId := "github.com/kurtosis-tech/sample-startosis-load/sample-package/main.star" @@ -329,7 +397,7 @@ func TestGetAbsoluteLocator_ReplacePackageInternalModuleSucceeds(t *testing.T) { } func TestGetAbsoluteLocator_NoMainBranchReplaceSucceeds(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/kurtosis-tech/sample-startosis-load/sample-package" parentModuleId := "github.com/kurtosis-tech/sample-startosis-load/sample-package/main.star" @@ -345,7 +413,7 @@ func TestGetAbsoluteLocator_NoMainBranchReplaceSucceeds(t *testing.T) { } func TestGetAbsoluteLocator_ShouldBlockSamePackageAbsoluteLocator(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/main-package" locatorOfModuleInWhichThisBuiltInIsBeingCalled := "github.com/main-package/main.star" @@ -356,7 +424,7 @@ func TestGetAbsoluteLocator_ShouldBlockSamePackageAbsoluteLocator(t *testing.T) } func TestGetAbsoluteLocator_ShouldBlockSamePackageAbsoluteLocatorInSubfolder(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/main-package" locatorOfModuleInWhichThisBuiltInIsBeingCalled := "github.com/main-package/main.star" @@ -367,7 +435,7 @@ func TestGetAbsoluteLocator_ShouldBlockSamePackageAbsoluteLocatorInSubfolder(t * } func TestGetAbsoluteLocator_SameRepositorySubpackagesShouldNotBeBlocked(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/main-project/package1-in-subfolder" locatorOfModuleInWhichThisBuiltInIsBeingCalled := "github.com/main-project/package1-in-subfolder/main.star" @@ -378,7 +446,7 @@ func TestGetAbsoluteLocator_SameRepositorySubpackagesShouldNotBeBlocked(t *testi } func TestGetAbsoluteLocator_RelativeLocatorShouldNotBeBlocked(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/main-package" locatorOfModuleInWhichThisBuiltInIsBeingCalled := "github.com/main-package/main.star" @@ -389,7 +457,7 @@ func TestGetAbsoluteLocator_RelativeLocatorShouldNotBeBlocked(t *testing.T) { } func TestGetAbsoluteLocator_AbsoluteLocatorIsInRootPackageButSourceIsNotShouldNotBeBlocked(t *testing.T) { - provider := NewGitPackageContentProvider("", "", nil) + provider := NewGitPackageContentProvider("", "", "", nil) packageId := "github.com/main-package" locatorOfModuleInWhichThisBuiltInIsBeingCalled := "github.com/child-package/main.star" @@ -598,9 +666,15 @@ func TestCloneReplacedPackagesIfNeeded_Succeeds(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(packageTmpDir) + githubAuthDir, err := os.MkdirTemp("", githubAuthDirRelPath) + require.Nil(t, err) + githubAuthTokenFilePath, err := os.CreateTemp(githubAuthDir, githubAuthTokenFilename) + require.Nil(t, err) + defer os.RemoveAll(githubAuthDir) + enclaveDb := getEnclaveDbForTest(t) - provider := NewGitPackageContentProvider(packageDir, packageTmpDir, enclaveDb) + provider := NewGitPackageContentProvider(packageDir, packageTmpDir, githubAuthTokenFilePath.Name(), enclaveDb) firstRunReplacePackageOptions := map[string]string{ "github.com/kurtosis-tech/sample-dependency-package": "../from-local-folder", diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/images_validator.go b/core/server/api_container/server/startosis_engine/startosis_validator/images_validator.go index 8cc701b9aa..4934fd989f 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/images_validator.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/images_validator.go @@ -2,9 +2,11 @@ package startosis_validator import ( "context" + "sync" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" - "sync" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_download_mode" @@ -58,6 +60,11 @@ func (validator *ImagesValidator) Validate( wg.Add(1) go validator.buildImageUsingBackend(ctx, wg, imageCurrentlyValidating, validator.kurtosisBackend, imageName, imageBuildSpec, imageValidationErrors, imageValidationStarted, imageValidationFinished) } + for imageName, nixBuildSpec := range environment.nixToBuild { + wg.Add(1) + logrus.Warnf("%v - %v", imageName, nixBuildSpec) + go validator.nixBuildUsingBackend(ctx, wg, imageCurrentlyValidating, validator.kurtosisBackend, nixBuildSpec, imageValidationErrors, imageValidationStarted, imageValidationFinished) + } wg.Wait() logrus.Debug("All image validation submitted, currently in progress.") } @@ -116,3 +123,36 @@ func (validator *ImagesValidator) buildImageUsingBackend( } logrus.Debugf("Container image '%s' successfully built", imageName) } + +func (validator *ImagesValidator) nixBuildUsingBackend( + ctx context.Context, + wg *sync.WaitGroup, + imageCurrentlyBuilding chan bool, + backend *backend_interface.KurtosisBackend, + nixBuildSpec *nix_build_spec.NixBuildSpec, + buildErrors chan<- error, + nixBuildStarted chan<- string, + nixBuildFinished chan<- *ValidatedImage) { + imageRef := nixBuildSpec.GetFullFlakeReference() + logrus.Debugf("Requesting the build of image: '%s'", imageRef) + var imageName string + var imageArch string + imageBuiltLocally := true + imagePulledFromRemote := false + defer wg.Done() + imageCurrentlyBuilding <- true + nixBuildStarted <- imageRef + defer func() { + <-imageCurrentlyBuilding + nixBuildFinished <- NewValidatedImage(imageName, imagePulledFromRemote, imageBuiltLocally, imageArch) + }() + + logrus.Debugf("Starting the build of image: '%s'", imageRef) + imageName, err := (*backend).NixBuild(ctx, nixBuildSpec) + if err != nil { + logrus.Warnf("Container image '%s' build failed. Error was: '%s'", imageRef, err.Error()) + buildErrors <- startosis_errors.WrapWithValidationError(err, "Failed to build the required image '%v'.", imageRef) + return + } + logrus.Debugf("Container image '%s' successfully built from Nix definition %s", imageName, imageRef) +} diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go index b125a0a358..a0968d8457 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/validator_environment.go @@ -5,6 +5,7 @@ import ( "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_download_mode" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/image_registry_spec" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/nix_build_spec" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/service_directory" "github.com/kurtosis-tech/kurtosis/core/server/api_container/server/startosis_engine/startosis_errors" @@ -15,6 +16,7 @@ import ( type ValidatorEnvironment struct { imagesToPull map[string]*image_registry_spec.ImageRegistrySpec // "set" of images that need to be downloaded imagesToBuild map[string]*image_build_spec.ImageBuildSpec + nixToBuild map[string]*nix_build_spec.NixBuildSpec serviceNames map[service.ServiceName]ComponentExistence artifactNames map[string]ComponentExistence persistentKeys map[service_directory.DirectoryPersistentKey]ComponentExistence @@ -39,6 +41,7 @@ func NewValidatorEnvironment(serviceNames map[service.ServiceName]bool, artifact return &ValidatorEnvironment{ imagesToPull: map[string]*image_registry_spec.ImageRegistrySpec{}, imagesToBuild: map[string]*image_build_spec.ImageBuildSpec{}, + nixToBuild: map[string]*nix_build_spec.NixBuildSpec{}, serviceNames: serviceNamesWithComponentExistence, artifactNames: artifactNamesWithComponentExistence, serviceNameToPrivatePortIDs: serviceNameToPrivatePortIds, @@ -65,8 +68,12 @@ func (environmemt *ValidatorEnvironment) AppendImageToPullWithAuth(containerImag environmemt.imagesToPull[containerImage] = registrySpec } +func (environment *ValidatorEnvironment) AppendRequiredNixBuild(containerImage string, nixBuildSpec *nix_build_spec.NixBuildSpec) { + environment.nixToBuild[containerImage] = nixBuildSpec +} + func (environment *ValidatorEnvironment) GetNumberOfContainerImagesToProcess() uint32 { - return uint32(len(environment.imagesToPull) + len(environment.imagesToBuild)) + return uint32(len(environment.imagesToPull) + len(environment.imagesToBuild) + len(environment.nixToBuild)) } func (environment *ValidatorEnvironment) AddServiceName(serviceName service.ServiceName) { diff --git a/core/server/commons/enclave_data_directory/enclave_data_directory.go b/core/server/commons/enclave_data_directory/enclave_data_directory.go index e9506b3364..92a5632ebc 100644 --- a/core/server/commons/enclave_data_directory/enclave_data_directory.go +++ b/core/server/commons/enclave_data_directory/enclave_data_directory.go @@ -26,6 +26,12 @@ const ( // We place the temp folder here so that the move to the final destination is atomic // Move from places outside the enclave data dir are not atomic as they're over the network tmpRepositoriesStoreDirname = "tmp-repositories" + + // Name of directory INSIDE THE ENCLAVE DATA DIR at [absMountDirPath] that contains info for authenticating GitHub operations + githubAuthStoreDirname = "github-auth" + + // Name of file within [githubAuthStoreDirname] that contains the GitHub auth token + githubAuthTokenFilename = "token.txt" ) // A directory containing all the data associated with a certain enclave (i.e. a Docker subnetwork where services are spun up) @@ -77,5 +83,11 @@ func (dir EnclaveDataDirectory) GetGitPackageContentProvider(enclaveDb *enclave_ return nil, stacktrace.Propagate(err, "An error occurred ensuring the temporary repositories store dirpath '%v' exists.", tempRepositoriesStoreDirpath) } - return git_package_content_provider.NewGitPackageContentProvider(repositoriesStoreDirpath, tempRepositoriesStoreDirpath, enclaveDb), nil + githubAuthStoreDirpath := path.Join(dir.absMountDirpath, githubAuthStoreDirname) + if err := ensureDirpathExists(githubAuthStoreDirpath); err != nil { + return nil, stacktrace.Propagate(err, "An error occurred ensuring the GitHub auth store dirpath '%v' exists.", githubAuthStoreDirpath) + } + githubAuthTokenFilepath := path.Join(dir.absMountDirpath, githubAuthStoreDirname, githubAuthTokenFilename) + + return git_package_content_provider.NewGitPackageContentProvider(repositoriesStoreDirpath, tempRepositoriesStoreDirpath, githubAuthTokenFilepath, enclaveDb), nil } diff --git a/docs/docs/api-reference/starlark-reference/nix-support.md b/docs/docs/api-reference/starlark-reference/nix-support.md new file mode 100644 index 0000000000..19443d7f23 --- /dev/null +++ b/docs/docs/api-reference/starlark-reference/nix-support.md @@ -0,0 +1,52 @@ +--- +title: NixSupport +sidebar_label: NixSupport +--- + +You can provide Kurtosis just with source code and a Nix definition on how to build an image. Kurtosis will take care of building and deploying the image directly into the enclave without the need to upload or register the image beforehand. + +For that, we use Nix flakes, which is a way to package build definitions and dependencies in a reproducible manner. Using Nix flakes, you can define your system configurations and dependencies in a single file (`flake.nix`), making it easier to manage and share. + +Here's a basic explanation of how you can generate Docker images from services using Nix flakes: + +1. **Install Nix**: Installing Nix isn't strictly necessary with Kurtosis, but it's recommended if you are creating or developing the package. You can install it by following the instructions on the Nix website: [https://nixos.org/download.html](https://nixos.org/download.html) + +2. **Create a Nix Flake**: Go to your project root directory and initialize a Nix flake. You can do this by running: + ```bash + cd myproject + nix flake init -t simple + ``` + +3. **Define Your Services**: Inside the `flake.nix` file, you can define your services and their dependencies. For example: + ```nix + { + description = "My project"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs, myservice }: { + defaultPackage.aarch64-darwin = nixpkgs.lib.dockerTools.buildImage { + name = "myservice"; + tag = "latest"; + contents = [ myservice ]; + config.Cmd = [ "myservice-binary" ]; + }; + }; + } + ``` + +4. **Add a service definition to Starlark**: Now just add a service to your Starlark configuration: + ```python + plan.add_service( + name = "nix-example", + config = ServiceConfig( + image = NixBuildSpec(image_name = "myservice", flake_location_dir = ".", build_context_dir = "./"), + ), + ) + ``` + +5. **Build and Deploy with Kurtosis**: From your package folder, simply run `kurtosis run .` to get your cluster up and running. + +This is just a basic example. Depending on your specific use case and requirements, you may need to adjust the configuration and dependencies in your `flake.nix` file accordingly. Additionally, you can add more services, configure networking, volumes, environment variables, etc., based on your needs. \ No newline at end of file diff --git a/docs/docs/api-reference/starlark-reference/plan.md b/docs/docs/api-reference/starlark-reference/plan.md index 34d18ee71e..99b570c1ec 100644 --- a/docs/docs/api-reference/starlark-reference/plan.md +++ b/docs/docs/api-reference/starlark-reference/plan.md @@ -557,7 +557,7 @@ The instruction returns a `struct` with [future references][future-references-re ..., config=ServiceConfig( name="service_one", - files={"/src": results.file_artifacts[0]}, # copies the directory task into service_one + files={"/src": result.file_artifacts[0]}, # copies the directory task into service_one ) ) # the path to the file will look like: /src/task/test.txt @@ -565,7 +565,7 @@ The instruction returns a `struct` with [future references][future-references-re ..., config=ServiceConfig( name="service_two", - files={"/src": results.file_artifacts[1]}, # copies the file test.txt into service_two + files={"/src": result.file_artifacts[1]}, # copies the file test.txt into service_two ), ) # the path to the file will look like: /src/test.txt ``` diff --git a/docs/docs/api-reference/starlark-reference/service-config.md b/docs/docs/api-reference/starlark-reference/service-config.md index d94fca7794..11afaa6592 100644 --- a/docs/docs/api-reference/starlark-reference/service-config.md +++ b/docs/docs/api-reference/starlark-reference/service-config.md @@ -52,9 +52,31 @@ config = ServiceConfig( # The URL of the registry registry = "http://my.registry.io/" ) + + OR + + image = NixBuildSpec( + # The name of the image that needs to be pulled qualified with the registry + # MANDATORY + image_name = "hello-world-server", + + # Locator to build context within the Kurtosis package + # This allows to select a sub-package where the context is going be used to build the image + # MANDATORY + build_context_dir = "./" + + # The relative path (from the `build_context_dir`) to the folder containing the flake.nix file + # MANDATORY + flake_location_dir = "./hello-go", + + # The selector for the Flake output with the image derivation. Fallbacks to the default package. + flake_output = "containerImage", + ) - # The ports that the container should listen on, identified by a user-friendly ID that can be used to select the port again in the future. - # If no ports are provided, no ports will be exposed on the host machine, unless there is an EXPOSE in the Dockerfile + # The ports that the container should listen on, identified by a user-friendly ID that can be used to select the port again in the future. + # Kurtosis will automatically perform a check to ensure all declared UDP and TCP ports are open and ready for traffic and connections upon startup. + # You may specify a custom wait timeout duration or disable the feature entirely, learn more via PortSpec docs + # If no ports are provided, no ports will be exposed on the host machine, unless there is an EXPOSE in the Dockerfile. # OPTIONAL (Default: {}) ports = { "grpc": PortSpec( @@ -174,7 +196,16 @@ config = ServiceConfig( effect = "NoSchedule", toleration_seconds = 64, ) - ] + ], + + # A map of node selectors + # This refers to Node Selectors in Kubernetes https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + # This has no effect on Docker + # This is an experimental feature that might get replaced with a better abstraction suited for Kurtosis + # OPTIONAL + node_selectors = { + "disktype": "ssd", + } ) ``` Note that `ImageBuildSpec` can only be used in packages and not standalone scripts as it relies on build context in package. @@ -185,6 +216,8 @@ The `ports` dictionary argument accepts a key value pair, where `key` is a user The `files` dictionary argument accepts a key value pair, where `key` is the path where the contents of the artifact will be mounted to and `value` is a [Directory][directory] object or files artifact name. Using a `Directory` object with `artifact_name` is strictly equivalent to directly using the files artifact name as the value of the dictionary. This is just to simplify usage. +See [Nix Support][nix-support] for more information on how to use the Nix and Kurtosis together. + You can view more information on [configuring the `ReadyCondition` type here][ready-condition]. :::tip @@ -236,3 +269,4 @@ The `tolerations` field expects a list of [`Toleration`][toleration] objects bei [package]: ../../advanced-concepts/packages.md [user]: ./user.md [toleration]: ./toleration.md +[nix-support]: ./nix-support.md diff --git a/docs/docs/guides/running-docker-compose.md b/docs/docs/guides/running-docker-compose.md index 0561d99d96..a0f5402c13 100644 --- a/docs/docs/guides/running-docker-compose.md +++ b/docs/docs/guides/running-docker-compose.md @@ -73,7 +73,7 @@ kurtosis run . ``` OR using github link: ``` -kurtosis run github.com/awesome-compose/nextcloud-redis-mariadb +kurtosis run github.com/docker/awesome-compose/nextcloud-redis-mariadb ``` Behind the scenes, Kurtosis will interpret your Docker Compose setup as a Kurtosis [package](../get-started/basic-concepts.md#package) and convert it into [starlark](../advanced-concepts/starlark.md) that is executed on an [enclave](../get-started/basic-concepts.md#enclave). The output will look like this: diff --git a/docs/yarn.lock b/docs/yarn.lock index 3d3f411a12..4b50e3a37e 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -4147,9 +4147,9 @@ flux@^4.0.1: fbjs "^3.0.1" follow-redirects@^1.0.0, follow-redirects@^1.14.7: - version "1.15.0" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz" - integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.2" diff --git a/enclave-manager/server/server.go b/enclave-manager/server/server.go index 3b4dabb8ff..5952e7a188 100644 --- a/enclave-manager/server/server.go +++ b/enclave-manager/server/server.go @@ -204,6 +204,7 @@ func (c *WebServer) ListFilesArtifactNamesAndUuids(ctx context.Context, req *con return nil, stacktrace.Propagate(err, "Failed to create the APIC client") } + // nolint: exhaustruct serviceRequest := &connect.Request[emptypb.Empty]{} result, err := (*apiContainerServiceClient).ListFilesArtifactNamesAndUuids(ctx, serviceRequest) if err != nil { @@ -291,6 +292,7 @@ func (c *WebServer) DestroyEnclave(ctx context.Context, req *connect.Request[kur if err != nil { return nil, err } + // nolint: exhaustruct return &connect.Response[emptypb.Empty]{}, nil } diff --git a/enclave-manager/web/cypress.config.ts b/enclave-manager/web/cypress.config.ts index c0d7712df5..33076b7561 100644 --- a/enclave-manager/web/cypress.config.ts +++ b/enclave-manager/web/cypress.config.ts @@ -3,6 +3,10 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { defaultCommandTimeout: 20000, + retries: { + runMode: 2, + openMode: 0, + }, setupNodeEvents(on, config) { // implement node event listeners here }, diff --git a/enclave-manager/web/cypress/e2e/enclaveList.cy.ts b/enclave-manager/web/cypress/e2e/enclaveList.cy.ts index a8530221f9..73bc64ac5a 100644 --- a/enclave-manager/web/cypress/e2e/enclaveList.cy.ts +++ b/enclave-manager/web/cypress/e2e/enclaveList.cy.ts @@ -21,6 +21,8 @@ describe("Enclave List", () => { // Update the postgres instance cy.contains("button", "Edit").click(); + cy.contains("Loading").parents(".chakra-modal__content").contains("Error").should("not.exist"); + cy.contains("Loading").should("not.exist"); cy.focusInputWithLabel("Max CPU").type("1024"); cy.contains("button", "Update").click(); cy.contains("Script completed", { timeout: 30 * 1000 }); diff --git a/enclave-manager/web/cypress/support/commands.ts b/enclave-manager/web/cypress/support/commands.ts index aa5578694a..d5dada8bc8 100644 --- a/enclave-manager/web/cypress/support/commands.ts +++ b/enclave-manager/web/cypress/support/commands.ts @@ -24,11 +24,11 @@ Cypress.Commands.add("createAndGoToEnclave", (enclaveName: string) => { cy.contains("button", "Run").click(); - cy.url({ timeout: 10 * 1000 }).should("match", /enclave\/[^/]+\/logs/); + cy.url({ timeout: 30 * 1000 }).should("match", /enclave\/[^/]+\/logs/); cy.contains("button", "Edit").should("be.disabled"); - cy.contains("Validating", { timeout: 10 * 1000 }); - cy.contains("Script completed", { timeout: 10 * 1000 }); + cy.contains("Validating", { timeout: 30 * 1000 }); + cy.contains("Script completed", { timeout: 30 * 1000 }); cy.contains("button", "Edit").should("be.enabled"); // Go to the enclave overview diff --git a/enclave-manager/web/lerna.json b/enclave-manager/web/lerna.json index f445c92376..3aa2007da1 100644 --- a/enclave-manager/web/lerna.json +++ b/enclave-manager/web/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.86.14", + "version": "0.86.21", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useNx": false, diff --git a/enclave-manager/web/packages/app/package.json b/enclave-manager/web/packages/app/package.json index 44e657832d..24b98767b7 100644 --- a/enclave-manager/web/packages/app/package.json +++ b/enclave-manager/web/packages/app/package.json @@ -1,22 +1,28 @@ { "name": "@kurtosis/emui-app", - "version": "0.86.14", + "version": "0.86.21", "private": true, "homepage": ".", "dependencies": { + "@dagrejs/dagre": "^1.0.4", "ansi-to-html": "^0.7.2", "enclave-manager-sdk": "file:../../../api/typescript", "html-react-parser": "^4.2.2", "js-cookie": "^3.0.5", "kurtosis-cloud-indexer-sdk": "^0.0.2", - "kurtosis-ui-components": "0.86.14", + "kurtosis-ui-components": "0.86.21", "react-error-boundary": "^4.0.11", "react-hook-form": "^7.47.0", + "react-mentions": "^4.4.10", + "reactflow": "^11.10.2", + "uuid": "^9.0.1", "yaml": "^2.3.4" }, "devDependencies": { "@types/js-cookie": "^3.0.6", + "@types/react-mentions": "^4.1.13", "@types/streamsaver": "^2.0.4", + "@types/uuid": "^9.0.8", "serve": "^14.2.1", "source-map-explorer": "^2.5.3" }, diff --git a/enclave-manager/web/packages/app/src/client/enclaveManager/KurtosisClient.ts b/enclave-manager/web/packages/app/src/client/enclaveManager/KurtosisClient.ts index 53f7c97b10..68074ecdab 100644 --- a/enclave-manager/web/packages/app/src/client/enclaveManager/KurtosisClient.ts +++ b/enclave-manager/web/packages/app/src/client/enclaveManager/KurtosisClient.ts @@ -3,6 +3,7 @@ import { DownloadFilesArtifactArgs, FilesArtifactNameAndUuid, RunStarlarkPackageArgs, + RunStarlarkScriptArgs, ServiceInfo, } from "enclave-manager-sdk/build/api_container_service_pb"; import { @@ -22,6 +23,7 @@ import { GetStarlarkRunRequest, InspectFilesArtifactContentsRequest, RunStarlarkPackageRequest, + RunStarlarkScriptRequest, } from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_pb"; import { assertDefined, asyncResult, isDefined, RemoveFunctions } from "kurtosis-ui-components"; import { EnclaveFullInfo } from "../../emui/enclaves/types"; @@ -202,17 +204,37 @@ export abstract class KurtosisClient { apicInfo: RemoveFunctions, packageId: string, args: Record, + dryRun: boolean = false, ) { // Not currently using asyncResult as the return type here is an asyncIterable const request = new RunStarlarkPackageRequest({ apicIpAddress: apicInfo.bridgeIpAddress, apicPort: apicInfo.grpcPortInsideEnclave, RunStarlarkPackageArgs: new RunStarlarkPackageArgs({ - dryRun: false, + dryRun, packageId: packageId, serializedParams: JSON.stringify(args), }), }); return this.client.runStarlarkPackage(request, this.getHeaderOptions()); } + + async runStarlarkScript( + apicInfo: RemoveFunctions, + serializedScript: string, + args: Record = {}, + dryRun: boolean = false, + ) { + // Not currently using asyncResult as the return type here is an asyncIterable + const request = new RunStarlarkScriptRequest({ + apicIpAddress: apicInfo.bridgeIpAddress, + apicPort: apicInfo.grpcPortInsideEnclave, + RunStarlarkScriptArgs: new RunStarlarkScriptArgs({ + dryRun, + serializedScript, + serializedParams: JSON.stringify(args), + }), + }); + return this.client.runStarlarkScript(request, this.getHeaderOptions()); + } } diff --git a/enclave-manager/web/packages/app/src/emui/App.tsx b/enclave-manager/web/packages/app/src/emui/App.tsx index ad7979afc8..10475abd39 100644 --- a/enclave-manager/web/packages/app/src/emui/App.tsx +++ b/enclave-manager/web/packages/app/src/emui/App.tsx @@ -5,10 +5,12 @@ import { KurtosisClientProvider, useKurtosisClient } from "../client/enclaveMana import { KurtosisPackageIndexerProvider } from "../client/packageIndexer/KurtosisPackageIndexerClientContext"; import { CatalogContextProvider } from "./catalog/CatalogContext"; import { catalogRoutes } from "./catalog/CatalogRoutes"; +import { BuildEnclave } from "./enclaves/components/BuildEnclave"; import { CreateEnclave } from "./enclaves/components/CreateEnclave"; import { enclaveRoutes } from "./enclaves/EnclaveRoutes"; import { EnclavesContextProvider } from "./enclaves/EnclavesContext"; import { Navbar } from "./Navbar"; +import { SettingsContextProvider } from "./settings"; const logLogo = (t: string) => console.log(`%c ${t}`, "background: black; color: #00C223"); logLogo(` @@ -35,13 +37,15 @@ console.log(`Kurtosis web UI version: ${process.env.REACT_APP_VERSION || "Unknow export const EmuiApp = () => { return ( - - - - - - - + + + + + + + + + ); }; @@ -65,6 +69,7 @@ const KurtosisRouter = () => { + ), children: enclaveRoutes(), diff --git a/enclave-manager/web/packages/app/src/emui/Navbar.tsx b/enclave-manager/web/packages/app/src/emui/Navbar.tsx index 3176b6c93f..ae4e647cf3 100644 --- a/enclave-manager/web/packages/app/src/emui/Navbar.tsx +++ b/enclave-manager/web/packages/app/src/emui/Navbar.tsx @@ -1,5 +1,7 @@ import { Button, + FormControl, + FormLabel, Input, InputGroup, InputRightElement, @@ -10,6 +12,7 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Switch, Text, } from "@chakra-ui/react"; import { CopyButton, NavButton, Navigation, NavigationDivider } from "kurtosis-ui-components"; @@ -21,8 +24,10 @@ import { PiLinkSimpleBold } from "react-icons/pi"; import { Link, useLocation } from "react-router-dom"; import { KURTOSIS_CLOUD_CONNECT_URL } from "../client/constants"; import { useKurtosisClient } from "../client/enclaveManager/KurtosisClientContext"; +import { settingKeys, useSettings } from "./settings"; export const Navbar = () => { + const { updateSetting, settings } = useSettings(); const location = useLocation(); const kurtosisClient = useKurtosisClient(); const [showAboutDialog, setShowAboutDialog] = useState(false); @@ -78,7 +83,32 @@ export const Navbar = () => { /> - + + Settings: + + + + Enable experimental enclave builder interface? + + + updateSetting( + settingKeys.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE, + !settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE, + ) + } + isChecked={settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE} + /> + diff --git a/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx b/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx index 577f13d542..fb253de90e 100644 --- a/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx +++ b/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx @@ -2,10 +2,10 @@ import { Flex, Heading, Spinner } from "@chakra-ui/react"; import { GetPackagesResponse, KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; import { ReadPackageResponse } from "kurtosis-cloud-indexer-sdk/build/kurtosis_package_indexer_pb"; import { isDefined, SavedPackagesProvider } from "kurtosis-ui-components"; -import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from "react"; +import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useLayoutEffect, useState } from "react"; import { Result } from "true-myth"; import { useKurtosisPackageIndexerClient } from "../../client/packageIndexer/KurtosisPackageIndexerClientContext"; -import { loadSavedPackageNames, storeSavedPackages } from "./storage"; +import { settingKeys, useSettings } from "../settings"; export type CatalogState = { catalog: Result; @@ -16,6 +16,7 @@ export type CatalogState = { const CatalogContext = createContext(null as any); export const CatalogContextProvider = ({ children }: PropsWithChildren) => { + const { settings, updateSetting } = useSettings(); const packageIndexerClient = useKurtosisPackageIndexerClient(); const [catalog, setCatalog] = useState>(); const [savedPackages, setSavedPackages] = useState([]); @@ -25,24 +26,25 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => { const catalog = await packageIndexerClient.getPackages(); setCatalog(catalog); - if (catalog.isOk) { - const savedPackageNames = new Set(loadSavedPackageNames()); - setSavedPackages(catalog.value.packages.filter((kurtosisPackage) => savedPackageNames.has(kurtosisPackage.name))); - } - return catalog; }, [packageIndexerClient]); - const togglePackageSaved = useCallback((kurtosisPackage: KurtosisPackage) => { - setSavedPackages((savedPackages) => { - const packageSavedAlready = savedPackages.some((p) => p.name === kurtosisPackage.name); - const newSavedPackages: KurtosisPackage[] = packageSavedAlready - ? savedPackages.filter((p) => p.name !== kurtosisPackage.name) - : [...savedPackages, kurtosisPackage]; - storeSavedPackages(newSavedPackages); - return newSavedPackages; - }); - }, []); + const togglePackageSaved = useCallback( + (kurtosisPackage: KurtosisPackage) => { + setSavedPackages((savedPackages) => { + const packageSavedAlready = savedPackages.some((p) => p.name === kurtosisPackage.name); + const newSavedPackages: KurtosisPackage[] = packageSavedAlready + ? savedPackages.filter((p) => p.name !== kurtosisPackage.name) + : [...savedPackages, kurtosisPackage]; + updateSetting( + settingKeys.SAVED_PACKAGES, + newSavedPackages.map((kurtosisPackage) => kurtosisPackage.name), + ); + return newSavedPackages; + }); + }, + [updateSetting], + ); const getSinglePackage = useCallback( async (packageName: string) => { @@ -55,6 +57,14 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => { refreshCatalog(); }, [refreshCatalog]); + // Use a Layout effect so that the saved packages are set before first render. + useLayoutEffect(() => { + if (isDefined(catalog) && catalog.isOk) { + const savedPackageNames = new Set(settings.SAVED_PACKAGES); + setSavedPackages(catalog.value.packages.filter((kurtosisPackage) => savedPackageNames.has(kurtosisPackage.name))); + } + }, [catalog, settings.SAVED_PACKAGES]); + if (!isDefined(catalog)) { return ( diff --git a/enclave-manager/web/packages/app/src/emui/catalog/storage.ts b/enclave-manager/web/packages/app/src/emui/catalog/storage.ts deleted file mode 100644 index 988b02e552..0000000000 --- a/enclave-manager/web/packages/app/src/emui/catalog/storage.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; -import { isDefined, stringifyError } from "kurtosis-ui-components"; - -const SAVED_PACKAGES_LOCAL_STORAGE_KEY = "kurtosis-saved-packages"; - -export const storeSavedPackages = (kurtosisPackages: KurtosisPackage[]) => { - localStorage.setItem( - SAVED_PACKAGES_LOCAL_STORAGE_KEY, - JSON.stringify(kurtosisPackages.map((kurtosisPackage) => kurtosisPackage.name)), - ); -}; - -export const loadSavedPackageNames = () => { - try { - const savedRawValue = localStorage.getItem(SAVED_PACKAGES_LOCAL_STORAGE_KEY); - - if (!isDefined(savedRawValue)) { - return []; - } - - return JSON.parse(savedRawValue); - } catch (error: any) { - console.error(`Unable to load saved package names. Got error: ${stringifyError(error)}`); - return []; - } -}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/EnclavesContext.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/EnclavesContext.tsx index 0e91aac480..e1b8c63af4 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/EnclavesContext.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/EnclavesContext.tsx @@ -47,6 +47,13 @@ export type EnclavesState = { enclave: RemoveFunctions, packageId: string, args: Record, + dryRun?: boolean, + ) => Promise>; + runStarlarkScript: ( + enclave: RemoveFunctions, + script: string, + args: Record, + dryRun?: boolean, ) => Promise>; updateStarlarkFinishedInEnclave: (enclave: RemoveFunctions) => void; }; @@ -168,10 +175,30 @@ export const EnclavesContextProvider = ({ skipInitialLoad, children }: EnclavesC ); const runStarlarkPackage = useCallback( - async (enclave: RemoveFunctions, packageId: string, args: Record) => { + async ( + enclave: RemoveFunctions, + packageId: string, + args: Record, + dryRun: boolean = false, + ) => { + setState((state) => ({ ...state, starlarkRunningInEnclaves: [...state.starlarkRunningInEnclaves, enclave] })); + assertDefined(enclave.apiContainerInfo, `apic info not defined in enclave ${enclave.name}`); + const resp = await kurtosisClient.runStarlarkPackage(enclave.apiContainerInfo, packageId, args, dryRun); + return resp; + }, + [kurtosisClient], + ); + + const runStarlarkScript = useCallback( + async ( + enclave: RemoveFunctions, + script: string, + args: Record, + dryRun: boolean = false, + ) => { setState((state) => ({ ...state, starlarkRunningInEnclaves: [...state.starlarkRunningInEnclaves, enclave] })); assertDefined(enclave.apiContainerInfo, `apic info not defined in enclave ${enclave.name}`); - const resp = await kurtosisClient.runStarlarkPackage(enclave.apiContainerInfo, packageId, args); + const resp = await kurtosisClient.runStarlarkScript(enclave.apiContainerInfo, script, args, dryRun); return resp; }, [kurtosisClient], @@ -217,6 +244,7 @@ export const EnclavesContextProvider = ({ skipInitialLoad, children }: EnclavesC createEnclave, destroyEnclaves, runStarlarkPackage, + runStarlarkScript, updateStarlarkFinishedInEnclave, }} > diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/BuildEnclave.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/BuildEnclave.tsx new file mode 100644 index 0000000000..1e3dfa0880 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/BuildEnclave.tsx @@ -0,0 +1,31 @@ +import { isDefined } from "kurtosis-ui-components"; +import { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useSettings } from "../../settings"; +import { KURTOSIS_BUILD_ENCLAVE_URL_ARG } from "./configuration/drawer/constants"; +import { EnclaveBuilderModal } from "./modals/EnclaveBuilderModal"; + +export const BuildEnclave = () => { + const { settings } = useSettings(); + const navigate = useNavigate(); + const location = useLocation(); + + const [buildEnclaveOpen, setBuildEnclaveOpen] = useState(false); + + useEffect(() => { + setBuildEnclaveOpen(location.hash === `#${KURTOSIS_BUILD_ENCLAVE_URL_ARG}`); + }, [location]); + + const handleCloseBuildEnclave = () => { + setBuildEnclaveOpen(false); + if (isDefined(location.hash)) { + navigate(`${location.pathname}${location.search}`); + } + }; + + if (!settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE) { + return null; + } + + return ; +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/EditEnclaveButton.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/EditEnclaveButton.tsx index 994a1fea76..d7ae8eacb9 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/EditEnclaveButton.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/EditEnclaveButton.tsx @@ -3,8 +3,11 @@ import { KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; import { isDefined } from "kurtosis-ui-components"; import { useState } from "react"; import { FiEdit2 } from "react-icons/fi"; +import { useSettings } from "../../settings"; import { EnclaveFullInfo } from "../types"; import { CreateOrConfigureEnclaveDrawer } from "./configuration/drawer/CreateOrConfigureEnclaveDrawer"; +import { starlarkScriptContainsEMUIBuildState } from "./modals/enclaveBuilder/utils"; +import { EnclaveBuilderModal } from "./modals/EnclaveBuilderModal"; import { PackageLoadingModal } from "./modals/PackageLoadingModal"; type EditEnclaveButtonProps = ButtonProps & { @@ -12,15 +15,7 @@ type EditEnclaveButtonProps = ButtonProps & { }; export const EditEnclaveButton = ({ enclave, ...buttonProps }: EditEnclaveButtonProps) => { - const [showPackageLoader, setShowPackageLoader] = useState(false); - const [showEnclaveConfiguration, setShowEnclaveConfiguration] = useState(false); - const [kurtosisPackage, setKurtosisPackage] = useState(); - - const handlePackageLoaded = (kurtosisPackage: KurtosisPackage) => { - setShowPackageLoader(false); - setKurtosisPackage(kurtosisPackage); - setShowEnclaveConfiguration(true); - }; + const { settings } = useSettings(); if (!isDefined(enclave.starlarkRun)) { return ( @@ -40,6 +35,32 @@ export const EditEnclaveButton = ({ enclave, ...buttonProps }: EditEnclaveButton ); } + if ( + settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE && + starlarkScriptContainsEMUIBuildState(enclave.starlarkRun.value.serializedScript) + ) { + return ; + } + + return ; +}; + +type EditFromPackageButtonProps = ButtonProps & { + enclave: EnclaveFullInfo; + packageId: string; +}; + +const EditFromPackageButton = ({ enclave, packageId, ...buttonProps }: EditFromPackageButtonProps) => { + const [showPackageLoader, setShowPackageLoader] = useState(false); + const [showEnclaveConfiguration, setShowEnclaveConfiguration] = useState(false); + const [kurtosisPackage, setKurtosisPackage] = useState(); + + const handlePackageLoaded = (kurtosisPackage: KurtosisPackage) => { + setShowPackageLoader(false); + setKurtosisPackage(kurtosisPackage); + setShowEnclaveConfiguration(true); + }; + return ( <> - {showPackageLoader && ( - - )} + {showPackageLoader && } { @@ -71,3 +90,35 @@ export const EditEnclaveButton = ({ enclave, ...buttonProps }: EditEnclaveButton ); }; + +type EditFromScriptButtonProps = ButtonProps & { + enclave: EnclaveFullInfo; +}; + +const EditFromScriptButton = ({ enclave, ...buttonProps }: EditFromScriptButtonProps) => { + const [showBuilderModal, setShowBuilderModal] = useState(false); + + return ( + <> + + + + setShowBuilderModal(false)} + existingEnclave={enclave} + /> + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisArgumentTypeInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisArgumentTypeInput.tsx new file mode 100644 index 0000000000..cc7c8e3fd3 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisArgumentTypeInput.tsx @@ -0,0 +1,97 @@ +import { ArgumentValueType } from "kurtosis-cloud-indexer-sdk"; +import { assertDefined } from "kurtosis-ui-components"; +import { BooleanArgumentInput } from "../form/BooleanArgumentInput"; +import { DictArgumentInput } from "../form/DictArgumentInput"; +import { IntegerArgumentInput } from "../form/IntegerArgumentInput"; +import { JSONArgumentInput } from "../form/JSONArgumentInput"; +import { ListArgumentInput } from "../form/ListArgumentInput"; +import { StringArgumentInput } from "../form/StringArgumentInput"; +import { KurtosisFormInputProps } from "../form/types"; +import { ConfigureEnclaveForm } from "./types"; + +type KurtosisArgumentTypeInputProps = KurtosisFormInputProps & { + type?: ArgumentValueType; + subType1?: ArgumentValueType; + subType2?: ArgumentValueType; +}; + +export const KurtosisArgumentTypeInput = ({ + type, + subType1, + subType2, + name, + placeholder, + isRequired, + validate, + disabled, + width, + size, + tabIndex, +}: KurtosisArgumentTypeInputProps) => { + const childProps: KurtosisFormInputProps = { + name, + placeholder, + isRequired, + validate, + disabled, + width, + size, + tabIndex, + }; + + switch (type) { + case ArgumentValueType.INTEGER: + return ; + case ArgumentValueType.DICT: + assertDefined( + subType1, + `innerType1 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, + ); + assertDefined( + subType2, + `innerType2 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, + ); + return ( + ( + + )} + ValueFieldComponent={(props) => ( + + )} + {...childProps} + /> + ); + case ArgumentValueType.LIST: + assertDefined( + subType1, + `innerType1 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, + ); + return ( + ( + + )} + createNewValue={() => ({ value: "" })} + {...childProps} + /> + ); + case ArgumentValueType.BOOL: + return ; + case ArgumentValueType.STRING: + return ; + case ArgumentValueType.JSON: + default: + return ; + } +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisPackageArgumentInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisPackageArgumentInput.tsx index 8694d79ec9..8a5aac7824 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisPackageArgumentInput.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/KurtosisPackageArgumentInput.tsx @@ -1,6 +1,6 @@ import { PackageArg } from "kurtosis-cloud-indexer-sdk"; -import { KurtosisArgumentTypeInput } from "./inputs/KurtosisArgumentTypeInput"; -import { KurtosisArgumentFormControl } from "./KurtosisArgumentFormControl"; +import { KurtosisFormControl } from "../form/KurtosisFormControl"; +import { KurtosisArgumentTypeInput } from "./KurtosisArgumentTypeInput"; import { argToTypeString } from "./utils"; type KurtosisPackageArgumentInputProps = { @@ -22,7 +22,7 @@ export const KurtosisPackageArgumentInput = ({ argument, disabled }: KurtosisPac .join(" "); return ( - - + ); }; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/EnclaveConfigureBody.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/EnclaveConfigureBody.tsx index ba86b69e3f..b8c8ca78bd 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/EnclaveConfigureBody.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/EnclaveConfigureBody.tsx @@ -38,11 +38,11 @@ import { useNavigate } from "react-router-dom"; import { useKurtosisClient } from "../../../../../../client/enclaveManager/KurtosisClientContext"; import { useEnclavesContext } from "../../../../EnclavesContext"; import { EnclaveFullInfo } from "../../../../types"; +import { BooleanArgumentInput } from "../../../form/BooleanArgumentInput"; +import { KurtosisFormControl } from "../../../form/KurtosisFormControl"; +import { StringArgumentInput } from "../../../form/StringArgumentInput"; import { allowedEnclaveNamePattern, isEnclaveNameAllowed } from "../../../utils"; import { EnclaveConfigurationForm, EnclaveConfigurationFormImperativeAttributes } from "../../EnclaveConfigurationForm"; -import { BooleanArgumentInput } from "../../inputs/BooleanArgumentInput"; -import { StringArgumentInput } from "../../inputs/StringArgumentInput"; -import { KurtosisArgumentFormControl } from "../../KurtosisArgumentFormControl"; import { KurtosisPackageArgumentInput } from "../../KurtosisPackageArgumentInput"; import { ConfigureEnclaveForm } from "../../types"; import { transformKurtosisArgsToFormArgs } from "../../utils"; @@ -272,7 +272,7 @@ export const EnclaveConfigureBody = forwardRef - + - + ({ find: () => searchRef.current?.focus() }), [searchRef])); + useEffect(() => { startCheckSinglePackage(searchTerm); }, [startCheckSinglePackage, searchTerm]); @@ -197,6 +200,16 @@ export const PackageSelectBody = ({ onClick={() => onPackageSelected(kurtosisPackage)} /> ))} + + All Packages + + {searchResults.value.map((kurtosisPackage) => ( + onPackageSelected(kurtosisPackage)} + /> + ))} )} diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/constants.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/constants.ts index 24c20621ee..f4db1f11bc 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/constants.ts +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/constants.ts @@ -1,3 +1,4 @@ export const KURTOSIS_PACKAGE_PARAMS_URL_ARG = "package-args"; export const KURTOSIS_PACKAGE_ID_URL_ARG = "package-id"; export const KURTOSIS_CREATE_ENCLAVE_URL_ARG = "create-enclave"; +export const KURTOSIS_BUILD_ENCLAVE_URL_ARG = "build-enclave"; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/KurtosisArgumentTypeInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/KurtosisArgumentTypeInput.tsx deleted file mode 100644 index cda736a1de..0000000000 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/KurtosisArgumentTypeInput.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as CSS from "csstype"; -import { ArgumentValueType } from "kurtosis-cloud-indexer-sdk"; -import { assertDefined } from "kurtosis-ui-components"; -import { FieldPath } from "react-hook-form"; -import { ConfigureEnclaveForm } from "../types"; -import { BooleanArgumentInput } from "./BooleanArgumentInput"; -import { DictArgumentInput } from "./DictArgumentInput"; -import { IntegerArgumentInput } from "./IntegerArgumentInput"; -import { JSONArgumentInput } from "./JSONArgumentInput"; -import { ListArgumentInput } from "./ListArgumentInput"; -import { StringArgumentInput } from "./StringArgumentInput"; - -type KurtosisArgumentTypeInputProps = { - type?: ArgumentValueType; - subType1?: ArgumentValueType; - subType2?: ArgumentValueType; - name: FieldPath; - placeholder?: string; - isRequired?: boolean; - validate?: (value: any) => string | undefined; - disabled?: boolean; - width?: CSS.Property.Width; - size?: string; - tabIndex?: number; -}; - -export type KurtosisArgumentTypeInputImplProps = Omit; - -export const KurtosisArgumentTypeInput = ({ - type, - subType1, - subType2, - name, - placeholder, - isRequired, - validate, - disabled, - width, - size, - tabIndex, -}: KurtosisArgumentTypeInputProps) => { - const childProps: KurtosisArgumentTypeInputImplProps = { - name, - placeholder, - isRequired, - validate, - disabled, - width, - size, - tabIndex, - }; - - switch (type) { - case ArgumentValueType.INTEGER: - return ; - case ArgumentValueType.DICT: - assertDefined( - subType1, - `innerType1 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, - ); - assertDefined( - subType2, - `innerType2 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, - ); - return ; - case ArgumentValueType.LIST: - assertDefined( - subType1, - `innerType1 was not defined on DICT argument ${name}, check the format used matches https://docs.kurtosis.com/api-reference/starlark-reference/docstring-syntax#types`, - ); - return ; - case ArgumentValueType.BOOL: - return ; - case ArgumentValueType.STRING: - return ; - case ArgumentValueType.JSON: - default: - return ; - } -}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts index 90ab3d0626..d894ce30c1 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts @@ -57,7 +57,7 @@ export function transformFormArgsToKurtosisArgs(data: Record, kurto case ArgumentValueType.LIST: return value.map((v: any) => transformValue(innerValuetype, v)); case ArgumentValueType.BOOL: - return isDefined(value) ? isStringTrue(value) : null; + return isDefined(value) && value !== "" ? isStringTrue(value) : null; case ArgumentValueType.INTEGER: return isNaN(value) || isNaN(parseFloat(value)) ? null : parseFloat(value); case ArgumentValueType.STRING: diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/BooleanArgumentInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/BooleanArgumentInput.tsx similarity index 68% rename from enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/BooleanArgumentInput.tsx rename to enclave-manager/web/packages/app/src/emui/enclaves/components/form/BooleanArgumentInput.tsx index b28b6ea7ac..aaeddad9b1 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/BooleanArgumentInput.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/BooleanArgumentInput.tsx @@ -1,13 +1,17 @@ import { Radio, RadioGroup, Stack, Switch } from "@chakra-ui/react"; -import { useEnclaveConfigurationFormContext } from "../EnclaveConfigurationForm"; -import { KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; -type BooleanArgumentInputProps = KurtosisArgumentTypeInputImplProps & { +import { useFormContext } from "react-hook-form"; +import { KurtosisFormInputProps } from "./types"; + +type BooleanArgumentInputProps = KurtosisFormInputProps & { inputType?: "radio" | "switch"; }; -export const BooleanArgumentInput = ({ inputType, ...props }: BooleanArgumentInputProps) => { - const { register, getValues } = useEnclaveConfigurationFormContext(); +export const BooleanArgumentInput = ({ + inputType, + ...props +}: BooleanArgumentInputProps) => { + const { register, getValues } = useFormContext(); const currentDefault = getValues(props.name); @@ -17,7 +21,8 @@ export const BooleanArgumentInput = ({ inputType, ...props }: BooleanArgumentInp {...register(props.name, { disabled: props.disabled, required: props.isRequired, - value: true, + // any required to force this initial value to work. + value: true as any, validate: props.validate, })} /> diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/form/CodeEditorInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/CodeEditorInput.tsx new file mode 100644 index 0000000000..1e0bc2815b --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/CodeEditorInput.tsx @@ -0,0 +1,43 @@ +import { CodeEditor } from "kurtosis-ui-components"; +import { Controller } from "react-hook-form"; +import { FieldPath, FieldValues } from "react-hook-form/dist/types"; +import { ControllerRenderProps } from "react-hook-form/dist/types/controller"; + +import { KurtosisFormInputProps } from "./types"; + +type CodeEditorInputProps = KurtosisFormInputProps & { + fileName: string; +}; + +export const CodeEditorInput = (props: CodeEditorInputProps) => { + return ( + } + name={props.name} + defaultValue={"" as any} + rules={{ + required: props.isRequired, + validate: props.validate, + }} + disabled={props.disabled} + /> + ); +}; + +type CodeEditorImplProps< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + field: ControllerRenderProps; + fileName: string; +}; + +const CodeEditorInputImpl = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + field, + fileName, +}: CodeEditorImplProps) => { + return ; +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/DictArgumentInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/DictArgumentInput.tsx similarity index 61% rename from enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/DictArgumentInput.tsx rename to enclave-manager/web/packages/app/src/emui/enclaves/components/form/DictArgumentInput.tsx index 27bb67f96d..4a8f53e793 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/inputs/DictArgumentInput.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/form/DictArgumentInput.tsx @@ -1,30 +1,30 @@ import { Button, ButtonGroup, Flex, useToast } from "@chakra-ui/react"; -import { ArgumentValueType } from "kurtosis-cloud-indexer-sdk"; import { CopyButton, PasteButton, stringifyError } from "kurtosis-ui-components"; +import { FC } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; import { FiDelete, FiPlus } from "react-icons/fi"; -import { KurtosisArgumentSubtypeFormControl } from "../KurtosisArgumentFormControl"; -import { ConfigureEnclaveForm } from "../types"; -import { KurtosisArgumentTypeInput, KurtosisArgumentTypeInputImplProps } from "./KurtosisArgumentTypeInput"; +import { KurtosisSubtypeFormControl } from "./KurtosisFormControl"; +import { KurtosisFormInputProps } from "./types"; -type DictArgumentInputProps = KurtosisArgumentTypeInputImplProps & { - keyType: ArgumentValueType; - valueType: ArgumentValueType; +type DictArgumentInputProps = KurtosisFormInputProps & { + KeyFieldComponent: FC>; + ValueFieldComponent: FC>; }; -export const DictArgumentInput = ({ keyType, valueType, ...otherProps }: DictArgumentInputProps) => { +export const DictArgumentInput = ({ + KeyFieldComponent, + ValueFieldComponent, + ...otherProps +}: DictArgumentInputProps) => { const toast = useToast(); - const { getValues, setValue } = useFormContext(); + const { getValues, setValue } = useFormContext(); const { fields, append, remove } = useFieldArray({ name: otherProps.name }); const handleValuePaste = (value: string) => { try { const parsed = JSON.parse(value); - setValue( - otherProps.name, - Object.entries(parsed).map(([key, value]) => ({ key, value })), - ); + setValue(otherProps.name, Object.entries(parsed).map(([key, value]) => ({ key, value })) as any); } catch (err: any) { toast({ title: `Could not read pasted input, was it a json object? Got error: ${stringifyError(err)}`, @@ -40,7 +40,7 @@ export const DictArgumentInput = ({ keyType, valueType, ...otherProps }: DictArg contentName={"value"} valueToCopy={() => JSON.stringify( - getValues(otherProps.name).reduce( + (getValues(otherProps.name) as any[]).reduce( (acc: Record, { key, value }: { key: string; value: any }) => ({ ...acc, [key]: value }), {}, ), @@ -50,35 +50,33 @@ export const DictArgumentInput = ({ keyType, valueType, ...otherProps }: DictArg {fields.map((field, i) => ( - - + - - - + - - + @@ -86,7 +84,7 @@ export const DictArgumentInput = ({ keyType, valueType, ...otherProps }: DictArg ))} @@ -64,7 +55,7 @@ export const ListArgumentInput = ({ valueType, ...otherProps }: ListArgumentInpu ))} + + + There are data issues that must be addressed before this enclave can run: + + {dataIssues.map((issue, i) => ( + {issue} + ))} + + + ) + } + > + + + + + + setCurrentStarlarkPreview(undefined)} + starlark={currentStarlarkPreview} + /> + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisArtifactNode.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisArtifactNode.tsx new file mode 100644 index 0000000000..84fb5d78b1 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisArtifactNode.tsx @@ -0,0 +1,41 @@ +import { isDefined } from "kurtosis-ui-components"; +import { memo } from "react"; +import { NodeProps } from "reactflow"; +import { KurtosisFormControl } from "../../form/KurtosisFormControl"; +import { StringArgumentInput } from "../../form/StringArgumentInput"; +import { FileTreeArgumentInput } from "./input/FileTreeArgumentInput"; +import { validateName } from "./input/validators"; +import { KurtosisNode } from "./KurtosisNode"; +import { KurtosisArtifactNodeData } from "./types"; +import { useVariableContext } from "./VariableContextProvider"; + +export const KurtosisArtifactNode = memo( + ({ id, selected }: NodeProps) => { + const { data } = useVariableContext(); + const nodeData = data[id] as KurtosisArtifactNodeData; + + if (!isDefined(nodeData)) { + // Node has probably been deleted. + return null; + } + + return ( + + name={"artifactName"} label={"Artifact Name"} isRequired> + + + + + + + ); + }, + (oldProps, newProps) => oldProps.id !== newProps.id || oldProps.selected !== newProps.selected, +); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisExecNode.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisExecNode.tsx new file mode 100644 index 0000000000..d24ff1b9f3 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisExecNode.tsx @@ -0,0 +1,105 @@ +import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; +import { isDefined } from "kurtosis-ui-components"; +import { memo, useMemo } from "react"; +import { NodeProps } from "reactflow"; +import { IntegerArgumentInput } from "../../form/IntegerArgumentInput"; +import { KurtosisFormControl } from "../../form/KurtosisFormControl"; +import { ListArgumentInput } from "../../form/ListArgumentInput"; +import { SelectArgumentInput, SelectOption } from "../../form/SelectArgumentInput"; +import { StringArgumentInput } from "../../form/StringArgumentInput"; +import { KurtosisFormInputProps } from "../../form/types"; +import { MentionStringArgumentInput } from "./input/MentionStringArgumentInput"; +import { validateName } from "./input/validators"; +import { KurtosisNode } from "./KurtosisNode"; +import { KurtosisExecNodeData, KurtosisServiceNodeData } from "./types"; +import { useVariableContext } from "./VariableContextProvider"; + +export const KurtosisExecNode = memo( + ({ id, selected }: NodeProps) => { + const { data, variables } = useVariableContext(); + const nodeData = data[id] as KurtosisExecNodeData; + + const serviceVariableOptions = useMemo((): SelectOption[] => { + return variables + .filter((variable) => variable.id.match(/^service\.[^.]+\.name+$/)) + .map((variable) => ({ + display: variable.displayName.replace(/service\.(.*)\.name/, "$1"), + value: `{{${variable.id}}}`, + })); + }, [variables]); + + if (!isDefined(nodeData)) { + // Node has probably been deleted. + return null; + } + + return ( + + name={"execName"} label={"Exec Name"} isRequired> + + + + + Config + Advanced + + + + {" "} + + name={"serviceName"} + label={"Service"} + helperText={"Choose which service to run this command in."} + isRequired + > + + options={serviceVariableOptions} + isRequired + size={"sm"} + placeholder={"Select a Service"} + name={`serviceName`} + /> + + name={"command"} label={"Command"} isRequired> + + + + + + name={"acceptableCodes"} + label={"Acceptable Exit Codes"} + isRequired + > + + FieldComponent={AcceptableCodeInput} + size={"sm"} + name={"acceptableCodes"} + createNewValue={() => ({ value: 0 })} + isRequired + /> + + + + + + ); + }, + (oldProps, newProps) => oldProps.id !== newProps.id || oldProps.selected !== newProps.selected, +); + +const AcceptableCodeInput = (props: KurtosisFormInputProps) => { + return ( + + {...props} + size={"sm"} + name={`${props.name as `acceptableCodes.${number}`}.value`} + /> + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisNode.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisNode.tsx new file mode 100644 index 0000000000..1a41c7334b --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisNode.tsx @@ -0,0 +1,141 @@ +import { Flex, IconButton, Text, useToken } from "@chakra-ui/react"; +import { debounce } from "lodash"; +import { memo, PropsWithChildren, useEffect, useMemo } from "react"; +import { DefaultValues, FormProvider, useForm } from "react-hook-form"; +import { FiTrash } from "react-icons/fi"; +import { RxCornerBottomRight } from "react-icons/rx"; +import { Handle, NodeResizeControl, Position, useReactFlow } from "reactflow"; +import { KurtosisNodeData } from "./types"; +import { useVariableContext } from "./VariableContextProvider"; + +type KurtosisNodeProps = PropsWithChildren<{ + id: string; + name: string; + selected: boolean; + minWidth: number; + maxWidth: number; + color: string; +}>; + +export const KurtosisNode = memo( + ({ + id, + name, + selected, + minWidth, + maxWidth, + children, + color, + }: KurtosisNodeProps) => { + const chakraColor = useToken("colors", color); + const { data, updateData, removeData } = useVariableContext(); + const formMethods = useForm({ + defaultValues: (data[id] as DefaultValues) || {}, + mode: "onBlur", + shouldFocusError: false, + }); + + const { deleteElements, zoomOut, zoomIn } = useReactFlow(); + + const handleDeleteNode = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + deleteElements({ nodes: [{ id }] }); + removeData(id); + }; + + const handleChange = useMemo( + () => + debounce(async () => { + const isValid = await formMethods.trigger(); + updateData(id, { ...formMethods.getValues(), isValid }); + }, 500), + [updateData, formMethods, id], + ); + + useEffect(() => { + const watcher = formMethods.watch(handleChange); + return () => watcher.unsubscribe(); + }, [formMethods, handleChange]); + + const handleScroll = (e: React.WheelEvent) => { + if (e.currentTarget.scrollTop === 0 && e.deltaY < 0) { + zoomIn(); + } + if ( + Math.abs(e.currentTarget.scrollHeight - e.currentTarget.clientHeight - e.currentTarget.scrollTop) <= 1 && + e.deltaY > 0 + ) { + zoomOut(); + } + }; + + return ( + + + + + + + + + + {name || Unnamed} + } + colorScheme={"red"} + variant={"ghost"} + size={"sm"} + onClick={handleDeleteNode} + /> + + + {children} + + + + ); + }, +); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisServiceNode.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisServiceNode.tsx new file mode 100644 index 0000000000..5ddb6abb4b --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisServiceNode.tsx @@ -0,0 +1,90 @@ +import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; +import { memo } from "react"; +import { NodeProps } from "reactflow"; +import { DictArgumentInput } from "../../form/DictArgumentInput"; +import { KurtosisFormControl } from "../../form/KurtosisFormControl"; +import { ListArgumentInput } from "../../form/ListArgumentInput"; +import { StringArgumentInput } from "../../form/StringArgumentInput"; +import { MentionStringArgumentInput } from "./input/MentionStringArgumentInput"; +import { MountArtifactFileInput } from "./input/MountArtifactFileInput"; +import { PortConfigurationField } from "./input/PortConfigurationInput"; +import { validateDockerLocator, validateName } from "./input/validators"; +import { KurtosisNode } from "./KurtosisNode"; +import { KurtosisFileMount, KurtosisPort, KurtosisServiceNodeData } from "./types"; +import { useVariableContext } from "./VariableContextProvider"; + +export const KurtosisServiceNode = memo( + ({ id, selected }: NodeProps) => { + const { data } = useVariableContext(); + + return ( + + + name={"serviceName"} label={"Service Name"} isRequired> + + + name={"image"} label={"Container Image"} isRequired> + + + + + + Environment + Ports + Files + + + + + name={"env"} label={"Environment Variables"}> + + name={"env"} + KeyFieldComponent={StringArgumentInput} + ValueFieldComponent={MentionStringArgumentInput} + /> + + + + name={"ports"} label={"Ports"}> + ({ + portName: "", + applicationProtocol: "", + transportProtocol: "TCP", + port: 0, + })} + /> + + + + + name={"files"} + label={"Files"} + helperText={"Choose where to mount artifacts on this services filesystem"} + > + ({ + mountPoint: "", + artifactName: "", + })} + /> + + + + + + ); + }, + (oldProps, newProps) => oldProps.id !== newProps.id || oldProps.selected !== newProps.selected, +); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisShellNode.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisShellNode.tsx new file mode 100644 index 0000000000..f354bb5a6e --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/KurtosisShellNode.tsx @@ -0,0 +1,135 @@ +import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; +import { isDefined } from "kurtosis-ui-components"; +import { memo } from "react"; +import { NodeProps } from "reactflow"; +import { BooleanArgumentInput } from "../../form/BooleanArgumentInput"; +import { CodeEditorInput } from "../../form/CodeEditorInput"; +import { DictArgumentInput } from "../../form/DictArgumentInput"; +import { KurtosisFormControl } from "../../form/KurtosisFormControl"; +import { ListArgumentInput } from "../../form/ListArgumentInput"; +import { StringArgumentInput } from "../../form/StringArgumentInput"; +import { MentionStringArgumentInput } from "./input/MentionStringArgumentInput"; +import { MountArtifactFileInput } from "./input/MountArtifactFileInput"; +import { validateDockerLocator, validateDurationString, validateName } from "./input/validators"; +import { KurtosisNode } from "./KurtosisNode"; +import { KurtosisFileMount, KurtosisShellNodeData } from "./types"; +import { useVariableContext } from "./VariableContextProvider"; + +export const KurtosisShellNode = memo( + ({ id, selected }: NodeProps) => { + const { data } = useVariableContext(); + const nodeData = data[id] as KurtosisShellNodeData; + + if (!isDefined(nodeData)) { + // Node has probably been deleted. + return null; + } + + return ( + + + name={"shellName"} label={"Shell Name"} isRequired> + + + name={"image"} label={"Container Image"}> + + + + + + Script + Environment + Files + Advanced + + + + + name={"command"} label={"Script to run"} isRequired> + + + + + name={"env"} label={"Environment Variables"}> + + name={"env"} + KeyFieldComponent={StringArgumentInput} + ValueFieldComponent={MentionStringArgumentInput} + /> + + + + + name={"files"} + label={"Input Files"} + helperText={"Choose where to mount artifacts on this execution tasks filesystem"} + > + ({ + mountPoint: "", + artifactName: "", + })} + /> + + + name={"store"} + label={"Output File/Directory"} + helperText={ + "Choose which files to expose from this execution task. You can use either an absolute path, a directory, or a glob." + } + isRequired + > + + name={"store"} + placeholder={"/some/output/location"} + isRequired + /> + + + + + + name={"wait_enabled"} + label={"Wait enabled"} + isRequired + helperText={"Whether kurtosis should wait a preset time for this step to complete."} + > + name={"wait_enabled"} /> + + + name={"wait"} + label={"Wait"} + isDisabled={nodeData.wait_enabled === "false"} + helperText={"Whether kurtosis should wait a preset time for this step to complete."} + > + + name={"wait"} + isDisabled={nodeData.wait_enabled === "false"} + size={"sm"} + placeholder={"180s"} + validate={nodeData.wait_enabled === "false" ? undefined : validateDurationString} + /> + + + + + + + ); + }, + (oldProps, newProps) => oldProps.id !== newProps.id || oldProps.selected !== newProps.selected, +); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/VariableContextProvider.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/VariableContextProvider.tsx new file mode 100644 index 0000000000..f071f4f782 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/VariableContextProvider.tsx @@ -0,0 +1,47 @@ +import { createContext, PropsWithChildren, useCallback, useContext, useMemo, useState } from "react"; +import { KurtosisNodeData, Variable } from "./types"; +import { getVariablesFromNodes } from "./utils"; + +type VariableContextState = { + data: Record; + variables: Variable[]; + updateData: (id: string, data: KurtosisNodeData) => void; + removeData: (id: string) => void; +}; + +const VariableContext = createContext({ + data: {}, + variables: [], + updateData: () => null, + removeData: () => null, +}); + +type VariableContextProviderProps = { + initialData: Record; +}; + +export const VariableContextProvider = ({ initialData, children }: PropsWithChildren) => { + const [data, setData] = useState>(initialData); + + const variables = useMemo((): Variable[] => { + return getVariablesFromNodes(data); + }, [data]); + + const updateData = useCallback((id: string, data: KurtosisNodeData) => { + setData((oldData) => ({ ...oldData, [id]: data })); + }, []); + + const removeData = useCallback((id: string) => { + setData((oldData) => { + const r = { ...oldData }; + delete r[id]; + return r; + }); + }, []); + + return ( + {children} + ); +}; + +export const useVariableContext = () => useContext(VariableContext); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/Visualiser.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/Visualiser.tsx new file mode 100644 index 0000000000..ece3844b1b --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/Visualiser.tsx @@ -0,0 +1,260 @@ +import { Box, Button, ButtonGroup, Flex } from "@chakra-ui/react"; +import Dagre from "@dagrejs/dagre"; +import { RemoveFunctions } from "kurtosis-ui-components"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react"; +import { FiPlusCircle } from "react-icons/fi"; +import { + Background, + BackgroundVariant, + Controls, + Edge, + Node, + ReactFlow, + useEdgesState, + useNodesState, + useReactFlow, + XYPosition, +} from "reactflow"; +import { v4 as uuidv4 } from "uuid"; +import { EnclaveFullInfo } from "../../../types"; +import { KurtosisArtifactNode } from "./KurtosisArtifactNode"; +import { KurtosisExecNode } from "./KurtosisExecNode"; +import { KurtosisServiceNode } from "./KurtosisServiceNode"; +import { KurtosisShellNode } from "./KurtosisShellNode"; +import { generateStarlarkFromGraph, getNodeDependencies } from "./utils"; +import { useVariableContext } from "./VariableContextProvider"; + +const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); +const getLayoutedElements = (nodes: Node[], edges: Edge[]) => { + if (nodes.length === 0) { + return { nodes, edges }; + } + g.setGraph({ rankdir: "LR", ranksep: 100 }); + + edges.forEach((edge) => g.setEdge(edge.source, edge.target)); + nodes.forEach((node) => + g.setNode(node.id, node as Node<{ label: string }, string | undefined> & { width?: number; height?: number }), + ); + + Dagre.layout(g); + + return { + nodes: nodes.map((node) => { + const { x, y } = g.node(node.id); + + return { ...node, position: { x, y } }; + }), + edges, + }; +}; + +const nodeTypes = { + serviceNode: KurtosisServiceNode, + artifactNode: KurtosisArtifactNode, + shellNode: KurtosisShellNode, + execNode: KurtosisExecNode, +}; + +export type VisualiserImperativeAttributes = { + getStarlark: () => string; +}; +type VisualiserProps = { + initialNodes: Node[]; + initialEdges: Edge[]; + existingEnclave?: RemoveFunctions; +}; +export const Visualiser = forwardRef( + ({ initialNodes, initialEdges, existingEnclave }, ref) => { + const { data, updateData } = useVariableContext(); + const insertOffset = useRef(0); + const { fitView, addNodes, getViewport } = useReactFlow(); + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes || []); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges || []); + + const onLayout = useCallback(() => { + const layouted = getLayoutedElements(nodes, edges); + + setNodes([...layouted.nodes]); + setEdges([...layouted.edges]); + + window.requestAnimationFrame(() => { + fitView(); + }); + }, [nodes, edges, fitView, setEdges, setNodes]); + + const getNewNodePosition = (): XYPosition => { + const viewport = getViewport(); + insertOffset.current += 1; + return { x: -viewport.x + insertOffset.current * 20 + 400, y: -viewport.y + insertOffset.current * 20 }; + }; + + const handleAddServiceNode = () => { + const id = uuidv4(); + updateData(id, { + type: "service", + serviceName: "", + image: "", + ports: [], + env: [], + files: [], + isValid: false, + }); + addNodes({ + id, + position: getNewNodePosition(), + width: 650, + style: { width: "650px" }, + type: "serviceNode", + data: {}, + }); + }; + + const handleAddArtifactNode = () => { + const id = uuidv4(); + updateData(id, { type: "artifact", artifactName: "", files: {}, isValid: false }); + addNodes({ + id, + position: getNewNodePosition(), + width: 400, + style: { width: "400px" }, + type: "artifactNode", + data: {}, + }); + }; + + const handleAddShellNode = () => { + const id = uuidv4(); + updateData(id, { + type: "shell", + shellName: "", + command: "", + image: "", + env: [], + files: [], + store: "", + wait_enabled: "true", + wait: "", + isValid: false, + }); + addNodes({ + id, + position: getNewNodePosition(), + width: 650, + style: { width: "650px" }, + type: "shellNode", + data: {}, + }); + }; + + const handleAddExecNode = () => { + const id = uuidv4(); + updateData(id, { + type: "exec", + execName: "", + serviceName: "", + command: "", + acceptableCodes: [], + isValid: false, + }); + addNodes({ + id, + position: getNewNodePosition(), + width: 400, + style: { width: "400px" }, + type: "execNode", + data: {}, + }); + }; + + const handleNodeDoubleClick = useCallback( + (e: React.MouseEvent, node: Node) => { + fitView({ nodes: [node], maxZoom: 1, duration: 500 }); + }, + [fitView], + ); + + useEffect(() => { + setEdges((prevState) => { + return Object.entries(getNodeDependencies(data)).flatMap(([to, froms]) => + [...froms].map((from) => ({ + id: `${from}-${to}`, + source: from, + target: to, + animated: true, + style: { strokeWidth: "3px" }, + })), + ); + }); + }, [setEdges, data]); + + // Remove the resizeObserver error + useEffect(() => { + const errorHandler = (e: any) => { + if ( + e.message.includes( + "ResizeObserver loop completed with undelivered notifications" || "ResizeObserver loop limit exceeded", + ) + ) { + const resizeObserverErr = document.getElementById("webpack-dev-server-client-overlay"); + if (resizeObserverErr) { + resizeObserverErr.style.display = "none"; + } + } + }; + window.addEventListener("error", errorHandler); + + return () => { + window.removeEventListener("error", errorHandler); + }; + }, []); + + useImperativeHandle( + ref, + () => ({ + getStarlark: () => { + return generateStarlarkFromGraph(nodes, edges, data, existingEnclave); + }, + }), + [nodes, edges, data, existingEnclave], + ); + + return ( + + + + + + + + + + (insertOffset.current = 1)} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} + onNodeDoubleClick={handleNodeDoubleClick} + nodeTypes={nodeTypes} + fitView + > + + + + + + ); + }, +); +Visualiser.displayName = "ForwardRef Visualiser"; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/FileTreeArgumentInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/FileTreeArgumentInput.tsx new file mode 100644 index 0000000000..dc9cb78434 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/FileTreeArgumentInput.tsx @@ -0,0 +1,126 @@ +import { Button, ButtonGroup, Flex } from "@chakra-ui/react"; +import { FileTree, FileTreeNode, isDefined } from "kurtosis-ui-components"; +import { useMemo, useState } from "react"; +import { Controller } from "react-hook-form"; +import { FiPlus } from "react-icons/fi"; +import { KurtosisFormInputProps } from "../../../form/types"; +import { EditFileModal } from "../modals/EditFileModal"; +import { NewFileModal } from "../modals/NewFileModal"; + +type FileTreeArgumentInputProps = KurtosisFormInputProps; + +export const FileTreeArgumentInput = ({ + name, + isRequired, + validate, + disabled, +}: FileTreeArgumentInputProps) => { + return ( + { + return ; + }} + /> + ); +}; + +type FileTreeInputProps = { + files: Record; + onUpdateFiles: (newFiles: Record) => void; +}; + +const FileTreeInput = ({ files, onUpdateFiles }: FileTreeInputProps) => { + const [selectedPath, setSelectedPath] = useState(); + const [showNewFileInputDialog, setShowNewFileInputDialog] = useState(false); + const [editingFilePath, setEditingFilePath] = useState(); + + const fileTree = useMemo((): FileTreeNode[] => { + return ( + Object.entries(files).reduce( + (acc, [fileName, fileContent]) => { + let filePath = fileName.split("/"); + if (filePath[0] === "") { + filePath = filePath.slice(1); + } + let destinationNode = acc; + let i = 0; + while (i < filePath.length - 1) { + const filePart = filePath[i]; + let nextNode = destinationNode.childNodes?.find((node) => node.name === filePart); + if (!isDefined(nextNode)) { + nextNode = { name: filePart, childNodes: [] }; + destinationNode.childNodes?.push(nextNode); + } + destinationNode = nextNode; + i++; + } + destinationNode.childNodes?.push({ + name: filePath[filePath.length - 1], + size: BigInt(fileContent.length), + }); + + return acc; + }, + { name: "/", childNodes: [] } as FileTreeNode, + ).childNodes || [] + ); + }, [files]); + + const handleNewFile = (newFileName: string) => { + setShowNewFileInputDialog(false); + onUpdateFiles({ ...files, [newFileName]: "" }); + }; + + const handleSaveEditedFile = (text: string) => { + if (!isDefined(editingFilePath)) { + return; + } + onUpdateFiles({ ...files, ["/" + editingFilePath.join("/")]: text }); + setEditingFilePath(undefined); + }; + + const handleDeleteSelectedFile = () => { + if (!isDefined(selectedPath)) { + return; + } + const newFiles = { ...files }; + delete newFiles["/" + selectedPath.join("/")]; + onUpdateFiles(newFiles); + }; + + return ( + + + + + + + setShowNewFileInputDialog(false)} + onConfirm={handleNewFile} + /> + setEditingFilePath(undefined)} + filePath={editingFilePath || []} + file={files["/" + editingFilePath?.join("/")]} + onSave={handleSaveEditedFile} + /> + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.css b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.css new file mode 100644 index 0000000000..65a156a440 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.css @@ -0,0 +1,63 @@ +.mentions { + appearance: none; + height: var(--input-height); + font-size: var(--input-font-size); + border: 1px solid; + border-color: inherit; + border-radius: var(--input-border-radius); + outline: 2px solid transparent; + outline-offset: 2px; + padding-inline-start: var(--input-padding); + padding-inline-end: var(--input-padding); + --input-font-size: var(--chakra-fontSizes-sm); + --input-padding: var(--chakra-space-3); + --input-border-radius: var(--chakra-radii-sm); + --input-height: var(--chakra-sizes-8); + padding-top: 4px; + + &[aria-invalid="true"] { + border-color: var(--chakra-colors-red-300); + box-shadow: 0 0 0 1px var(--chakra-colors-red-300); + } + + &:focus-within { + z-index: 1; + border-color: #63b3ed; + box-shadow: 0 0 0 1px #63b3ed; + } +} + +.mentions--singleLine .mentions__highlighter { +} + +.mentions--singleLine .mentions__input { + padding-inline-start: var(--input-padding); + padding-inline-end: var(--input-padding); + --input-padding: var(--chakra-space-3); + + padding-top: 4px; + + &:focus-visible { + outline: none; + } +} + +.mentions__suggestions__list { + background-color: var(--chakra-colors-gray-800); + border: 1px solid rgba(0, 0, 0, 0.15); + font-size: 10pt; +} + +.mentions__suggestions__item { + padding: 5px 15px; + border-bottom: 1px solid rgba(0, 0, 0, 0.15); +} + +.mentions__suggestions__item--focused { + background-color: var(--chakra-colors-gray-600); +} + +.mentions__mention { + background-color: var(--chakra-colors-blue-500); + border-radius: 2px; +} diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.tsx new file mode 100644 index 0000000000..83db6c565c --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MentionStringArgumentInput.tsx @@ -0,0 +1,73 @@ +import { isDefined } from "kurtosis-ui-components"; +import { useCallback } from "react"; +import { Controller } from "react-hook-form"; +import { Mention, MentionsInput } from "react-mentions"; +import { KurtosisFormInputProps } from "../../../form/types"; +import { useVariableContext } from "../VariableContextProvider"; +import "./MentionStringArgumentInput.css"; + +type MentionStringArgumentInputProps = KurtosisFormInputProps; + +export const MentionStringArgumentInput = ({ + name, + placeholder, + isRequired, + validate, + disabled, + width, + size, + tabIndex, +}: MentionStringArgumentInputProps) => { + const { variables } = useVariableContext(); + + const handleQuery = useCallback( + (query?: string) => { + if (!isDefined(query)) { + return []; + } + const suggestions = variables.map((v) => ({ display: v.displayName, id: v.id })); + const queryTerms = query.toLowerCase().split(/\s+|\./); + return suggestions.filter((variable) => + queryTerms.every((term) => variable.display.toLowerCase().includes(term)), + ); + }, + [variables], + ); + + return ( + { + return ( + field.onChange(newValue)} + > + + variables.find((variable) => variable.id === id)?.displayName || "Missing Variable" + } + /> + + ); + }} + /> + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MountArtifactFileInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MountArtifactFileInput.tsx new file mode 100644 index 0000000000..8f0631044b --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/MountArtifactFileInput.tsx @@ -0,0 +1,38 @@ +import { Grid, GridItem } from "@chakra-ui/react"; +import { useMemo } from "react"; +import { SelectArgumentInput, SelectOption } from "../../../form/SelectArgumentInput"; +import { StringArgumentInput } from "../../../form/StringArgumentInput"; +import { KurtosisFormInputProps } from "../../../form/types"; +import { KurtosisServiceNodeData } from "../types"; +import { useVariableContext } from "../VariableContextProvider"; + +export const MountArtifactFileInput = (props: KurtosisFormInputProps) => { + const { variables } = useVariableContext(); + const artifactVariableOptions = useMemo((): SelectOption[] => { + return variables + .filter((variable) => variable.id.match(/^(?:artifact|shell)\.[^.]+$/)) + .map((variable) => ({ display: variable.displayName, value: `{{${variable.id}}}` })); + }, [variables]); + + return ( + + + + {...props} + size={"sm"} + placeholder={"/some/path"} + name={`${props.name as `files.${number}`}.mountPoint`} + /> + + + + options={artifactVariableOptions} + {...props} + size={"sm"} + placeholder={"Select an Artifact"} + name={`${props.name as `files.${number}`}.artifactName`} + /> + + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/PortConfigurationInput.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/PortConfigurationInput.tsx new file mode 100644 index 0000000000..b9a8760ac3 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/PortConfigurationInput.tsx @@ -0,0 +1,49 @@ +import { Grid, GridItem } from "@chakra-ui/react"; +import { IntegerArgumentInput } from "../../../form/IntegerArgumentInput"; +import { OptionsArgumentInput } from "../../../form/OptionArgumentInput"; +import { StringArgumentInput } from "../../../form/StringArgumentInput"; +import { KurtosisFormInputProps } from "../../../form/types"; +import { KurtosisServiceNodeData } from "../types"; + +export const PortConfigurationField = (props: KurtosisFormInputProps) => ( + + + + {...props} + size={"sm"} + placeholder={"Port Name (eg postgres)"} + name={`${props.name as `ports.${number}`}.portName`} + /> + + + + {...props} + size={"sm"} + placeholder={"Application Protocol (eg postgresql)"} + name={`${props.name as `ports.${number}`}.applicationProtocol`} + validate={(val) => { + if (typeof val !== "string") { + return "Value should be a string"; + } + if (val.includes(" ")) { + return "Application protocol cannot include spaces"; + } + }} + /> + + + + {...props} + options={["TCP", "UDP"]} + name={`${props.name as `ports.${number}`}.transportProtocol`} + /> + + + + {...props} + name={`${props.name as `ports.${number}`}.port`} + size={"sm"} + /> + + +); diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/validators.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/validators.ts new file mode 100644 index 0000000000..528f1226d7 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/input/validators.ts @@ -0,0 +1,48 @@ +export function validateName(value?: string) { + if (typeof value !== "string") { + return "Value should be a string"; + } + if (value.match(/^\d+/)) { + return "Value cannot start with numbers"; + } +} +export function validateDockerLocator(value?: string) { + if (typeof value !== "string") { + return "Value should be a string"; + } + if (value === "") { + return; + } + + if ( + !value.match( + /^(?[\w.\-_]+((?::\d+|)(?=\/[a-z0-9._-]+\/[a-z0-9._-]+))|)(?:\/|)(?[a-z0-9.\-_]+(?:\/[a-z0-9.\-_]+|))(:(?[\w.\-_]{1,127})|)$/gim, + ) + ) { + return "Value does not look like a docker image"; + } +} + +export function validateDurationString(value?: string) { + if (typeof value !== "string") { + return "Value should be a string"; + } + if (value === "") { + return; + } + + if (!value.match(/^\d+[msd]?$/)) { + return "Value should be a custom wait duration with like '10s' or '3m'."; + } +} + +export function combineValidators(...validators: ((v?: string) => string | void)[]): (v?: string) => string | void { + return function (v?: string) { + for (const validator of validators) { + const r = validator(v); + if (r) { + return r; + } + } + }; +} diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/EditFileModal.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/EditFileModal.tsx new file mode 100644 index 0000000000..65361a7928 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/EditFileModal.tsx @@ -0,0 +1,55 @@ +import { + Button, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/react"; +import { CodeEditor, CodeEditorImperativeAttributes } from "kurtosis-ui-components"; +import { useMemo, useRef } from "react"; + +type EditFileModalProps = { + isOpen: boolean; + onClose: () => void; + filePath: string[]; + file: string; + onSave: (newContents: string) => void; +}; +export const EditFileModal = ({ isOpen, onClose, filePath, file, onSave }: EditFileModalProps) => { + const codeEditorRef = useRef(null); + + const fileName = useMemo(() => "/" + filePath.join("/"), [filePath]); + + return ( + + + + + Editing {fileName} + + + + + + + + + + + + + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/NewFileModal.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/NewFileModal.tsx new file mode 100644 index 0000000000..eb498a75e5 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/NewFileModal.tsx @@ -0,0 +1,73 @@ +import { + Button, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/react"; +import { isDefined } from "kurtosis-ui-components"; +import { FormProvider, useForm } from "react-hook-form"; +import { KurtosisFormControl } from "../../../form/KurtosisFormControl"; +import { StringArgumentInput } from "../../../form/StringArgumentInput"; + +type NewFileModalProps = { + isOpen: boolean; + onClose: () => void; + onConfirm: (newFileName: string) => void; +}; +export const NewFileModal = ({ isOpen, onClose, onConfirm }: NewFileModalProps) => { + const formMethods = useForm<{ fileName: string }>({ + defaultValues: { fileName: "" }, + }); + + const onValidSubmit = (data: { fileName: string }) => { + onConfirm(data.fileName); + }; + + return ( + + + + + Create a New File + + + + { + if (!isDefined(v)) { + return "input must be defined"; + } + if (!v.startsWith("/")) { + return "File paths must start with a /"; + } + }} + /> + + + + + + + + + + + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/ViewStarlarkModal.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/ViewStarlarkModal.tsx new file mode 100644 index 0000000000..3b408ce5ac --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/modals/ViewStarlarkModal.tsx @@ -0,0 +1,39 @@ +import { + Button, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/react"; +import { FileDisplay, isDefined } from "kurtosis-ui-components"; + +type ViewStarlarkModalProps = { + isOpen: boolean; + onClose: () => void; + starlark?: string; +}; +export const ViewStarlarkModal = ({ isOpen, onClose, starlark }: ViewStarlarkModalProps) => { + return ( + + + + Previewing Starlark + + + {isDefined(starlark) && } + + + + + + + + + ); +}; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/types.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/types.ts new file mode 100644 index 0000000000..b11df47b8e --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/types.ts @@ -0,0 +1,67 @@ +export type Variable = { + id: string; + displayName: string; + value: string; +}; + +export type KurtosisPort = { + portName: string; + port: number; + transportProtocol: "TCP" | "UDP"; + applicationProtocol: string; +}; + +export type KurtosisEnvironmentVar = { key: string; value: string }; + +export type KurtosisFileMount = { + mountPoint: string; + artifactName: string; +}; + +export type KurtosisAcceptableCode = { + value: number; +}; + +export type KurtosisExecNodeData = { + type: "exec"; + execName: string; + serviceName: string; + command: string; + acceptableCodes: KurtosisAcceptableCode[]; + isValid: boolean; +}; + +export type KurtosisServiceNodeData = { + type: "service"; + serviceName: string; + image: string; + env: KurtosisEnvironmentVar[]; + ports: KurtosisPort[]; + files: KurtosisFileMount[]; + isValid: boolean; +}; +export type KurtosisArtifactNodeData = { + type: "artifact"; + artifactName: string; + files: Record; + isValid: boolean; +}; + +export type KurtosisShellNodeData = { + type: "shell"; + shellName: string; + command: string; + image: string; + env: KurtosisEnvironmentVar[]; + files: KurtosisFileMount[]; + store: string; + wait_enabled: "true" | "false"; + wait: string; + isValid: boolean; +}; + +export type KurtosisNodeData = + | KurtosisArtifactNodeData + | KurtosisServiceNodeData + | KurtosisShellNodeData + | KurtosisExecNodeData; diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/utils.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/utils.ts new file mode 100644 index 0000000000..87e53645a2 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/modals/enclaveBuilder/utils.ts @@ -0,0 +1,357 @@ +import { isDefined, RemoveFunctions, stringifyError } from "kurtosis-ui-components"; +import { Edge, Node } from "reactflow"; +import { Result } from "true-myth"; +import { EnclaveFullInfo } from "../../../types"; +import { KurtosisNodeData, KurtosisServiceNodeData, Variable } from "./types"; + +export const EMUI_BUILD_STATE_KEY = "EMUI_BUILD_STATE"; + +export function starlarkScriptContainsEMUIBuildState(script: string) { + return script.includes(EMUI_BUILD_STATE_KEY); +} + +export function getInitialGraphStateFromEnclave( + enclave?: RemoveFunctions, +): Result<{ nodes: Node[]; edges: Edge[]; data: Record }, string> { + if (!isDefined(enclave)) { + return Result.ok({ nodes: [], edges: [], data: {} }); + } + if (!isDefined(enclave.starlarkRun)) { + return Result.err("Enclave has no previous starlark run."); + } + if (enclave.starlarkRun.isErr) { + return Result.err(`Fetching previous starlark run resulted in an error: ${enclave.starlarkRun.error}`); + } + const b64State = enclave.starlarkRun.value.serializedScript + .split("\n") + .find((line) => line.includes(EMUI_BUILD_STATE_KEY)); + if (!isDefined(b64State)) { + return Result.err("Enclave wasn't created with the EMUI enclave builder."); + } + try { + return Result.ok(JSON.parse(atob(b64State.split("=")[1]))); + } catch (error: any) { + return Result.err(`Couldn't parse previous state: ${stringifyError(error)}`); + } +} + +export function getNodeName(kurtosisNodeData: KurtosisNodeData): string { + if (kurtosisNodeData.type === "service") { + return kurtosisNodeData.serviceName; + } + if (kurtosisNodeData.type === "artifact") { + return kurtosisNodeData.artifactName; + } + if (kurtosisNodeData.type === "shell") { + return kurtosisNodeData.shellName; + } + if (kurtosisNodeData.type === "exec") { + return kurtosisNodeData.execName; + } + throw new Error(`Unknown node type.`); +} + +function normaliseNameToStarlarkVariable(name: string) { + return name.replace(/\s|-/g, "_").toLowerCase(); +} + +function escapeString(value: string): string { + return value.replaceAll(/(["\\])/g, "\\$1"); +} + +const variablePattern = /\{\{((?:service|artifact|shell).([^.]+)\.?.*)}}/; +export function getVariablesFromNodes(nodes: Record): Variable[] { + return Object.entries(nodes).flatMap(([id, data]) => { + if (data.type === "service") { + return [ + { + id: `service.${id}.name`, + displayName: `service.${data.serviceName}.name`, + value: `${normaliseNameToStarlarkVariable(data.serviceName)}.name`, + }, + { + id: `service.${id}.hostname`, + displayName: `service.${data.serviceName}.hostname`, + value: `${normaliseNameToStarlarkVariable(data.serviceName)}.hostname`, + }, + ...data.ports.flatMap((port, i) => [ + { + id: `service.${id}.port.${i}`, + displayName: `service.${data.serviceName}.port.${port.portName}`, + value: `"{}://{}:{}".format(${normaliseNameToStarlarkVariable(data.serviceName)}.ports["${ + port.portName + }"].application_protocol, ${normaliseNameToStarlarkVariable( + data.serviceName, + )}.hostname, ${normaliseNameToStarlarkVariable(data.serviceName)}.ports["${port.portName}"].number)`, + }, + { + id: `service.${id}.port.${i}.port`, + displayName: `service.${data.serviceName}.port.${port.portName}.port`, + value: `${normaliseNameToStarlarkVariable(data.serviceName)}.ports["${port.portName}"].number`, + }, + { + id: `service.${id}.port.${i}.applicationProtocol`, + displayName: `service.${data.serviceName}.port.${port.portName}.application_protocol`, + value: `${normaliseNameToStarlarkVariable(data.serviceName)}.ports["${ + port.portName + }"].application_protocol`, + }, + ]), + ...data.env.map((env, i) => ({ + id: `service.${id}.env.${i}`, + displayName: `service.${data.serviceName}.env.${env.key}`, + value: `"${env.value}"`, + })), + ]; + } + if (data.type === "artifact") { + return [ + { + id: `artifact.${id}`, + displayName: `artifact.${data.artifactName}`, + value: `${normaliseNameToStarlarkVariable(data.artifactName)}`, + }, + ]; + } + + if (data.type === "shell") { + return [ + { + id: `shell.${id}`, + displayName: `shell.${data.shellName}`, + value: `${normaliseNameToStarlarkVariable(data.shellName)}.files_artifacts[0]`, + }, + ...data.env.map((env, i) => ({ + id: `shell.${id}.env.${i}`, + displayName: `shell.${data.shellName}.env.${env.key}`, + value: `"${env.value}"`, + })), + ]; + } + + return []; + }); +} + +export function getNodeDependencies(nodes: Record): Record> { + const dependencies: Record> = {}; + const getDependenciesFor = (key: string): Set => { + if (!isDefined(dependencies[key])) { + dependencies[key] = new Set(); + } + return dependencies[key]; + }; + Object.entries(nodes).forEach(([id, data]) => { + if (data.type === "service") { + const nameMatches = data.serviceName.match(variablePattern); + if (nameMatches) { + getDependenciesFor(id).add(nameMatches[2]); + } + data.env.forEach((env) => { + const envMatches = env.key.match(variablePattern) || env.value.match(variablePattern); + if (envMatches) { + getDependenciesFor(id).add(envMatches[2]); + } + }); + data.ports.forEach((port) => { + const portMatches = port.portName.match(variablePattern) || port.applicationProtocol.match(variablePattern); + if (portMatches) { + getDependenciesFor(id).add(portMatches[2]); + } + }); + data.files.forEach((file) => { + const fileMatches = file.mountPoint.match(variablePattern) || file.artifactName.match(variablePattern); + if (fileMatches) { + getDependenciesFor(id).add(fileMatches[2]); + } + }); + } + if (data.type === "shell") { + const nameMatches = data.shellName.match(variablePattern); + if (nameMatches) { + getDependenciesFor(id).add(nameMatches[2]); + } + data.env.forEach((env) => { + const envMatches = env.key.match(variablePattern) || env.value.match(variablePattern); + if (envMatches) { + getDependenciesFor(id).add(envMatches[2]); + } + }); + data.files.forEach((file) => { + const fileMatches = file.mountPoint.match(variablePattern) || file.artifactName.match(variablePattern); + if (fileMatches) { + getDependenciesFor(id).add(fileMatches[2]); + } + }); + } + if (data.type === "exec") { + const nameMatches = data.execName.match(variablePattern); + if (nameMatches) { + getDependenciesFor(id).add(nameMatches[2]); + } + const serviceMatches = data.serviceName.match(variablePattern); + if (serviceMatches) { + getDependenciesFor(id).add(serviceMatches[2]); + } + const commandMatches = data.command.match(variablePattern); + if (commandMatches) { + getDependenciesFor(id).add(commandMatches[2]); + } + } + }); + return dependencies; +} + +export function generateStarlarkFromGraph( + nodes: Node[], + edges: Edge[], + data: Record, + existingEnclave?: RemoveFunctions, +): string { + // Topological sort + const sortedNodes: Node[] = []; + let remainingEdges = [...edges]; + while (remainingEdges.length > 0 || sortedNodes.length !== nodes.length) { + const nodesRemoved = nodes + .filter((node) => remainingEdges.every((edge) => edge.target !== node.id)) // eslint-disable-line no-loop-func + .filter((node) => !sortedNodes.includes(node)); + if (nodesRemoved.length === 0) { + throw new Error( + "Topological sort couldn't remove nodes. This indicates a cycle has been detected. Starlark cannot be rendered.", + ); + } + sortedNodes.push(...nodesRemoved); + remainingEdges = remainingEdges.filter((edge) => sortedNodes.every((node) => edge.source !== node.id)); + } + const variablesById = getVariablesFromNodes(data).reduce( + (acc, cur) => ({ ...acc, [cur.id]: cur }), + {} as Record, + ); + const interpolateValue = (input: string): string => { + let formatString = input; + let variableMatches = formatString.match(variablePattern); + if (!isDefined(variableMatches)) { + return `"${formatString}"`; + } + + const references: string[] = []; + while (isDefined(variableMatches)) { + formatString = formatString.replace(variableMatches[0], "{}"); + references.push(variablesById[variableMatches[1]].value); + variableMatches = formatString.match(variablePattern); + } + if (formatString === "{}") { + return references[0]; + } + + return `"${formatString}".format(${references.join(", ")})`; + }; + + let starlark = "def run(plan):\n"; + for (const node of sortedNodes) { + const nodeData = data[node.id]; + if (nodeData.type === "service") { + const serviceName = normaliseNameToStarlarkVariable(nodeData.serviceName); + starlark += ` ${serviceName} = plan.add_service(\n`; + starlark += ` name = ${interpolateValue(nodeData.serviceName)},\n`; + starlark += ` config = ServiceConfig (\n`; + starlark += ` image = ${interpolateValue(nodeData.image)},\n`; + starlark += ` ports = {\n`; + for (const { portName, port, applicationProtocol, transportProtocol } of nodeData.ports) { + starlark += ` ${interpolateValue(portName)}: PortSpec(\n`; + starlark += ` number = ${port},\n`; + starlark += ` transport_protocol = "${transportProtocol}",\n`; + starlark += ` application_protocol = ${interpolateValue(applicationProtocol)},\n`; + starlark += ` ),\n`; + } + starlark += ` },\n`; + starlark += ` env_vars = {\n`; + for (const { key, value } of nodeData.env) { + starlark += ` ${interpolateValue(key)}: ${interpolateValue(value)},\n`; + } + starlark += ` },\n`; + starlark += ` files = {\n`; + for (const { mountPoint, artifactName } of nodeData.files) { + starlark += ` ${interpolateValue(mountPoint)}: ${interpolateValue(artifactName)},\n`; + } + starlark += ` },\n`; + starlark += ` ),\n`; + starlark += ` )\n\n`; + } + + if (nodeData.type === "artifact") { + const artifactName = normaliseNameToStarlarkVariable(nodeData.artifactName); + starlark += ` ${artifactName} = plan.render_templates(\n`; + starlark += ` name = "${nodeData.artifactName}",\n`; + starlark += ` config = {\n`; + for (const [fileName, fileText] of Object.entries(nodeData.files)) { + starlark += ` "${fileName}": struct(\n`; + starlark += ` template="""${escapeString(fileText)}""",\n`; + starlark += ` data={},\n`; + starlark += ` ),\n`; + } + starlark += ` },\n`; + starlark += ` )\n\n`; + } + + if (nodeData.type === "shell") { + const shellName = normaliseNameToStarlarkVariable(nodeData.shellName); + starlark += ` ${shellName} = plan.run_sh(\n`; + starlark += ` run = """${escapeString(nodeData.command)}""",\n`; + const image = interpolateValue(nodeData.image); + if (image !== '""') { + starlark += ` image = ${image},\n`; + } + starlark += ` env_vars = {\n`; + for (const { key, value } of nodeData.env) { + starlark += ` ${interpolateValue(key)}: ${interpolateValue(value)},\n`; + } + starlark += ` },\n`; + starlark += ` files = {\n`; + for (const { mountPoint, artifactName } of nodeData.files) { + starlark += ` ${interpolateValue(mountPoint)}: ${interpolateValue(artifactName)},\n`; + } + starlark += ` },\n`; + starlark += ` store = [\n`; + starlark += ` StoreSpec(src = ${interpolateValue(nodeData.store)}, name="${shellName}"),\n`; + starlark += ` ],\n`; + const wait = interpolateValue(nodeData.wait); + if (nodeData.wait_enabled === "false" || wait !== '""') { + starlark += ` wait=${nodeData.wait_enabled === "true" ? wait : "None"},\n`; + } + starlark += ` )\n\n`; + } + + if (nodeData.type === "exec") { + const execName = normaliseNameToStarlarkVariable(nodeData.execName); + starlark += ` ${execName} = plan.exec(\n`; + starlark += ` service_name = ${interpolateValue(nodeData.serviceName)},\n`; + starlark += ` recipe = ExecRecipe(\n`; + starlark += ` command = [${nodeData.command.split(" ").map(interpolateValue).join(", ")}],`; + starlark += ` ),\n`; + if (nodeData.acceptableCodes.length > 0) { + starlark += ` acceptable_codes = [${nodeData.acceptableCodes.map(({ value }) => value).join(", ")}],\n`; + } + starlark += ` )\n\n`; + } + } + + // Delete any services from any existing enclave that aren't defined anymore + if (isDefined(existingEnclave) && existingEnclave.services?.isOk) { + for (const existingService of Object.values(existingEnclave.services.value.serviceInfo)) { + const serviceNoLongerExists = sortedNodes.every((node) => { + const nodeData = data[node.id]; + return nodeData.type !== "service" || nodeData.serviceName !== existingService.name; + }); + if (serviceNoLongerExists) { + starlark += ` plan.remove_service(name = "${existingService.name}")\n`; + } + } + } + + starlark += `\n\n# ${EMUI_BUILD_STATE_KEY}=${btoa(JSON.stringify({ nodes, edges, data }))}`; + + console.log(starlark); + + return starlark; +} diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/widgets/CreateEnclaveButton.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/widgets/CreateEnclaveButton.tsx index 1b6ceaa4ae..fc2bec4e60 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/widgets/CreateEnclaveButton.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/widgets/CreateEnclaveButton.tsx @@ -1,33 +1,36 @@ -import { Button, Menu, MenuButton, Tooltip } from "@chakra-ui/react"; -import { FiPlus } from "react-icons/fi"; +import { Button, ButtonGroup, Tooltip } from "@chakra-ui/react"; +import { FiPlus, FiTool } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; -import { KURTOSIS_CREATE_ENCLAVE_URL_ARG } from "../configuration/drawer/constants"; +import { useSettings } from "../../../settings"; +import { KURTOSIS_BUILD_ENCLAVE_URL_ARG, KURTOSIS_CREATE_ENCLAVE_URL_ARG } from "../configuration/drawer/constants"; export const CreateEnclaveButton = () => { + const { settings } = useSettings(); const navigate = useNavigate(); return ( - <> - - - } + + {settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE && ( + + - {/**/} - {/* navigate(`#${KURTOSIS_CREATE_ENCLAVE_URL_ARG}`)} icon={}>*/} - {/* Manual*/} - {/* */} - {/* navigate("/catalog")} icon={}>*/} - {/* Catalog*/} - {/* */} - {/**/} - - + )} + + + + ); }; diff --git a/enclave-manager/web/packages/app/src/emui/settings.tsx b/enclave-manager/web/packages/app/src/emui/settings.tsx new file mode 100644 index 0000000000..03f88a83c9 --- /dev/null +++ b/enclave-manager/web/packages/app/src/emui/settings.tsx @@ -0,0 +1,66 @@ +import { isDefined, maybeParse } from "kurtosis-ui-components"; +import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from "react"; + +type Settings = { + ENABLE_EXPERIMENTAL_BUILD_ENCLAVE: boolean; + SAVED_PACKAGES: string[]; +}; + +export const settingKeys: { [k in keyof Settings]: k } = { + ENABLE_EXPERIMENTAL_BUILD_ENCLAVE: "ENABLE_EXPERIMENTAL_BUILD_ENCLAVE", + SAVED_PACKAGES: "SAVED_PACKAGES", +} as const; + +const defaultSettings: Settings = { ENABLE_EXPERIMENTAL_BUILD_ENCLAVE: false, SAVED_PACKAGES: [] }; + +const SETTINGS_LOCAL_STORAGE_KEY = "kurtosis-settings"; + +export const storeSettings = (settings: Settings) => { + localStorage.setItem(SETTINGS_LOCAL_STORAGE_KEY, JSON.stringify(settings)); +}; + +export const loadSettings = (): Settings => { + // TODO: Remove once confident all users have migrated from the old settings key + const oldSavedPackages = localStorage.getItem("kurtosis-saved-packages"); + const migratedDefaultSettings = { + ...defaultSettings, + SAVED_PACKAGES: isDefined(oldSavedPackages) + ? maybeParse(oldSavedPackages, defaultSettings.SAVED_PACKAGES) + : defaultSettings.SAVED_PACKAGES, + }; + + const savedRawValue = localStorage.getItem(SETTINGS_LOCAL_STORAGE_KEY); + + if (!isDefined(savedRawValue)) { + return migratedDefaultSettings; + } + + return maybeParse(savedRawValue, migratedDefaultSettings); +}; + +type SettingsContextState = { + settings: Settings; + updateSetting: (setting: S, value: Settings[S]) => void; +}; + +const SettingsContext = createContext(null as any); + +export const SettingsContextProvider = ({ children }: PropsWithChildren) => { + const [settings, setSettings] = useState(defaultSettings); + + const updateSetting = useCallback((setting: S, value: Settings[S]) => { + setSettings((settings) => { + const newSettings = { ...settings, [setting]: value }; + storeSettings(newSettings); + return newSettings; + }); + }, []); + + useEffect(() => { + setSettings(loadSettings()); + }, []); + + return {children}; +}; + +export const useSettings = () => useContext(SettingsContext); diff --git a/enclave-manager/web/packages/components/package.json b/enclave-manager/web/packages/components/package.json index 954a25634d..df30883870 100644 --- a/enclave-manager/web/packages/components/package.json +++ b/enclave-manager/web/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "kurtosis-ui-components", - "version": "0.86.14", + "version": "0.86.21", "private": false, "main": "build/index", "description": "This repo contains components used by Kurtosis UI applications.", diff --git a/enclave-manager/web/packages/components/src/CodeEditor.tsx b/enclave-manager/web/packages/components/src/CodeEditor.tsx index 1b234d3575..3b5147b645 100644 --- a/enclave-manager/web/packages/components/src/CodeEditor.tsx +++ b/enclave-manager/web/packages/components/src/CodeEditor.tsx @@ -10,6 +10,7 @@ const MONACO_READ_ONLY_CHANGE_EVENT_ID = 89; type CodeEditorProps = { text: string; fileName?: string; + isEditable?: boolean; onTextChange?: (newText: string) => void; showLineNumbers?: boolean; }; @@ -17,12 +18,13 @@ type CodeEditorProps = { export type CodeEditorImperativeAttributes = { formatCode: () => Promise; setText: (text: string) => void; + getText: () => string; setLanguage: (language: string) => void; }; export const CodeEditor = forwardRef( - ({ text, fileName, onTextChange, showLineNumbers }, ref) => { - const isReadOnly = !isDefined(onTextChange); + ({ text, fileName, isEditable, onTextChange, showLineNumbers }, ref) => { + const isReadOnly = !isEditable && !isDefined(onTextChange); const [monaco, setMonaco] = useState(); const [editor, setEditor] = useState(); @@ -56,8 +58,8 @@ export const CodeEditor = forwardRef { if (isDefined(value) && onTextChange) { onTextChange(value); - resizeEditorBasedOnContent(); } + resizeEditorBasedOnContent(); }; useImperativeHandle( @@ -111,6 +113,9 @@ export const CodeEditor = forwardRef { + return editor?.getValue() || ""; + }, setLanguage: (language: string) => { if (!isDefined(editor) || !isDefined(monaco)) { return; diff --git a/enclave-manager/web/packages/components/src/FileTree.tsx b/enclave-manager/web/packages/components/src/FileTree.tsx index c67ea182ac..a917160320 100644 --- a/enclave-manager/web/packages/components/src/FileTree.tsx +++ b/enclave-manager/web/packages/components/src/FileTree.tsx @@ -21,11 +21,18 @@ type FileTreeProps = { nodes: FileTreeNode[]; selectedFilePath?: string[]; onFileSelected: (selectedFilePath: string[]) => void; + onFileDblClicked?: (selectedFilePath: string[]) => void; // Internal prop used for padding _isChildNode?: boolean; }; -export const FileTree = ({ nodes, selectedFilePath, onFileSelected, _isChildNode }: FileTreeProps) => { +export const FileTree = ({ + nodes, + selectedFilePath, + onFileSelected, + onFileDblClicked, + _isChildNode, +}: FileTreeProps) => { return ( {nodes.map((node, i) => ( @@ -38,6 +45,7 @@ export const FileTree = ({ nodes, selectedFilePath, onFileSelected, _isChildNode : undefined } onFileSelected={onFileSelected} + onFileDblClicked={onFileDblClicked} /> ))} @@ -48,6 +56,7 @@ type FileTreeNodeComponentProps = { node: FileTreeNode; selectedFilePath?: string[]; onFileSelected: (selectedFilePath: string[]) => void; + onFileDblClicked?: (selectedFilePath: string[]) => void; }; const FileTreeNodeComponent = React.memo((props: FileTreeNodeComponentProps) => { @@ -62,6 +71,7 @@ const DirectoryNode = ({ node, selectedFilePath, onFileSelected, + onFileDblClicked, }: FileTreeNodeComponentProps & { node: { childNodes: FileTreeNode[] } }) => { const [collapsed, setCollapsed] = useState(false); @@ -82,6 +92,13 @@ const DirectoryNode = ({ [onFileSelected, node], ); + const handleFileDblClicked = useMemo(() => { + if (!isDefined(onFileDblClicked)) { + return undefined; + } + return (filePath: string[]) => onFileDblClicked([node.name, ...filePath]); + }, [onFileDblClicked]); + return ( <> - + {isDefined(onConfirm) && ( + + )} diff --git a/enclave-manager/web/packages/components/src/KurtosisThemeProvider.tsx b/enclave-manager/web/packages/components/src/KurtosisThemeProvider.tsx index 63cc961ef4..8a7b13a232 100644 --- a/enclave-manager/web/packages/components/src/KurtosisThemeProvider.tsx +++ b/enclave-manager/web/packages/components/src/KurtosisThemeProvider.tsx @@ -331,7 +331,7 @@ const theme = extendTheme({ Form: formsTheme, Input: { baseStyle: { - field: { _dark: { bg: "gray.850" } }, + field: { _dark: { bg: "gray.850" }, _placeholder: { color: "gray.200" } }, }, }, Menu: { diff --git a/enclave-manager/web/packages/components/src/catalog/widgets/SaveKurtosisPackageButton.tsx b/enclave-manager/web/packages/components/src/catalog/widgets/SaveKurtosisPackageButton.tsx index 6a3ccffea5..d83fd2de07 100644 --- a/enclave-manager/web/packages/components/src/catalog/widgets/SaveKurtosisPackageButton.tsx +++ b/enclave-manager/web/packages/components/src/catalog/widgets/SaveKurtosisPackageButton.tsx @@ -37,6 +37,7 @@ const SaveKurtosisPackageButtonImpl = ({ const handleClick = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); + e.preventDefault(); togglePackageSaved(kurtosisPackage); }, [togglePackageSaved, kurtosisPackage], diff --git a/enclave-manager/web/packages/components/src/utils/index.ts b/enclave-manager/web/packages/components/src/utils/index.ts index 5c62c028ac..90d357e0dc 100644 --- a/enclave-manager/web/packages/components/src/utils/index.ts +++ b/enclave-manager/web/packages/components/src/utils/index.ts @@ -135,3 +135,12 @@ export function wrapResult(c: () => T, errorMessage?: string): Result(input: string, defaultValue: T): T { + try { + return JSON.parse(input); + } catch (error: any) { + console.error(`Unable to load input. Got error: ${stringifyError(error)}`); + return defaultValue; + } +} diff --git a/enclave-manager/web/yarn.lock b/enclave-manager/web/yarn.lock index ca53ceb7fe..a75ef5da2b 100644 --- a/enclave-manager/web/yarn.lock +++ b/enclave-manager/web/yarn.lock @@ -1111,6 +1111,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" + integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" @@ -1118,6 +1125,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.3.4": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -2155,6 +2169,18 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@dagrejs/dagre@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@dagrejs/dagre/-/dagre-1.0.4.tgz#16ab98b457dbceccdbd3b23bc96e76bb8b925580" + integrity sha512-jrEore+HhW1yg1Rsd9H1PPMcoEOD4bVh0WCXc6GqzyzubnJj4GaWGg8ETOrskTd/3n/g5LOzumGM4CCgpNLJNw== + dependencies: + "@dagrejs/graphlib" "2.1.13" + +"@dagrejs/graphlib@2.1.13": + version "2.1.13" + resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.1.13.tgz#7d0481966e67226d0a864484524f0db933ffc753" + integrity sha512-calbMa7Gcyo+/t23XBaqQqon8LlgE9regey4UVoikoenKBXvUnCUL3s9RP6USCxttfr0XWVICtYUuKMdehKqMw== + "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -3693,6 +3719,72 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@reactflow/background@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.7.tgz#ab951ce1c882c7ebbe8dc66ad497a1826a27fb34" + integrity sha512-PhkvoFtO/NXJgFtBvfbPwdR/6/dl25egQlFhKWS3T4aYa7rh80dvf6dF3t6+JXJS4q5ToYJizD2/n8/qylo1yQ== + dependencies: + "@reactflow/core" "11.10.2" + classcat "^5.0.3" + zustand "^4.4.1" + +"@reactflow/controls@11.2.7": + version "11.2.7" + resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.2.7.tgz#8586b02df69c423469e2b44de9fef017a545ba6e" + integrity sha512-mugzVALH/SuKlVKk+JCRm1OXQ+p8e9+k8PCTIaqL+nBl+lPF8KA4uMm8ApsOvhuSAb2A80ezewpyvYHr0qSYVA== + dependencies: + "@reactflow/core" "11.10.2" + classcat "^5.0.3" + zustand "^4.4.1" + +"@reactflow/core@11.10.2": + version "11.10.2" + resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.10.2.tgz#89ded00049a8564dcbdd8cfb1fcd95d249245c23" + integrity sha512-/cbTxtFpfkIGReSVkcnQhS4Jx4VFY2AhPlJ5n0sbPtnR7OWowF9zodh5Yyzr4j1NOUoBgJ9h+UqGEwwY2dbAlw== + dependencies: + "@types/d3" "^7.4.0" + "@types/d3-drag" "^3.0.1" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.4.1" + +"@reactflow/minimap@11.7.7": + version "11.7.7" + resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.7.7.tgz#1f5971a2d587b31d621e22c2dd41590d5f5732f9" + integrity sha512-Pwqw31tJ663cJur6ypqyJU33nPckvTepmz96erdQZoHsfOyLmFj4nXT7afC30DJ48lp0nfNsw+028mlf7f/h4g== + dependencies: + "@reactflow/core" "11.10.2" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.4.1" + +"@reactflow/node-resizer@2.2.7": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.2.7.tgz#50d81d60592ea9c04080ac9fcbcd0fb81a5da84d" + integrity sha512-BMBstmWNiklHnnAjHu8irkiPQ8/k8nnjzqlTql4acbVhD6Tsdxx/t/saOkELmfQODqGZNiPw9+pHcAHgtE6oNQ== + dependencies: + "@reactflow/core" "11.10.2" + classcat "^5.0.4" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + zustand "^4.4.1" + +"@reactflow/node-toolbar@1.3.7": + version "1.3.7" + resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.3.7.tgz#ddcc5b720a457f1f9b329c2ad3663a618950d337" + integrity sha512-75moEQKg23YKA3A2DNSFhq719ZPmby5mpwOD+NO7ZffJ88oMS/2eY8l8qpA3hvb1PTBHDxyKazhJirW+f4t0Wg== + dependencies: + "@reactflow/core" "11.10.2" + classcat "^5.0.3" + zustand "^4.4.1" + "@remix-run/router@1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.1.tgz#6d2dd03d52e604279c38911afc1079d58c50a755" @@ -3982,6 +4074,216 @@ dependencies: "@types/node" "*" +"@types/d3-array@*": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-axis@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" + integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" + integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" + integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-contour@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" + integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" + integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== + +"@types/d3-dispatch@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7" + integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== + +"@types/d3-drag@*", "@types/d3-drag@^3.0.1": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" + integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== + +"@types/d3-ease@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-fetch@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" + integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" + integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== + +"@types/d3-format@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" + integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== + +"@types/d3-geo@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" + integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz#8d3638df273ec90da34b3ac89d8784c59708cb0d" + integrity sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw== + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.2.tgz#4327f4a05d475cf9be46a93fc2e0f8d23380805a" + integrity sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA== + +"@types/d3-polygon@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" + integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== + +"@types/d3-quadtree@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" + integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== + +"@types/d3-random@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" + integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== + +"@types/d3-scale-chromatic@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644" + integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw== + +"@types/d3-scale@*": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*", "@types/d3-selection@^3.0.3": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" + integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== + +"@types/d3-shape@*": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" + integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== + +"@types/d3-time@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/d3-transition@*": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f" + integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*", "@types/d3-zoom@^3.0.1": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" + integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -4042,6 +4344,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/geojson@*": + version "7946.0.13" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e" + integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ== + "@types/graceful-fs@^4.1.2": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -4224,6 +4531,13 @@ dependencies: "@types/react" "*" +"@types/react-mentions@^4.1.13": + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/react-mentions/-/react-mentions-4.1.13.tgz#293e56e14c502f6a73217fece0b870e54a0cc657" + integrity sha512-kRulAAjlmhCtsJ9bapO0foocknaE/rEuFKpmFEU81fBfnXZmZNBaJ9J/DBjwigT3WDHjQVUmYoi5sxEXrcdzAw== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.0.0": version "18.2.43" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.43.tgz#c58e5abe241e6f71f60ce30e2a9aceb9d3a2a374" @@ -4321,6 +4635,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== +"@types/uuid@^9.0.8": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^8.5.5": version "8.5.10" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" @@ -5720,6 +6039,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +classcat@^5.0.3, classcat@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.4.tgz#e12d1dfe6df6427f260f03b80dc63571a5107ba6" + integrity sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g== + clean-css@^5.2.2: version "5.3.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" @@ -6471,6 +6795,68 @@ cypress@^13.6.2: untildify "^4.0.0" yauzl "^2.10.0" +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +"d3-dispatch@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-ease@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-interpolate@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +"d3-timer@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -12422,7 +12808,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -12692,6 +13078,16 @@ react-markdown@^9.0.0: unist-util-visit "^5.0.0" vfile "^6.0.0" +react-mentions@^4.4.10: + version "4.4.10" + resolved "https://registry.yarnpkg.com/react-mentions/-/react-mentions-4.4.10.tgz#ae6c1e310a405597e83ce786f12c5bfb93b097ce" + integrity sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA== + dependencies: + "@babel/runtime" "7.4.5" + invariant "^2.2.4" + prop-types "^15.5.8" + substyle "^9.1.0" + react-property@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6" @@ -12812,6 +13208,18 @@ react@^18.2.0: dependencies: loose-envify "^1.1.0" +reactflow@^11.10.2: + version "11.10.2" + resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.10.2.tgz#b7b1a1778f31975c0fc8afed92a023a268bc4d02" + integrity sha512-tqQJfPEiIkXonT3piVYf+F9CvABI5e28t5I6rpaLTnO8YVCAOh1h0f+ziDKz0Bx9Y2B/mFgyz+H7LZeUp/+lhQ== + dependencies: + "@reactflow/background" "11.3.7" + "@reactflow/controls" "11.2.7" + "@reactflow/core" "11.10.2" + "@reactflow/minimap" "11.7.7" + "@reactflow/node-resizer" "2.2.7" + "@reactflow/node-toolbar" "1.3.7" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -12963,7 +13371,7 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.9: +regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.9: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== @@ -14008,6 +14416,14 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +substyle@^9.1.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/substyle/-/substyle-9.4.1.tgz#6a4647f363bc14fecc51aac371d4dbeda082aa50" + integrity sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA== + dependencies: + "@babel/runtime" "^7.3.4" + invariant "^2.2.4" + sucrase@^3.32.0: version "3.34.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" @@ -14802,6 +15218,11 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -14832,6 +15253,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -15607,6 +16033,13 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zustand@^4.4.1: + version "4.4.7" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c" + integrity sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw== + dependencies: + use-sync-external-store "1.2.0" + zwitch@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" diff --git a/engine/launcher/engine_server_launcher/engine_server_launcher.go b/engine/launcher/engine_server_launcher/engine_server_launcher.go index 664dd11f2a..18591ddd97 100644 --- a/engine/launcher/engine_server_launcher/engine_server_launcher.go +++ b/engine/launcher/engine_server_launcher/engine_server_launcher.go @@ -45,7 +45,7 @@ func (launcher *EngineServerLauncher) LaunchWithDefaultVersion( cloudInstanceID metrics_client.CloudInstanceID, allowedCORSOrigins *[]string, shouldStartInDebugMode bool, -) ( + githubAuthToken string) ( resultPublicIpAddr net.IP, resultPublicGrpcPortSpec *port_spec.PortSpec, resultErr error, @@ -66,7 +66,7 @@ func (launcher *EngineServerLauncher) LaunchWithDefaultVersion( cloudInstanceID, allowedCORSOrigins, shouldStartInDebugMode, - ) + githubAuthToken) if err != nil { return nil, nil, stacktrace.Propagate(err, "An error occurred launching the engine server container with default version tag '%v'", kurtosis_version.KurtosisVersion) } @@ -89,12 +89,13 @@ func (launcher *EngineServerLauncher) LaunchWithCustomVersion( cloudInstanceID metrics_client.CloudInstanceID, allowedCORSOrigins *[]string, shouldStartInDebugMode bool, -) ( + githubAuthToken string) ( resultPublicIpAddr net.IP, resultPublicGrpcPortSpec *port_spec.PortSpec, resultErr error, ) { kurtosisBackendType, kurtosisBackendConfig := backendConfigSupplier.getKurtosisBackendConfig() + argsObj, err := args.NewEngineServerArgs( grpcListenPortNum, logLevel.String(), @@ -127,6 +128,7 @@ func (launcher *EngineServerLauncher) LaunchWithCustomVersion( grpcListenPortNum, envVars, shouldStartInDebugMode, + githubAuthToken, ) if err != nil { return nil, nil, stacktrace.Propagate(err, "An error occurred launching the engine server container with environment variables '%+v'", envVars) diff --git a/engine/server/engine/enclave_manager/enclave_creator.go b/engine/server/engine/enclave_manager/enclave_creator.go index 523c898da9..d8526c2a50 100644 --- a/engine/server/engine/enclave_manager/enclave_creator.go +++ b/engine/server/engine/enclave_manager/enclave_creator.go @@ -2,7 +2,6 @@ package enclave_manager import ( "context" - "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/api_container" "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface/objects/enclave" @@ -113,8 +112,7 @@ func (creator *EnclaveCreator) CreateEnclave( isCI, cloudUserID, cloudInstanceID, - shouldAPICRunInDebugMode, - ) + shouldAPICRunInDebugMode) if err != nil { return nil, stacktrace.Propagate(err, "An error occurred launching the API container") } @@ -223,8 +221,7 @@ func (creator *EnclaveCreator) launchApiContainer( isCI, cloudUserID, cloudInstanceID, - shouldStartInDebugMode, - ) + shouldStartInDebugMode) if err != nil { return nil, stacktrace.Propagate(err, "Expected to be able to launch api container for enclave '%v' with custom version '%v', but an error occurred", enclaveUuid, apiContainerImageVersionTag) } diff --git a/engine/server/engine/enclave_manager/enclave_pool.go b/engine/server/engine/enclave_manager/enclave_pool.go index 459e7a36e4..5acd2d3f4f 100644 --- a/engine/server/engine/enclave_manager/enclave_pool.go +++ b/engine/server/engine/enclave_manager/enclave_pool.go @@ -58,7 +58,6 @@ func CreateEnclavePool( isCI bool, cloudUserID metrics_client.CloudUserID, cloudInstanceID metrics_client.CloudInstanceID, - ) (*EnclavePool, error) { //TODO the current implementation only removes the previous idle enclave, it's pending to implement the reusable feature diff --git a/engine/server/engine/main.go b/engine/server/engine/main.go index 17d8681c46..f9bac6cb38 100644 --- a/engine/server/engine/main.go +++ b/engine/server/engine/main.go @@ -177,7 +177,19 @@ func runMain() error { logFileManager := log_file_manager.NewLogFileManager(kurtosisBackend, osFs, realTime) logFileManager.StartLogFileManagement(ctx) - enclaveManager, err := getEnclaveManager(kurtosisBackend, serverArgs.KurtosisBackendType, serverArgs.ImageVersionTag, serverArgs.PoolSize, serverArgs.EnclaveEnvVars, logFileManager, serverArgs.MetricsUserID, serverArgs.DidUserAcceptSendingMetrics, serverArgs.IsCI, serverArgs.CloudUserID, serverArgs.CloudInstanceID, serverArgs.KurtosisLocalBackendConfig) + enclaveManager, err := getEnclaveManager( + kurtosisBackend, + serverArgs.KurtosisBackendType, + serverArgs.ImageVersionTag, + serverArgs.PoolSize, + serverArgs.EnclaveEnvVars, + logFileManager, + serverArgs.MetricsUserID, + serverArgs.DidUserAcceptSendingMetrics, + serverArgs.IsCI, + serverArgs.CloudUserID, + serverArgs.CloudInstanceID, + serverArgs.KurtosisLocalBackendConfig) if err != nil { return stacktrace.Propagate(err, "Failed to create an enclave manager for backend type '%v' and config '%+v'", serverArgs.KurtosisBackendType, backendConfig) } diff --git a/engine/server/engine/streaming/websocket_pump.go b/engine/server/engine/streaming/websocket_pump.go index ede4a7efa1..f9d89d9121 100644 --- a/engine/server/engine/streaming/websocket_pump.go +++ b/engine/server/engine/streaming/websocket_pump.go @@ -52,6 +52,7 @@ func NewWebsocketPump[T interface{}](ctx echo.Context, cors cors.Cors) (*Websock ctxWithCancel, cancelFunc := context.WithCancel(context.Background()) + // nolint: exhaustruct pump := &WebsocketPump[T]{ websocket: conn, inputChan: make(chan *T), diff --git a/flake.nix b/flake.nix index 44d25796ac..ef4d08fceb 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,7 @@ golangci-lint delve enumer + go-mockery nodejs_20 node2nix yarn diff --git a/go.work.sum b/go.work.sum index 4f28d7c718..e69dce23b6 100644 --- a/go.work.sum +++ b/go.work.sum @@ -53,9 +53,7 @@ cloud.google.com/go/cloudtasks v1.11.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8o cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/contactcenterinsights v1.9.1 h1:hy4L0bc3fQNZZrhPjuoH62RiisD5B71/S1OZNunsTRk= cloud.google.com/go/contactcenterinsights v1.9.1/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= cloud.google.com/go/container v1.22.1 h1:WKBegIfJJc+CL2PIgNpQuvLgGW/CoGJjge5Yjpc0YuU= @@ -251,6 +249,8 @@ cloud.google.com/go/websecurityscanner v1.6.1 h1:CfEF/vZ+xXyAR3zC9iaC/QRdf1MEgS2 cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv5/rGpFCsBOvPbYgszpg= cloud.google.com/go/workflows v1.11.1 h1:2akeQ/PgtRhrNuD/n1WvJd5zb7YyuDZrlOanBj2ihPg= cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3 h1:hJiie5Bf3QucGRa4ymsAUOxyhYwGEz1xrsVk0P8erlw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0 h1:SPOUaucgtVls75mg+X7CXigS71EnsfVUK/2CgVrwqgw= @@ -261,6 +261,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 h1:+vTEFqeoeur6XSq06bs+roX3YiT49gUniJK7Zky7Xjg= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= +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/azure-sdk-for-go v56.3.0+incompatible h1:DmhwMrUIvpeoTDiWRDtNHqelNUd3Og8JCkrLHQK795c= github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw= @@ -298,6 +300,8 @@ github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= @@ -316,6 +320,8 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= @@ -323,6 +329,8 @@ github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= @@ -402,7 +410,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq 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/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= @@ -411,6 +418,10 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo= @@ -475,6 +486,9 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba h1:p6poVbjHDkKa+wtC8frBMwQtT3BmqGYBjzMwJ63tuR4= @@ -482,6 +496,8 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw= @@ -501,6 +517,11 @@ github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA= @@ -521,11 +542,21 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db h1:gb2Z18BhTPJPpLQW github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k= +github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -533,10 +564,10 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +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/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= @@ -549,8 +580,6 @@ github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -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/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -573,8 +602,6 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -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.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= @@ -601,6 +628,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hanwen/go-fuse/v2 v2.2.0 h1:jo5QZYmBLNcl9ovypWaQ5yXMSSV+Ch68xoC3rtZvvBM= github.com/hanwen/go-fuse/v2 v2.2.0/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78= @@ -625,6 +653,8 @@ github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sL github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= 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= @@ -643,6 +673,10 @@ github.com/intel/goresctrl v0.3.0 h1:K2D3GOzihV7xSBedGxONSlaw/un1LZgWsc9IfqipN4c github.com/intel/goresctrl v0.3.0/go.mod h1:fdz3mD85cmP9sHD8JUlrNWAxvwM86CrbmVXltEKd7zk= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= +github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1 h1:ujPKutqRlJtcfWk6toYVYagwra7HQHbXOaS171b4Tg8= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -665,12 +699,15 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +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/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -692,8 +729,13 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/microsoft/dev-tunnels v0.0.25 h1:UlMKUI+2O8cSu4RlB52ioSyn1LthYSVkJA+CSTsdKoA= +github.com/microsoft/dev-tunnels v0.0.25/go.mod h1:frU++12T/oqxckXkDpTuYa427ncguEOodSPZcGCCrzQ= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -713,6 +755,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mmcloughlin/avo v0.5.0 h1:nAco9/aI9Lg2kiuROBY6BhCI/z0t5jEvJfjWbL8qXLU= +github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/moby/buildkit v0.12.3 h1:cFaPVnyC0PwAP5xHHfzdU5v9rgQrCi6HnGSg3WuFKp4= github.com/moby/buildkit v0.12.3/go.mod h1:adB4y0SxxX8trnrY+oEulb48ODLqPO6pKMF0ppGcCoI= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -729,6 +773,10 @@ github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo= +github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76 h1:0xuRacu/Zr+jX+KyLLPPktbwXqyOvnOPUQmMLzX1jxU= 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= @@ -745,8 +793,13 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= @@ -797,11 +850,17 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d h1:jKIUJdMcIVGOSHi6LSqJqw9RqblyblE2ZrHvFbWR3S0= +github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d/go.mod h1:YX2wUZOcJGOIycErz2s9KvDaP0jnWwRCirQMPLPpQ+Y= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= +github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 h1:BN/Nyn2nWMoqGRA7G7paDNDqTXE30mXGqzzybrfo05w= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -838,6 +897,9 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82 h1:LneqU9PHDsg/ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537 h1:YGaxtkYjb8mnTvtufv2LKLwCQu2/C7qFB7UtrOlTWOY= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133 h1:JtcyT0rk/9PKOdnKQzuDR+FSjh7SGtJwpgVpfZBRKlQ= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= @@ -889,11 +951,16 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= 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= @@ -933,35 +1000,38 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d h1:E2M5QgjZ/Jg+ObCQAudsXxuTsLj7Nl5RV/lZcQZmKSo= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A= @@ -997,13 +1067,13 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQ gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= diff --git a/internal_testsuites/golang/scripts/test.sh b/internal_testsuites/golang/scripts/test.sh index 66e2b6d813..9d86eb9938 100755 --- a/internal_testsuites/golang/scripts/test.sh +++ b/internal_testsuites/golang/scripts/test.sh @@ -12,13 +12,17 @@ lang_root_dirpath="$(dirname "${script_dirpath}")" # ================================================================================================== PARALLELISM=2 DOCKER_TIMEOUT="3m" # This must be Go-parseable timeout -KUBERNETES_TIMEOUT="510s" # K8S takes longer than docker +KUBERNETES_TIMEOUT="8m" # K8S takes longer than docker TESTSUITE_CLUSTER_BACKEND_DOCKER="docker" TESTSUITE_CLUSTER_BACKEND_KUBERNETES="kubernetes" +TEST_IS_RUNNING_ON_CIRCLE_CI="true" +TEST_IS_NOT_RUNNING_ON_CIRCLE_CI="false" + # By default, run testsuite against docker DEFAULT_TESTSUITE_CLUSTER_BACKEND="${TESTSUITE_CLUSTER_BACKEND_DOCKER}" +DEFAULT_IS_RUNNING_ON_CIRCLE_CI="${TEST_IS_NOT_RUNNING_ON_CIRCLE_CI}" # ================================================================================================== # Arg Parsing & Validation @@ -27,6 +31,7 @@ show_helptext_and_exit() { echo "Usage: $(basename "${0}") cli_cluster_backend_arg" echo "" echo " cli_cluster_backend_arg Optional argument describing the cluster backend tests are running against. Must be one of 'docker', 'kubernetes' (default: ${DEFAULT_TESTSUITE_CLUSTER_BACKEND})" + echo " circle_ci_arg Optional argument that allows for test splitting on Circle CI. Must be one of 'true' or 'false'" echo "" exit 1 # Exit with an error so that if this is accidentally called by CI, the script will fail } @@ -38,6 +43,13 @@ if [ "${testsuite_cluster_backend_arg}" != "${TESTSUITE_CLUSTER_BACKEND_DOCKER}" show_helptext_and_exit fi +testsuite_is_running_on_circleci=${2:-${DEFAULT_IS_RUNNING_ON_CIRCLE_CI}} +if [ "${testsuite_is_running_on_circleci}" != "${TEST_IS_RUNNING_ON_CIRCLE_CI}" ] && + [ "${testsuite_is_running_on_circleci}" != "${TEST_IS_NOT_RUNNING_ON_CIRCLE_CI}" ]; then + echo "Error: unknown value for whether the test is running against circleci. Must be one of 'false', 'true'" + show_helptext_and_exit +fi + # ================================================================================================== # Main Logic # ================================================================================================== @@ -51,8 +63,16 @@ if [ "${testsuite_cluster_backend_arg}" == "${TESTSUITE_CLUSTER_BACKEND_KUBERNET # The only reason this exists is because, some Kurtosis feature doesn't work on Kubernetes so we have to know to skip # those tests # K8S is also slower than docker, so they have different timeouts - CGO_ENABLED=0 go test ./... -p "${PARALLELISM}" -count=1 -timeout "${KUBERNETES_TIMEOUT}" -tags kubernetes + if [ "${testsuite_is_running_on_circleci}" == "${TEST_IS_RUNNING_ON_CIRCLE_CI}" ]; then + CGO_ENABLED=0 go test -v $(go list -tags kubernetes ./...| circleci tests split) -p "${PARALLELISM}" -count=1 -timeout "${KUBERNETES_TIMEOUT}" -tags kubernetes + else + CGO_ENABLED=0 go test ./... -p "${PARALLELISM}" -count=1 -timeout "${KUBERNETES_TIMEOUT}" -tags kubernetes + fi else - CGO_ENABLED=0 go test ./... -p "${PARALLELISM}" -count=1 -timeout "${DOCKER_TIMEOUT}" + if [ "${testsuite_is_running_on_circleci}" == "${TEST_IS_RUNNING_ON_CIRCLE_CI}" ]; then + CGO_ENABLED=0 go test -v $(go list ./... | circleci tests split) -p "${PARALLELISM}" -count=1 -timeout "${DOCKER_TIMEOUT}" + else + CGO_ENABLED=0 go test ./... -p "${PARALLELISM}" -count=1 -timeout "${DOCKER_TIMEOUT}" + fi fi diff --git a/internal_testsuites/golang/testsuite/startosis_run_sh_task_test/run_task_sh_task_test.go b/internal_testsuites/golang/testsuite/startosis_run_sh_task_test/run_task_sh_task_test.go index 6b12a8d4ef..26fd3e2f7a 100644 --- a/internal_testsuites/golang/testsuite/startosis_run_sh_task_test/run_task_sh_task_test.go +++ b/internal_testsuites/golang/testsuite/startosis_run_sh_task_test/run_task_sh_task_test.go @@ -11,13 +11,13 @@ const ( runshTest = "run-sh-test" runshStarlarkSimple = ` def run(plan): - result1 = plan.run_sh(run="echo kurtosis | tr -d '\n'") + result1 = plan.run_sh(run="echo $EXAMPLE | tr -d '\n'", env_vars={"EXAMPLE": "kurtosis"}) result2 = plan.run_sh(run="mkdir -p /src/{0} && cd /src/{0} && echo $(pwd)".format(result1.output)) plan.verify(result2.output, "==", "/src/kurtosis\n") ` runshStarlarkFileArtifact = ` def run(plan): - result = plan.run_sh(run="mkdir -p /src && echo kurtosis > /src/tech.txt && echo example > /src/example.txt", store=["/src/tech.txt", StoreSpec(src="/src", name="src"), StoreSpec(src="/src/example.txt")], image="ethpandaops/ethereum-genesis-generator:1.0.14") + result = plan.run_sh(run="mkdir -p /src && echo kurtosis > /src/tech.txt && echo example > /src/example.txt", store=["/src/tech.txt", StoreSpec(src="/src", name="src"), StoreSpec(src="/src/*")], image="ethpandaops/ethereum-genesis-generator:1.0.14") file_artifacts = result.files_artifacts result2 = plan.run_sh(run="cat /temp/tech.txt", files={"/temp": file_artifacts[0]}) plan.verify(result2.output, "==", "kurtosis\n") @@ -27,40 +27,11 @@ def run(plan): plan.verify(result4.output, "==", "example\n") ` - runshStarlarkFileArtifactFailure = ` -def run(plan): - result = plan.run_sh(run="cat /tmp/kurtosis.txt") - plan.verify(value=result.code, assertion="==", target_value="0") -` - runshStarlarkWithTimeout = ` def run(plan): - result = plan.run_sh(run="sleep 45s", wait="30s") + result = plan.run_sh(run="sleep 10s", wait="5s") plan.verify(value=result.code, assertion="==", target_value="0") ` - - runshStarlarkIgnoreParentDir = ` -def run(plan): - result1 = plan.run_sh(run="mkdir -p /src/data && echo kurtosis > /src/data/kurtosis.txt",store=["/src/*"]) - files_artifacts = result1.files_artifacts - result2 = plan.run_sh(files={"/temp": files_artifacts[0]}, run="cat /temp/data/kurtosis.txt") - plan.verify(result2.output, "==", "kurtosis\n") -` - runShWithNewLineRemoval = ` -def run(plan): - result = plan.run_sh(run="mkdir -p /src && echo kurtosis > /src/tech.txt", store=["/src/tech.txt"]) - file_artifacts = result.files_artifacts - result2 = plan.run_sh(run="cat /temp/tech.txt", files={"/temp": file_artifacts[0]}) - plan.verify(result2.output, "==", "kurtosis\n") - result2 = plan.run_sh(run="cat /temp/tech.txt | tr -d '\n'", files={"/temp": file_artifacts[0]}) - plan.verify(result2.output, "==", "kurtosis") -` - - runShWithEnvVar = ` -def run(plan): - result = plan.run_sh(run="mkdir -p kurtosis && echo $EXAMPLE",image="badouralix/curl-jq",env_vars={"EXAMPLE": "value"}) - plan.verify(result.output, "==", "value\n") -` ) func TestStarlark_RunshTaskSimple(t *testing.T) { @@ -79,37 +50,10 @@ func TestStarlark_RunshTaskFileArtifact(t *testing.T) { require.Equal(t, expectedOutput, string(runResult.RunOutput)) } -func TestStarlark_RunshTaskFileArtifactFailure(t *testing.T) { - ctx := context.Background() - runResult, _ := test_helpers.SetupSimpleEnclaveAndRunScript(t, ctx, runshTest, runshStarlarkFileArtifactFailure) - expectedErrorMessage := "cat: can't open '/tmp/kurtosis.txt': No such file or directory" - require.NotNil(t, runResult.ExecutionError) - require.Contains(t, runResult.ExecutionError.GetErrorMessage(), expectedErrorMessage) -} - func TestStarlark_RunshTimesoutSuccess(t *testing.T) { ctx := context.Background() runResult, _ := test_helpers.SetupSimpleEnclaveAndRunScript(t, ctx, runshTest, runshStarlarkWithTimeout) - expectedErrorMessage := "The exec request timed out after 30 seconds" + expectedErrorMessage := "The exec request timed out after 5 seconds" require.NotNil(t, runResult.ExecutionError) require.Contains(t, runResult.ExecutionError.GetErrorMessage(), expectedErrorMessage) } - -func TestStarlark_RunshFileArtifactWithoutParentDir(t *testing.T) { - ctx := context.Background() - runResult, _ := test_helpers.SetupSimpleEnclaveAndRunScript(t, ctx, runshTest, runshStarlarkIgnoreParentDir) - expectedOutput := "Command returned with exit code '0' with no output\nCommand returned with exit code '0' and the following output:\n--------------------\nkurtosis\n\n--------------------\nVerification succeeded. Value is '\"kurtosis\\n\"'.\n" - require.Equal(t, expectedOutput, string(runResult.RunOutput)) -} - -func TestStarlark_RunShWithNewLineRemovalPipe(t *testing.T) { - ctx := context.Background() - _, err := test_helpers.SetupSimpleEnclaveAndRunScript(t, ctx, runshTest, runShWithNewLineRemoval) - require.Nil(t, err) -} - -func TestStarlark_RunShWithEnvVars(t *testing.T) { - ctx := context.Background() - _, err := test_helpers.SetupSimpleEnclaveAndRunScript(t, ctx, runshTest, runShWithEnvVar) - require.Nil(t, err) -} diff --git a/version.txt b/version.txt index fb98da8728..785922b0b8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.86.14 +0.86.21