├── .gitignore ├── .github ├── workflows │ ├── templates │ │ ├── tos-not-found.md │ │ ├── tos-accepted.md │ │ ├── tos-not-accepted.md │ │ ├── validation-repository-not-found.md │ │ ├── validation-errored.md │ │ ├── validation-failed.md │ │ └── validation-succeeded.md │ ├── tos.yaml │ └── validation.yaml ├── actions-scripts │ ├── utils │ │ ├── hub.js │ │ ├── repository.js │ │ ├── repository.test.js │ │ └── hub.test.js │ └── sanitize-repository.js └── ISSUE_TEMPLATE │ ├── config.yml │ └── 1_automatic_review.yaml ├── jest.config.js ├── babel.config.json ├── test ├── issues │ ├── no_tos.json │ ├── no_repository.json │ ├── tos_not_accepted.json │ ├── tos_accepted.json │ └── repository_url.json ├── issue_comment │ ├── with_command.json │ ├── without_command.json │ └── with_command_on_closed_issue.json ├── README.md └── validation_outputs │ └── failed.txt ├── package.json ├── README.md ├── tests.bats └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.github/workflows/templates/tos-not-found.md: -------------------------------------------------------------------------------- 1 | :no_entry_sign: Checkbox for the terms of service not found. 2 | 3 | @docker/extensions, can you add it back to the issue body? 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default async () => { 2 | return { 3 | verbose: true, 4 | moduleDirectories: ['node_modules', '.github/actions-script'], 5 | }; 6 | }; -------------------------------------------------------------------------------- /.github/workflows/templates/tos-accepted.md: -------------------------------------------------------------------------------- 1 | :white_check_mark: @{{ .user }} has accepted the terms of service. 2 | 3 | The automatic validation of your extension has started, you will see the result in ~10 minutes :hourglass_flowing_sand:. 4 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /.github/workflows/templates/tos-not-accepted.md: -------------------------------------------------------------------------------- 1 | :x: Terms of Service not accepted yet. 2 | 3 | @{{ .user }} to proceed with the submission of your extension you must accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/). 4 | -------------------------------------------------------------------------------- /.github/workflows/templates/validation-repository-not-found.md: -------------------------------------------------------------------------------- 1 | :no_entry_sign: Docker Hub repository name not found. 2 | 3 | Please add the following section to the body of the issue with the extension name below it: 4 | 5 | ```md 6 | ### Docker Hub repository name 7 | ``` 8 | -------------------------------------------------------------------------------- /.github/workflows/templates/validation-errored.md: -------------------------------------------------------------------------------- 1 | :x: There was a problem during the validation job. 2 | 3 | Someone from the @docker/extensions will have to take a look at this issue. 4 | We will let you know in this issue when it is fixed. 5 | 6 | --- 7 | See [action]({{ .workflow_url }}) for more details. 8 | -------------------------------------------------------------------------------- /test/issues/no_tos.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "event_name": "issues", 4 | "action": "opened", 5 | "issue":{ 6 | "state": "open", 7 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension", 8 | "user": { 9 | "login": "benjaming" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/issues/no_repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "event_name": "issues", 4 | "action": "opened", 5 | "issue":{ 6 | "state": "open", 7 | "body": "### Terms of services\n\n- [x] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)", 8 | "user": { 9 | "login": "benjaming" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/issue_comment/with_command.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "action": "created", 4 | "comment": { 5 | "body": "/validate" 6 | }, 7 | "issue":{ 8 | "state": "open", 9 | "number": 1, 10 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension\n\n### Terms of services\n\n- [X] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)" 11 | } 12 | } -------------------------------------------------------------------------------- /test/issues/tos_not_accepted.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "event_name": "issues", 4 | "action": "opened", 5 | "issue":{ 6 | "state": "open", 7 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension\n\n### Terms of services\n\n- [ ] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)", 8 | "user": { 9 | "login": "benjaming" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/issue_comment/without_command.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "action": "created", 4 | "comment": { 5 | "body": "nothing should happen" 6 | }, 7 | "issue":{ 8 | "state": "open", 9 | "number": 1, 10 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension\n\n### Terms of services\n\n- [X] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)" 11 | } 12 | } -------------------------------------------------------------------------------- /test/issue_comment/with_command_on_closed_issue.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "action": "created", 4 | "comment": { 5 | "body": "/validate" 6 | }, 7 | "issue":{ 8 | "state": "closed", 9 | "number": 1, 10 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension\n\n### Terms of services\n\n- [X] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)" 11 | } 12 | } -------------------------------------------------------------------------------- /test/issues/tos_accepted.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "event_name": "issues", 4 | "action": "opened", 5 | "issue":{ 6 | "state": "open", 7 | "number": 1, 8 | "body": "### Docker Hub repository name\n\nbenjaming/extension-builder-extension\n\n### Terms of services\n\n- [X] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)", 9 | "user": { 10 | "login": "benjaming" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/issues/repository_url.json: -------------------------------------------------------------------------------- 1 | { 2 | "act": true, 3 | "event_name": "issues", 4 | "action": "opened", 5 | "issue":{ 6 | "state": "open", 7 | "number": 1, 8 | "body": "### Docker Hub repository name\n\nhttps://hub.docker.com/r/benjaming/extension-builder-extension\n\n### Terms of services\n\n- [X] I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/)", 9 | "user": { 10 | "login": "benjaming" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.github/workflows/templates/validation-failed.md: -------------------------------------------------------------------------------- 1 | :x: Validation failed with the following errors 2 | 3 | > {{ .validation_output }} 4 | 5 | Please fix the issues and check everything is ok locally with the following command: 6 | 7 | ```bash 8 | docker extension validate -a -s -i {{ .extension }} 9 | ``` 10 | 11 | Then you can trigger the validation commenting `/validate` when you are ready. 12 | 13 | See https://docs.docker.com/desktop/extensions-sdk/extensions/validate/ for more information. 14 | -------------------------------------------------------------------------------- /.github/workflows/templates/validation-succeeded.md: -------------------------------------------------------------------------------- 1 | :white_check_mark: The extension [{{ .extension }}]({{ .share_link }}) is valid :tada:. 2 | 3 | Now, @docker/extensions will authorise the publication of the extension to the marketplace. 4 | Once the extension is published, this issue will be closed. 5 | 6 | In the meantime, please tell us about your experience building a Docker Desktop extension here: https://docs.google.com/forms/d/e/1FAIpQLSdruIWGS9Em0p2jVIixQeiVyQJs27EzLA4ks2YzlBZpWUJC2Q/viewform. 7 | -------------------------------------------------------------------------------- /.github/actions-scripts/utils/hub.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Extract the repository name from a Docker Hub URL. 4 | * If the URL is not from Docker Hub it throws an error. 5 | * 6 | * @param url URL 7 | * @return string 8 | */ 9 | export function getRepoFromHubURL(url) { 10 | if (url.host !== "hub.docker.com") { 11 | throw "URL must be from hub.docker.com domain"; 12 | } 13 | 14 | if (url.pathname.indexOf('/r/') === -1) { 15 | throw "URL must be a repository URL from Docker Hub"; 16 | } 17 | 18 | return url.pathname.replace('/r/', ''); 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "license": "Apache-2.0", 4 | "scripts": { 5 | "build": "tsc", 6 | "test": "bats tests.bats", 7 | "test:unit": "jest" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.22.1", 11 | "@babel/preset-env": "^7.22.4", 12 | "babel-jest": "^29.5.0", 13 | "bats": "bats-core/bats-core#v1.9.0", 14 | "bats-assert": "bats-core/bats-assert#v2.1.0", 15 | "bats-support": "bats-core/bats-support#v0.3.0", 16 | "jest": "^29.5.0" 17 | }, 18 | "dependencies": { 19 | "@actions/core": "^1.10.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # How to test the workflows locally? 2 | 3 | ## Prerequisites 4 | 5 | You must install [act](https://github.com/nektos/act) 6 | 7 | ## Run the workflows locally 8 | 9 | The test folder contains json files for each scenario you want to test. 10 | You can run any of the scenarios by running the following command: 11 | 12 | ```bash 13 | act -e test/.json 14 | ``` 15 | 16 | ## Use tests.bats 17 | 18 | You can also run tests using [bats](https://github.com/bats-core/bats-core). 19 | 20 | ```node 21 | npm install 22 | GITHUB_TOKEN=$(gh auth token) npm run test 23 | ``` 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Share an extension idea 4 | url: https://github.com/docker/extension-ideas/discussions 5 | about: Have an idea for an extension? Share it with us! 6 | - name: Extension SDK issues 7 | url: https://github.com/docker/extensions-sdk 8 | about: For issues related to the extension SDK, please use the docker/extensions-sdk repository. 9 | - name: Extension SDK documentation issues 10 | url: https://github.com/docker/docs 11 | about: For issues related to the documentation, please use the docker/docs repository. 12 | -------------------------------------------------------------------------------- /.github/actions-scripts/utils/repository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the namespace/repository (e.g. docker/disk-usage-extension) from a 3 | * repository string. 4 | * 5 | * @param repository string 6 | * @returns string 7 | */ 8 | export function sanitize(repository) { 9 | if (repository.indexOf(' ') !== -1) { 10 | throw `Invalid repository name "${repository}"`; 11 | } 12 | 13 | const parts = repository.split('/'); 14 | if (parts.length !== 2) { 15 | throw `Invalid repository name "${repository}"`; 16 | } 17 | 18 | const [name, _] = parts[1].split(':'); 19 | 20 | return `${parts[0]}/${name}`; 21 | } -------------------------------------------------------------------------------- /.github/actions-scripts/utils/repository.test.js: -------------------------------------------------------------------------------- 1 | import {sanitize} from "./repository.js"; 2 | 3 | describe('repository utils', () => { 4 | it('should sanitize a repository name', () => { 5 | expect(sanitize('docker/disk-usage-extension')).toBe('docker/disk-usage-extension'); 6 | expect(sanitize('docker/disk-usage-extension:0.0.0')).toBe('docker/disk-usage-extension'); 7 | }); 8 | 9 | it('should throw when sanitizing an invalid repository name', () => { 10 | expect(() => sanitize('invalid repository name')).toThrow(); 11 | expect(() => sanitize('invalid/repository/name')).toThrow(); 12 | }); 13 | }); -------------------------------------------------------------------------------- /.github/actions-scripts/utils/hub.test.js: -------------------------------------------------------------------------------- 1 | import { getRepoFromHubURL } from './hub'; 2 | 3 | describe('hub utils', () => { 4 | it('should find the repository name from a hub url', () => { 5 | const url = new URL('https://hub.docker.com/r/username/repository'); 6 | const name = getRepoFromHubURL(url); 7 | expect(name).toBe('username/repository'); 8 | }); 9 | 10 | it('should throw if the URL is not a repository url', () => { 11 | const url = new URL('https://hub.docker.com/invalid-url'); 12 | expect(() => getRepoFromHubURL(url)).toThrow(); 13 | }); 14 | 15 | it('should throw if the URL is not from Docker Hub', () => { 16 | const url = new URL('https://hub.elsewhere.com/r/username/repository'); 17 | expect(() => getRepoFromHubURL(url)).toThrow(); 18 | }); 19 | }); -------------------------------------------------------------------------------- /.github/actions-scripts/sanitize-repository.js: -------------------------------------------------------------------------------- 1 | import {setFailed, setOutput} from '@actions/core'; 2 | import { getRepoFromHubURL } from './utils/hub.js'; 3 | import { sanitize } from './utils/repository.js'; 4 | 5 | function run() { 6 | let { REPOSITORY: repository } = process.env 7 | if (!repository) throw new Error('REPOSITORY env var is not set') 8 | 9 | if (repository.includes('http')) { 10 | try { 11 | // When parsed the url will be like 12 | const url = new URL(repository.replace(/[<>]/g, '')); 13 | repository = getRepoFromHubURL(url); 14 | } catch (e) { 15 | setFailed(e); 16 | 17 | return; 18 | } 19 | } 20 | 21 | try { 22 | repository = sanitize(repository) 23 | } catch (e) { 24 | setFailed(e); 25 | 26 | return; 27 | } 28 | 29 | setOutput('repository', repository); 30 | } 31 | 32 | run(); 33 | 34 | -------------------------------------------------------------------------------- /test/validation_outputs/failed.txt: -------------------------------------------------------------------------------- 1 | level=info msg="Tag not specified, greatest semver tag resolved: 0.0.9" 2 | level=info msg="Pulled image" 3 | level=info msg="Checked image labels" 4 | level=error msg="(required) image label "com.docker.desktop.extension.icon" is missing" 5 | level=error msg="(required) image label "com.docker.extension.categories" is missing" 6 | Image labels check failed. Learn more at: https://docs.docker.com/desktop/extensions-sdk/extensions/labels/ 7 | level=info msg="Checked metadata.json" 8 | level=info msg="Checked image tag is semver" 9 | level=info msg="Checked image is multiplatform" 10 | level=info msg="Checked extension is compatible with current SDK version" 11 | level=error msg="(required) image label "com.docker.desktop.extension.api.version" ("0.3.0") is incompatible with current SDK version "0.3.3". Consider changing it to ">=0.3.3"" 12 | level=info msg="Installed extension" 13 | level=info msg="Removed extension" 14 | The extension "benjaming/hey-moby-extension:0.0.9" hasn't passed the checks to be published. 15 | Please fix the errors above and run the validate command again. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_automatic_review.yaml: -------------------------------------------------------------------------------- 1 | name: Submit to extension marketplace 2 | description: Submit your extension to be automatically validated and published on the marketplace. 3 | title: "[Submission]: " 4 | assignees: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for creating an extension and submitting it to our marketplace! 10 | - type: input 11 | attributes: 12 | label: Docker Hub repository name 13 | description: Provide the Docker Hub repository name for your extension. 14 | placeholder: docker/resource-usage-extension 15 | validations: 16 | required: true 17 | - type: markdown 18 | attributes: 19 | value: | 20 | :warning: We automatically validate the greatest [semver](https://semver.org/) tag available on Docker Hub. See [the documentation](https://docs.docker.com/desktop/extensions-sdk/extensions/DISTRIBUTION/#release-your-extension) for more details. 21 | - type: checkboxes 22 | attributes: 23 | label: Terms of services 24 | options: 25 | - label: I accept the [term of services](https://www.docker.com/legal/extensions_marketplace_developer_agreement/) 26 | validations: 27 | required: true 28 | - type: markdown 29 | attributes: 30 | value: | 31 | Once you have filled the form and submitted this issue, we will run automated validation on the extension. 32 | 33 | :+1: If everything goes well, a new label `validation/succeeded` will be added and the @docker/extensions will authorise the publication of the extension to the marketplace. 34 | 35 | :confused: If there are issues, you will see details in comments on the issue and information about how to fix them and re-run validation. 36 | 37 | If needed, we will use the email address associated with your Docker Hub account for any communication related to your extension. 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker Extensions Submissions 2 | 3 | ## 👋 Welcome! 4 | 5 | You have created a Docker Extension and now want to make it available to every Docker Desktop users? Great! This is the right place to submit your extension. 6 | 7 | Maybe you are not ready yet but still are seeking feedback? Feel free to use the Show and tell category to share it with the community. 8 | 9 | If you haven't created an extension yet, you can find more information on how to do it [here](https://docs.docker.com/desktop/extensions-sdk/). 10 | 11 | ## How to submit your extension 12 | 13 | To submit your extension you need to create a new issue in this repository. Once the issue submitted, the extension 14 | will be automatically validated. If the validation is successful, someone from the @docker/extensions team will add 15 | a label and, some time later, your extension will be published and available in the Marketplace to all Docker Desktop 16 | users. 17 | 18 | Under the hood, the validation is done by a GitHub Action. Essentially, it runs the `docker extension validate` 19 | command. To speed up the process and avoid failures, we recommend you to run this command locally before submitting your extension. 20 | 21 | ## How my extension can be reviewed 22 | 23 | If you want your extension to be listed as reviewed in the Marketplace, you need to fill the form [here](https://www.docker.com/products/extensions/submissions/). 24 | Note that the review process is significantly longer. 25 | 26 | ## My extension is marked as published but not yet visible on the marketplace 27 | 28 | The Marketplace in Docker Desktop is updated when starting and every 12 hours. To force the update, you can restart Docker Desktop. 29 | 30 | ## My extension is marked as `validation/succeeded` but not published yet? 31 | 32 | For the moment, a member of the @docker/extensions team needs to manually add the `publish/ready` label to your 33 | issue in order to be published automatically. -------------------------------------------------------------------------------- /tests.bats: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | setup() { 3 | load 'node_modules/bats-support/load' 4 | load 'node_modules/bats-assert/load' 5 | } 6 | 7 | 8 | @test "tos accepted - validation succeeded" { 9 | run act issues -e test/issues/tos_accepted.json \ 10 | --container-architecture linux/amd64 \ 11 | -s GITHUB_TOKEN="$GITHUB_TOKEN" \ 12 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 13 | 14 | assert_line --regexp '^\[TOS Check\/Post a comment to new issues[[:blank:]]*\] 🏁 Job succeeded$' 15 | assert_line --regexp '^\[TOS Check\/tos-accepted[[:blank:]]*\] ✅ Success - Main Render template' 16 | assert_line --regexp '^\[Extension validation\/Extension validation\/parse-issue[[:blank:]]*\] ✅ Success - Main Find extension repository$' 17 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-succeeded[[:blank:]]*\] ✅ Success - Main Render template$' 18 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-succeeded[[:blank:]]*\] 🏁 Job succeeded$' 19 | refute_line --partial 'validation-failed' 20 | refute_line --partial 'validation-error' 21 | assert_success 22 | } 23 | 24 | @test "tos accepted - validation succeeded with URL as repository" { 25 | run act issues -e test/issues/repository_url.json \ 26 | --container-architecture linux/amd64 \ 27 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 28 | 29 | assert_line --regexp '^\[TOS Check\/Post a comment to new issues[[:blank:]]*\] 🏁 Job succeeded$' 30 | assert_line --regexp '^\[TOS Check\/tos-accepted[[:blank:]]*\] ✅ Success - Main Render template' 31 | assert_line --regexp '^\[Extension validation\/Extension validation\/parse-issue[[:blank:]]*\] ✅ Success - Main Find extension repository$' 32 | assert_line --regexp '^\[Extension validation\/Extension validation\/parse-issue[[:blank:]]*\] ✅ Success - Main Validate repository$' 33 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-succeeded[[:blank:]]*\] ✅ Success - Main Render template$' 34 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-succeeded[[:blank:]]*\] 🏁 Job succeeded$' 35 | refute_line --partial 'validation-failed' 36 | refute_line --partial 'validation-error' 37 | assert_success 38 | } 39 | 40 | @test "tos accepted - validation failed" { 41 | run act issues -e test/issues/tos_accepted.json \ 42 | --env TEST_VALIDATION_FAILED=true \ 43 | --container-architecture linux/amd64 \ 44 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 45 | 46 | assert_line --regexp '^\[TOS Check\/tos-accepted[[:blank:]]*\] ✅ Success - Main Render template$' 47 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-failed[[:blank:]]*\] ✅ Success - Main Render template$' 48 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-failed[[:blank:]]*\] ❌ Failure - Main Mark job as failed$' 49 | refute_line --partial 'validation-succeeded' 50 | refute_line --partial 'validation-error' 51 | assert_failure 52 | } 53 | 54 | @test "tos accepted - validation errored" { 55 | run act issues -e test/issues/tos_accepted.json \ 56 | --env TEST_VALIDATION_ERRORED=true \ 57 | --container-architecture linux/amd64 \ 58 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 59 | 60 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-errored[[:blank:]]*\] ✅ Success - Main Render template$' 61 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-errored[[:blank:]]*\] ❌ Failure - Main Mark job as failed$' 62 | refute_line --partial 'validation-succeeded' 63 | refute_line --partial 'validation-failed' 64 | assert_failure 65 | } 66 | 67 | @test "tos not accepted - no validation" { 68 | run act issues -e test/issues/tos_not_accepted.json \ 69 | --container-architecture linux/amd64 \ 70 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 71 | 72 | assert_line --regexp '^\[TOS Check\/tos-not-accepted[[:blank:]]*\] ✅ Success - Main Render template' 73 | assert_line --regexp '^\[TOS Check\/tos-not-accepted[[:blank:]]*\] 🏁 Job succeeded' 74 | refute_line --regexp '^\[Extension validation.*' 75 | assert_success 76 | } 77 | 78 | @test "tos missing - no validation" { 79 | run act issues -e test/issues/no_tos.json \ 80 | --container-architecture linux/amd64 \ 81 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 82 | 83 | assert_line --regexp '^\[TOS Check\/Ensure Terms of Service are accepted[[:blank:]]*\] ❌ Failure - Main Validate tos$' 84 | assert_line --regexp '^\[TOS Check\/tos-not-found[[:blank:]]*\] ❌ Failure - Main Mark job as failed' 85 | assert_failure 86 | } 87 | 88 | @test "tos accepted - no repository - no validation" { 89 | run act issues -e test/issues/no_repository.json \ 90 | --container-architecture linux/amd64 \ 91 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 92 | 93 | assert_line --regexp '^\[Extension validation\/Extension validation\/parse-issue[[:blank:]]*\] ❌ Failure - Main Ensure repository is filled$' 94 | assert_line --regexp '^\[Extension validation\/Extension validation\/error[[:blank:]]*\] ❌ Failure - Main Mark job as failed$' 95 | assert_failure 96 | } 97 | 98 | @test "/validate - validate succeeded" { 99 | run act issue_comment -e test/issue_comment/with_command.json \ 100 | --container-architecture linux/amd64 \ 101 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 102 | 103 | assert_line --regexp '^\[TOS Check.* ✅ Success - Main Run /validate command$' 104 | assert_line --regexp '^\[Extension validation\/Extension validation\/validation-succeeded[[:blank:]]*\] 🏁 Job succeeded$' 105 | refute_line --partial 'validation-failed' 106 | refute_line --partial 'validation-error' 107 | assert_success 108 | } 109 | 110 | @test "/validate on a closed issue - no tos check - no validation" { 111 | run act issue_comment -e test/issue_comment/with_command_on_closed_issue.json \ 112 | --container-architecture linux/amd64 \ 113 | -s GITHUB_TOKEN="$GITHUB_TOKEN" 114 | 115 | refute_output 116 | assert_success 117 | } 118 | 119 | @test "comment without /validate - no validation" { 120 | run act issue_comment -e test/issue_comment/without_command.json \ 121 | --container-architecture linux/amd64 122 | 123 | assert_line --regexp '^\[TOS Check.* ❌ Failure - Main Run /validate command$' 124 | assert_failure 125 | } 126 | -------------------------------------------------------------------------------- /.github/workflows/tos.yaml: -------------------------------------------------------------------------------- 1 | name: TOS Check 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | - edited 9 | issue_comment: 10 | types: 11 | - created 12 | - edited 13 | 14 | env: 15 | GH_TOKEN: ${{ github.token }} 16 | GH_REPO: ${{ github.repository }} 17 | 18 | jobs: 19 | react-to-new-issue: 20 | name: Post a comment to new issues 21 | if: github.event_name == 'issues' && github.event.action == 'opened' 22 | runs-on: ubuntu-latest 23 | outputs: 24 | comment-id: ${{ steps.comment.outputs.comment-id }} 25 | steps: 26 | - name: Add comment 27 | if: env.ACT == false 28 | id: comment 29 | uses: peter-evans/create-or-update-comment@v3 30 | with: 31 | issue-number: ${{ github.event.issue.number }} 32 | body: | 33 | Thank you for submitting your extension. 34 | 35 | The validation of your submission will start soon. 36 | 37 | is-revalidation: 38 | if: github.event.issue.state == 'open' && github.event_name == 'issue_comment' 39 | name: Verify if the comment is a revalidation request 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Run /validate command 43 | env: 44 | COMMENT_BODY: ${{ github.event.comment.body }} 45 | run: | 46 | command=$(echo "$COMMENT_BODY" | head -1) 47 | if [[ $command != "/validate"* ]]; then 48 | echo "No /validate command found in first line of the comment \"${command}\", skipping" >> $GITHUB_STEP_SUMMARY 49 | exit 1 50 | fi 51 | - name: Add reactions 52 | if: env.ACT == false 53 | uses: peter-evans/create-or-update-comment@v3 54 | with: 55 | comment-id: ${{ github.event.comment.id }} 56 | reactions: '+1' 57 | 58 | parse-issue: 59 | needs: [ react-to-new-issue, is-revalidation ] 60 | if: failure() == false && github.event.issue.state == 'open' 61 | name: Ensure Terms of Service are accepted 62 | runs-on: ubuntu-latest 63 | outputs: 64 | accepted: ${{ steps.set-output.outputs.accepted }} 65 | steps: 66 | - name: Parse issue body 67 | id: parse 68 | uses: zentered/issue-forms-body-parser@v2.2.0 69 | 70 | - name: Find TOS checkbox state 71 | id: set-output 72 | env: 73 | JSON_DATA: ${{ steps.parse.outputs.data }} 74 | run: | 75 | accepted=$(echo $JSON_DATA | jq -r '.["terms-of-services"].list[0].checked') 76 | echo "accepted=$accepted" >> $GITHUB_OUTPUT 77 | 78 | - name: Validate tos 79 | if: steps.set-output.outputs.accepted == 'null' 80 | env: 81 | PARSED_DATA: ${{ steps.parse.outputs.data }} 82 | run: | 83 | # Check if $GITHUB_STEP_SUMMARY exist to avoid the following lines to error when running with act 84 | if [[ -w "$GITHUB_STEP_SUMMARY" ]]; then 85 | echo "No checkbox for the tos found in the body of the issue ${{ inputs.issue_number }}" >> $GITHUB_STEP_SUMMARY 86 | echo "Is the \"terms-of-services\" field present?" >> $GITHUB_STEP_SUMMARY 87 | echo $PARSED_DATA >> $GITHUB_STEP_SUMMARY 88 | fi 89 | exit 1 90 | 91 | tos-not-accepted: 92 | runs-on: ubuntu-latest 93 | needs: [ react-to-new-issue, parse-issue ] 94 | if: always() && needs.parse-issue.outputs.accepted == 'false' 95 | steps: 96 | - uses: actions/checkout@v4 97 | 98 | - name: Render template 99 | id: render 100 | uses: chuhlomin/render-template@v1.8 101 | with: 102 | template: .github/workflows/templates/tos-not-accepted.md 103 | vars: | 104 | user: ${{ github.event.issue.user.login}} 105 | 106 | - name: Add comment 107 | if: env.ACT == false 108 | uses: peter-evans/create-or-update-comment@v3 109 | with: 110 | comment-id: ${{ needs.react-to-new-issue.outputs.comment-id }} 111 | edit-mode: replace 112 | issue-number: ${{ github.event.issue.number }} 113 | body: ${{ steps.render.outputs.result }} 114 | 115 | - name: Remove 'tos/accepted' label 116 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'tos/accepted') 117 | run: gh issue edit ${{ github.event.issue.number }} --remove-label tos/accepted 118 | 119 | - name: Add 'tos/not-accepted' label 120 | if: env.ACT == false && !contains(github.event.issue.labels.*.name, 'tos/not-accepted') 121 | run: gh issue edit ${{ github.event.issue.number }} --add-label tos/not-accepted 122 | 123 | tos-accepted: 124 | runs-on: ubuntu-latest 125 | needs: [ react-to-new-issue, parse-issue ] 126 | if: failure() == false && needs.parse-issue.outputs.accepted == 'true' && !contains(github.event.issue.labels.*.name, 'tos/accepted') 127 | steps: 128 | - uses: actions/checkout@v4 129 | 130 | - name: Render template 131 | id: render 132 | uses: chuhlomin/render-template@v1.8 133 | with: 134 | template: .github/workflows/templates/tos-accepted.md 135 | vars: | 136 | user: ${{ github.event.issue.user.login}} 137 | 138 | - name: Add comment 139 | if: env.ACT == false 140 | uses: peter-evans/create-or-update-comment@v3 141 | with: 142 | comment-id: ${{ needs.react-to-new-issue.outputs.comment-id }} 143 | edit-mode: replace 144 | issue-number: ${{ github.event.issue.number }} 145 | body: ${{ steps.render.outputs.result }} 146 | 147 | - name: Remove 'tos/not-accepted' label 148 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'tos/not-accepted') 149 | run: gh issue edit ${{ github.event.issue.number }} --remove-label tos/not-accepted 150 | 151 | - name: Add 'tos/accepted' label 152 | if: env.ACT == false 153 | run: gh issue edit ${{ github.event.issue.number }} --add-label tos/accepted 154 | 155 | tos-not-found: 156 | runs-on: ubuntu-latest 157 | needs: [ react-to-new-issue, parse-issue ] 158 | if: always() && needs.parse-issue.outputs.accepted == 'null' 159 | steps: 160 | - uses: actions/checkout@v4 161 | 162 | - name: Render template when tos are not found 163 | id: render 164 | uses: chuhlomin/render-template@v1.8 165 | with: 166 | template: .github/workflows/templates/tos-not-found.md 167 | 168 | - name: Add comment if tos not found 169 | if: env.ACT == false 170 | uses: peter-evans/create-or-update-comment@v3 171 | with: 172 | comment-id: ${{ needs.react-to-new-issue.outputs.comment-id }} 173 | edit-mode: replace 174 | issue-number: ${{ github.event.issue.number }} 175 | body: ${{ steps.render.outputs.result }} 176 | 177 | - name: Mark job as failed 178 | run: exit 1 179 | 180 | validate: 181 | name: Extension validation 182 | needs: parse-issue 183 | if: always() && needs.parse-issue.outputs.accepted == 'true' 184 | uses: ./.github/workflows/validation.yaml 185 | -------------------------------------------------------------------------------- /.github/workflows/validation.yaml: -------------------------------------------------------------------------------- 1 | name: Extension validation 2 | 3 | on: 4 | workflow_call: 5 | 6 | env: 7 | GH_TOKEN: ${{ github.token }} 8 | GH_REPO: ${{ github.repository }} 9 | 10 | jobs: 11 | parse-issue: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | repository: ${{ steps.validate.outputs.repository }} 15 | steps: 16 | - name: Parse issue body 17 | id: parse 18 | uses: zentered/issue-forms-body-parser@v2.2.0 19 | 20 | - name: Find extension repository 21 | id: set-output 22 | env: 23 | JSON_DATA: ${{ steps.parse.outputs.data }} 24 | run: | 25 | repository=$(echo $JSON_DATA | jq -r '.["docker-hub-repository-name"].text' ) 26 | echo "repository=$repository" >> $GITHUB_OUTPUT 27 | 28 | - name: Ensure repository is filled 29 | if: steps.set-output.outputs.repository == 'null' 30 | env: 31 | PARSED_DATA: ${{ steps.parse.outputs.data }} 32 | run: | 33 | # Check if $GITHUB_STEP_SUMMARY exist to avoid the following lines to error when running with act 34 | if [[ -w "$GITHUB_STEP_SUMMARY" ]]; then 35 | echo "No repository found in the body of the issue ${{ inputs.issue_number }}" >> $GITHUB_STEP_SUMMARY 36 | echo "Is the \"docker-hub-repository-name\" field present?" >> $GITHUB_STEP_SUMMARY 37 | echo $PARSED_DATA >> $GITHUB_STEP_SUMMARY 38 | fi 39 | exit 1 40 | 41 | - uses: actions/checkout@v4 42 | - uses: actions/setup-node@v3 43 | with: 44 | node-version: 20 45 | cache: npm 46 | 47 | - name: Install dependencies 48 | run: npm ci 49 | 50 | - name: Validate repository 51 | id: validate 52 | env: 53 | REPOSITORY: ${{ steps.set-output.outputs.repository }} 54 | run: node .github/actions-scripts/sanitize-repository.js 55 | 56 | error: 57 | runs-on: ubuntu-latest 58 | needs: parse-issue 59 | if: failure() 60 | steps: 61 | - uses: actions/checkout@v4 62 | 63 | - name: Render template 64 | id: render 65 | uses: chuhlomin/render-template@v1.8 66 | with: 67 | template: .github/workflows/templates/validation-repository-not-found.md 68 | vars: | 69 | workflow_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 70 | 71 | - name: Add comment 72 | if: env.ACT == false # do not comment when running with act 73 | uses: peter-evans/create-or-update-comment@v3 74 | with: 75 | issue-number: ${{ github.event.issue.number }} 76 | body: ${{ steps.render.outputs.result }} 77 | 78 | - name: Mark job as failed 79 | run: exit 1 80 | 81 | noop-validate: 82 | if: github.event.act == true 83 | runs-on: ubuntu-latest 84 | needs: parse-issue 85 | outputs: 86 | validation_output: ${{ steps.set-output.outputs.validation_output || '' }} 87 | steps: 88 | - uses: actions/checkout@v4 89 | - id: set-output 90 | run: | 91 | delimiter="$(openssl rand -hex 8)" 92 | 93 | if [[ ${TEST_VALIDATION_FAILED} ]]; then 94 | echo "validation_output<<${delimiter}" >> $GITHUB_OUTPUT 95 | echo "$(cat test/validation_outputs/failed.txt)" >> $GITHUB_OUTPUT 96 | echo "${delimiter}" >> $GITHUB_OUTPUT 97 | 98 | exit 1 99 | fi 100 | 101 | if [[ ${TEST_VALIDATION_ERRORED} ]]; then 102 | # Do not set the validation_output variable on failure 103 | exit 1 104 | fi 105 | 106 | validate: 107 | if: github.event.act == false 108 | runs-on: ubuntu-latest 109 | needs: parse-issue 110 | environment: production 111 | outputs: 112 | validation_output: ${{ steps.set-output.outputs.validation_output }} 113 | share_link: ${{ steps.share-link.outputs.share-link }} 114 | steps: 115 | - if: ${{ vars.DOCKER_DESKTOP_BUILD_URL != '' }} 116 | run: echo "Docker Desktop build URL ${{ vars.DOCKER_DESKTOP_BUILD_URL }}" >> $GITHUB_STEP_SUMMARY 117 | 118 | - uses: docker/desktop-action/start@v0.3.6 119 | with: 120 | docker-desktop-build-url: ${{ vars.DOCKER_DESKTOP_BUILD_URL || 'latest' }} 121 | 122 | - name: Change Desktop settings and allow installing non marketplace extensions 123 | run: | 124 | curl --unix-socket ~/.docker/desktop/backend.sock --data '{"onlyMarketplaceExtensions": false}' -H "content-type: application/json" -X POST http://localhost/app/settings 125 | 126 | - name: Validate extension 127 | id: validate 128 | continue-on-error: true 129 | env: 130 | REPOSITORY: ${{ needs.parse-issue.outputs.repository }} 131 | run: | 132 | touch output.txt 133 | docker extension validate --auto-resolve-tag --errors-only --sdk-compatibility --validate-install-uninstall $REPOSITORY &> output.txt 134 | 135 | - name: Read validation output 136 | id: set-output 137 | run: | 138 | delimiter="$(openssl rand -hex 8)" 139 | echo "validation_output<<${delimiter}" >> $GITHUB_OUTPUT 140 | echo "$(cat output.txt)" >> $GITHUB_OUTPUT 141 | echo "${delimiter}" >> $GITHUB_OUTPUT 142 | 143 | - name: Mark job as failed 144 | if: steps.validate.outcome != 'success' 145 | run: exit 1 146 | 147 | - name: Generate share link 148 | id: share-link 149 | env: 150 | REPOSITORY: ${{ needs.parse-issue.outputs.repository }} 151 | run: | 152 | echo "share-link=$(docker extension share $REPOSITORY)" >> $GITHUB_OUTPUT 153 | 154 | validation-succeeded: 155 | runs-on: ubuntu-latest 156 | needs: [parse-issue, noop-validate, validate] 157 | if: always() && (needs.validate.result == 'success' || needs.noop-validate.result == 'success') 158 | steps: 159 | - uses: actions/checkout@v4 160 | 161 | - name: Render template 162 | id: render 163 | uses: chuhlomin/render-template@v1.8 164 | with: 165 | template: .github/workflows/templates/validation-succeeded.md 166 | vars: | 167 | extension: ${{ needs.parse-issue.outputs.repository }} 168 | share_link: ${{ needs.validate.outputs.share_link }} 169 | 170 | - name: Add Comment 171 | if: env.ACT == false 172 | uses: peter-evans/create-or-update-comment@v3 173 | with: 174 | issue-number: ${{ github.event.issue.number }} 175 | body: ${{ steps.render.outputs.result }} 176 | 177 | - name: Remove label docker/validation-errored 178 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'docker/validation-errored') 179 | run: gh issue edit ${{ github.event.issue.number }} --remove-label "docker/validation-errored" 180 | 181 | - name: Remove label validation/failed 182 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'validation/failed') 183 | run: gh issue edit ${{ github.event.issue.number }} --remove-label "validation/failed" 184 | 185 | - name: Add label validation/succeeded 186 | if: env.ACT == false && !contains(github.event.issue.labels.*.name, 'validation/succeeded') 187 | run: gh issue edit ${{ github.event.issue.number }} --add-label "validation/succeeded" 188 | 189 | validation-failed: 190 | runs-on: ubuntu-latest 191 | needs: [parse-issue, noop-validate, validate] 192 | if: | 193 | always() && 194 | (needs.validate.result == 'failure' && needs.validate.outputs.validation_output != '') || 195 | (needs.noop-validate.result == 'failure' && needs.noop-validate.outputs.validation_output != '') 196 | steps: 197 | - uses: actions/checkout@v4 198 | 199 | - name: Render template 200 | id: render 201 | uses: chuhlomin/render-template@v1.8 202 | with: 203 | template: .github/workflows/templates/validation-failed.md 204 | vars: | 205 | extension: ${{ needs.parse-issue.outputs.repository }} 206 | validation_output: ${{ toJSON(format('{0}{1}', needs.validate.outputs.validation_output, needs.noop-validate.outputs.validation_output)) }} 207 | 208 | - name: Add comment 209 | if: env.ACT == false 210 | uses: peter-evans/create-or-update-comment@v3 211 | with: 212 | issue-number: ${{ github.event.issue.number }} 213 | body: ${{ steps.render.outputs.result }} 214 | 215 | - name: Remove label docker/validation-errored 216 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'docker/validation-errored') 217 | run: gh issue edit ${{ github.event.issue.number }} --remove-label "docker/validation-errored" 218 | 219 | - name: Remove label validation/succeeded 220 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'validation/succeeded') 221 | run: gh issue edit ${{ github.event.issue.number }} --remove-label "validation/succeeded" 222 | 223 | - name: Add label validation/failed 224 | if: env.ACT == false && !contains(github.event.issue.labels.*.name, 'validation/failed') 225 | run: gh issue edit ${{ github.event.issue.number }} --add-label "validation/failed" 226 | 227 | - name: Mark job as failed 228 | run: exit 1 229 | 230 | validation-errored: 231 | runs-on: ubuntu-latest 232 | needs: [noop-validate, validate] 233 | if: | 234 | always() && 235 | (needs.validate.result == 'failure' && needs.validate.outputs.validation_output == '') || 236 | (needs.noop-validate.result == 'failure' && needs.noop-validate.outputs.validation_output == '') 237 | steps: 238 | - uses: actions/checkout@v4 239 | 240 | - name: Render template 241 | id: render 242 | uses: chuhlomin/render-template@v1.8 243 | with: 244 | template: .github/workflows/templates/validation-errored.md 245 | vars: | 246 | workflow_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 247 | 248 | - name: Add comment 249 | if: env.ACT == false 250 | uses: peter-evans/create-or-update-comment@v3 251 | with: 252 | issue-number: ${{ github.event.issue.number }} 253 | body: ${{ steps.render.outputs.result }} 254 | 255 | - name: Add 'docker/validation-errored' label 256 | if: env.ACT == false && contains(github.event.issue.labels.*.name, 'docker/validation-errored') 257 | run: gh issue edit ${{ github.event.issue.number }} --add-label "docker/validation-errored" 258 | 259 | - name: Mark job as failed 260 | run: exit 1 261 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Docker Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------