diff --git a/Makefile b/Makefile index 1b620409d..d7ea54500 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ update-nix-vendor-hash: $(MAKE) nix-run 2>&1 | grep -o 'sha256-[A-Za-z0-9+/=]\+' | tail -n1 | xargs -I {} sed -i.bak -E 's|(vendorHash = ")[^"]+(")|vendorHash = "{}"|' pkgs/defang/cli.nix rm pkgs/defang/cli.nix.bak git add pkgs/defang/cli.nix - @echo cli.nix vendorHash has been updated; commit and push + @echo 'cli.nix vendorHash has been updated; commit and push' @false .PHONY: nix-run diff --git a/pkgs/defang/cli.nix b/pkgs/defang/cli.nix index effae677b..9a88f9ec0 100644 --- a/pkgs/defang/cli.nix +++ b/pkgs/defang/cli.nix @@ -7,7 +7,7 @@ buildGo124Module { pname = "defang-cli"; version = "git"; src = lib.cleanSource ../../src; - vendorHash = "sha256-v1CdVCvXnWJSZykHc7VeV08hRnCer0jxo+je/5+bJUo="; + vendorHash = "sha256-mQ0YcinZlzeygghBGcbC2EdntqtOFMAWBbwkHOLAyq4="; subPackages = [ "cmd/cli" ]; diff --git a/src/cmd/cli/command/cd.go b/src/cmd/cli/command/cd.go index ec38c29e4..6edde9cb7 100644 --- a/src/cmd/cli/command/cd.go +++ b/src/cmd/cli/command/cd.go @@ -199,7 +199,8 @@ var cdInstallCmd = &cobra.Command{ return err } - return cli.InstallCD(ctx, session.Provider) + force, _ := cmd.Flags().GetBool("force") + return cli.InstallCD(ctx, session.Provider, force) }, } diff --git a/src/cmd/cli/command/commands.go b/src/cmd/cli/command/commands.go index f3d79a89c..b828a4e9c 100644 --- a/src/cmd/cli/command/commands.go +++ b/src/cmd/cli/command/commands.go @@ -187,6 +187,7 @@ func SetupCommands(version string) { cdPreviewCmd.Flags().VarP(&global.Stack.Mode, "mode", "m", fmt.Sprintf("deployment mode; one of %v", modes.AllDeploymentModes())) cdPreviewCmd.RegisterFlagCompletionFunc("mode", cobra.FixedCompletions(modes.AllDeploymentModes(), cobra.ShellCompDirectiveNoFileComp)) cdCmd.AddCommand(cdPreviewCmd) + cdInstallCmd.Flags().Bool("force", false, "force the installation of the CD resources into the cluster (allow downgrades)") cdCmd.AddCommand(cdInstallCmd) cdCmd.AddCommand(cdCloudformationCmd) diff --git a/src/cmd/crun/main.go b/src/cmd/crun/main.go deleted file mode 100644 index c45a848e0..000000000 --- a/src/cmd/crun/main.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/DefangLabs/defang/src/pkg/crun" - "github.com/DefangLabs/defang/src/pkg/term" - "github.com/spf13/pflag" -) - -var ( - _ = pflag.BoolP("help", "h", false, "Show this help message") - region = pflag.StringP("region", "r", os.Getenv("AWS_REGION"), "Which cloud region to use, or blank for local Docker") - - runFlags = pflag.NewFlagSet(os.Args[0]+" run", pflag.ExitOnError) - envs = runFlags.StringArrayP("env", "e", nil, "Environment variables to pass to the run command") - memory = runFlags.StringP("memory", "m", "2g", "Memory limit in bytes") - envFiles = runFlags.StringArray("env-file", nil, "Read in a file of environment variables") - platform = runFlags.String("platform", "", "Set platform if host is multi-platform capable") - vpcid = runFlags.String("vpcid", "", "VPC to use for the task") - subnetid = runFlags.String("subnetid", "", "Subnet to use for the task") - // driver = pflag.StringP("driver", "d", "auto", "Container runner to use. Choices are: pulumi-ecs, docker") - - version = "development" // overwritten by build script -ldflags "-X main.version=..." -) - -func init() { - runFlags.StringVarP(region, "region", "r", os.Getenv("AWS_REGION"), "Which cloud region to use, or blank for local Docker") -} - -func usage() { - fmt.Printf("Cloud runner (%s)\n\n", version) - fmt.Printf("Usage: \n %s [command] [options]\n\nGlobal Flags:\n", os.Args[0]) - pflag.PrintDefaults() - fmt.Println(` -Commands: - run [arg...] Create and run a new task from an image - logs Fetch the logs of a task - stop Stop a running task - info Show information about a task - destroy Destroy all resources created by this tool`) - fmt.Printf("\n\nUsage of run subcommand: \n %s run [options]\n\nFlags:\n", os.Args[0]) - runFlags.PrintDefaults() -} - -func main() { - pflag.Usage = usage - if len(os.Args) < 2 { - usage() - return - } - - command := os.Args[1] - - if command == "run" || command == "r" { - runFlags.Parse(os.Args[2:]) - } else { - pflag.Parse() - } - - region := crun.Region(*region) - ctx := context.Background() - - requireTaskID := func() string { - if pflag.NArg() != 2 { - term.Fatal(command + " requires a single task ID argument") - } - return pflag.Arg(1) - } - - var err error - switch command { - default: - err = errors.New("unknown command: " + command) - case "help", "": - usage() - case "run", "r": - if runFlags.NArg() < 1 { - term.Fatal("run requires an image name (and optional arguments)") - } - - envMap := make(map[string]string) - // Apply env vars from files first, so they can be overridden by the command line - for _, envFile := range *envFiles { - if _, err := crun.ParseEnvFile(envFile, envMap); err != nil { - term.Fatal(err) - } - } - // Apply env vars from the command line last, so they take precedence - for _, env := range *envs { - if key, value := crun.ParseEnvLine(env); key != "" { - envMap[key] = value - } - } - - memory := crun.ParseMemory(*memory) - err = crun.Run(ctx, crun.RunContainerArgs{ - Region: region, - Image: runFlags.Arg(0), - Memory: memory, - Args: runFlags.Args()[1:], - Env: envMap, - Platform: *platform, - VpcID: *vpcid, - SubnetID: *subnetid, - }) - case "stop", "s": - taskID := requireTaskID() - err = crun.Stop(ctx, region, &taskID) - case "logs", "tail", "l": - taskID := requireTaskID() - err = crun.Logs(ctx, region, &taskID) - case "destroy", "teardown", "d": - if pflag.NArg() != 1 { - term.Fatal("destroy does not take any arguments") - } - err = crun.Destroy(ctx, region) - case "info", "i": - taskID := requireTaskID() - err = crun.PrintInfo(ctx, region, &taskID) - } - - if err != nil { - term.Fatal(err) - } -} diff --git a/src/go.mod b/src/go.mod index 86f1878b0..eea365f4c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -37,7 +37,6 @@ require ( github.com/compose-spec/compose-go/v2 v2.10.1 github.com/digitalocean/godo v1.131.1 github.com/docker/cli v29.2.0+incompatible - github.com/docker/docker v28.0.0+incompatible github.com/firebase/genkit/go v1.2.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 @@ -51,7 +50,6 @@ require ( github.com/moby/patternmatcher v0.6.0 github.com/muesli/termenv v0.15.2 github.com/openai/openai-go v1.12.0 - github.com/opencontainers/image-spec v1.1.0 github.com/pelletier/go-toml/v2 v2.2.2 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -79,7 +77,6 @@ require ( cloud.google.com/go/compute/metadata v0.7.0 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 // indirect @@ -114,6 +111,7 @@ require ( github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -148,7 +146,6 @@ require ( ) require ( - github.com/Microsoft/go-winio v0.6.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.16.16 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect @@ -180,7 +177,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect diff --git a/src/go.sum b/src/go.sum index da7d54791..6368623cd 100644 --- a/src/go.sum +++ b/src/go.sum @@ -34,8 +34,6 @@ connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/DefangLabs/cobra v1.8.0-defang h1:rTzAg1XbEk3yXUmQPumcwkLgi8iNCby5CjyG3sCwzKk= github.com/DefangLabs/cobra v1.8.0-defang/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/DefangLabs/secret-detector v0.0.0-20250811234530-d4b4214cd679 h1:qNT7R4qrN+5u5ajSbqSW1opHP4LA8lzA+ASyw5MQZjs= @@ -48,8 +46,6 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0/go.mod h1:f/ad5NuHnYz8AOZGuR0cY+l36oSCstdxD73YlIchr6I= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 h1:wbMd4eG/fOhsCa6+IP8uEDvWF5vl7rNoUWmP5f72Tbs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -118,16 +114,12 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= @@ -147,8 +139,6 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM= -github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -211,8 +201,6 @@ github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3 github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -279,10 +267,6 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/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.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= @@ -382,10 +366,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= @@ -396,8 +376,6 @@ go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4A go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= @@ -435,7 +413,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index 0e388a3fd..824c20ecc 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -155,14 +155,14 @@ func (b *ByocAws) PrintCloudFormationTemplate() ([]byte, error) { return template.YAML() } -func (b *ByocAws) SetUpCD(ctx context.Context) error { +func (b *ByocAws) SetUpCD(ctx context.Context, force bool) error { if b.SetupDone { return nil } term.Debugf("Using CD image: %q", b.CDImage) - created, err := b.driver.SetUp(ctx, b.makeContainers()) + created, err := b.driver.SetUp(ctx, b.makeContainers(), force) if err != nil { return AnnotateAwsError(err) } @@ -214,7 +214,7 @@ func (b *ByocAws) deploy(ctx context.Context, req *client.DeployRequest, cmd str return nil, err } - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } @@ -682,7 +682,7 @@ func (b *ByocAws) ListConfig(ctx context.Context, req *defangv1.ListConfigsReque } func (b *ByocAws) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } @@ -915,7 +915,7 @@ func (b *ByocAws) TearDownCD(ctx context.Context) error { } func (b *ByocAws) CdCommand(ctx context.Context, req client.CdCommandRequest) (string, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return "", err } etag := types.NewEtag() diff --git a/src/pkg/cli/client/byoc/do/byoc.go b/src/pkg/cli/client/byoc/do/byoc.go index 885e49179..c5f8748d2 100644 --- a/src/pkg/cli/client/byoc/do/byoc.go +++ b/src/pkg/cli/client/byoc/do/byoc.go @@ -148,7 +148,7 @@ func (b *ByocDo) deploy(ctx context.Context, req *client.DeployRequest, cmd stri return nil, err } - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } @@ -234,7 +234,7 @@ func (b *ByocDo) GetDeploymentStatus(ctx context.Context) (bool, error) { } func (b *ByocDo) CdCommand(ctx context.Context, req client.CdCommandRequest) (string, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return "", err } @@ -287,7 +287,7 @@ func (b *ByocDo) CdList(ctx context.Context, _allRegions bool) (iter.Seq[state.I } func (b *ByocDo) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } @@ -671,7 +671,7 @@ func (b *ByocDo) environment(projectName, delegateDomain string, mode defangv1.D return env, nil } -func (b *ByocDo) SetUpCD(ctx context.Context) error { +func (b *ByocDo) SetUpCD(ctx context.Context, force bool) error { if b.SetupDone { return nil } diff --git a/src/pkg/cli/client/byoc/gcp/byoc.go b/src/pkg/cli/client/byoc/gcp/byoc.go index 43bba822e..449573284 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc.go +++ b/src/pkg/cli/client/byoc/gcp/byoc.go @@ -165,7 +165,7 @@ func getGcpProjectID() string { return projectId } -func (b *ByocGcp) SetUpCD(ctx context.Context) error { +func (b *ByocGcp) SetUpCD(ctx context.Context, force bool) error { if b.setupDone { return nil } @@ -370,7 +370,7 @@ func (b *ByocGcp) AccountInfo(ctx context.Context) (*client.AccountInfo, error) } func (b *ByocGcp) CdCommand(ctx context.Context, req client.CdCommandRequest) (types.ETag, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return "", err } etag := types.NewEtag() @@ -499,7 +499,7 @@ func (b *ByocGcp) runCdCommand(ctx context.Context, cmd cdCommand) error { } func (b *ByocGcp) CreateUploadURL(ctx context.Context, req *defangv1.UploadURLRequest) (*defangv1.UploadURLResponse, error) { - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } @@ -533,7 +533,7 @@ func (b *ByocGcp) deploy(ctx context.Context, req *client.DeployRequest, command // FIXME: Get cd image tag for the project - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { return nil, err } diff --git a/src/pkg/cli/client/byoc/gcp/byoc_test.go b/src/pkg/cli/client/byoc/gcp/byoc_test.go index 87f54eec7..73bd6983b 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc_test.go +++ b/src/pkg/cli/client/byoc/gcp/byoc_test.go @@ -30,7 +30,7 @@ func TestSetUpCD(t *testing.T) { t.Errorf("AccountInfo() error = %v, want nil", err) } t.Logf("account: %+v", account) - if err := b.SetUpCD(ctx); err != nil { + if err := b.SetUpCD(ctx, false); err != nil { t.Errorf("SetUpCD() error = %v, want nil", err) } diff --git a/src/pkg/cli/client/playground.go b/src/pkg/cli/client/playground.go index aadad898c..e36d2fed2 100644 --- a/src/pkg/cli/client/playground.go +++ b/src/pkg/cli/client/playground.go @@ -140,7 +140,7 @@ func (g *PlaygroundProvider) TearDownCD(ctx context.Context) error { return errors.New("the teardown command is not valid for the Defang playground; did you forget --stack or --provider?") } -func (g *PlaygroundProvider) SetUpCD(ctx context.Context) error { +func (g *PlaygroundProvider) SetUpCD(ctx context.Context, force bool) error { return errors.New("this command is not valid for the Defang playground; did you forget --stack or --provider?") } diff --git a/src/pkg/cli/client/provider.go b/src/pkg/cli/client/provider.go index d271443d0..a78cef6e1 100644 --- a/src/pkg/cli/client/provider.go +++ b/src/pkg/cli/client/provider.go @@ -89,7 +89,7 @@ type Provider interface { // Deprecated: should use stacks instead of ProjectName fallback. RemoteProjectName(context.Context) (string, error) SetCanIUseConfig(*defangv1.CanIUseResponse) - SetUpCD(context.Context) error + SetUpCD(context.Context, bool) error Subscribe(context.Context, *defangv1.SubscribeRequest) (iter.Seq2[*defangv1.SubscribeResponse, error], error) TearDownCD(context.Context) error } diff --git a/src/pkg/cli/install_cd.go b/src/pkg/cli/install_cd.go index 1ab26f4c4..6e2db7392 100644 --- a/src/pkg/cli/install_cd.go +++ b/src/pkg/cli/install_cd.go @@ -9,10 +9,10 @@ import ( "github.com/DefangLabs/defang/src/pkg/term" ) -func InstallCD(ctx context.Context, provider client.Provider) error { +func InstallCD(ctx context.Context, provider client.Provider, force bool) error { if dryrun.DoDryRun { return errors.New("dry run") } term.Info("Installing the CD resources into the cluster") - return provider.SetUpCD(ctx) + return provider.SetUpCD(ctx, force) } diff --git a/src/pkg/clouds/aws/ecs/cfn/setup.go b/src/pkg/clouds/aws/ecs/cfn/setup.go index f42c00965..a87c30eea 100644 --- a/src/pkg/clouds/aws/ecs/cfn/setup.go +++ b/src/pkg/clouds/aws/ecs/cfn/setup.go @@ -60,7 +60,7 @@ func (a *AwsEcsCfn) newClient(ctx context.Context) (*cloudformation.Client, erro return cloudformation.NewFromConfig(cfg), nil } -func (a *AwsEcsCfn) updateStackAndWait(ctx context.Context, templateBody string, parameters []cfnTypes.Parameter) error { +func (a *AwsEcsCfn) updateStackAndWait(ctx context.Context, templateBody string, force bool, parameters []cfnTypes.Parameter) error { cfn, err := a.newClient(ctx) if err != nil { return err @@ -72,7 +72,7 @@ func (a *AwsEcsCfn) updateStackAndWait(ctx context.Context, templateBody string, for _, output := range dso.Stacks[0].Outputs { if *output.OutputKey == OutputsTemplateVersion { deployedRev, _ := strconv.Atoi(*output.OutputValue) - if deployedRev > TemplateRevision { + if deployedRev > TemplateRevision && !force { return fmt.Errorf("This version of the CLI expects CloudFormation template v%d, but the deployed %s stack is v%d: please update the CLI", TemplateRevision, a.stackName, deployedRev) } } @@ -150,7 +150,7 @@ func (a *AwsEcsCfn) createStackAndWait(ctx context.Context, templateBody string, return a.fillWithOutputs(dso) } -func (a *AwsEcsCfn) SetUp(ctx context.Context, containers []clouds.Container) (bool, error) { +func (a *AwsEcsCfn) SetUp(ctx context.Context, containers []clouds.Container, force bool) (bool, error) { template, err := CreateTemplate(a.stackName, containers) if err != nil { return false, fmt.Errorf("failed to create CloudFormation template: %w", err) @@ -196,12 +196,12 @@ func (a *AwsEcsCfn) SetUp(ctx context.Context, containers []clouds.Container) (b } // TODO: support DOCKER_AUTH_CONFIG - return a.upsertStackAndWait(ctx, templateBody, parameters...) + return a.upsertStackAndWait(ctx, templateBody, force, parameters...) } -func (a *AwsEcsCfn) upsertStackAndWait(ctx context.Context, templateBody []byte, parameters ...cfnTypes.Parameter) (bool, error) { +func (a *AwsEcsCfn) upsertStackAndWait(ctx context.Context, templateBody []byte, force bool, parameters ...cfnTypes.Parameter) (bool, error) { // Upsert with parameters - if err := a.updateStackAndWait(ctx, string(templateBody), parameters); err != nil { + if err := a.updateStackAndWait(ctx, string(templateBody), force, parameters); err != nil { // Check if the stack doesn't exist; if so, create it, otherwise return the error var apiError smithy.APIError if ok := errors.As(err, &apiError); !ok || (apiError.ErrorCode() != "ValidationError") || !strings.HasSuffix(apiError.ErrorMessage(), "does not exist") { diff --git a/src/pkg/clouds/aws/ecs/cfn/setup_test.go b/src/pkg/clouds/aws/ecs/cfn/setup_test.go index b154a7977..970de7010 100644 --- a/src/pkg/clouds/aws/ecs/cfn/setup_test.go +++ b/src/pkg/clouds/aws/ecs/cfn/setup_test.go @@ -34,7 +34,7 @@ func TestCloudFormation(t *testing.T) { t.Setenv("DOCKERHUB_USERNAME", "defanglabs2") t.Setenv("DOCKERHUB_ACCESS_TOKEN", "defanglabs") - _, err := aws.SetUp(ctx, testContainers) + _, err := aws.SetUp(ctx, testContainers, false) if err != nil { t.Fatal(err) } diff --git a/src/pkg/clouds/driver.go b/src/pkg/clouds/driver.go index a4975e177..927cd5a6b 100644 --- a/src/pkg/clouds/driver.go +++ b/src/pkg/clouds/driver.go @@ -39,7 +39,7 @@ type TaskVolume struct { } type Driver interface { - SetUp(ctx context.Context, containers []Container) (bool, error) // returns true if newly created + SetUp(ctx context.Context, containers []Container, force bool) (bool, error) // returns true if newly created TearDown(ctx context.Context) error Run(ctx context.Context, env map[string]string, args ...string) (TaskID, error) Tail(ctx context.Context, taskID TaskID) error diff --git a/src/pkg/crun/common.go b/src/pkg/crun/common.go deleted file mode 100644 index 6473ce75b..000000000 --- a/src/pkg/crun/common.go +++ /dev/null @@ -1,81 +0,0 @@ -package crun - -import ( - "os" - "strconv" - "strings" - - "github.com/DefangLabs/defang/src/pkg/clouds/aws" - "github.com/DefangLabs/defang/src/pkg/term" -) - -type Region = aws.Region - -func ParseMemory(memory string) uint64 { - i := strings.IndexAny(memory, "BKMGIbkmgi") - var memUnit uint64 = 1 - if i > 0 { - switch strings.ToUpper(memory[i:]) { - default: - term.Fatal("invalid suffix: " + memory) - case "G", "GIB": - memUnit = 1024 * 1024 * 1024 - case "GB": - memUnit = 1000 * 1000 * 1000 - case "M", "MIB": - memUnit = 1024 * 1024 - case "MB": - memUnit = 1000 * 1000 - case "K", "KIB": - memUnit = 1024 - case "KB": - memUnit = 1000 - case "B": - } - memory = memory[:i] - } - memoryB, err := strconv.ParseUint(memory, 10, 64) - if err != nil { - term.Fatal(err.Error()) - } - return memoryB * memUnit -} - -func ParseEnvLine(line string) (key string, value string) { - parts := strings.SplitN(line, "=", 2) - key = strings.TrimSpace(parts[0]) // FIXME: docker only trims leading whitespace - if key == "" || key[0] == '#' { - return "", "" - } - if len(parts) == 1 { - var ok bool - if value, ok = os.LookupEnv(key); !ok { // exclude missing env vars, like docker does - return "", "" - } - } else { - value = parts[1] - } - return -} - -func parseEnvFile(content string, env map[string]string) map[string]string { - if env == nil { - env = make(map[string]string) - } - for _, line := range strings.Split(content, "\n") { - key, value := ParseEnvLine(strings.TrimSuffix(line, "\r")) // handle CRLF - if key == "" { - continue - } - env[key] = value - } - return env -} - -func ParseEnvFile(filename string, env map[string]string) (map[string]string, error) { - bytes, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - return parseEnvFile(string(bytes), env), nil -} diff --git a/src/pkg/crun/common_test.go b/src/pkg/crun/common_test.go deleted file mode 100644 index 00b0b565b..000000000 --- a/src/pkg/crun/common_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package crun - -import ( - "testing" -) - -func TestParseMemory(t *testing.T) { - testCases := []struct { - memory string - expected uint64 - }{ - { - memory: "5000000", - expected: 5000000, - }, - { - memory: "6MB", - expected: 6000000, - }, - { - memory: "7MiB", - expected: 7340032, - }, - { - memory: "8k", - expected: 8192, - }, - { - memory: "9gIb", - expected: 9663676416, - }, - } - for _, tC := range testCases { - t.Run(tC.memory, func(t *testing.T) { - actual := ParseMemory(tC.memory) - if actual != tC.expected { - t.Errorf("expected %v, got %v", tC.expected, actual) - } - }) - } -} - -func TestParseEnvLine(t *testing.T) { - const FROMENV = "blah" - t.Setenv("FROMENV", FROMENV) - - testCases := []struct { - line string - key string - value string - }{ - {}, // empty line - { - line: "# comment", - }, - { - line: " # comment with leading spaces", - }, - { - line: "key=value", - key: "key", - value: "value", - }, - { - line: "key = value", // docker errors on this, but we don't - key: "key", - value: " value", - }, - { - line: " key=value ", - key: "key", - value: "value ", - }, - { - line: "FROMENV", - key: "FROMENV", - value: FROMENV, - }, - { - line: "EMPTY=", - key: "EMPTY", - }, - { - line: "NONEXISTENT", - }, - } - for _, tC := range testCases { - t.Run(tC.line, func(t *testing.T) { - key, value := ParseEnvLine(tC.line) - if key != tC.key || value != tC.value { - t.Errorf("expected %v=%q, got %v=%q", tC.key, tC.value, key, value) - } - }) - } -} - -func TestParseEnvFile(t *testing.T) { - env := make(map[string]string) - parseEnvFile(`# comment -key=value -key2= value2 -# comment with leading space -key3=value3 # comment at end -key4=crlf`+"\r\n", env) - if len(env) != 4 { - t.Errorf("expected 4 env vars, got %v", len(env)) - } - if env["key"] != "value" { - t.Errorf("expected 'value', got %q", env["key"]) - } - if env["key2"] != " value2" { - t.Errorf("expected ' value2', got %q", env["key2"]) - } - if env["key3"] != "value3 # comment at end" { - t.Errorf("expected 'value3 # comment at end', got %q", env["key3"]) - } - if env["key4"] != "crlf" { - t.Errorf("expected 'crlf', got %q", env["key4"]) - } -} diff --git a/src/pkg/crun/destroy.go b/src/pkg/crun/destroy.go deleted file mode 100644 index 3261313ff..000000000 --- a/src/pkg/crun/destroy.go +++ /dev/null @@ -1,13 +0,0 @@ -package crun - -import ( - "context" -) - -func Destroy(ctx context.Context, region Region) error { - driver, err := createDriver(region) - if err != nil { - return err - } - return driver.TearDown(ctx) -} diff --git a/src/pkg/crun/docker/common.go b/src/pkg/crun/docker/common.go deleted file mode 100644 index c5384c1b8..000000000 --- a/src/pkg/crun/docker/common.go +++ /dev/null @@ -1,44 +0,0 @@ -package docker - -import ( - "context" - "errors" - - "github.com/DefangLabs/defang/src/pkg/clouds" - "github.com/docker/docker/client" -) - -type ContainerID = clouds.TaskID - -type Docker struct { - *client.Client - - image string - memory uint64 - platform string -} - -func New() *Docker { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - panic(err) - } - - return &Docker{ - Client: cli, - } -} - -var _ clouds.Driver = (*Docker)(nil) - -func (Docker) PutSecret(ctx context.Context, name, value string) error { - return errors.New("docker does not support secrets") -} - -func (Docker) ListSecrets(ctx context.Context) ([]string, error) { - return nil, errors.New("docker does not support secrets") -} - -func (Docker) CreateUploadURL(ctx context.Context, name string) (string, error) { - return "", errors.New("docker does not support uploads") -} diff --git a/src/pkg/crun/docker/info.go b/src/pkg/crun/docker/info.go deleted file mode 100644 index 7bcce7b01..000000000 --- a/src/pkg/crun/docker/info.go +++ /dev/null @@ -1,25 +0,0 @@ -package docker - -import ( - "context" - - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -func (d Docker) GetInfo(ctx context.Context, id ContainerID) (*clouds.TaskInfo, error) { - info, err := d.ContainerInspect(ctx, *id) - if err != nil { - return nil, err - } - - // b, _ := json.MarshalIndent(info, "", " ") - // println(string(b)) - - for _, mapping := range info.NetworkSettings.Ports { - // TODO: add port - // return "Host IP: " + mapping[0].HostIP + ":" + mapping[0].HostPort, nil - return &clouds.TaskInfo{IP: mapping[0].HostIP}, nil - } - - return nil, nil -} diff --git a/src/pkg/crun/docker/run.go b/src/pkg/crun/docker/run.go deleted file mode 100644 index de562146c..000000000 --- a/src/pkg/crun/docker/run.go +++ /dev/null @@ -1,58 +0,0 @@ -package docker - -import ( - "context" - "strings" - - "github.com/docker/docker/api/types/container" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -func (d Docker) Run(ctx context.Context, env map[string]string, cmd ...string) (ContainerID, error) { - resp, err := d.ContainerCreate(ctx, &container.Config{ - Image: d.image, - Env: mapToSlice(env), - Cmd: cmd, - }, &container.HostConfig{ - AutoRemove: true, // --rm; FIXME: this causes "No such container" if the container exits early - PublishAllPorts: true, // -P - Resources: container.Resources{ - Memory: int64(d.memory), // #nosec G115 - memory is expected to be a small number - }, - }, nil, parsePlatform(d.platform), "") - if err != nil { - return nil, err - } - - return &resp.ID, d.ContainerStart(ctx, resp.ID, container.StartOptions{}) -} - -func mapToSlice(m map[string]string) []string { - s := make([]string, 0, len(m)) - for k, v := range m { - // Ensure no = in key - if strings.ContainsRune(k, '=') { - panic("invalid environment variable key") - } - s = append(s, k+"="+v) - } - return s -} - -func parsePlatform(platform string) *v1.Platform { - parts := strings.Split(platform, "/") - var p = &v1.Platform{} - switch len(parts) { - case 3: - p.Variant = parts[2] - fallthrough - case 2: - p.OS = parts[0] - p.Architecture = parts[1] - case 1: - p.Architecture = parts[0] - default: - panic("invalid platform: " + platform) - } - return p -} diff --git a/src/pkg/crun/docker/run_test.go b/src/pkg/crun/docker/run_test.go deleted file mode 100644 index c9c6e926c..000000000 --- a/src/pkg/crun/docker/run_test.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build integration - -package docker - -import ( - "context" - "testing" - "time" - - "github.com/DefangLabs/defang/src/pkg/clouds" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -func TestRun(t *testing.T) { - if testing.Short() { - t.Skip("skipping Docker test") - } - - d := New() - - _, err := d.SetUp(context.Background(), []clouds.Container{{Image: "alpine:latest", Platform: d.platform}}) - if err != nil { - t.Fatal(err) - } - defer d.TearDown(context.Background()) - - id, err := d.Run(context.Background(), nil, "sh", "-c", "echo hello world") - if err != nil { - t.Fatal(err) - } - if id == nil || *id == "" { - t.Fatal("id is empty") - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - err = d.Tail(ctx, id) - if err != nil { - t.Fatal(err) - } -} - -func TestParsePlatform(t *testing.T) { - tdt := []struct { - platform string - expected v1.Platform - }{ - { - platform: "linux/amd64", - expected: v1.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - { - platform: "linux/arm64/v8", - expected: v1.Platform{ - Architecture: "arm64", - OS: "linux", - Variant: "v8", - }, - }, - { - platform: "linux/arm64", - expected: v1.Platform{ - Architecture: "arm64", - OS: "linux", - }, - }, - { - platform: "arm64", - expected: v1.Platform{ - Architecture: "arm64", - }, - }, - } - for _, tt := range tdt { - t.Run(tt.platform, func(t *testing.T) { - p := parsePlatform(tt.platform) - if p.Architecture != tt.expected.Architecture { - t.Errorf("expected architecture %q, got %q", tt.expected.Architecture, p.Architecture) - } - if p.OS != tt.expected.OS { - t.Errorf("expected OS %q, got %q", tt.expected.OS, p.OS) - } - if p.Variant != tt.expected.Variant { - t.Errorf("expected variant %q, got %q", tt.expected.Variant, p.Variant) - } - }) - } -} diff --git a/src/pkg/crun/docker/setup.go b/src/pkg/crun/docker/setup.go deleted file mode 100644 index 195f102f3..000000000 --- a/src/pkg/crun/docker/setup.go +++ /dev/null @@ -1,42 +0,0 @@ -package docker - -import ( - "context" - "errors" - "io" - "os" - - "github.com/DefangLabs/defang/src/pkg/clouds" - "github.com/docker/docker/api/types/image" -) - -func (d *Docker) SetUp(ctx context.Context, containers []clouds.Container) (bool, error) { - if len(containers) != 1 { - return false, errors.New("only one container is supported with docker driver") - } - task := containers[0] - rc, err := d.ImagePull(ctx, task.Image, image.PullOptions{Platform: task.Platform}) - if err != nil { - return false, err - } - defer rc.Close() - _, err = io.Copy(contextAwareWriter{ctx, os.Stderr}, rc) // FIXME: this outputs JSON to stderr - d.image = task.Image - d.memory = task.Memory - d.platform = task.Platform - return false, err -} - -type contextAwareWriter struct { - ctx context.Context - io.Writer -} - -func (cw contextAwareWriter) Write(p []byte) (n int, err error) { - select { - case <-cw.ctx.Done(): // Detect context cancelation - return 0, cw.ctx.Err() - default: - return cw.Writer.Write(p) - } -} diff --git a/src/pkg/crun/docker/stop.go b/src/pkg/crun/docker/stop.go deleted file mode 100644 index 790bb88bd..000000000 --- a/src/pkg/crun/docker/stop.go +++ /dev/null @@ -1,11 +0,0 @@ -package docker - -import ( - "context" - - "github.com/docker/docker/api/types/container" -) - -func (d Docker) Stop(ctx context.Context, id ContainerID) error { - return d.ContainerStop(ctx, *id, container.StopOptions{}) -} diff --git a/src/pkg/crun/docker/tail.go b/src/pkg/crun/docker/tail.go deleted file mode 100644 index f0165a4fd..000000000 --- a/src/pkg/crun/docker/tail.go +++ /dev/null @@ -1,23 +0,0 @@ -package docker - -import ( - "context" - "os" - - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/pkg/stdcopy" -) - -func (d Docker) Tail(ctx context.Context, id ContainerID) error { - rc, err := d.Client.ContainerLogs(ctx, *id, container.LogsOptions{ - Follow: true, - ShowStderr: true, - ShowStdout: true, - }) - if err != nil { - return err - } - defer rc.Close() - _, err = stdcopy.StdCopy(os.Stdout, os.Stderr, rc) - return err -} diff --git a/src/pkg/crun/docker/teardown.go b/src/pkg/crun/docker/teardown.go deleted file mode 100644 index bf3fb85bf..000000000 --- a/src/pkg/crun/docker/teardown.go +++ /dev/null @@ -1,11 +0,0 @@ -package docker - -import ( - "context" - - "github.com/DefangLabs/defang/src/pkg/cli/client" -) - -func (d Docker) TearDown(ctx context.Context) error { - return client.ErrNotImplemented("not implemented") -} diff --git a/src/pkg/crun/factory.go b/src/pkg/crun/factory.go deleted file mode 100644 index c2f35f528..000000000 --- a/src/pkg/crun/factory.go +++ /dev/null @@ -1,71 +0,0 @@ -package crun - -import ( - "fmt" - - "github.com/DefangLabs/defang/src/pkg" - "github.com/DefangLabs/defang/src/pkg/clouds" - "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs/cfn" - "github.com/DefangLabs/defang/src/pkg/clouds/aws/region" - "github.com/DefangLabs/defang/src/pkg/crun/docker" -) - -type DriverOption func(clouds.Driver) error - -func createDriver(reg Region, opts ...DriverOption) (clouds.Driver, error) { - var driver clouds.Driver - switch reg { - case "docker", "local", "": - driver = docker.New() - case - region.AFSouth1, // "af-south-1" - region.APEast1, // "ap-east-1" - region.APNortheast1, // "ap-northeast-1" - region.APNortheast2, // "ap-northeast-2" - region.APNortheast3, // "ap-northeast-3" - region.APSouth1, // "ap-south-1" - region.APSouth2, // "ap-south-2" - region.APSoutheast1, // "ap-southeast-1" - region.APSoutheast2, // "ap-southeast-2" - region.APSoutheast3, // "ap-southeast-3" - region.APSoutheast4, // "ap-southeast-4" - region.CACentral, // "ca-central-1" - region.CNNorth1, // "cn-north-1" - region.CNNorthwest1, // "cn-northwest-1" - region.EUCentral1, // "eu-central-1" - region.EUCentral2, // "eu-central-2" - region.EUNorth1, // "eu-north-1" - region.EUSouth1, // "eu-south-1" - region.EUSouth2, // "eu-south-2" - region.EUWest1, // "eu-west-1" - region.EUWest2, // "eu-west-2" - region.EUWest3, // "eu-west-3" - region.MECentral1, // "me-central-1" - region.MESouth1, // "me-south-1" - region.SAEast1, // "sa-east-1" - region.USGovEast1, // "us-gov-east-1" - region.USGovWest1, // "us-gov-west-1" - region.USEast1, // "us-east-1" - region.USEast2, // "us-east-2" - region.USWest1, // "us-west-1" - region.USWest2: // "us-west-2" - driver = cfn.New(stackName(pkg.GetCurrentUser()), reg) - default: - return nil, fmt.Errorf("unsupported region: %v", reg) - } - - for _, opt := range opts { - if err := opt(driver); err != nil { - return nil, err - } - } - - return driver, nil -} - -func stackName(stack string) string { - if stack == "" { - return clouds.ProjectName - } - return clouds.ProjectName + "-" + stack -} diff --git a/src/pkg/crun/info.go b/src/pkg/crun/info.go deleted file mode 100644 index eb2f69298..000000000 --- a/src/pkg/crun/info.go +++ /dev/null @@ -1,21 +0,0 @@ -package crun - -import ( - "context" - "fmt" - - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -func PrintInfo(ctx context.Context, region Region, id clouds.TaskID) error { - driver, err := createDriver(region) - if err != nil { - return err - } - info, err := driver.GetInfo(ctx, id) - if err != nil { - return err - } - fmt.Println("IP:", info.IP) - return nil -} diff --git a/src/pkg/crun/local/local.go b/src/pkg/crun/local/local.go deleted file mode 100644 index 9ec24a064..000000000 --- a/src/pkg/crun/local/local.go +++ /dev/null @@ -1,118 +0,0 @@ -//go:build !windows -// +build !windows - -package local - -import ( - "context" - "errors" - "io" - "os" - "os/exec" - "strconv" - "syscall" - - "github.com/DefangLabs/defang/src/pkg/cli/client" - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -type PID = clouds.TaskID - -type Local struct { - entrypoint []string - cmd *exec.Cmd - outReader io.ReadCloser - errReader io.ReadCloser - workDir string -} - -var _ clouds.Driver = (*Local)(nil) - -func New() *Local { - return &Local{} -} - -func (l *Local) SetUp(ctx context.Context, containers []clouds.Container) (bool, error) { - if len(containers) != 1 { - return false, errors.New("expected exactly one container") - } - l.entrypoint = containers[0].EntryPoint - l.workDir = containers[0].WorkDir - return false, nil -} - -func (l *Local) TearDown(ctx context.Context) error { - if l.cmd == nil { - return nil - } - // l.cmd.Process.Kill() - return l.cmd.Wait() -} - -func (l *Local) Run(ctx context.Context, env map[string]string, args ...string) (PID, error) { - if l.cmd != nil { - return nil, errors.New("already running") - } - args = append(l.entrypoint, args...) - // TODO - use enums to define commands instead of passing strings down from the caller - // #nosec G204 - cmd := exec.CommandContext(ctx, args[0], args[1:]...) - cmd.Dir = l.workDir - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - or, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - er, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - for k, v := range env { - cmd.Env = append(cmd.Env, k+"="+v) - } - if err := cmd.Start(); err != nil { - return nil, err - } - l.outReader = or - l.errReader = er - l.cmd = cmd - pid := strconv.Itoa(cmd.Process.Pid) - return &pid, nil -} - -func (l *Local) Tail(ctx context.Context, taskID PID) error { - if l.cmd == nil { - return errors.New("not running") - } - if strconv.Itoa(l.cmd.Process.Pid) != *taskID { - return errors.New("task ID does not match") - } - go io.Copy(os.Stderr, l.errReader) - _, err := io.Copy(os.Stdout, l.outReader) - os.Stderr.Close() // close stderr to stop the goroutine before returning - return err -} - -func (l *Local) Stop(ctx context.Context, taskID PID) error { - pid, err := strconv.Atoi(*taskID) - if err != nil { - return err - } - return syscall.Kill(-pid, syscall.SIGTERM) // negative pid kills the process group -} - -func (l *Local) GetInfo(ctx context.Context, taskID PID) (*clouds.TaskInfo, error) { - return nil, client.ErrNotImplemented("not implemented for local driver") -} - -func (l *Local) PutSecret(ctx context.Context, name, value string) error { - return client.ErrNotImplemented("not implemented for local driver") -} - -func (l *Local) ListSecrets(ctx context.Context) ([]string, error) { - return nil, client.ErrNotImplemented("not implemented for local driver") -} - -func (l *Local) CreateUploadURL(ctx context.Context, name string) (string, error) { - return "", client.ErrNotImplemented("not implemented for local driver") -} diff --git a/src/pkg/crun/local/local_test.go b/src/pkg/crun/local/local_test.go deleted file mode 100644 index d56bccea0..000000000 --- a/src/pkg/crun/local/local_test.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !windows -// +build !windows - -package local - -import ( - "context" - "testing" - "time" - - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -func TestLocal(t *testing.T) { - l := New() - ctx := t.Context() - - t.Run("SetUp", func(t *testing.T) { - if _, err := l.SetUp(ctx, []clouds.Container{{EntryPoint: []string{"/bin/sh"}}}); err != nil { - t.Fatal(err) - } - }) - defer l.TearDown(ctx) - - var pid PID - t.Run("Run", func(t *testing.T) { - env := map[string]string{"FOO": "bar"} - var err error - pid, err = l.Run(ctx, env, "-c", "sleep 1 ; echo $FOO") - if err != nil { - t.Fatal(err) - } - }) - - t.Run("Tail", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - // This should print "bar" to stdout - if err := l.Tail(ctx, pid); err != nil { - t.Error(err) - } - }) - - t.Run("TearDown", func(t *testing.T) { - if err := l.TearDown(ctx); err != nil { - t.Error(err) - } - }) -} diff --git a/src/pkg/crun/logs.go b/src/pkg/crun/logs.go deleted file mode 100644 index 97bb16305..000000000 --- a/src/pkg/crun/logs.go +++ /dev/null @@ -1,15 +0,0 @@ -package crun - -import ( - "context" - - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -func Logs(ctx context.Context, region Region, id clouds.TaskID) error { - driver, err := createDriver(region) - if err != nil { - return err - } - return driver.Tail(ctx, id) -} diff --git a/src/pkg/crun/run.go b/src/pkg/crun/run.go deleted file mode 100644 index c196d87ea..000000000 --- a/src/pkg/crun/run.go +++ /dev/null @@ -1,92 +0,0 @@ -package crun - -import ( - "context" - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "github.com/DefangLabs/defang/src/pkg/clouds" - "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs/cfn" -) - -type RunContainerArgs struct { - Region Region - Image string - Memory uint64 - Args []string - Env map[string]string - Platform string - VpcID string - SubnetID string -} - -var cleanup = make(chan func()) - -func init() { - go func() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - var clanupFns []func() - - for { - select { - case f := <-cleanup: - clanupFns = append(clanupFns, f) - case s := <-c: - fmt.Printf("Caught signal %v, cleaning up...\n", s) - for _, f := range clanupFns { - f() - } - os.Exit(0) - } - } - }() -} - -func Run(ctx context.Context, args RunContainerArgs) error { - driver, err := createDriver(args.Region, cfn.OptionVPCAndSubnetID(ctx, args.VpcID, args.SubnetID)) - if err != nil { // VPC affects the cloudformation template - return err - } - - containers := []clouds.Container{ - { - Image: args.Image, - Memory: args.Memory, - Platform: args.Platform, - }, - } - if _, err := driver.SetUp(ctx, containers); err != nil { - return err - } - - id, err := driver.Run(ctx, args.Env, args.Args...) - if err != nil { - return err - } - - cleanup <- func() { - fmt.Printf("Stopping task %s...\n", *id) - driver.Stop(ctx, id) - } - fmt.Println("Task ID:", *id) - - // Try 10 times to get the public IP address - for range 10 { - info, err := driver.GetInfo(ctx, id) - if err != nil { - time.Sleep(time.Second) - continue - } - if info != nil { - fmt.Println("IP:", info.IP) - } - break - } - - // FIXME: stop task on context cancelation/ctrl-c? - return driver.Tail(ctx, id) -} diff --git a/src/pkg/crun/stop.go b/src/pkg/crun/stop.go deleted file mode 100644 index 6c05b011a..000000000 --- a/src/pkg/crun/stop.go +++ /dev/null @@ -1,15 +0,0 @@ -package crun - -import ( - "context" - - "github.com/DefangLabs/defang/src/pkg/clouds" -) - -func Stop(ctx context.Context, region Region, id clouds.TaskID) error { - driver, err := createDriver(region) - if err != nil { - return err - } - return driver.Stop(ctx, id) -}