├── .github ├── CODEOWNERS └── workflows │ ├── artifact-cleanup.yml │ ├── command-dispatch.yml │ ├── main.yml │ ├── pull-request.yml │ ├── release.yml │ └── run-acceptance-tests.yml ├── .gitignore ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── build └── common.mk ├── design ├── 01-nouns.md └── 02-policy-validation.md ├── scripts ├── promote.js ├── publish_packages.sh └── reversion.js ├── sdk ├── nodejs │ └── policy │ │ ├── .eslintrc.js │ │ ├── Makefile │ │ ├── deserialize.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── policy.ts │ │ ├── protoutil.ts │ │ ├── proxy.ts │ │ ├── schema.ts │ │ ├── secret.ts │ │ ├── server.ts │ │ ├── tests │ │ ├── deserialize.spec.ts │ │ ├── pb.spec.ts │ │ ├── policy.spec.ts │ │ ├── proxy.spec.ts │ │ └── util.ts │ │ ├── tsconfig.json │ │ └── version.ts └── python │ ├── .gitignore │ ├── .pylintrc │ ├── Makefile │ ├── Pipfile │ ├── lib │ ├── pulumi_policy │ │ ├── __init__.py │ │ ├── deserialize.py │ │ ├── policy.py │ │ ├── proxy.py │ │ ├── pulumi-plugin.json │ │ ├── py.typed │ │ ├── secret.py │ │ └── version.py │ ├── setup.py │ └── test │ │ ├── __init__.py │ │ ├── test_config.py │ │ ├── test_deserialize.py │ │ ├── test_policy.py │ │ └── test_proxy.py │ └── mypy.ini └── tests └── integration ├── config ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── deserialize ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json ├── enforcementlevel ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── go.mod ├── go.sum ├── integration_test.go ├── invalid_policy ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── parent_dependencies ├── policy-pack-python │ ├── PulumiPolicy.yaml │ └── __main__.py ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json ├── provider ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json ├── remote_component ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── program │ ├── Pulumi.yaml │ ├── component.ts │ ├── index.ts │ └── package.json └── testcomponent │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── pulumi-resource-testcomponent │ ├── pulumi-resource-testcomponent.cmd │ └── pulumiTypes.go ├── resource_options ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json ├── runtime_data ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json ├── unknown_values ├── policy-pack-python │ ├── PulumiPolicy.yaml │ ├── __main__.py │ └── requirements.txt ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── validate_python_resource ├── policy-pack-python │ ├── PulumiPolicy.yaml │ └── __main__.py ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── .gitignore │ ├── Pulumi.yaml │ ├── __main__.py │ └── requirements.txt ├── validate_resource ├── policy-pack-python │ ├── PulumiPolicy.yaml │ └── __main__.py ├── policy-pack │ ├── PulumiPolicy.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── program │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── resource.ts │ └── tsconfig.json └── validate_stack ├── policy-pack-python ├── PulumiPolicy.yaml └── __main__.py ├── policy-pack ├── PulumiPolicy.yaml ├── index.ts ├── package.json └── tsconfig.json └── program ├── Pulumi.yaml ├── index.ts ├── package.json ├── resource.ts └── tsconfig.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pulumi/platform 2 | -------------------------------------------------------------------------------- /.github/workflows/artifact-cleanup.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | remove-old-artifacts: 3 | runs-on: ubuntu-latest 4 | steps: 5 | - name: Remove old artifacts 6 | uses: c-hive/gha-remove-artifacts@v1 7 | with: 8 | age: 1 month 9 | skip-tags: true 10 | name: cleanup 11 | "on": 12 | schedule: 13 | - cron: 0 1 1 * * 14 | -------------------------------------------------------------------------------- /.github/workflows/command-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: command-dispatch-for-testing 2 | on: 3 | issue_comment: 4 | types: [created, edited] 5 | 6 | jobs: 7 | command-dispatch-for-testing: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Run Build 12 | uses: peter-evans/slash-command-dispatch@v2 13 | with: 14 | token: ${{ secrets.PULUMI_BOT_TOKEN }} 15 | reaction-token: ${{ secrets.GITHUB_TOKEN }} 16 | commands: run-acceptance-tests 17 | permission: write 18 | issue-type: pull-request 19 | repository: pulumi/pulumi-policy 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | env: 2 | AWS_REGION: us-west-2 3 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 4 | GO111MODULE: "on" 5 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 6 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 7 | PROVIDER: policy 8 | PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} 9 | PULUMI_API: https://api.pulumi-staging.io 10 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 11 | PYPI_USERNAME: __token__ 12 | PYPI_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 13 | VERSION: ${{ github.event.client_payload.ref }} 14 | jobs: 15 | lint: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Set up Go ${{ matrix.go-version }} 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Set up Node 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{matrix.node-version}} 31 | registry-url: https://registry.npmjs.org 32 | - name: Install pipenv 33 | run: | 34 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet 35 | - name: Install pulumictl 36 | uses: jaxxstorm/action-install-gh-release@v1.5.0 37 | with: 38 | repo: pulumi/pulumictl 39 | - name: Checkout Repo 40 | uses: actions/checkout@v2 41 | - name: Unshallow clone for tags 42 | run: git fetch --prune --unshallow --tags 43 | - name: Install Yarn 44 | run: curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0 45 | - name: Ensure 46 | run: | 47 | make ensure 48 | - name: Lint Node 49 | run: | 50 | cd sdk/nodejs/policy && make lint 51 | - name: Lint Python 52 | run: | 53 | cd sdk/python && make lint 54 | strategy: 55 | fail-fast: true 56 | matrix: 57 | platform: [ ubuntu-latest ] 58 | go-version: [ 1.21.x ] 59 | python-version: [ 3.9.x ] 60 | node-version: [ 18.x ] 61 | build_test_publish: 62 | name: Build, Test, and Publish 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout Repo 66 | uses: actions/checkout@v2 67 | - name: Unshallow clone for tags 68 | run: git fetch --prune --unshallow --tags 69 | - name: Install Go 70 | uses: actions/setup-go@v4 71 | with: 72 | go-version: ${{ matrix.go-version }} 73 | - name: Install pulumictl 74 | uses: jaxxstorm/action-install-gh-release@v1.5.0 75 | with: 76 | repo: pulumi/pulumictl 77 | - name: Install Pulumi CLI 78 | uses: pulumi/actions@v4 79 | with: 80 | pulumi-version: ">=3.157.0" 81 | - name: Setup Node 82 | uses: actions/setup-node@v3 83 | with: 84 | node-version: ${{matrix.node-version}} 85 | registry-url: https://registry.npmjs.org 86 | - name: Setup Python 87 | uses: actions/setup-python@v4 88 | with: 89 | python-version: ${{matrix.python-version}} 90 | - name: Install pipenv 91 | run: | 92 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet twine 93 | - name: Ensure dependencies 94 | run: make ensure 95 | - name: Checkout Scripts Repo 96 | uses: actions/checkout@v2 97 | with: 98 | path: ci-scripts 99 | repository: pulumi/scripts 100 | - name: Configure AWS Credentials 101 | uses: aws-actions/configure-aws-credentials@v1 102 | with: 103 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 104 | aws-region: ${{ env.AWS_REGION }} 105 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 106 | role-duration-seconds: 3600 107 | role-session-name: ${{ env.PROVIDER }}@githubActions 108 | role-to-assume: ${{ secrets.AWS_CI_ROLE_ARN }} 109 | - name: Build SDK 110 | run: make only_build 111 | - name: Check worktree clean 112 | run: ./ci-scripts/ci/check-worktree-is-clean 113 | - name: Run Unit Tests 114 | run: make only_test_fast 115 | - name: Run Integration Tests 116 | run: make test_all 117 | - name: Publish 118 | run: make publish_packages 119 | - name: Trigger Docs Build 120 | run: | 121 | ./ci-scripts/ci/build-package-docs.sh "policy" 122 | env: 123 | TRAVIS: true 124 | PULUMI_BOT_GITHUB_API_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }} 125 | TRAVIS_TAG: ${{ env.VERSION }} 126 | strategy: 127 | fail-fast: true 128 | matrix: 129 | platform: [ ubuntu-latest ] 130 | go-version: [ 1.21.x ] 131 | python-version: [ 3.9.x ] 132 | node-version: [ 18.x ] 133 | name: main 134 | "on": 135 | push: 136 | branches: 137 | - main 138 | paths-ignore: 139 | - CHANGELOG.md 140 | tags-ignore: 141 | - v* 142 | - sdk/* 143 | - '**' 144 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | "on": 3 | pull_request_target: 4 | 5 | env: 6 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 7 | 8 | jobs: 9 | comment-on-pr: 10 | # We only care about commenting on a PR if the PR is from a fork 11 | if: github.event.pull_request.head.repo.full_name != github.repository 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Comment PR 16 | uses: thollander/actions-comment-pull-request@v2 17 | with: 18 | message: | 19 | PR is now waiting for a maintainer to run the acceptance tests. This PR will only perform build and linting. 20 | **Note for the maintainer:** To run the acceptance tests, please comment */run-acceptance-tests* on the PR 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | env: 2 | AWS_REGION: us-west-2 3 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 4 | GO111MODULE: "on" 5 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 6 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 7 | PROVIDER: policy 8 | PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} 9 | PULUMI_API: https://api.pulumi-staging.io 10 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 11 | PYPI_USERNAME: __token__ 12 | PYPI_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 13 | VERSION: ${{ github.event.client_payload.ref }} 14 | jobs: 15 | lint: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Set up Go ${{ matrix.go-version }} 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Set up Node 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{matrix.node-version}} 31 | registry-url: https://registry.npmjs.org 32 | - name: Install pipenv 33 | run: | 34 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet 35 | - name: Install pulumictl 36 | uses: jaxxstorm/action-install-gh-release@v1.5.0 37 | with: 38 | repo: pulumi/pulumictl 39 | - name: Checkout Repo 40 | uses: actions/checkout@v2 41 | - name: Unshallow clone for tags 42 | run: git fetch --prune --unshallow --tags 43 | - name: Install Yarn 44 | run: curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0 45 | - name: Ensure 46 | run: | 47 | make ensure 48 | - name: Lint Node 49 | run: | 50 | cd sdk/nodejs/policy && make lint 51 | - name: Lint Python 52 | run: | 53 | cd sdk/python && make lint 54 | strategy: 55 | fail-fast: true 56 | matrix: 57 | platform: [ ubuntu-latest ] 58 | go-version: [ 1.21.x ] 59 | python-version: [ 3.9.x ] 60 | node-version: [ 18.x ] 61 | build_test_publish: 62 | name: Build, Test, and Publish 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout Repo 66 | uses: actions/checkout@v2 67 | - name: Unshallow clone for tags 68 | run: git fetch --prune --unshallow --tags 69 | - name: Install Go 70 | uses: actions/setup-go@v4 71 | with: 72 | go-version: ${{ matrix.go-version }} 73 | - name: Install pulumictl 74 | uses: jaxxstorm/action-install-gh-release@v1.5.0 75 | with: 76 | repo: pulumi/pulumictl 77 | - name: Install Pulumi CLI 78 | uses: pulumi/actions@v4 79 | with: 80 | pulumi-version: ">=3.157.0" 81 | - name: Setup Node 82 | uses: actions/setup-node@v3 83 | with: 84 | node-version: ${{matrix.node-version}} 85 | registry-url: https://registry.npmjs.org 86 | - name: Setup Python 87 | uses: actions/setup-python@v4 88 | with: 89 | python-version: ${{matrix.python-version}} 90 | - name: Install pipenv 91 | run: | 92 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet twine 93 | - name: Ensure dependencies 94 | run: make ensure 95 | - name: Checkout Scripts Repo 96 | uses: actions/checkout@v2 97 | with: 98 | path: ci-scripts 99 | repository: pulumi/scripts 100 | - name: Build SDK 101 | run: make only_build 102 | - name: Check worktree clean 103 | run: ./ci-scripts/ci/check-worktree-is-clean 104 | - name: Run Unit Tests 105 | run: make only_test_fast 106 | - name: Run Integration Tests 107 | run: make test_all 108 | - name: Publish 109 | run: make publish_packages 110 | - name: Trigger Docs Build 111 | run: | 112 | ./ci-scripts/ci/build-package-docs.sh "policy" 113 | env: 114 | TRAVIS: true 115 | PULUMI_BOT_GITHUB_API_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }} 116 | TRAVIS_TAG: ${{ env.VERSION }} 117 | strategy: 118 | fail-fast: true 119 | matrix: 120 | platform: [ ubuntu-latest ] 121 | go-version: [ 1.21.x ] 122 | python-version: [ 3.9.x ] 123 | node-version: [ 18.x ] 124 | name: release 125 | "on": 126 | push: 127 | tags: 128 | - v*.*.* 129 | -------------------------------------------------------------------------------- /.github/workflows/run-acceptance-tests.yml: -------------------------------------------------------------------------------- 1 | env: 2 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 3 | GO111MODULE: "on" 4 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 5 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 6 | PROVIDER: policy 7 | PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} 8 | PULUMI_API: https://api.pulumi-staging.io 9 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 10 | PR_COMMIT_SHA: ${{ github.event.client_payload.pull_request.head.sha }} 11 | jobs: 12 | comment-notification: 13 | # We only care about adding the result to the PR if it's a repository_dispatch event 14 | if: github.event_name == 'repository_dispatch' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Create URL to the run output 18 | id: vars 19 | run: echo run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID >> "$GITHUB_OUTPUT" 20 | - name: Update with Result 21 | uses: peter-evans/create-or-update-comment@v1 22 | with: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | repository: ${{ github.event.client_payload.github.payload.repository.full_name }} 25 | issue-number: ${{ github.event.client_payload.github.payload.issue.number }} 26 | body: | 27 | Please view the PR build - ${{ steps.vars.outputs.run-url }} 28 | lint: 29 | name: lint 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Set up Go ${{ matrix.go-version }} 33 | uses: actions/setup-go@v4 34 | with: 35 | go-version: ${{ matrix.go-version }} 36 | - name: Set up Python ${{ matrix.python-version }} 37 | uses: actions/setup-python@v4 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | - name: Set up Node 41 | uses: actions/setup-node@v3 42 | with: 43 | node-version: ${{matrix.node-version}} 44 | registry-url: https://registry.npmjs.org 45 | - name: Install pipenv 46 | run: | 47 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet 48 | - name: Install pulumictl 49 | uses: jaxxstorm/action-install-gh-release@v1.5.0 50 | with: 51 | repo: pulumi/pulumictl 52 | - name: Checkout Repo 53 | uses: actions/checkout@v2 54 | - name: Unshallow clone for tags 55 | run: git fetch --prune --unshallow --tags 56 | - name: Install Yarn 57 | run: curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0 58 | - name: Ensure 59 | run: | 60 | make ensure 61 | - name: Lint Node 62 | run: | 63 | cd sdk/nodejs/policy && make lint 64 | - name: Lint Python 65 | run: | 66 | cd sdk/python && make lint 67 | strategy: 68 | fail-fast: true 69 | matrix: 70 | platform: [ ubuntu-latest ] 71 | go-version: [ 1.21.x ] 72 | python-version: [ 3.9.x ] 73 | node-version: [ 18.x ] 74 | build_and_test: 75 | name: Build and Test SDK 76 | runs-on: ${{ matrix.platform }} 77 | if: github.event_name == 'repository_dispatch' || github.event.pull_request.head.repo.full_name == github.repository 78 | steps: 79 | - name: Checkout Repo 80 | uses: actions/checkout@v2 81 | with: 82 | ref: ${{ env.PR_COMMIT_SHA }} 83 | - name: Unshallow clone for tags 84 | run: git fetch --prune --unshallow --tags 85 | - name: Install Go 86 | uses: actions/setup-go@v4 87 | with: 88 | go-version: ${{ matrix.go-version }} 89 | - name: Install pulumictl 90 | uses: jaxxstorm/action-install-gh-release@v1.5.0 91 | with: 92 | repo: pulumi/pulumictl 93 | - name: Install Pulumi CLI 94 | uses: pulumi/actions@v4 95 | with: 96 | pulumi-version: ">=3.157.0" 97 | - name: Setup Node 98 | uses: actions/setup-node@v3 99 | with: 100 | node-version: ${{matrix.node-version}} 101 | registry-url: https://registry.npmjs.org 102 | - name: Setup Python 103 | uses: actions/setup-python@v4 104 | with: 105 | python-version: ${{matrix.python-version}} 106 | - name: Install pipenv 107 | run: | 108 | python -m pip install --upgrade pipenv pip requests wheel urllib3 chardet 109 | - name: Ensure dependencies 110 | run: make ensure 111 | - name: Checkout Scripts Repo 112 | uses: actions/checkout@v2 113 | with: 114 | path: ci-scripts 115 | repository: pulumi/scripts 116 | - name: Build SDK 117 | run: make only_build 118 | - name: Check worktree clean 119 | run: ./ci-scripts/ci/check-worktree-is-clean 120 | - name: Run Unit Tests 121 | run: make only_test_fast 122 | - name: Run Integration Tests 123 | run: make test_all 124 | strategy: 125 | fail-fast: true 126 | matrix: 127 | platform: [ ubuntu-latest ] 128 | go-version: [ 1.21.x ] 129 | python-version: [ 3.9.x ] 130 | node-version: [ 18.x ] 131 | name: Run Acceptance Tests from PR 132 | on: 133 | repository_dispatch: 134 | types: [run-acceptance-tests-command] 135 | pull_request: 136 | branches: 137 | - main 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pulumi 2 | **/bin/ 3 | **/node_modules/ 4 | vendor/ 5 | **/Pulumi.*.yaml 6 | **/yarn-error.log 7 | **/yarn.lock 8 | .idea 9 | **/Pipfile.lock 10 | ci-scripts 11 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at code-of-conduct@pulumi.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Changelog 4 | 5 | The changelog in this repo is managed manually in the `CHANGELOG.md` file. 6 | PRs with a notable change should include an entry in the `HEAD (Unreleased)` 7 | section of the file. 8 | 9 | ## Releasing 10 | 11 | To release a new version of `pulumi-policy`, update the `CHANGELOG.md` file, 12 | moving all items from the `HEAD (Unreleased)` section to a new section with 13 | the new version number. Once this is merged, a new version will be published 14 | when a tag of the form `v*.*.*` is pushed to the repo. To push the tag, 15 | ask the `@release-bot` in the internal Pulumi Slack channel `#release-ops` 16 | to do a release, for example: 17 | 18 | ``` 19 | @release-bot release pulumi-policy minor 20 | ``` 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := policy 2 | SUB_PROJECTS := sdk/nodejs/policy sdk/python 3 | include build/common.mk 4 | 5 | .PHONY: ensure 6 | ensure:: 7 | # Golang dependencies for the integration tests. 8 | cd ./tests/integration && go mod download && go mod tidy 9 | 10 | .PHONY: publish_packages 11 | publish_packages: 12 | $(call STEP_MESSAGE) 13 | ./scripts/publish_packages.sh 14 | 15 | .PHONY: test_all 16 | test_all:: 17 | cd ./tests/integration && go test . -v -timeout 30m 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://github.com/pulumi/pulumi-policy/actions/workflows/main.yml/badge.svg) 2 | 3 | # Pulumi Policy SDK 4 | 5 | ## Overview 6 | 7 | Define and manage policy for cloud resources deployed through Pulumi. 8 | 9 | Policy rules run during `pulumi preview` and `pulumi up`, asserting that cloud resource definitions 10 | comply with the policy immediately before they are created or updated. Policies may optionally define 11 | remediations that automatically fix policy violations rather than issue warnings. 12 | 13 | During `preview`, every rule is run on every resource, and policy violations are batched up 14 | into a final report. During the update, the first policy violation will halt the deployment. 15 | 16 | Policy violations can have enforcement levels that are **advisory**, which results in a printed 17 | warning, or **mandatory**, which results in an error after `pulumi preview` or `pulumi up` completes. 18 | The enforcement level **remediate** is stronger than both and enables automatic remediations. 19 | 20 | ## Getting Started 21 | 22 | Please see [Get Started with Policy as Code](https://www.pulumi.com/docs/get-started/crossguard/) to get 23 | started authoring and enforcing policies. 24 | 25 | ## Documentation 26 | 27 | For additional documentation, guides, best practices, and FAQs, see [Policy as Code](https://www.pulumi.com/docs/guides/crossguard/). 28 | 29 | ## Examples 30 | 31 | Looking for examples? Please refer to the [examples repo](https://github.com/pulumi/examples/tree/master/policy-packs). 32 | 33 | ## Languages 34 | 35 | Policies can be written in TypeScript/JavaScript (Node.js) or Python and can be applied to Pulumi stacks written in any language. 36 | 37 | | | Language | Status | 38 | | -- | -------- | ------ | 39 | | | [TypeScript](./sdk/nodejs) | Stable | 40 | | | [JavaScript](./sdk/nodejs) | Stable | 41 | | | [Python](./sdk/python) | Preview | 42 | | | .NET | Coming Soon | 43 | | | Go | Coming Soon | 44 | -------------------------------------------------------------------------------- /design/02-policy-validation.md: -------------------------------------------------------------------------------- 1 | # Policy validation API by example 2 | 3 | In this text, we'll talk generally about the sequence of API calls required to validate a resource 4 | against a bunch of policies, and to push information about validation failures to the Pulumi 5 | service. As we will see, this contains two API boundaries: the gRPC API for the analyzer API (which 6 | validates resources against the policies), and the Pulumi service API for receiving policy 7 | violations. 8 | 9 | ## Step 1: The Analyzer API 10 | 11 | The `StepGenerator`, broadly, is in charge of taking events from the running Pulumi program and 12 | turning them into goal states and operations that Pulumi is supposed to drive towards. 13 | 14 | Before any step can be executed, the goal state for the given resource must be validated against the 15 | current analyzers (in this case including a `PolicyPack`). 16 | 17 | Thus, the first API boundary we cross is the interface between the `StepGenerator` and the analyzer 18 | plugins. This section will describe this API. 19 | 20 | ### `PolicyPack` is registered 21 | 22 | The engine will lazily load the analyzer plugins as they are needed. Policies are implemented as 23 | analyzer plugins, using the `PolicyPack` abstraction. The `PolicyPack` will start a gRPC server that 24 | can respond to `Analyze(...)` RPC calls. 25 | 26 | The code looks like this: 27 | 28 | ```typescript 29 | const policies = new PolicyPack("k8s-sec-rules", { 30 | policies: [ 31 | { 32 | name: "no-public-services", 33 | description: "No Kubernetes Service objects should have type `LoadBalancer`", 34 | message: 35 | "Security team requires all publicly-exposed services to go through audit and approval " 36 | tags: ["security"], 37 | enforcementLevel: "mandatory", 38 | rule: (type, svc) => { 39 | return type === "kubernetes:core/v1:Service" && svc.type === "LoadBalancer"; 40 | }, 41 | }, 42 | ], 43 | }); 44 | ``` 45 | 46 | ### `Analyze(...)` is called 47 | 48 | When `RegisterResource` is called, the Pulumi engine will call the `Analyze(...)` RPC on each 49 | analyzer -- in this case, there is just one analyzer, and it contains the `PolicyPack`. 50 | 51 | The raw RPC request (i.e., beneath all the sugar) will look something like this: 52 | 53 | ```javascript 54 | // Request 55 | { 56 | Type: "kubernetes:core/v1:Service", 57 | Properties: { kind: "Service", apiVersion: "v1", ... }, 58 | } 59 | ``` 60 | 61 | The policy registered in the previous step will receive this property bag and validate it. In the 62 | event of a failure, we would get the following response back. 63 | 64 | ```javascript 65 | // Response: list of policy violations 66 | { 67 | Diagnostics: [{ 68 | ID: "k8s-sec-rules/no-public-services", 69 | Description: "No Kubernetes Service objects should have type `LoadBalancer`", 70 | Message: "Security team requires all publicly-exposed services to go through audit and approval ", 71 | Tags: ["security"], 72 | EnforcementLevel: "mandatory", // Technically, gRPC implements this field as an enum. 73 | }], 74 | } 75 | ``` 76 | 77 | Note that this response does not contain the URN of the resource. In general, analyzer plugins don’t 78 | know about URNs -- since the `StepGenerator` invoked `Analyze(...)`, it keeps track of the 79 | additional metadata needed to communicate with the Pulumi service about what resources failed. 80 | 81 | In the next section, we will see that the `StepGenerator` takes this diagnostic information and 82 | marshals it into an _event_ representing a policy violation, which the Pulumi service can 83 | understand. 84 | 85 | ## Step 2: The Pulumi service events API 86 | 87 | As we mentioned in the previous section, when the `StepGenerator` calls `Analyze(...)` on a 88 | particular goal state, it receives back only enough information to know that the goal state is 89 | invalid. To make this useful to the Pulumi service, it must now convert this response into an 90 | _event_ that contains enough information that the Pulumi service can understand it. 91 | 92 | Thus, the second API boundary we reach is the event sink boundary. 93 | 94 | ### Results of `Analyze` are turned into a policy violation event 95 | 96 | The response `Analyze` returned in the last step is converted by the `StepGenerator` into the 97 | following event. Notably, it now contains: 98 | 99 | 1. The URN of the resource that failed validation 100 | 1. The ID of the policy (taking the form `/`) 101 | 1. Information useful for printing and colorizing the message 102 | 103 | > NOTE: If there were multiple policy violations, they would be "rendered" as multiple policy 104 | > violation events, and each individually sent to the Pulumi service. 105 | 106 | ```typescript 107 | { 108 | Type: "policy-violation", 109 | Payload: { 110 | URN "", 111 | Message "No Kubernetes Service objects should have type `LoadBalancer`: " + 112 | "Security team requires all publicly-exposed services to go through audit and approval ", 113 | Color "", 114 | ID "k8s-sec-rules/no-public-services", 115 | EnforcementLevel "mandatory", 116 | Prefix "", 117 | } 118 | } 119 | ``` 120 | 121 | ### Policy violation event is sent to the Pulumi service 122 | 123 | Finally, once this is converted to an event that the Pulumi service understands, it is sent to the 124 | Pulumi service. 125 | -------------------------------------------------------------------------------- /scripts/promote.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018, Pulumi Corporation. All rights reserved. 2 | 3 | // This program simply reads a package.json from stdin, takes a set of arguments representing 4 | // package names, and for each one, promotes that package from a peerDependency to a real dependency. 5 | 6 | // Read the package.json from stdin. 7 | let packageJSONText = ""; 8 | const readline = require("readline"); 9 | const stdin = readline.createInterface({ 10 | input: process.stdin, 11 | output: process.stdout, 12 | terminal: false, 13 | }); 14 | stdin.on("line", function(line) { 15 | packageJSONText += `${line}\n`; 16 | }); 17 | stdin.on("close", function() { 18 | // All stdin is available. Parse the JSON and move dependencies around. 19 | const packageJSON = JSON.parse(packageJSONText); 20 | for (const arg of process.argv.slice(2)) { 21 | if (!packageJSON.peerDependencies || !packageJSON.peerDependencies[arg]) { 22 | throw new Error(`No peerDependency for "${arg}" found`); 23 | } 24 | 25 | // Add this dependency. 26 | if (!packageJSON.dependencies) { 27 | packageJSON.dependencies = {}; 28 | } 29 | packageJSON.dependencies[arg] = packageJSON.peerDependencies[arg]; 30 | 31 | // And now delete the peer dependency. 32 | delete packageJSON.peerDependencies[arg]; 33 | if (Object.keys(packageJSON.peerDependencies).length === 0) { 34 | delete packageJSON.peerDependencies; 35 | } 36 | } 37 | 38 | // Now print out the result to stdout. 39 | console.log(JSON.stringify(packageJSON, null, 4)); 40 | }); 41 | -------------------------------------------------------------------------------- /scripts/publish_packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # publish.sh builds and publishes a release. 3 | set -o nounset -o errexit -o pipefail 4 | ROOT=$(dirname $0)/.. 5 | 6 | echo "Publishing NPM packages to NPMjs.com:" 7 | 8 | # For each package, first create the package.json to publish. This must be different than the one we use for 9 | # development and testing the SDK, since we use symlinking for those workflows. Namely, we must promote the SDK 10 | # dependencies from peerDependencies that are resolved via those links, to real installable dependencies. 11 | publish() { 12 | node $(dirname $0)/promote.js ${@:2} < \ 13 | ${ROOT}/sdk/nodejs/$1/bin/package.json > \ 14 | ${ROOT}/sdk/nodejs/$1/bin/package.json.publish 15 | pushd ${ROOT}/sdk/nodejs/$1/bin 16 | mv package.json package.json.dev 17 | mv package.json.publish package.json 18 | 19 | NPM_TAG="dev" 20 | 21 | # If the package doesn't have a pre-release tag, use the tag of latest instead of 22 | # dev. NPM uses this tag as the default version to add, so we want it to mean 23 | # the newest released version. 24 | if [[ $(jq -r .version < package.json) != *-* ]]; then 25 | NPM_TAG="latest" 26 | fi 27 | 28 | # Now, perform the publish. 29 | npm publish -tag ${NPM_TAG} 30 | npm info 2>/dev/null 31 | 32 | # And finally restore the original package.json. 33 | mv package.json package.json.publish 34 | mv package.json.dev package.json 35 | popd 36 | } 37 | 38 | publish policy 39 | 40 | echo "Publishing Pip package to pypi.org:" 41 | twine upload \ 42 | -u "${PYPI_USERNAME}" -p "${PYPI_PASSWORD}" \ 43 | "${ROOT}/sdk/python/env/src/dist"/*.whl \ 44 | --skip-existing \ 45 | -------------------------------------------------------------------------------- /scripts/reversion.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Pulumi Corporation. All rights reserved. 2 | 3 | var fs = require("fs"); 4 | 5 | if (process.argv.length < 4) { 6 | console.error("error: missing arguments; usage: