diff --git a/.github/workflows/check-sample.yml b/.github/workflows/check-sample.yml index d56b783d..5579a2b9 100644 --- a/.github/workflows/check-sample.yml +++ b/.github/workflows/check-sample.yml @@ -54,7 +54,7 @@ jobs: } - name: Install script dependencies - run: npm install + run: npm ci working-directory: scripts - name: Create / Update Template Repo diff --git a/.github/workflows/deploy-changed-samples.yml b/.github/workflows/deploy-changed-samples.yml index 3b22b503..e1ab0989 100644 --- a/.github/workflows/deploy-changed-samples.yml +++ b/.github/workflows/deploy-changed-samples.yml @@ -59,8 +59,8 @@ jobs: eval "$(curl -fsSL s.defang.io/install)" if: env.should_continue == 'true' - - name: Run tests - id: run-tests + - name: Deploy changed samples to staging + id: deploy-samples shell: bash # implies set -o pipefail, so pipe below will keep the exit code from loadtest if: env.should_continue == 'true' env: @@ -125,7 +125,7 @@ jobs: ./tools/testing/loadtest -c fabric-staging.defang.dev:443 --timeout=15m --concurrency=10 -s $SAMPLES -o output --markdown=true | tee output/summary.log | grep -v '^\s*[-*]' # removes load sample log lines - name: Upload Output as Artifact uses: actions/upload-artifact@v4 - if: env.should_continue == 'true' && (success() || steps.run-tests.outcome == 'failure') # Always upload result unless cancelled + if: env.should_continue == 'true' && (success() || steps.deploy-samples.outcome == 'failure') # Always upload result unless cancelled with: name: program-output path: output/** diff --git a/.github/workflows/publish-sample-template.yml b/.github/workflows/publish-sample-template.yml index 3df3d436..47f54194 100644 --- a/.github/workflows/publish-sample-template.yml +++ b/.github/workflows/publish-sample-template.yml @@ -26,7 +26,7 @@ jobs: cat modified.txt - name: Install script dependencies - run: npm install + run: npm ci working-directory: scripts - name: Create / Update Template Repo Main diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index da6a5f05..71db6873 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -30,7 +30,7 @@ jobs: node-version: "20" - name: Install dependencies - run: npm install + run: npm ci working-directory: scripts - name: Run tests @@ -52,7 +52,7 @@ jobs: node-version: "20" - name: Install dependencies - run: npm install + run: npm ci working-directory: scripts - name: Get changed samples diff --git a/.github/workflows/update-template-workflows.yml b/.github/workflows/update-template-workflows.yml index 90dee98d..b96b6264 100644 --- a/.github/workflows/update-template-workflows.yml +++ b/.github/workflows/update-template-workflows.yml @@ -23,7 +23,7 @@ jobs: node-version: "20" - name: Install dependencies - run: npm install + run: npm ci working-directory: scripts - name: Update Template Workflows diff --git a/samples/defang-provider-handoff/README.md b/samples/defang-provider-handoff/README.md new file mode 100644 index 00000000..51b936f4 --- /dev/null +++ b/samples/defang-provider-handoff/README.md @@ -0,0 +1,14 @@ +# Defang Provider Handoff Sample + +If you are using Defang to deploy your application into your customer's cloud accounts, you may want to provide a white-labeled static site that your customers can use to configure their cloud account for your deployment. This sample demonstrates how to do that. + +The `compose.yaml` file in this directory defines a single service, `app`, which serves a static site on port 80. The static site is built from the `./app` directory, which contains an `index.html` file that provides instructions for the customer on how to configure their cloud account for your deployment. + +--- + +Title: Defang Provider Handoff Sample + +Short Description: A sample application that demonstrates how to provide a white-labeled static site for customers to configure their cloud accounts for your deployment. + +Tags: Defang, Cloud, Deployment, Static Site +Languages: HTML, CSS, JavaScript diff --git a/samples/defang-provider-handoff/compose.yaml b/samples/defang-provider-handoff/compose.yaml index a3b3bc6a..24738267 100644 --- a/samples/defang-provider-handoff/compose.yaml +++ b/samples/defang-provider-handoff/compose.yaml @@ -1,3 +1,4 @@ +name: defang-provider-handoff services: app: restart: unless-stopped diff --git a/samples/html-css-js/app/.dockerignore b/samples/html-css-js/app/.dockerignore new file mode 100644 index 00000000..12f6b36d --- /dev/null +++ b/samples/html-css-js/app/.dockerignore @@ -0,0 +1,27 @@ +# Default .dockerignore file for Defang +**/__pycache__ +**/.direnv +**/.DS_Store +**/.envrc +**/.git +**/.github +**/.idea +**/.next +**/.vscode +**/compose.*.yaml +**/compose.*.yml +**/compose.yaml +**/compose.yml +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/node_modules +**/Thumbs.db +Dockerfile +*.Dockerfile +# Ignore our own binary, but only in the root to avoid ignoring subfolders +defang +defang.exe +# Ignore our project-level state +.defang* diff --git a/tools/testing/deployer/deployer.go b/tools/testing/deployer/deployer.go index 4b10b69d..9463beef 100644 --- a/tools/testing/deployer/deployer.go +++ b/tools/testing/deployer/deployer.go @@ -14,6 +14,7 @@ import ( "regexp" "strings" "time" + "encoding/json" "defang.io/tools/testing/detector" "defang.io/tools/testing/logger" @@ -207,15 +208,27 @@ func (d *CliDeployer) RunDeployTest(ctx context.Context, t test.TestInfo) (*test result.DeploySucceeded = cmd.ProcessState.Success() } - urls := findUrlsInOutput(d.Stdout.String()) - if len(urls) == 0 { - result.Message = "No service URLs found in deployment output" + // run `defang ps --json` to get the service URLs instead of parsing the output of compose up, as the output may not be stable and may change in the future + cmd, err = d.RunCommand(ctx, nil, "defang", "ps", "--json") + if err != nil { + result.Message = fmt.Sprintf("Failed to run `defang ps --json` to get service URLs: %v", err) + log.Printf(result.Message) + return result, fmt.Errorf(result.Message) + } + + services, err := parseTrailingServicesJSON(d.Stdout.Bytes(), log) + if err != nil { + result.Message = fmt.Sprintf("Failed to parse service URLs: %v", err) log.Printf(result.Message) return result, fmt.Errorf(result.Message) } - result.TotalServices = len(urls) - for _, url := range urls { + result.TotalServices = len(services) + for _, svc := range services { + if svc.Endpoint == "" { + continue + } + url := svc.Endpoint log.Printf(" * Testing service URL %v", url) code, err := testURL(context.Background(), log, url) // Still do a URL test if the test context is cancelledt if err == nil { @@ -241,6 +254,25 @@ func (d *CliDeployer) RunDeployTest(ctx context.Context, t test.TestInfo) (*test return result, nil } +type ServiceEndpoint struct { + Service string `json:"service"` + Endpoint string `json:"endpoint"` +} + +func parseTrailingServicesJSON(data []byte, log logger.Logger) ([]ServiceEndpoint, error) { + idx := bytes.LastIndex(data, []byte("[")) + if idx == -1 { + return nil, fmt.Errorf("no JSON array found in output") + } + + var services []ServiceEndpoint + if err := json.NewDecoder(bytes.NewReader(data[idx:])).Decode(&services); err != nil { + return nil, fmt.Errorf("Failed to decode `defang ps --json` output: %v", err) + } + + return services, nil +} + func testURL(ctx context.Context, logger logger.Logger, url string) (int, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil {