├── .pulumi.version ├── examples ├── ec2_copyfile │ ├── src │ │ └── file1 │ ├── size.ts │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── ec2_dir_copy │ ├── src │ │ ├── file1 │ │ └── one │ │ │ ├── file2 │ │ │ └── two │ │ │ └── file3 │ ├── step2 │ │ ├── src │ │ │ └── newfile │ │ └── size.ts │ ├── size.ts │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── simple-py │ ├── .gitignore │ ├── requirements.txt │ ├── __main__.py │ └── Pulumi.yaml ├── aws-py-ec2-command │ ├── myapp.conf │ ├── requirements.txt │ ├── Pulumi.yaml │ └── __main__.py ├── ec2_remote │ ├── size.ts │ ├── replace_instance │ │ └── size.ts │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── gcp-py-compute-command │ ├── myapp.conf │ ├── requirements.txt │ ├── Pulumi.yaml │ └── __main__.py ├── lambda-ts │ ├── .gitignore │ ├── lambda │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── package.json │ ├── Pulumi.yaml │ ├── tsconfig.json │ └── index.ts ├── ec2_remote_proxy │ ├── size.ts │ ├── replace_instance │ │ └── size.ts │ ├── Pulumi.yaml │ ├── package.json │ └── index.ts ├── simple-ansible │ ├── requirements.txt │ ├── Pulumi.yaml │ ├── __main__.py │ └── hello-world.yml ├── curl │ ├── Pulumi.yaml │ ├── delete_label.sh │ ├── create_label.sh │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── stdin │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── kubernetes │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── random-go │ ├── Pulumi.yaml │ └── main.go ├── random │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── simple │ ├── Pulumi.yaml │ ├── extras.ts │ ├── fail │ │ └── extras.ts │ ├── update │ │ └── extras.ts │ ├── replace │ │ └── extras.ts │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── stderr │ ├── Pulumi.yaml │ ├── package.json │ ├── index.ts │ └── tsconfig.json ├── stdin-go │ ├── Pulumi.yaml │ └── main.go ├── lambda-invoke │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── simple-run │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── simple-with-update │ ├── extras.ts │ ├── Pulumi.yaml │ ├── package.json │ ├── tsconfig.json │ ├── index.ts │ └── update-change │ │ └── index.ts ├── delete-from-stdout │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── examples_test.go ├── examples_go_test.go └── testdata │ └── recorded │ └── TestProviderUpgrade │ └── stdin │ └── 0.11.1 │ └── stack.json ├── sdk ├── dotnet │ ├── .gitignore │ ├── .gitattributes │ ├── logo.png │ ├── pulumi-plugin.json │ ├── README.md │ ├── Local │ │ ├── README.md │ │ └── Enums.cs │ ├── Remote │ │ ├── README.md │ │ ├── Enums.cs │ │ └── Outputs │ │ │ ├── ProxyConnection.cs │ │ │ └── Connection.cs │ ├── Provider.cs │ ├── Pulumi.Command.csproj │ └── Utilities.cs ├── python │ ├── pulumi_command │ │ ├── py.typed │ │ ├── pulumi-plugin.json │ │ ├── README.md │ │ ├── local │ │ │ ├── __init__.py │ │ │ └── _enums.py │ │ ├── remote │ │ │ ├── __init__.py │ │ │ └── _enums.py │ │ ├── __init__.py │ │ └── provider.py │ ├── .gitattributes │ ├── .gitignore │ └── pyproject.toml ├── .gitignore ├── go │ ├── .gitattributes │ └── command │ │ ├── pulumi-plugin.json │ │ ├── doc.go │ │ ├── internal │ │ └── pulumiVersion.go │ │ ├── init.go │ │ ├── local │ │ └── init.go │ │ ├── remote │ │ └── init.go │ │ └── provider.go ├── java │ ├── .gitattributes │ ├── README.md │ ├── settings.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── pulumi │ │ └── command │ │ ├── ProviderArgs.java │ │ ├── local │ │ ├── enums │ │ │ └── Logging.java │ │ └── LocalFunctions.java │ │ ├── remote │ │ └── enums │ │ │ └── Logging.java │ │ ├── Provider.java │ │ └── Utilities.java └── nodejs │ ├── .gitignore │ ├── .gitattributes │ ├── README.md │ ├── types │ ├── enums │ │ ├── index.ts │ │ ├── local │ │ │ └── index.ts │ │ └── remote │ │ │ └── index.ts │ └── index.ts │ ├── package.json │ ├── tsconfig.json │ ├── index.ts │ ├── local │ └── index.ts │ ├── provider.ts │ ├── remote │ └── index.ts │ └── utilities.ts ├── assets ├── logo.png └── logo.svg ├── .golangci.yml ├── .vscode └── settings.json ├── .config ├── mise.test.toml └── mise.toml ├── .ci-mgmt.yaml ├── provider ├── pkg │ ├── provider │ │ ├── remote │ │ │ ├── connection_unix.go │ │ │ ├── connection_windows.go │ │ │ ├── logging_test.go │ │ │ ├── baseOutputs.go │ │ │ ├── logging.go │ │ │ ├── copyfile.go │ │ │ ├── proxyConnection.go │ │ │ ├── copy_test.go │ │ │ ├── copyfileController.go │ │ │ ├── commandController_test.go │ │ │ ├── copy.go │ │ │ ├── commandController.go │ │ │ └── command.go │ │ ├── local │ │ │ ├── logging_test.go │ │ │ ├── runController.go │ │ │ ├── logging.go │ │ │ ├── run.go │ │ │ ├── command.go │ │ │ ├── commandController_test.go │ │ │ └── commandController.go │ │ ├── util │ │ │ ├── testutil │ │ │ │ ├── testutil.go │ │ │ │ └── test_ssh_server.go │ │ │ └── util.go │ │ ├── common │ │ │ └── inputs.go │ │ └── provider.go │ └── version │ │ └── version.go └── cmd │ └── pulumi-resource-command │ └── main.go ├── .github ├── actions │ ├── esc-action │ │ ├── index.js │ │ └── action.yaml │ ├── download-sdk │ │ └── action.yml │ ├── download-provider │ │ └── action.yml │ └── setup-tools │ │ └── action.yml ├── workflows │ ├── pull-request.yml │ ├── export-repo-secrets.yml │ ├── command-dispatch.yml │ ├── community-moderation.yml │ ├── comment-on-stale-issues.yml │ └── release_command.yml └── ISSUE_TEMPLATE │ ├── epic.md │ └── bug.yaml ├── .gitignore ├── docs └── installation-configuration.md ├── .goreleaser.yml ├── .goreleaser.prerelease.yml └── scripts └── get-versions.sh /.pulumi.version: -------------------------------------------------------------------------------- 1 | 3.197.0 2 | -------------------------------------------------------------------------------- /examples/ec2_copyfile/src/file1: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/src/file1: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /sdk/dotnet/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/src/one/file2: -------------------------------------------------------------------------------- 1 | b 2 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/src/one/two/file3: -------------------------------------------------------------------------------- 1 | c 2 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/step2/src/newfile: -------------------------------------------------------------------------------- 1 | new 2 | -------------------------------------------------------------------------------- /sdk/.gitignore: -------------------------------------------------------------------------------- 1 | schema/ 2 | examples/*.txt 3 | -------------------------------------------------------------------------------- /sdk/go/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated 2 | -------------------------------------------------------------------------------- /sdk/java/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated 2 | -------------------------------------------------------------------------------- /sdk/nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /examples/simple-py/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | venv/ 3 | -------------------------------------------------------------------------------- /sdk/dotnet/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated 2 | -------------------------------------------------------------------------------- /sdk/nodejs/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated 2 | -------------------------------------------------------------------------------- /sdk/python/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated 2 | -------------------------------------------------------------------------------- /examples/aws-py-ec2-command/myapp.conf: -------------------------------------------------------------------------------- 1 | [test] 2 | x = 42 -------------------------------------------------------------------------------- /examples/ec2_remote/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.nano"; 2 | -------------------------------------------------------------------------------- /examples/gcp-py-compute-command/myapp.conf: -------------------------------------------------------------------------------- 1 | [test] 2 | x = 42 -------------------------------------------------------------------------------- /examples/lambda-ts/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | !yarn.lock -------------------------------------------------------------------------------- /examples/ec2_copyfile/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.nano"; 2 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.nano"; 2 | -------------------------------------------------------------------------------- /examples/ec2_remote_proxy/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.nano"; 2 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/step2/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.nano"; 2 | -------------------------------------------------------------------------------- /examples/ec2_remote/replace_instance/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.micro"; -------------------------------------------------------------------------------- /examples/ec2_remote_proxy/replace_instance/size.ts: -------------------------------------------------------------------------------- 1 | export const size = "t2.micro"; -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/pulumi-command/HEAD/assets/logo.png -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 10m 3 | 4 | issues: 5 | exclude-dirs: 6 | - sdk 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testTags": "all", 3 | "go.buildTags": "all", 4 | } -------------------------------------------------------------------------------- /examples/simple-py/requirements.txt: -------------------------------------------------------------------------------- 1 | pulumi>=3.0.0,<4.0.0 2 | pulumi-command>=1.0.1,<2 3 | -------------------------------------------------------------------------------- /examples/aws-py-ec2-command/requirements.txt: -------------------------------------------------------------------------------- 1 | pulumi>=3.0.0,<4.0.0 2 | pulumi-aws 3 | pulumi-command -------------------------------------------------------------------------------- /examples/simple-ansible/requirements.txt: -------------------------------------------------------------------------------- 1 | ansible 2 | pulumi>=3.0.0,<4.0.0 3 | pulumi-command 4 | -------------------------------------------------------------------------------- /sdk/dotnet/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/pulumi-command/HEAD/sdk/dotnet/logo.png -------------------------------------------------------------------------------- /sdk/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .mypy_cache 4 | dist 5 | build 6 | *.egg-info 7 | -------------------------------------------------------------------------------- /examples/gcp-py-compute-command/requirements.txt: -------------------------------------------------------------------------------- 1 | pulumi>=3.0.0,<4.0.0 2 | pulumi-command 3 | pulumi-gcp 4 | -------------------------------------------------------------------------------- /examples/curl/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-curl 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/stdin/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-random 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/kubernetes/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-simple 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/random-go/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-random-go 2 | runtime: go 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/random/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-random 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-simple 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple/extras.ts: -------------------------------------------------------------------------------- 1 | export const len = 10; 2 | export const fail = false; 3 | export const update = false; 4 | -------------------------------------------------------------------------------- /examples/stderr/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-stderr 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/stdin-go/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-random-go 2 | runtime: go 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/ec2_remote/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-ec2-remote 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/lambda-invoke/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-lambda 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple-run/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-simple-run 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple/fail/extras.ts: -------------------------------------------------------------------------------- 1 | export const len = 20; 2 | export const fail = true; 3 | export const update = false; 4 | -------------------------------------------------------------------------------- /examples/simple/update/extras.ts: -------------------------------------------------------------------------------- 1 | export const len = 10; 2 | export const fail = false; 3 | export const update = true; 4 | -------------------------------------------------------------------------------- /examples/ec2_remote_proxy/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-ec2-remote 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple-with-update/extras.ts: -------------------------------------------------------------------------------- 1 | export const len = 10; 2 | export const fail = false; 3 | export const update = false; 4 | -------------------------------------------------------------------------------- /examples/simple/replace/extras.ts: -------------------------------------------------------------------------------- 1 | export const len = 20; 2 | export const fail = false; 3 | export const update = false; 4 | -------------------------------------------------------------------------------- /sdk/dotnet/pulumi-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": true, 3 | "name": "command", 4 | "version": "1.0.0-alpha.0+dev" 5 | } 6 | -------------------------------------------------------------------------------- /sdk/go/command/pulumi-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": true, 3 | "name": "command", 4 | "version": "1.0.0-alpha.0+dev" 5 | } 6 | -------------------------------------------------------------------------------- /examples/delete-from-stdout/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-delete-from-stdout 2 | runtime: nodejs 3 | description: A simple command example 4 | -------------------------------------------------------------------------------- /examples/simple-with-update/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-simple-with-update 2 | runtime: nodejs 3 | description: test update field behavior 4 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/pulumi-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": true, 3 | "name": "command", 4 | "version": "1.0.0-alpha.0+dev" 5 | } 6 | -------------------------------------------------------------------------------- /sdk/dotnet/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /sdk/java/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /sdk/nodejs/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /sdk/dotnet/Local/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /sdk/dotnet/Remote/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/README.md: -------------------------------------------------------------------------------- 1 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | -------------------------------------------------------------------------------- /examples/lambda-ts/lambda/index.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "aws-lambda"; 2 | 3 | export async function handler(event: any, context: Context) { 4 | return context.functionName; 5 | } 6 | -------------------------------------------------------------------------------- /sdk/go/command/doc.go: -------------------------------------------------------------------------------- 1 | // The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 2 | package command 3 | -------------------------------------------------------------------------------- /examples/lambda-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-ts", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "@pulumi/aws": "7.15.0", 6 | "@pulumi/pulumi": "3.213.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/simple-ansible/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: hack-pulumi-command-examples 2 | runtime: 3 | name: python 4 | options: 5 | virtualenv: venv 6 | description: A minimal Python Pulumi program 7 | -------------------------------------------------------------------------------- /examples/aws-py-ec2-command/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: hack-pulumi-command-examples 2 | runtime: 3 | name: python 4 | options: 5 | virtualenv: venv 6 | description: A minimal Python Pulumi program 7 | -------------------------------------------------------------------------------- /examples/gcp-py-compute-command/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: hack-pulumi-command-examples 2 | runtime: 3 | name: python 4 | options: 5 | virtualenv: venv 6 | description: A minimal Python Pulumi program 7 | -------------------------------------------------------------------------------- /examples/simple-py/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi_command as command 2 | 3 | hello = command.local.Command( 4 | 'foo', 5 | create='echo hello', 6 | logging=command.local.Logging.STDOUT 7 | ) 8 | -------------------------------------------------------------------------------- /.config/mise.test.toml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | 3 | # Overrides for test workflows -- currently empty. 4 | -------------------------------------------------------------------------------- /examples/ec2_copyfile/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: copy-remote 2 | runtime: nodejs 3 | description: A command example copying a directory via sFTP 4 | config: 5 | pulumi:tags: 6 | value: 7 | pulumi:template: "" 8 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: copy-remote 2 | runtime: nodejs 3 | description: A command example copying a directory via sFTP 4 | config: 5 | pulumi:tags: 6 | value: 7 | pulumi:template: "" 8 | -------------------------------------------------------------------------------- /examples/random/index.ts: -------------------------------------------------------------------------------- 1 | import { local } from "@pulumi/command"; 2 | 3 | const random = new local.Command("random", { 4 | create: "openssl rand -hex 16", 5 | }); 6 | 7 | export const output = random.stdout; 8 | -------------------------------------------------------------------------------- /examples/curl/delete_label.sh: -------------------------------------------------------------------------------- 1 | curl \ 2 | -s \ 3 | -X DELETE \ 4 | -H "authorization: Bearer $GITHUB_TOKEN" \ 5 | -H "Accept: application/vnd.github.v3+json" \ 6 | https://api.github.com/repos/$OWNER/$REPO/labels/$NAME -------------------------------------------------------------------------------- /examples/lambda-ts/lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-ts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "typescript": "^4.0.0", 7 | "@types/aws-lambda": "^8.10.101" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/curl/create_label.sh: -------------------------------------------------------------------------------- 1 | curl \ 2 | -s \ 3 | -X POST \ 4 | -H "authorization: Bearer $GITHUB_TOKEN" \ 5 | -H "Accept: application/vnd.github.v3+json" \ 6 | https://api.github.com/repos/$OWNER/$REPO/labels \ 7 | -d "{\"name\":\"$NAME\"}" -------------------------------------------------------------------------------- /examples/lambda-ts/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: command-lambda-ts 2 | runtime: nodejs 3 | description: Examples of build Lambdas from Typescript 4 | template: 5 | config: 6 | aws:region: 7 | description: The AWS region to deploy into 8 | default: us-east-2 9 | -------------------------------------------------------------------------------- /examples/simple-py/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: simple-py 2 | runtime: 3 | name: python 4 | options: 5 | virtualenv: venv 6 | description: A minimal Python Pulumi Command program 7 | config: 8 | pulumi:tags: 9 | value: 10 | pulumi:template: "" 11 | -------------------------------------------------------------------------------- /examples/stdin/index.ts: -------------------------------------------------------------------------------- 1 | import { local } from "@pulumi/command"; 2 | 3 | const random = new local.Command("stdin", { 4 | create: "head -n 1", 5 | stdin: "the quick brown fox\njumped over\nthe lazy dog" 6 | }); 7 | 8 | export const output = random.stdout; 9 | -------------------------------------------------------------------------------- /examples/simple-ansible/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi 2 | import pulumi_command as command 3 | 4 | local_command = command.local.Command( 5 | 'ansible', 6 | create="ansible-playbook hello-world.yml" 7 | ) 8 | 9 | pulumi.export('output', local_command.stdout) 10 | -------------------------------------------------------------------------------- /examples/lambda-ts/lambda/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "pretty": true, 8 | "strict": true 9 | } 10 | } -------------------------------------------------------------------------------- /examples/curl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-curl", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/random/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-random", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/stderr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-stderr", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/stdin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-random", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/kubernetes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/lambda-invoke/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-lambda", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/aws": "7.15.0", 9 | "@pulumi/pulumi": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/simple-run/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/delete-from-stdout/index.ts: -------------------------------------------------------------------------------- 1 | import { local } from "@pulumi/command"; 2 | 3 | const mktemp = new local.Command('mktemp', { 4 | create: 'mktemp', 5 | update: 'echo $PULUMI_COMMAND_STDOUT', 6 | delete: 'rm $PULUMI_COMMAND_STDOUT' 7 | }) 8 | 9 | export const output = mktemp.stdout; 10 | -------------------------------------------------------------------------------- /examples/simple-with-update/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/delete-from-stdout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-delete-from-stdout", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/pulumi": "latest", 9 | "@pulumi/random": "4.18.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/stderr/index.ts: -------------------------------------------------------------------------------- 1 | import * as command from "@pulumi/command"; 2 | 3 | new command.local.Command("stdout-and-stderr-success", { 4 | create: "ls not-a-file index.ts not-a-file-2 || true" 5 | }); 6 | 7 | new command.local.Command("stdout-and-stderr-error", { 8 | create: "ls not-a-file index.ts not-a-file-2" 9 | }); -------------------------------------------------------------------------------- /examples/simple-ansible/hello-world.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # From https://gist.github.com/satrapu/31b1a03f321990f8d9ae067372a8b456 3 | # This playbook prints a simple debug message 4 | - name: Echo 5 | hosts: 127.0.0.1 6 | connection: local 7 | 8 | tasks: 9 | - name: Print debug message 10 | debug: 11 | msg: Hello, world! -------------------------------------------------------------------------------- /sdk/nodejs/types/enums/index.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | // Export sub-modules: 5 | import * as local from "./local"; 6 | import * as remote from "./remote"; 7 | 8 | export { 9 | local, 10 | remote, 11 | }; 12 | -------------------------------------------------------------------------------- /sdk/go/command/internal/pulumiVersion.go: -------------------------------------------------------------------------------- 1 | // Code generated by pulumi-language-go DO NOT EDIT. 2 | // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package internal 5 | 6 | import ( 7 | "github.com/blang/semver" 8 | ) 9 | 10 | var SdkVersion semver.Version = semver.Version{} 11 | var pluginDownloadURL string = "" 12 | -------------------------------------------------------------------------------- /.ci-mgmt.yaml: -------------------------------------------------------------------------------- 1 | template: native 2 | provider: command 3 | major-version: 0 4 | aws: true 5 | pulumiVersionFile: .pulumi.version 6 | providerDefaultBranch: main 7 | parallel: 3 8 | timeout: 0.0000001 9 | noSchema: true # Disable schema check. 10 | envOverride: 11 | AWS_REGION: us-west-2 12 | PULUMI_API: https://api.pulumi-staging.io 13 | esc: 14 | enabled: true 15 | -------------------------------------------------------------------------------- /examples/ec2_remote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/aws": "7.15.0", 9 | "@pulumi/command": "latest", 10 | "@pulumi/pulumi": "latest", 11 | "@pulumi/random": "4.18.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/ec2_remote_proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-typescript", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "@types/node": "latest" 6 | }, 7 | "dependencies": { 8 | "@pulumi/aws": "7.15.0", 9 | "@pulumi/command": "1.1.3", 10 | "@pulumi/pulumi": "latest", 11 | "@pulumi/random": "4.18.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/local/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # *** WARNING: this file was generated by pulumi-language-python. *** 3 | # *** Do not edit by hand unless you're certain you know what you are doing! *** 4 | 5 | import builtins as _builtins 6 | from .. import _utilities 7 | import typing 8 | # Export this package's modules as members: 9 | from ._enums import * 10 | from .command import * 11 | from .run import * 12 | -------------------------------------------------------------------------------- /sdk/nodejs/types/index.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | import * as utilities from "../utilities"; 5 | 6 | // Export sub-modules: 7 | import * as enums from "./enums"; 8 | import * as input from "./input"; 9 | import * as output from "./output"; 10 | 11 | export { 12 | enums, 13 | input, 14 | output, 15 | }; 16 | -------------------------------------------------------------------------------- /sdk/java/settings.gradle: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-java-gen. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | pluginManagement { 5 | repositories { 6 | maven { // The google mirror is less flaky than mavenCentral() 7 | url("https://maven-central.storage-download.googleapis.com/maven2/") 8 | } 9 | gradlePluginPortal() 10 | } 11 | } 12 | 13 | rootProject.name = "com.pulumi.command" 14 | include("lib") 15 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/connection_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package remote 4 | 5 | import ( 6 | "net" 7 | "os" 8 | ) 9 | 10 | const sshAgentSocketEnvVar = "SSH_AUTH_SOCK" 11 | 12 | func tryGetDefaultAgentSocket() *string { 13 | if envAgentSocketPath := os.Getenv(sshAgentSocketEnvVar); envAgentSocketPath != "" { 14 | return &envAgentSocketPath 15 | } 16 | return nil 17 | } 18 | 19 | func dialAgent(path string) (net.Conn, error) { 20 | return net.Dial("unix", path) 21 | } 22 | -------------------------------------------------------------------------------- /.github/actions/esc-action/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const file = process.env["GITHUB_OUTPUT"]; 4 | var stream = fs.createWriteStream(file, { flags: "a" }); 5 | 6 | for (const [name, value] of Object.entries(process.env)) { 7 | try { 8 | stream.write(`${name}< { 7 | const crypto = require("crypto"); 8 | return crypto.randomBytes(ev.len/2).toString('hex'); 9 | } 10 | }); 11 | 12 | const rand = new local.Command("execf", { 13 | create: `aws lambda invoke --function-name "$FN" --payload '{"len": 10}' --cli-binary-format raw-in-base64-out out.txt >/dev/null && cat out.txt | tr -d '"' && rm out.txt`, 14 | environment: { 15 | FN: f.qualifiedArn, 16 | AWS_REGION: aws.config.region!, 17 | AWS_PAGER: "", 18 | }, 19 | }) 20 | 21 | export const output = rand.stdout; 22 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/logging_test.go: -------------------------------------------------------------------------------- 1 | package remote_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pulumi/pulumi-command/provider/pkg/provider/remote" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestShouldLog(t *testing.T) { 11 | for _, tc := range []struct { 12 | logging remote.Logging 13 | expectStdout, expectStderr bool 14 | }{ 15 | {remote.LogStdoutAndStderr, true, true}, 16 | {remote.LogStdout, true, false}, 17 | {remote.LogStderr, false, true}, 18 | {remote.NoLogging, false, false}, 19 | } { 20 | t.Run(string(tc.logging), func(t *testing.T) { 21 | assert.Equal(t, tc.expectStdout, tc.logging.ShouldLogStdout()) 22 | assert.Equal(t, tc.expectStderr, tc.logging.ShouldLogStderr()) 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/ProviderArgs.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command; 5 | 6 | 7 | 8 | 9 | public final class ProviderArgs extends com.pulumi.resources.ResourceArgs { 10 | 11 | public static final ProviderArgs Empty = new ProviderArgs(); 12 | 13 | public static Builder builder() { 14 | return new Builder(); 15 | } 16 | 17 | public static final class Builder { 18 | private ProviderArgs $; 19 | 20 | public Builder() { 21 | $ = new ProviderArgs(); 22 | } 23 | public ProviderArgs build() { 24 | return $; 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /.github/actions/download-provider/action.yml: -------------------------------------------------------------------------------- 1 | name: Download Provider Binary 2 | description: Downloads the provider binary artifact and restores executable permissions 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Download provider 8 | uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 9 | with: 10 | name: pulumi-${{ env.PROVIDER }}-provider.tar.gz 11 | path: ${{ github.workspace }}/bin 12 | 13 | - name: UnTar provider binaries 14 | shell: bash 15 | run: tar -zxf ${{ github.workspace }}/bin/provider.tar.gz -C ${{ github.workspace}}/bin 16 | 17 | - name: Restore Binary Permissions 18 | shell: bash 19 | run: find ${{ github.workspace }} -name "pulumi-*-${{ env.PROVIDER }}" -print -exec chmod +x {} \; 20 | -------------------------------------------------------------------------------- /provider/pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | // Version is initialized by the Go linker to contain the semver of this build. 18 | var Version string = "0.7.1" 19 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/local/_enums.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # *** WARNING: this file was generated by pulumi-language-python. *** 3 | # *** Do not edit by hand unless you're certain you know what you are doing! *** 4 | 5 | import builtins as _builtins 6 | import pulumi 7 | from enum import Enum 8 | 9 | __all__ = [ 10 | 'Logging', 11 | ] 12 | 13 | 14 | @pulumi.type_token("command:local:Logging") 15 | class Logging(_builtins.str, Enum): 16 | STDOUT = "stdout" 17 | """ 18 | Capture stdout in logs but not stderr 19 | """ 20 | STDERR = "stderr" 21 | """ 22 | Capture stderr in logs but not stdout 23 | """ 24 | STDOUT_AND_STDERR = "stdoutAndStderr" 25 | """ 26 | Capture stdout and stderr in logs 27 | """ 28 | NONE = "none" 29 | """ 30 | Capture no logs 31 | """ 32 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/remote/_enums.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # *** WARNING: this file was generated by pulumi-language-python. *** 3 | # *** Do not edit by hand unless you're certain you know what you are doing! *** 4 | 5 | import builtins as _builtins 6 | import pulumi 7 | from enum import Enum 8 | 9 | __all__ = [ 10 | 'Logging', 11 | ] 12 | 13 | 14 | @pulumi.type_token("command:remote:Logging") 15 | class Logging(_builtins.str, Enum): 16 | STDOUT = "stdout" 17 | """ 18 | Capture stdout in logs but not stderr 19 | """ 20 | STDERR = "stderr" 21 | """ 22 | Capture stderr in logs but not stdout 23 | """ 24 | STDOUT_AND_STDERR = "stdoutAndStderr" 25 | """ 26 | Capture stdout and stderr in logs 27 | """ 28 | NONE = "none" 29 | """ 30 | Capture no logs 31 | """ 32 | -------------------------------------------------------------------------------- /examples/lambda-ts/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2019, Pulumi Corporation. All rights reserved. 2 | 3 | import * as aws from "@pulumi/aws"; 4 | import { local } from "@pulumi/command"; 5 | 6 | export = async () => { 7 | const build = await local.run({ 8 | command: "yarn && yarn tsc", 9 | dir: "lambda", 10 | archivePaths: ["*.js", "*.js.map"], 11 | }); 12 | 13 | // Lambda 14 | const role = new aws.iam.Role("lambda-role", { 15 | assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ 16 | Service: "lambda.amazonaws.com", 17 | }), 18 | }); 19 | const lambda = new aws.lambda.Function("ts-lambda", { 20 | role: role.arn, 21 | code: build.archive, 22 | handler: "index.handler", 23 | runtime: aws.lambda.Runtime.NodeJS16dX, 24 | }); 25 | 26 | // Exports 27 | return { 28 | invokeArn: lambda.invokeArn, 29 | lambdaArchive: build.archive, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /sdk/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pulumi_command" 3 | description = "The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model." 4 | dependencies = ["parver>=0.2.1", "pulumi>=3.165.0,<4.0.0", "semver>=2.8.1", "typing-extensions>=4.11,<5; python_version < \"3.11\""] 5 | keywords = ["pulumi", "command", "category/utility", "kind/native"] 6 | readme = "README.md" 7 | requires-python = ">=3.9" 8 | version = "1.0.0a0+dev" 9 | [project.license] 10 | text = "Apache-2.0" 11 | [project.urls] 12 | Homepage = "https://pulumi.com" 13 | Repository = "https://github.com/pulumi/pulumi-command" 14 | 15 | [build-system] 16 | requires = ["setuptools>=61.0"] 17 | build-backend = "setuptools.build_meta" 18 | 19 | [tool] 20 | [tool.setuptools] 21 | [tool.setuptools.package-data] 22 | pulumi_command = ["py.typed", "pulumi-plugin.json"] 23 | -------------------------------------------------------------------------------- /provider/pkg/provider/util/testutil/testutil.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | 7 | p "github.com/pulumi/pulumi-go-provider" 8 | "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 9 | ) 10 | 11 | // TestContext is an implementation of p.Context that records all log messages in a buffer, regardless of severity. 12 | type TestContext struct { 13 | context.Context 14 | Output bytes.Buffer 15 | } 16 | 17 | func (c *TestContext) log(msg string) { 18 | c.Output.WriteString(msg) 19 | } 20 | 21 | func (c *TestContext) Log(_ diag.Severity, msg string) { c.log(msg) } 22 | func (c *TestContext) Logf(_ diag.Severity, msg string, _ ...any) { c.log(msg) } 23 | func (c *TestContext) LogStatus(_ diag.Severity, msg string) { c.log(msg) } 24 | func (c *TestContext) LogStatusf(_ diag.Severity, msg string, _ ...any) { c.log(msg) } 25 | func (c *TestContext) RuntimeInformation() p.RunInfo { return p.RunInfo{} } 26 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten if not made via https://github.com/pulumi/ci-mgmt 2 | 3 | name: pull-request 4 | on: 5 | pull_request_target: {} 6 | 7 | jobs: 8 | comment-on-pr: 9 | runs-on: ubuntu-latest 10 | name: comment-on-pr 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 14 | with: 15 | lfs: true 16 | - name: Comment PR 17 | uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 18 | with: 19 | message: > 20 | PR is now waiting for a maintainer to run the acceptance tests. 21 | 22 | **Note for the maintainer:** To run the acceptance tests, please comment */run-acceptance-tests* on the PR 23 | github-token: ${{ secrets.GITHUB_TOKEN }} 24 | if: github.event.pull_request.head.repo.full_name != github.repository 25 | -------------------------------------------------------------------------------- /sdk/nodejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "bin", 4 | "target": "ES2020", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "stripInternal": true, 10 | "experimentalDecorators": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true 14 | }, 15 | "files": [ 16 | "index.ts", 17 | "local/command.ts", 18 | "local/index.ts", 19 | "local/run.ts", 20 | "provider.ts", 21 | "remote/command.ts", 22 | "remote/copyFile.ts", 23 | "remote/copyToRemote.ts", 24 | "remote/index.ts", 25 | "types/enums/index.ts", 26 | "types/enums/local/index.ts", 27 | "types/enums/remote/index.ts", 28 | "types/index.ts", 29 | "types/input.ts", 30 | "types/output.ts", 31 | "utilities.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Pulumi Corporation. All rights reserved. 2 | 3 | package examples 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "testing" 9 | 10 | "github.com/pulumi/pulumi/pkg/v3/testing/integration" 11 | ) 12 | 13 | func getRegion(t *testing.T) string { 14 | region := os.Getenv("AWS_REGION") 15 | if region == "" { 16 | region = "us-east-2" 17 | fmt.Println("Defaulting region to 'us-east-2'. You can override using the AWS_REGION variable.") 18 | } 19 | 20 | return region 21 | } 22 | 23 | func getBaseOptions(t *testing.T) integration.ProgramTestOptions { 24 | awsRegion := getRegion(t) 25 | return integration.ProgramTestOptions{ 26 | ExpectRefreshChanges: true, 27 | Config: map[string]string{ 28 | "aws:region": awsRegion, 29 | }, 30 | } 31 | } 32 | 33 | func getCwd(t *testing.T) string { 34 | cwd, err := os.Getwd() 35 | if err != nil { 36 | t.FailNow() 37 | } 38 | 39 | return cwd 40 | } 41 | 42 | func skipIfShort(t *testing.T) { 43 | if testing.Short() { 44 | t.Skip("skipping long-running test in short mode") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sdk/go/command/init.go: -------------------------------------------------------------------------------- 1 | // Code generated by pulumi-language-go DO NOT EDIT. 2 | // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package command 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/blang/semver" 10 | "github.com/pulumi/pulumi-command/sdk/go/command/internal" 11 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 12 | ) 13 | 14 | type pkg struct { 15 | version semver.Version 16 | } 17 | 18 | func (p *pkg) Version() semver.Version { 19 | return p.version 20 | } 21 | 22 | func (p *pkg) ConstructProvider(ctx *pulumi.Context, name, typ, urn string) (pulumi.ProviderResource, error) { 23 | if typ != "pulumi:providers:command" { 24 | return nil, fmt.Errorf("unknown provider type: %s", typ) 25 | } 26 | 27 | r := &Provider{} 28 | err := ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn)) 29 | return r, err 30 | } 31 | 32 | func init() { 33 | version, err := internal.PkgVersion() 34 | if err != nil { 35 | version = semver.Version{Major: 1} 36 | } 37 | pulumi.RegisterResourcePackage( 38 | "command", 39 | &pkg{version}, 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /sdk/go/command/local/init.go: -------------------------------------------------------------------------------- 1 | // Code generated by pulumi-language-go DO NOT EDIT. 2 | // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package local 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/blang/semver" 10 | "github.com/pulumi/pulumi-command/sdk/go/command/internal" 11 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 12 | ) 13 | 14 | type module struct { 15 | version semver.Version 16 | } 17 | 18 | func (m *module) Version() semver.Version { 19 | return m.version 20 | } 21 | 22 | func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi.Resource, err error) { 23 | switch typ { 24 | case "command:local:Command": 25 | r = &Command{} 26 | default: 27 | return nil, fmt.Errorf("unknown resource type: %s", typ) 28 | } 29 | 30 | err = ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn)) 31 | return 32 | } 33 | 34 | func init() { 35 | version, err := internal.PkgVersion() 36 | if err != nil { 37 | version = semver.Version{Major: 1} 38 | } 39 | pulumi.RegisterResourceModule( 40 | "command", 41 | "local", 42 | &module{version}, 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/export-repo-secrets.yml: -------------------------------------------------------------------------------- 1 | permissions: write-all # Equivalent to default permissions plus id-token: write 2 | name: Export secrets to ESC 3 | on: [workflow_dispatch] 4 | jobs: 5 | export-to-esc: 6 | runs-on: ubuntu-latest 7 | name: export GitHub secrets to ESC 8 | steps: 9 | - name: Generate a GitHub token 10 | id: generate-token 11 | uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 12 | with: 13 | app-id: 1256780 # Export Secrets GitHub App 14 | private-key: ${{ secrets.EXPORT_SECRETS_PRIVATE_KEY }} 15 | - name: Export secrets to ESC 16 | uses: pulumi/esc-export-secrets-action@9d6485759b6adff2538ae91f1b77cc96265c9dad # v1 17 | with: 18 | organization: pulumi 19 | org-environment: imports/github-secrets 20 | exclude-secrets: EXPORT_SECRETS_PRIVATE_KEY 21 | github-token: ${{ steps.generate-token.outputs.token }} 22 | oidc-auth: true 23 | oidc-requested-token-type: urn:pulumi:token-type:access_token:organization 24 | env: 25 | GITHUB_SECRETS: ${{ toJSON(secrets) }} 26 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sdk/nodejs/index.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | import * as pulumi from "@pulumi/pulumi"; 5 | import * as utilities from "./utilities"; 6 | 7 | // Export members: 8 | export { ProviderArgs } from "./provider"; 9 | export type Provider = import("./provider").Provider; 10 | export const Provider: typeof import("./provider").Provider = null as any; 11 | utilities.lazyLoad(exports, ["Provider"], () => require("./provider")); 12 | 13 | 14 | // Export sub-modules: 15 | import * as local from "./local"; 16 | import * as remote from "./remote"; 17 | import * as types from "./types"; 18 | 19 | export { 20 | local, 21 | remote, 22 | types, 23 | }; 24 | pulumi.runtime.registerResourcePackage("command", { 25 | version: utilities.getVersion(), 26 | constructProvider: (name: string, type: string, urn: string): pulumi.ProviderResource => { 27 | if (type !== "pulumi:providers:command") { 28 | throw new Error(`unknown provider type ${type}`); 29 | } 30 | return new Provider(name, undefined, { urn }); 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /examples/simple-run/index.ts: -------------------------------------------------------------------------------- 1 | import * as local from "@pulumi/command/local"; 2 | import * as random from "@pulumi/random"; 3 | import { interpolate } from "@pulumi/pulumi"; 4 | 5 | const pw = new random.RandomPassword("pw", { length: 10, special: false }); 6 | 7 | const plainFile = local.runOutput({ 8 | command: `echo "Hello world!" > hello.txt`, 9 | assetPaths: ["*.txt", "!**password**"], 10 | archivePaths: ["*.txt", "!**password**"], 11 | }); 12 | 13 | const secretFile = local.runOutput({ 14 | command: interpolate`echo "${pw.result}" > password.txt`, 15 | assetPaths: ["password.txt"] 16 | }); 17 | 18 | const globTest = local.runOutput({ 19 | command: "pwd", 20 | dir: process.cwd(), 21 | archivePaths: [ 22 | "**/*.txt", 23 | "*", 24 | "!yarn.lock", 25 | "!**password**", 26 | ] 27 | }) 28 | 29 | export const plainOutput = plainFile.stdout; 30 | export const plainAssets = plainFile.assets; 31 | export const plainArchive = plainFile.archive; 32 | export const secretOutput = secretFile.stdout; 33 | export const secretAssets = secretFile.assets; 34 | export const secretArchive = secretFile.archive; 35 | export const globTestAssets = globTest.archive; 36 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/runController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package local 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/pulumi/pulumi-go-provider/infer" 21 | ) 22 | 23 | // Invoke takes a RunInputs parameter and runs the command specified in 24 | // it. 25 | func (*Run) Invoke(ctx context.Context, req infer.FunctionRequest[RunInputs]) (infer.FunctionResponse[RunOutputs], error) { 26 | input := req.Input 27 | r := RunOutputs{RunInputs: input} 28 | err := run(ctx, input.Command, r.RunInputs.BaseInputs, &r.BaseOutputs, input.Logging) 29 | return infer.FunctionResponse[RunOutputs]{Output: r}, err 30 | } 31 | -------------------------------------------------------------------------------- /provider/pkg/provider/util/testutil/test_ssh_server.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/gliderlabs/ssh" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type TestSshServer struct { 15 | Host string 16 | Port int64 17 | } 18 | 19 | // NewTestSshServer creates a new in-process SSH server with the specified handler. 20 | // The server is bound to an arbitrary free port, and automatically closed 21 | // during test cleanup. 22 | func NewTestSshServer(t *testing.T, handler ssh.Handler) TestSshServer { 23 | const host = "127.0.0.1" 24 | 25 | listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, 0)) 26 | require.NoErrorf(t, err, "net.Listen()") 27 | 28 | port, err := strconv.ParseInt(strings.Split(listener.Addr().String(), ":")[1], 10, 64) 29 | require.NoErrorf(t, err, "parse address %s allocated port number as int", listener.Addr()) 30 | 31 | server := ssh.Server{Handler: handler} 32 | go func() { 33 | // "Serve always returns a non-nil error." 34 | _ = server.Serve(listener) 35 | }() 36 | t.Cleanup(func() { 37 | _ = server.Close() 38 | }) 39 | 40 | return TestSshServer{ 41 | Host: host, 42 | Port: port, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sdk/go/command/remote/init.go: -------------------------------------------------------------------------------- 1 | // Code generated by pulumi-language-go DO NOT EDIT. 2 | // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package remote 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/blang/semver" 10 | "github.com/pulumi/pulumi-command/sdk/go/command/internal" 11 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 12 | ) 13 | 14 | type module struct { 15 | version semver.Version 16 | } 17 | 18 | func (m *module) Version() semver.Version { 19 | return m.version 20 | } 21 | 22 | func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi.Resource, err error) { 23 | switch typ { 24 | case "command:remote:Command": 25 | r = &Command{} 26 | case "command:remote:CopyFile": 27 | r = &CopyFile{} 28 | case "command:remote:CopyToRemote": 29 | r = &CopyToRemote{} 30 | default: 31 | return nil, fmt.Errorf("unknown resource type: %s", typ) 32 | } 33 | 34 | err = ctx.RegisterResource(typ, name, nil, r, pulumi.URN_(urn)) 35 | return 36 | } 37 | 38 | func init() { 39 | version, err := internal.PkgVersion() 40 | if err != nil { 41 | version = semver.Version{Major: 1} 42 | } 43 | pulumi.RegisterResourceModule( 44 | "command", 45 | "remote", 46 | &module{version}, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/baseOutputs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "github.com/pulumi/pulumi-go-provider/infer" 19 | ) 20 | 21 | type BaseOutputs struct { 22 | Stdout string `pulumi:"stdout"` 23 | Stderr string `pulumi:"stderr"` 24 | } 25 | 26 | // Implementing Annotate lets you provide descriptions and default values for fields and they will 27 | // be visible in the provider's schema and the generated SDKs. 28 | func (c *BaseOutputs) Annotate(a infer.Annotator) { 29 | a.Describe(&c.Stdout, "The standard output of the command's process") 30 | a.Describe(&c.Stderr, "The standard error of the command's process") 31 | } 32 | -------------------------------------------------------------------------------- /.github/actions/setup-tools/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Tools 2 | description: Installs all tools (Go, Node, Python, .NET, Java, Pulumi, etc.) using mise 3 | 4 | inputs: 5 | cache: 6 | description: Enable caching 7 | required: false 8 | default: "false" 9 | github_token: 10 | description: GitHub token 11 | required: true 12 | 13 | runs: 14 | using: "composite" 15 | steps: 16 | - name: Setup mise 17 | uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3 18 | env: 19 | MISE_FETCH_REMOTE_VERSIONS_TIMEOUT: 30s 20 | with: 21 | version: 2025.11.6 22 | cache_save: ${{ inputs.cache }} 23 | github_token: ${{ inputs.github_token }} 24 | 25 | - name: Setup Go Cache 26 | uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 27 | with: 28 | cache: ${{ inputs.cache }} 29 | cache-dependency-path: | 30 | provider/*.sum 31 | upstream/*.sum 32 | sdk/go/*.sum 33 | sdk/*.sum 34 | *.sum 35 | 36 | - name: Setup Node 37 | uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 38 | with: 39 | # we don't set node-version because we install with mise. 40 | # this step is needed to setup npm auth 41 | registry-url: https://registry.npmjs.org 42 | -------------------------------------------------------------------------------- /.config/mise.toml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | # You can create your own root-level mise.toml file to override/augment this. See https://mise.jdx.dev/configuration.html 3 | 4 | [env] 5 | _.source = "{{config_root}}/scripts/get-versions.sh" 6 | PULUMI_HOME = "{{config_root}}/.pulumi" 7 | 8 | [tools] 9 | 10 | # Runtimes 11 | # TODO: we may not need 'get_env' once https://github.com/jdx/mise/discussions/6339 is fixed 12 | go = "{{ get_env(name='GO_VERSION_MISE', default='latest') }}" 13 | node = '20.19.5' 14 | python = '3.11.8' 15 | dotnet = '8.0.414' 16 | # Corretto version used as Java SE/OpenJDK version no longer offered 17 | java = 'corretto-11' 18 | 19 | # Executable tools 20 | pulumi = "{{ get_env(name='PULUMI_VERSION_MISE', default='latest') }}" 21 | "github:pulumi/pulumictl" = '0.0.50' 22 | "github:pulumi/schema-tools" = "0.6.0" 23 | "aqua:gradle/gradle-distributions" = '7.6.6' 24 | golangci-lint = "1.64.8" # See note about about overrides if you need to customize this. 25 | "npm:yarn" = "1.22.22" 26 | 27 | [settings] 28 | experimental = true # Required for Go binaries (e.g. pulumictl). 29 | lockfile = false 30 | http_retries = 3 31 | pin = true # `mise use` should pin versions instead of defaulting to latest. 32 | 33 | [plugins] 34 | vfox-pulumi = "https://github.com/pulumi/vfox-pulumi" 35 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/logging.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | // TODO This file (along with logging_test.go) should be in the `common` package since its contents are used by `local`` and `remote`. 4 | // It's duplicated in `local` and `remote` for the time being due to pulumi/pulumi#16221, and changes need to be made in both copies. 5 | 6 | import "github.com/pulumi/pulumi-go-provider/infer" 7 | 8 | type Logging string 9 | 10 | const ( 11 | LogStdout Logging = "stdout" 12 | LogStderr Logging = "stderr" 13 | LogStdoutAndStderr Logging = "stdoutAndStderr" 14 | NoLogging Logging = "none" 15 | ) 16 | 17 | func (Logging) Values() []infer.EnumValue[Logging] { 18 | return []infer.EnumValue[Logging]{ 19 | {Name: string(LogStdout), Value: LogStdout, Description: "Capture stdout in logs but not stderr"}, 20 | {Name: string(LogStderr), Value: LogStderr, Description: "Capture stderr in logs but not stdout"}, 21 | {Name: string(LogStdoutAndStderr), Value: LogStdoutAndStderr, Description: "Capture stdout and stderr in logs"}, 22 | {Name: string(NoLogging), Value: NoLogging, Description: "Capture no logs"}, 23 | } 24 | } 25 | 26 | func (l *Logging) ShouldLogStdout() bool { 27 | return l == nil || *l == LogStdout || *l == LogStdoutAndStderr 28 | } 29 | func (l *Logging) ShouldLogStderr() bool { 30 | return l == nil || *l == LogStderr || *l == LogStdoutAndStderr 31 | } 32 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/logging.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | // TODO This file (along with logging_test.go) should be in the `common` package since its contents are used by `local`` and `remote`. 4 | // It's duplicated in `local` and `remote` for the time being due to pulumi/pulumi#16221, and changes need to be made in both copies. 5 | 6 | import "github.com/pulumi/pulumi-go-provider/infer" 7 | 8 | type Logging string 9 | 10 | const ( 11 | LogStdout Logging = "stdout" 12 | LogStderr Logging = "stderr" 13 | LogStdoutAndStderr Logging = "stdoutAndStderr" 14 | NoLogging Logging = "none" 15 | ) 16 | 17 | func (Logging) Values() []infer.EnumValue[Logging] { 18 | return []infer.EnumValue[Logging]{ 19 | {Name: string(LogStdout), Value: LogStdout, Description: "Capture stdout in logs but not stderr"}, 20 | {Name: string(LogStderr), Value: LogStderr, Description: "Capture stderr in logs but not stdout"}, 21 | {Name: string(LogStdoutAndStderr), Value: LogStdoutAndStderr, Description: "Capture stdout and stderr in logs"}, 22 | {Name: string(NoLogging), Value: NoLogging, Description: "Capture no logs"}, 23 | } 24 | } 25 | 26 | func (l *Logging) ShouldLogStdout() bool { 27 | return l == nil || *l == LogStdout || *l == LogStdoutAndStderr 28 | } 29 | func (l *Logging) ShouldLogStderr() bool { 30 | return l == nil || *l == LogStderr || *l == LogStdoutAndStderr 31 | } 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten if not made via https://github.com/pulumi/ci-mgmt 2 | project_name: pulumi-command 3 | before: 4 | hooks: 5 | - make codegen 6 | builds: 7 | - id: build-provider 8 | dir: provider 9 | env: 10 | - CGO_ENABLED=0 11 | - GO111MODULE=on 12 | goos: 13 | - darwin 14 | - linux 15 | goarch: 16 | - amd64 17 | - arm64 18 | ignore: &a1 [] 19 | main: ./cmd/pulumi-resource-command/ 20 | ldflags: &a2 21 | - -s 22 | - -w 23 | - -X github.com/pulumi/pulumi-command/provider/pkg/version.Version={{.Tag}} 24 | binary: pulumi-resource-command 25 | - id: build-provider-sign-windows 26 | dir: provider 27 | env: 28 | - CGO_ENABLED=0 29 | - GO111MODULE=on 30 | goos: 31 | - windows 32 | goarch: 33 | - amd64 34 | - arm64 35 | ignore: *a1 36 | main: ./cmd/pulumi-resource-command/ 37 | ldflags: *a2 38 | binary: pulumi-resource-command 39 | hooks: 40 | post: 41 | - make sign-goreleaser-exe-{{ .Arch }} 42 | archives: 43 | - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}" 44 | id: archive 45 | snapshot: 46 | name_template: "{{ .Tag }}-SNAPSHOT" 47 | changelog: 48 | skip: true 49 | release: 50 | disable: false 51 | blobs: 52 | - provider: s3 53 | region: us-west-2 54 | bucket: get.pulumi.com 55 | folder: releases/plugins/ 56 | ids: 57 | - archive 58 | -------------------------------------------------------------------------------- /sdk/nodejs/local/index.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | import * as pulumi from "@pulumi/pulumi"; 5 | import * as utilities from "../utilities"; 6 | 7 | // Export members: 8 | export { CommandArgs } from "./command"; 9 | export type Command = import("./command").Command; 10 | export const Command: typeof import("./command").Command = null as any; 11 | utilities.lazyLoad(exports, ["Command"], () => require("./command")); 12 | 13 | export { RunArgs, RunResult, RunOutputArgs } from "./run"; 14 | export const run: typeof import("./run").run = null as any; 15 | export const runOutput: typeof import("./run").runOutput = null as any; 16 | utilities.lazyLoad(exports, ["run","runOutput"], () => require("./run")); 17 | 18 | 19 | // Export enums: 20 | export * from "../types/enums/local"; 21 | 22 | const _module = { 23 | version: utilities.getVersion(), 24 | construct: (name: string, type: string, urn: string): pulumi.Resource => { 25 | switch (type) { 26 | case "command:local:Command": 27 | return new Command(name, undefined, { urn }) 28 | default: 29 | throw new Error(`unknown resource type ${type}`); 30 | } 31 | }, 32 | }; 33 | pulumi.runtime.registerResourceModule("command", "local", _module) 34 | -------------------------------------------------------------------------------- /.goreleaser.prerelease.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten if not made via https://github.com/pulumi/ci-mgmt 2 | 3 | project_name: pulumi-command 4 | before: 5 | hooks: 6 | - make codegen 7 | builds: 8 | - id: build-provider 9 | dir: provider 10 | env: 11 | - CGO_ENABLED=0 12 | - GO111MODULE=on 13 | goos: 14 | - darwin 15 | - linux 16 | goarch: 17 | - amd64 18 | - arm64 19 | ignore: &a1 [] 20 | main: ./cmd/pulumi-resource-command/ 21 | ldflags: &a2 22 | - -s 23 | - -w 24 | - -X github.com/pulumi/pulumi-command/provider/pkg/version.Version={{.Tag}} 25 | binary: pulumi-resource-command 26 | - id: build-provider-sign-windows 27 | dir: provider 28 | env: 29 | - CGO_ENABLED=0 30 | - GO111MODULE=on 31 | goos: 32 | - windows 33 | goarch: 34 | - amd64 35 | - arm64 36 | ignore: *a1 37 | main: ./cmd/pulumi-resource-command/ 38 | ldflags: *a2 39 | binary: pulumi-resource-command 40 | hooks: 41 | post: 42 | - make sign-goreleaser-exe-{{ .Arch }} 43 | archives: 44 | - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}" 45 | id: archive 46 | snapshot: 47 | name_template: "{{ .Tag }}-SNAPSHOT" 48 | changelog: 49 | skip: true 50 | release: 51 | disable: true 52 | blobs: 53 | - provider: s3 54 | region: us-west-2 55 | bucket: get.pulumi.com 56 | folder: releases/plugins/ 57 | ids: 58 | - archive 59 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # *** WARNING: this file was generated by pulumi-language-python. *** 3 | # *** Do not edit by hand unless you're certain you know what you are doing! *** 4 | 5 | import builtins as _builtins 6 | from . import _utilities 7 | import typing 8 | # Export this package's modules as members: 9 | from .provider import * 10 | 11 | # Make subpackages available: 12 | if typing.TYPE_CHECKING: 13 | import pulumi_command.local as __local 14 | local = __local 15 | import pulumi_command.remote as __remote 16 | remote = __remote 17 | else: 18 | local = _utilities.lazy_import('pulumi_command.local') 19 | remote = _utilities.lazy_import('pulumi_command.remote') 20 | 21 | _utilities.register( 22 | resource_modules=""" 23 | [ 24 | { 25 | "pkg": "command", 26 | "mod": "local", 27 | "fqn": "pulumi_command.local", 28 | "classes": { 29 | "command:local:Command": "Command" 30 | } 31 | }, 32 | { 33 | "pkg": "command", 34 | "mod": "remote", 35 | "fqn": "pulumi_command.remote", 36 | "classes": { 37 | "command:remote:Command": "Command", 38 | "command:remote:CopyFile": "CopyFile", 39 | "command:remote:CopyToRemote": "CopyToRemote" 40 | } 41 | } 42 | ] 43 | """, 44 | resource_packages=""" 45 | [ 46 | { 47 | "pkg": "command", 48 | "token": "pulumi:providers:command", 49 | "fqn": "pulumi_command", 50 | "class": "Provider" 51 | } 52 | ] 53 | """ 54 | ) 55 | -------------------------------------------------------------------------------- /examples/curl/index.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from "@pulumi/pulumi"; 2 | import * as random from "@pulumi/random"; 3 | import { local } from "@pulumi/command"; 4 | 5 | interface LabelArgs { 6 | owner: pulumi.Input; 7 | repo: pulumi.Input; 8 | name: pulumi.Input; 9 | githubToken: pulumi.Input; 10 | } 11 | 12 | class GitHubLabel extends pulumi.ComponentResource { 13 | public url: pulumi.Output; 14 | 15 | constructor(name: string, args: LabelArgs, opts?: pulumi.ComponentResourceOptions) { 16 | super("example:github:Label", name, args, opts); 17 | 18 | const label = new local.Command("label", { 19 | create: "./create_label.sh", 20 | delete: "./delete_label.sh", 21 | environment: { 22 | OWNER: args.owner, 23 | REPO: args.repo, 24 | NAME: args.name, 25 | GITHUB_TOKEN: args.githubToken, 26 | } 27 | }, { parent: this }); 28 | 29 | const response = label.stdout.apply(JSON.parse); 30 | this.url = response.apply((x: any) => x.url as string); 31 | } 32 | } 33 | 34 | const config = new pulumi.Config(); 35 | const rand = new random.RandomString("s", { length: 10, special: false }); 36 | 37 | const label = new GitHubLabel("l", { 38 | owner: "pulumi", 39 | repo: "pulumi-command", 40 | name: rand.result, 41 | githubToken: config.requireSecret("githubToken"), 42 | }); 43 | 44 | export const labelUrl = label.url; 45 | -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/local/enums/Logging.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command.local.enums; 5 | 6 | import com.pulumi.core.annotations.EnumType; 7 | import java.lang.String; 8 | import java.util.Objects; 9 | import java.util.StringJoiner; 10 | 11 | @EnumType 12 | public enum Logging { 13 | /** 14 | * Capture stdout in logs but not stderr 15 | * 16 | */ 17 | Stdout("stdout"), 18 | /** 19 | * Capture stderr in logs but not stdout 20 | * 21 | */ 22 | Stderr("stderr"), 23 | /** 24 | * Capture stdout and stderr in logs 25 | * 26 | */ 27 | StdoutAndStderr("stdoutAndStderr"), 28 | /** 29 | * Capture no logs 30 | * 31 | */ 32 | None("none"); 33 | 34 | private final String value; 35 | 36 | Logging(String value) { 37 | this.value = Objects.requireNonNull(value); 38 | } 39 | 40 | @EnumType.Converter 41 | public String getValue() { 42 | return this.value; 43 | } 44 | 45 | @Override 46 | public java.lang.String toString() { 47 | return new StringJoiner(", ", "Logging[", "]") 48 | .add("value='" + this.value + "'") 49 | .toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/remote/enums/Logging.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command.remote.enums; 5 | 6 | import com.pulumi.core.annotations.EnumType; 7 | import java.lang.String; 8 | import java.util.Objects; 9 | import java.util.StringJoiner; 10 | 11 | @EnumType 12 | public enum Logging { 13 | /** 14 | * Capture stdout in logs but not stderr 15 | * 16 | */ 17 | Stdout("stdout"), 18 | /** 19 | * Capture stderr in logs but not stdout 20 | * 21 | */ 22 | Stderr("stderr"), 23 | /** 24 | * Capture stdout and stderr in logs 25 | * 26 | */ 27 | StdoutAndStderr("stdoutAndStderr"), 28 | /** 29 | * Capture no logs 30 | * 31 | */ 32 | None("none"); 33 | 34 | private final String value; 35 | 36 | Logging(String value) { 37 | this.value = Objects.requireNonNull(value); 38 | } 39 | 40 | @EnumType.Converter 41 | public String getValue() { 42 | return this.value; 43 | } 44 | 45 | @Override 46 | public java.lang.String toString() { 47 | return new StringJoiner(", ", "Logging[", "]") 48 | .add("value='" + this.value + "'") 49 | .toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /provider/cmd/pulumi-resource-command/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "strings" 22 | 23 | command "github.com/pulumi/pulumi-command/provider/pkg/provider" 24 | "github.com/pulumi/pulumi-command/provider/pkg/version" 25 | ) 26 | 27 | // A provider is a program that listens for requests from the Pulumi engine 28 | // to interact with cloud providers using a CRUD-based model. 29 | func main() { 30 | version := strings.TrimPrefix(version.Version, "v") 31 | 32 | // This method defines the provider implemented in this repository. 33 | commandProvider := command.NewProvider() 34 | 35 | // This method starts serving requests using the Command provider. 36 | err := commandProvider.Run(context.Background(), "command", version) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Error: %s", err.Error()) 39 | os.Exit(1) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Epic 3 | about: Tracks a shippable unit of work 4 | title: '[Epic] {your-title-here}' 5 | labels: kind/epic 6 | projects: ['pulumi/32'] 7 | assignees: '' 8 | type: Epic 9 | --- 10 | 11 | ## Overview 12 | 13 | 14 | ## Key KPIs 15 | 16 | 17 | ## Key Stakeholders 18 | - Product and Engineering: 19 | - Documentation: 20 | - Marketing/Partnerships: 21 | - Customers: 22 | 23 | ## Key Deliverables 24 | 25 | 26 | ### References 📔 27 | 28 | 29 | - [ ] Project View 30 | - [ ] PR/FAQ 31 | - [ ] Design Doc 32 | - [ ] UX Designs 33 | - [ ] Decision Log 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/simple/index.ts: -------------------------------------------------------------------------------- 1 | import * as local from "@pulumi/command/local"; 2 | import * as random from "@pulumi/random"; 3 | import { interpolate } from "@pulumi/pulumi"; 4 | import { len, fail, update } from "./extras"; 5 | 6 | const pw = new random.RandomPassword("pw", { length: len, special: false }); 7 | 8 | const pwd = new local.Command("pwd", { 9 | create: interpolate`echo "${pw.result}" > password.txt`, 10 | delete: `rm -f password.txt`, 11 | triggers: [pw.result], 12 | }, { deleteBeforeReplace: true }); 13 | 14 | 15 | let deleteCommand = "rm -f password2.txt"; 16 | if (update) { 17 | deleteCommand += " && echo 'deleted'"; 18 | } 19 | const pwd2 = new local.Command("pwd2", { 20 | create: `echo "$PASSWORD" > password2.txt`, 21 | delete: deleteCommand, 22 | environment: { 23 | PASSWORD: pw.result, 24 | }, 25 | triggers: [pw.result], 26 | }, { deleteBeforeReplace: true }); 27 | 28 | // Manage an external artifact which is created after a resource is created, 29 | // deleted before a resource is destroyed, and recreated when the resource is 30 | // replaced. This could also register/deregister the resource with an external 31 | // registration or other remote API instead of just writing to local disk. 32 | const pwd3 = new local.Command("pwd3", { 33 | create: interpolate`touch "${pw.result}.txt"`, 34 | delete: interpolate`rm "${pw.result}.txt"`, 35 | triggers: [pw.result], 36 | }) 37 | 38 | if (fail) { 39 | new local.Command("fail", { 40 | create: `echo "couldn't do what I wanted..." && false`, 41 | }); 42 | } 43 | 44 | export const output = pwd.stdout; 45 | -------------------------------------------------------------------------------- /scripts/get-versions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # This script can be simplified to use go when https://github.com/jdx/mise/discussions/6374 is fixed 5 | # e.g. go list -m -f '{{.GoVersion}}' 6 | 7 | module_path="github.com/pulumi/pulumi/pkg/v3" 8 | go_mod_path="provider" 9 | gomod="go.mod" 10 | 11 | if [[ "$go_mod_path" != "" && "$go_mod_path" != "." ]]; then 12 | gomod="$go_mod_path/$gomod" 13 | fi 14 | 15 | if [[ ! -f "$gomod" ]]; then 16 | echo "missing $gomod" >&2 17 | exit 1 18 | fi 19 | 20 | raw_version=$(awk -v module="$module_path" ' 21 | $1 == module || $2 == module { 22 | for (i = 1; i <= NF; i++) { 23 | if ($i ~ /^v[0-9]/) { 24 | sub(/^v/, "", $i) 25 | print $i 26 | exit 27 | } 28 | } 29 | } 30 | ' "$gomod") 31 | 32 | if [[ -z "${raw_version:-}" ]]; then 33 | echo "failed to determine Pulumi version from $gomod" >&2 34 | exit 1 35 | fi 36 | 37 | echo "PULUMI_VERSION_MISE=$raw_version" 38 | export PULUMI_VERSION_MISE=$raw_version 39 | 40 | # Prefer the toolchain directive if present, otherwise fall back to the `go` version line 41 | go_toolchain=$(awk '/^toolchain[[:space:]]+go[0-9]/{ print $2; exit }' "$gomod") 42 | 43 | if [[ -n "${go_toolchain:-}" ]]; then 44 | go_version=${go_toolchain#go} 45 | else 46 | go_version=$(awk '/^go[[:space:]]+[0-9]/{ print $2; exit }' "$gomod") 47 | fi 48 | 49 | if [[ -z "${go_version:-}" ]]; then 50 | echo "failed to determine Go version from $gomod" >&2 51 | exit 1 52 | fi 53 | 54 | echo "GO_VERSION_MISE=$go_version" 55 | export GO_VERSION_MISE=$go_version 56 | -------------------------------------------------------------------------------- /examples/examples_go_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2021, Pulumi Corporation. All rights reserved. 2 | //go:build go || all 3 | // +build go all 4 | 5 | package examples 6 | 7 | import ( 8 | "path/filepath" 9 | "testing" 10 | 11 | "github.com/pulumi/pulumi/pkg/v3/testing/integration" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestRandomGo(t *testing.T) { 16 | test := getGoBaseOptions(t). 17 | With(integration.ProgramTestOptions{ 18 | Dir: filepath.Join(getCwd(t), "random-go"), 19 | ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { 20 | out, ok := stack.Outputs["output"].(string) 21 | assert.True(t, ok) 22 | assert.Len(t, out, 32) 23 | }, 24 | }) 25 | integration.ProgramTest(t, &test) 26 | } 27 | 28 | func TestStdinGo(t *testing.T) { 29 | test := getGoBaseOptions(t). 30 | With(integration.ProgramTestOptions{ 31 | Dir: filepath.Join(getCwd(t), "stdin-go"), 32 | ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) { 33 | out, ok := stack.Outputs["output"].(string) 34 | assert.True(t, ok) 35 | assert.Equal(t, "the quick brown fox", out) 36 | assets, ok := stack.Outputs["assets"].(map[string]interface{}) 37 | assert.True(t, ok) 38 | assert.Len(t, assets, 1) 39 | }, 40 | }) 41 | integration.ProgramTest(t, &test) 42 | } 43 | 44 | func getGoBaseOptions(t *testing.T) integration.ProgramTestOptions { 45 | base := getBaseOptions(t) 46 | baseGo := base.With(integration.ProgramTestOptions{ 47 | Verbose: true, 48 | Dependencies: []string{ 49 | "github.com/pulumi/pulumi-command/sdk", 50 | }, 51 | }) 52 | 53 | return baseGo 54 | } 55 | -------------------------------------------------------------------------------- /sdk/nodejs/provider.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | import * as pulumi from "@pulumi/pulumi"; 5 | import * as utilities from "./utilities"; 6 | 7 | export class Provider extends pulumi.ProviderResource { 8 | /** @internal */ 9 | public static readonly __pulumiType = 'command'; 10 | 11 | /** 12 | * Returns true if the given object is an instance of Provider. This is designed to work even 13 | * when multiple copies of the Pulumi SDK have been loaded into the same process. 14 | */ 15 | public static isInstance(obj: any): obj is Provider { 16 | if (obj === undefined || obj === null) { 17 | return false; 18 | } 19 | return obj['__pulumiType'] === "pulumi:providers:" + Provider.__pulumiType; 20 | } 21 | 22 | 23 | /** 24 | * Create a Provider resource with the given unique name, arguments, and options. 25 | * 26 | * @param name The _unique_ name of the resource. 27 | * @param args The arguments to use to populate this resource's properties. 28 | * @param opts A bag of options that control this resource's behavior. 29 | */ 30 | constructor(name: string, args?: ProviderArgs, opts?: pulumi.ResourceOptions) { 31 | let resourceInputs: pulumi.Inputs = {}; 32 | opts = opts || {}; 33 | { 34 | } 35 | opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts); 36 | super(Provider.__pulumiType, name, resourceInputs, opts); 37 | } 38 | } 39 | 40 | /** 41 | * The set of arguments for constructing a Provider resource. 42 | */ 43 | export interface ProviderArgs { 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/command-dispatch.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | 3 | env: 4 | AWS_REGION: us-west-2 5 | PULUMI_API: https://api.pulumi-staging.io 6 | PULUMI_PULUMI_ENABLE_JOURNALING: "true" 7 | 8 | jobs: 9 | command-dispatch-for-testing: 10 | name: command-dispatch-for-testing 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | id-token: write # For ESC secrets. 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 18 | with: 19 | persist-credentials: false 20 | - env: 21 | ESC_ACTION_ENVIRONMENT: github-secrets/${{ github.repository_owner }}-${{ github.event.repository.name }} 22 | ESC_ACTION_EXPORT_ENVIRONMENT_VARIABLES: "false" 23 | ESC_ACTION_OIDC_AUTH: "true" 24 | ESC_ACTION_OIDC_ORGANIZATION: pulumi 25 | ESC_ACTION_OIDC_REQUESTED_TOKEN_TYPE: urn:pulumi:token-type:access_token:organization 26 | id: esc-secrets 27 | name: Fetch secrets from ESC 28 | uses: pulumi/esc-action@9eb774255b1a4afb7855678ae8d4a77359da0d9b 29 | - uses: peter-evans/slash-command-dispatch@5c11dc7efead556e3bdabf664302212f79eb26fa # v5 30 | with: 31 | commands: | 32 | run-acceptance-tests 33 | release 34 | issue-type: pull-request 35 | permission: write 36 | reaction-token: ${{ secrets.GITHUB_TOKEN }} 37 | repository: pulumi/pulumi-command 38 | token: ${{ steps.esc-secrets.outputs.PULUMI_BOT_TOKEN }} 39 | name: command-dispatch 40 | on: 41 | issue_comment: 42 | types: 43 | - created 44 | - edited 45 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/run.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package local 16 | 17 | import ( 18 | "github.com/pulumi/pulumi-go-provider/infer" 19 | ) 20 | 21 | // This is the type that implements the Run function methods. 22 | // The methods are declared in the runController.go file. 23 | type Run struct{} 24 | 25 | // Implementing Annotate lets you provide descriptions and default values for functions and they will 26 | // be visible in the provider's schema and the generated SDKs. 27 | func (r *Run) Annotate(a infer.Annotator) { 28 | a.Describe(&r, "A local command to be executed unconditionally.\n"+ 29 | "This command will always be run on any preview or deployment. "+ 30 | "Use `local.Command` to conditionally execute commands as part of the resource lifecycle.") 31 | } 32 | 33 | type RunInputs struct { 34 | BaseInputs 35 | Command string `pulumi:"command"` 36 | } 37 | 38 | // Implementing Annotate lets you provide descriptions and default values for fields and they will 39 | // be visible in the provider's schema and the generated SDKs. 40 | func (r *RunInputs) Annotate(a infer.Annotator) { 41 | a.Describe(&r.Command, "The command to run.") 42 | } 43 | 44 | type RunOutputs struct { 45 | RunInputs 46 | BaseOutputs 47 | } 48 | -------------------------------------------------------------------------------- /sdk/nodejs/remote/index.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | import * as pulumi from "@pulumi/pulumi"; 5 | import * as utilities from "../utilities"; 6 | 7 | // Export members: 8 | export { CommandArgs } from "./command"; 9 | export type Command = import("./command").Command; 10 | export const Command: typeof import("./command").Command = null as any; 11 | utilities.lazyLoad(exports, ["Command"], () => require("./command")); 12 | 13 | export { CopyFileArgs } from "./copyFile"; 14 | export type CopyFile = import("./copyFile").CopyFile; 15 | export const CopyFile: typeof import("./copyFile").CopyFile = null as any; 16 | utilities.lazyLoad(exports, ["CopyFile"], () => require("./copyFile")); 17 | 18 | export { CopyToRemoteArgs } from "./copyToRemote"; 19 | export type CopyToRemote = import("./copyToRemote").CopyToRemote; 20 | export const CopyToRemote: typeof import("./copyToRemote").CopyToRemote = null as any; 21 | utilities.lazyLoad(exports, ["CopyToRemote"], () => require("./copyToRemote")); 22 | 23 | 24 | // Export enums: 25 | export * from "../types/enums/remote"; 26 | 27 | const _module = { 28 | version: utilities.getVersion(), 29 | construct: (name: string, type: string, urn: string): pulumi.Resource => { 30 | switch (type) { 31 | case "command:remote:Command": 32 | return new Command(name, undefined, { urn }) 33 | case "command:remote:CopyFile": 34 | return new CopyFile(name, undefined, { urn }) 35 | case "command:remote:CopyToRemote": 36 | return new CopyToRemote(name, undefined, { urn }) 37 | default: 38 | throw new Error(`unknown resource type ${type}`); 39 | } 40 | }, 41 | }; 42 | pulumi.runtime.registerResourceModule("command", "remote", _module) 43 | -------------------------------------------------------------------------------- /.github/workflows/community-moderation.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | 3 | jobs: 4 | warn_codegen: 5 | name: warn_codegen 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout Repo 9 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 10 | with: 11 | persist-credentials: false 12 | - id: schema_changed 13 | name: Check for diff in schema 14 | uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 15 | with: 16 | filters: "changed: 'provider/cmd/**/schema.json'" 17 | - id: sdk_changed 18 | if: steps.schema_changed.outputs.changed == 'false' 19 | name: Check for diff in sdk/** 20 | uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 21 | with: 22 | filters: "changed: 'sdk/**'" 23 | - if: steps.sdk_changed.outputs.changed == 'true' && 24 | github.event.pull_request.head.repo.full_name != github.repository 25 | name: Send codegen warning as comment on PR 26 | uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | message: > 30 | Hello and thank you for your pull request! :heart: :sparkles: 31 | 32 | It looks like you're directly modifying files in the language SDKs, many of which are autogenerated. 33 | 34 | Be sure any files you're editing do not begin with a code generation warning. 35 | 36 | For generated files, you will need to make changes in `resources.go` instead, and [generate the code](https://github.com/pulumi/${{ github.event.repository.name }}/blob/master/CONTRIBUTING.md#committing-generated-code). 37 | name: warn-codegen 38 | on: 39 | pull_request_target: 40 | branches: 41 | - main 42 | types: 43 | - opened 44 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/command.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package local 16 | 17 | import ( 18 | _ "embed" 19 | 20 | "github.com/pulumi/pulumi-command/provider/pkg/provider/common" 21 | "github.com/pulumi/pulumi-go-provider/infer" 22 | ) 23 | 24 | //go:embed command.md 25 | var resourceDoc string 26 | 27 | // This is the type that implements the Command resource methods. 28 | // The methods are declared in the commandController.go file. 29 | type Command struct{} 30 | 31 | // The following statement is not required. It is a type assertion to indicate to Go that Command 32 | // implements the following interfaces. If the function signature doesn't match or isn't implemented, 33 | // we get nice compile time errors at this location. 34 | 35 | var _ = (infer.Annotated)((*Command)(nil)) 36 | 37 | // Implementing Annotate lets you provide descriptions and default values for resources and they will 38 | // be visible in the provider's schema and the generated SDKs. 39 | func (c *Command) Annotate(a infer.Annotator) { 40 | a.Describe(&c, resourceDoc) 41 | } 42 | 43 | // These are the inputs (or arguments) to a Command resource. 44 | type CommandInputs struct { 45 | common.ResourceInputs 46 | BaseInputs 47 | } 48 | 49 | // These are the outputs (or properties) of a Command resource. 50 | type CommandOutputs struct { 51 | CommandInputs 52 | BaseOutputs 53 | } 54 | -------------------------------------------------------------------------------- /sdk/dotnet/Provider.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Immutable; 7 | using System.Threading.Tasks; 8 | using Pulumi.Serialization; 9 | 10 | namespace Pulumi.Command 11 | { 12 | [CommandResourceType("pulumi:providers:command")] 13 | public partial class Provider : global::Pulumi.ProviderResource 14 | { 15 | /// 16 | /// Create a Provider resource with the given unique name, arguments, and options. 17 | /// 18 | /// 19 | /// The unique name of the resource 20 | /// The arguments used to populate this resource's properties 21 | /// A bag of options that control this resource's behavior 22 | public Provider(string name, ProviderArgs? args = null, CustomResourceOptions? options = null) 23 | : base("command", name, args ?? new ProviderArgs(), MakeResourceOptions(options, "")) 24 | { 25 | } 26 | 27 | private static CustomResourceOptions MakeResourceOptions(CustomResourceOptions? options, Input? id) 28 | { 29 | var defaultOptions = new CustomResourceOptions 30 | { 31 | Version = Utilities.Version, 32 | }; 33 | var merged = CustomResourceOptions.Merge(defaultOptions, options); 34 | // Override the ID if one was specified for consistency with other language SDKs. 35 | merged.Id = id ?? merged.Id; 36 | return merged; 37 | } 38 | } 39 | 40 | public sealed class ProviderArgs : global::Pulumi.ResourceArgs 41 | { 42 | public ProviderArgs() 43 | { 44 | } 45 | public static new ProviderArgs Empty => new ProviderArgs(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/commandController_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package local 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | "testing" 21 | 22 | "github.com/pulumi/pulumi-command/provider/pkg/provider/common" 23 | "github.com/pulumi/pulumi-command/provider/pkg/provider/util/testutil" 24 | "github.com/pulumi/pulumi-go-provider/infer" 25 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestOptionalLogging(t *testing.T) { 30 | for _, logMode := range Logging.Values(LogStdoutAndStderr) { 31 | t.Run(logMode.Name, func(t *testing.T) { 32 | cmd := Command{} 33 | 34 | ctx := &testutil.TestContext{Context: context.Background()} 35 | input := CommandInputs{ 36 | BaseInputs: BaseInputs{ 37 | Logging: &logMode.Value, 38 | }, 39 | ResourceInputs: common.ResourceInputs{ 40 | Create: pulumi.StringRef("echo foo; echo bar >> /dev/stderr"), 41 | }, 42 | } 43 | 44 | _, err := cmd.Create(ctx, infer.CreateRequest[CommandInputs]{Name: "name", Inputs: input, DryRun: false}) 45 | require.NoError(t, err) 46 | 47 | log := ctx.Output.String() 48 | 49 | // When logging both stdout and stderr, the output could be foobar or barfoo. 50 | require.Equal(t, logMode.Value.ShouldLogStdout(), strings.Contains(log, "foo")) 51 | require.Equal(t, logMode.Value.ShouldLogStderr(), strings.Contains(log, "bar")) 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sdk/dotnet/Local/Enums.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | using System; 5 | using System.ComponentModel; 6 | using Pulumi; 7 | 8 | namespace Pulumi.Command.Local 9 | { 10 | [EnumType] 11 | public readonly struct Logging : IEquatable 12 | { 13 | private readonly string _value; 14 | 15 | private Logging(string value) 16 | { 17 | _value = value ?? throw new ArgumentNullException(nameof(value)); 18 | } 19 | 20 | /// 21 | /// Capture stdout in logs but not stderr 22 | /// 23 | public static Logging Stdout { get; } = new Logging("stdout"); 24 | /// 25 | /// Capture stderr in logs but not stdout 26 | /// 27 | public static Logging Stderr { get; } = new Logging("stderr"); 28 | /// 29 | /// Capture stdout and stderr in logs 30 | /// 31 | public static Logging StdoutAndStderr { get; } = new Logging("stdoutAndStderr"); 32 | /// 33 | /// Capture no logs 34 | /// 35 | public static Logging None { get; } = new Logging("none"); 36 | 37 | public static bool operator ==(Logging left, Logging right) => left.Equals(right); 38 | public static bool operator !=(Logging left, Logging right) => !left.Equals(right); 39 | 40 | public static explicit operator string(Logging value) => value._value; 41 | 42 | [EditorBrowsable(EditorBrowsableState.Never)] 43 | public override bool Equals(object? obj) => obj is Logging other && Equals(other); 44 | public bool Equals(Logging other) => string.Equals(_value, other._value, StringComparison.Ordinal); 45 | 46 | [EditorBrowsable(EditorBrowsableState.Never)] 47 | public override int GetHashCode() => _value?.GetHashCode() ?? 0; 48 | 49 | public override string ToString() => _value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sdk/dotnet/Remote/Enums.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | using System; 5 | using System.ComponentModel; 6 | using Pulumi; 7 | 8 | namespace Pulumi.Command.Remote 9 | { 10 | [EnumType] 11 | public readonly struct Logging : IEquatable 12 | { 13 | private readonly string _value; 14 | 15 | private Logging(string value) 16 | { 17 | _value = value ?? throw new ArgumentNullException(nameof(value)); 18 | } 19 | 20 | /// 21 | /// Capture stdout in logs but not stderr 22 | /// 23 | public static Logging Stdout { get; } = new Logging("stdout"); 24 | /// 25 | /// Capture stderr in logs but not stdout 26 | /// 27 | public static Logging Stderr { get; } = new Logging("stderr"); 28 | /// 29 | /// Capture stdout and stderr in logs 30 | /// 31 | public static Logging StdoutAndStderr { get; } = new Logging("stdoutAndStderr"); 32 | /// 33 | /// Capture no logs 34 | /// 35 | public static Logging None { get; } = new Logging("none"); 36 | 37 | public static bool operator ==(Logging left, Logging right) => left.Equals(right); 38 | public static bool operator !=(Logging left, Logging right) => !left.Equals(right); 39 | 40 | public static explicit operator string(Logging value) => value._value; 41 | 42 | [EditorBrowsable(EditorBrowsableState.Never)] 43 | public override bool Equals(object? obj) => obj is Logging other && Equals(other); 44 | public bool Equals(Logging other) => string.Equals(_value, other._value, StringComparison.Ordinal); 45 | 46 | [EditorBrowsable(EditorBrowsableState.Never)] 47 | public override int GetHashCode() => _value?.GetHashCode() ?? 0; 48 | 49 | public override string ToString() => _value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/copyfile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "github.com/pulumi/pulumi-go-provider/infer" 19 | ) 20 | 21 | type CopyFile struct{} 22 | 23 | var _ = (infer.Annotated)((*CopyFile)(nil)) 24 | 25 | // CopyFile implements Annotate which allows you to attach descriptions to the CopyFile resource. 26 | func (c *CopyFile) Annotate(a infer.Annotator) { 27 | a.Describe(&c, "Copy a local file to a remote host.") 28 | a.Deprecate(&c, "This resource is deprecated and will be removed in a future release. "+ 29 | "Please use the `CopyToRemote` resource instead.") 30 | } 31 | 32 | type CopyFileInputs struct { 33 | Connection *Connection `pulumi:"connection" provider:"secret"` 34 | Triggers *[]interface{} `pulumi:"triggers,optional" providers:"replaceOnDelete"` 35 | LocalPath string `pulumi:"localPath"` 36 | RemotePath string `pulumi:"remotePath"` 37 | } 38 | 39 | // CopyFile implements Annotate which allows you to attach descriptions to the CopyFile resource's fields. 40 | func (c *CopyFileInputs) Annotate(a infer.Annotator) { 41 | a.Describe(&c.Connection, "The parameters with which to connect to the remote host.") 42 | a.Describe(&c.Triggers, "Trigger replacements on changes to this input.") 43 | a.Describe(&c.LocalPath, "The path of the file to be copied.") 44 | a.Describe(&c.RemotePath, "The destination path in the remote host.") 45 | } 46 | 47 | type CopyFileOutputs struct { 48 | CopyFileInputs 49 | } 50 | -------------------------------------------------------------------------------- /provider/pkg/provider/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "io" 21 | "sync" 22 | 23 | "github.com/pulumi/pulumi-command/provider/pkg/provider/util/testutil" 24 | 25 | p "github.com/pulumi/pulumi-go-provider" 26 | "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 27 | ) 28 | 29 | const PULUMI_COMMAND_STDOUT = "PULUMI_COMMAND_STDOUT" 30 | const PULUMI_COMMAND_STDERR = "PULUMI_COMMAND_STDERR" 31 | 32 | func LogOutput(ctx context.Context, r io.Reader, doneCh chan<- struct{}, severity diag.Severity) { 33 | defer close(doneCh) 34 | scanner := bufio.NewScanner(r) 35 | for scanner.Scan() { 36 | msg := scanner.Text() 37 | l := p.GetLogger(ctx) 38 | switch severity { 39 | case diag.Info: 40 | l.InfoStatus(msg) 41 | case diag.Warning: 42 | l.WarningStatus(msg) 43 | case diag.Error: 44 | l.ErrorStatus(msg) 45 | default: 46 | l.DebugStatus(msg) 47 | } 48 | 49 | if testCtx, ok := ctx.(*testutil.TestContext); ok { 50 | testCtx.Log(severity, msg) 51 | } 52 | } 53 | } 54 | 55 | // NoopLogger satisfies the expected logger shape but doesn't actually log. 56 | // It reads from the provided reader until EOF, discarding the output, then closes the channel. 57 | func NoopLogger(r io.Reader, done chan struct{}) { 58 | defer close(done) 59 | _, _ = io.Copy(io.Discard, r) 60 | } 61 | 62 | type ConcurrentWriter struct { 63 | Writer io.Writer 64 | mu sync.Mutex 65 | } 66 | 67 | func (w *ConcurrentWriter) Write(bs []byte) (int, error) { 68 | w.mu.Lock() 69 | defer w.mu.Unlock() 70 | return w.Writer.Write(bs) 71 | } 72 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/proxyConnection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "github.com/pulumi/pulumi-go-provider/infer" 19 | ) 20 | 21 | type ProxyConnection struct{ connectionBase } 22 | 23 | func (c *ProxyConnection) Annotate(a infer.Annotator) { 24 | a.Describe(&c, "Instructions for how to connect to a remote endpoint via a bastion host.") 25 | a.Describe(&c.User, "The user that we should use for the connection to the bastion host.") 26 | a.SetDefault(&c.User, "root") 27 | a.Describe(&c.Password, "The password we should use for the connection to the bastion host.") 28 | a.Describe(&c.Host, "The address of the bastion host to connect to.") 29 | a.Describe(&c.Port, "The port of the bastion host to connect to.") 30 | a.SetDefault(&c.Port, 22) 31 | a.Describe(&c.PrivateKey, "The contents of an SSH key to use for the connection. This takes preference over the password if provided.") 32 | a.Describe(&c.PrivateKeyPassword, "The password to use in case the private key is encrypted.") 33 | a.Describe(&c.AgentSocketPath, "SSH Agent socket path. Default to environment variable SSH_AUTH_SOCK if present.") 34 | a.Describe(&c.DialErrorLimit, "Max allowed errors on trying to dial the remote host. -1 set count to unlimited. Default value is 10.") 35 | a.SetDefault(&c.DialErrorLimit, dialErrorDefault) 36 | a.Describe(&c.PerDialTimeout, "Max number of seconds for each dial attempt. 0 implies no maximum. Default value is 15 seconds.") 37 | a.SetDefault(&c.PerDialTimeout, 15) 38 | a.Describe(&c.HostKey, "The expected host key to verify the server's identity. If not provided, the host key will be ignored.") 39 | } 40 | -------------------------------------------------------------------------------- /sdk/dotnet/Pulumi.Command.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | Pulumi 6 | Pulumi 7 | The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model. 8 | Apache-2.0 9 | https://pulumi.com 10 | https://github.com/pulumi/pulumi-command 11 | logo.png 12 | 1.0.0-alpha.0+dev 13 | 14 | net6.0 15 | enable 16 | 17 | 18 | 19 | true 20 | 1701;1702;1591 21 | 22 | 23 | 24 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 25 | true 26 | true 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | True 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.github/workflows/comment-on-stale-issues.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | name: "Comment on stale issues" 3 | 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: "46 4 * * *" # run once per day 8 | 9 | jobs: 10 | cleanup: 11 | runs-on: ubuntu-latest 12 | name: Stale issue job 13 | steps: 14 | # Workaround until https://github.com/aws-actions/stale-issue-cleanup/issues/385 is fixed 15 | - uses: pose/stale-issue-cleanup@04621e0eafc22d7bb8812c417325f35cc0ed0fce 16 | with: 17 | issue-types: issues # only look at issues (ignore pull-requests) 18 | 19 | # Setting messages to an empty string causes the automation to skip that category 20 | ancient-issue-message: "Unfortunately, it looks like this issue hasn't seen any updates in a while. If you're still experiencing this issue, could you leave a quick comment to let us know so we can prioritize it?" 21 | ancient-pr-message: "" 22 | stale-issue-message: "" 23 | stale-pr-message: "" 24 | 25 | # These labels are required 26 | stale-issue-label: awaiting-feedback # somewhat confusingly, this is also used for when labeling "ancient" issues 27 | exempt-issue-labels: kind/enhancement,kind/task,kind/epic,kind/engineering, awaiting-upstream # only run on kind/bug for now, ignore awaiting-upstream too. 28 | stale-pr-label: no-pr-activity # unused because we aren't processing PRs 29 | exempt-pr-labels: awaiting-approval # unused because we aren't processing PRs 30 | response-requested-label: response-requested # unused because we don't set a "stale-issue-message" above 31 | 32 | # Issue timing 33 | days-before-close: 10000 # this action lacks the option not to close, so just set this indefinitly far in the future 34 | days-before-ancient: 180 # 6 months 35 | 36 | # If you don't want to mark a issue as being ancient based on a 37 | # threshold of "upvotes", you can set this here. An "upvote" is 38 | # the total number of +1, heart, hooray, and rocket reactions 39 | # on an issue. 40 | minimum-upvotes-to-exempt: 2 41 | 42 | repo-token: ${{ secrets.GITHUB_TOKEN }} 43 | loglevel: DEBUG 44 | # Set dry-run to true to not perform label or close actions. 45 | dry-run: true 46 | -------------------------------------------------------------------------------- /sdk/go/command/provider.go: -------------------------------------------------------------------------------- 1 | // Code generated by pulumi-language-go DO NOT EDIT. 2 | // *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package command 5 | 6 | import ( 7 | "context" 8 | "reflect" 9 | 10 | "github.com/pulumi/pulumi-command/sdk/go/command/internal" 11 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 12 | ) 13 | 14 | type Provider struct { 15 | pulumi.ProviderResourceState 16 | } 17 | 18 | // NewProvider registers a new resource with the given unique name, arguments, and options. 19 | func NewProvider(ctx *pulumi.Context, 20 | name string, args *ProviderArgs, opts ...pulumi.ResourceOption) (*Provider, error) { 21 | if args == nil { 22 | args = &ProviderArgs{} 23 | } 24 | 25 | opts = internal.PkgResourceDefaultOpts(opts) 26 | var resource Provider 27 | err := ctx.RegisterResource("pulumi:providers:command", name, args, &resource, opts...) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &resource, nil 32 | } 33 | 34 | type providerArgs struct { 35 | } 36 | 37 | // The set of arguments for constructing a Provider resource. 38 | type ProviderArgs struct { 39 | } 40 | 41 | func (ProviderArgs) ElementType() reflect.Type { 42 | return reflect.TypeOf((*providerArgs)(nil)).Elem() 43 | } 44 | 45 | type ProviderInput interface { 46 | pulumi.Input 47 | 48 | ToProviderOutput() ProviderOutput 49 | ToProviderOutputWithContext(ctx context.Context) ProviderOutput 50 | } 51 | 52 | func (*Provider) ElementType() reflect.Type { 53 | return reflect.TypeOf((**Provider)(nil)).Elem() 54 | } 55 | 56 | func (i *Provider) ToProviderOutput() ProviderOutput { 57 | return i.ToProviderOutputWithContext(context.Background()) 58 | } 59 | 60 | func (i *Provider) ToProviderOutputWithContext(ctx context.Context) ProviderOutput { 61 | return pulumi.ToOutputWithContext(ctx, i).(ProviderOutput) 62 | } 63 | 64 | type ProviderOutput struct{ *pulumi.OutputState } 65 | 66 | func (ProviderOutput) ElementType() reflect.Type { 67 | return reflect.TypeOf((**Provider)(nil)).Elem() 68 | } 69 | 70 | func (o ProviderOutput) ToProviderOutput() ProviderOutput { 71 | return o 72 | } 73 | 74 | func (o ProviderOutput) ToProviderOutputWithContext(ctx context.Context) ProviderOutput { 75 | return o 76 | } 77 | 78 | func init() { 79 | pulumi.RegisterInputType(reflect.TypeOf((*ProviderInput)(nil)).Elem(), &Provider{}) 80 | pulumi.RegisterOutputType(ProviderOutput{}) 81 | } 82 | -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/Provider.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command; 5 | 6 | import com.pulumi.command.ProviderArgs; 7 | import com.pulumi.command.Utilities; 8 | import com.pulumi.core.Output; 9 | import com.pulumi.core.annotations.ResourceType; 10 | import com.pulumi.core.internal.Codegen; 11 | import javax.annotation.Nullable; 12 | 13 | @ResourceType(type="pulumi:providers:command") 14 | public class Provider extends com.pulumi.resources.ProviderResource { 15 | /** 16 | * 17 | * @param name The _unique_ name of the resulting resource. 18 | */ 19 | public Provider(java.lang.String name) { 20 | this(name, ProviderArgs.Empty); 21 | } 22 | /** 23 | * 24 | * @param name The _unique_ name of the resulting resource. 25 | * @param args The arguments to use to populate this resource's properties. 26 | */ 27 | public Provider(java.lang.String name, @Nullable ProviderArgs args) { 28 | this(name, args, null); 29 | } 30 | /** 31 | * 32 | * @param name The _unique_ name of the resulting resource. 33 | * @param args The arguments to use to populate this resource's properties. 34 | * @param options A bag of options that control this resource's behavior. 35 | */ 36 | public Provider(java.lang.String name, @Nullable ProviderArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { 37 | super("command", name, makeArgs(args, options), makeResourceOptions(options, Codegen.empty()), false); 38 | } 39 | 40 | private static ProviderArgs makeArgs(@Nullable ProviderArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { 41 | if (options != null && options.getUrn().isPresent()) { 42 | return null; 43 | } 44 | return args == null ? ProviderArgs.Empty : args; 45 | } 46 | 47 | private static com.pulumi.resources.CustomResourceOptions makeResourceOptions(@Nullable com.pulumi.resources.CustomResourceOptions options, @Nullable Output id) { 48 | var defaultOptions = com.pulumi.resources.CustomResourceOptions.builder() 49 | .version(Utilities.getVersion()) 50 | .build(); 51 | return com.pulumi.resources.CustomResourceOptions.merge(defaultOptions, options, id); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /provider/pkg/provider/common/inputs.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/pulumi/pulumi-go-provider/infer" 4 | 5 | // ResourceInputs are inputs common to resource CRUD operations. 6 | type ResourceInputs struct { 7 | // The field tags are used to provide metadata on the schema representation. 8 | // pulumi:"optional" specifies that a field is optional. This must be a pointer. 9 | // provider:"replaceOnChanges" specifies that the resource will be replaced if the field changes. 10 | Triggers *[]any `pulumi:"triggers,optional" provider:"replaceOnChanges"` 11 | 12 | Create *string `pulumi:"create,optional"` 13 | Update *string `pulumi:"update,optional"` 14 | Delete *string `pulumi:"delete,optional"` 15 | } 16 | 17 | // Annotate lets you provide descriptions and default values for fields and they will 18 | // be visible in the provider's schema and the generated SDKs. 19 | func (c *ResourceInputs) Annotate(a infer.Annotator) { 20 | a.Describe(&c.Triggers, "The resource will be updated (or replaced) if any of these values change.\n\n"+ 21 | "The trigger values can be of any type.\n\n"+ 22 | "If the `update` command was provided the resource will be updated, otherwise it will be replaced using the `create` command.\n\n"+ 23 | "Please see the resource documentation for examples.", 24 | ) 25 | a.Describe(&c.Create, "The command to run once on resource creation.\n\n"+ 26 | "If an `update` command isn't provided, then `create` will also be run when the resource's inputs are modified.\n\n"+ 27 | "Note that this command will not be executed if the resource has already been created and its inputs are unchanged.\n\n"+ 28 | "Use `local.runOutput` if you need to run a command on every execution of your program.", 29 | ) 30 | a.Describe(&c.Delete, "The command to run on resource delettion.\n\n"+ 31 | "The environment variables `PULUMI_COMMAND_STDOUT` and `PULUMI_COMMAND_STDERR` are set to the stdout and stderr properties of the Command resource from previous create or update steps.", 32 | ) 33 | a.Describe(&c.Update, "The command to run when the resource is updated.\n\n"+ 34 | "If empty, the create command will be executed instead.\n\n"+ 35 | "Note that this command will not run if the resource's inputs are unchanged.\n\n"+ 36 | "Use `local.runOutput` if you need to run a command on every execution of your program.\n\n"+ 37 | "The environment variables `PULUMI_COMMAND_STDOUT` and `PULUMI_COMMAND_STDERR` are set to the `stdout` and `stderr` properties of the Command resource from previous create or update steps.", 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /examples/testdata/recorded/TestProviderUpgrade/stdin/0.11.1/stack.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "deployment": { 4 | "manifest": { 5 | "time": "2024-06-28T17:09:49.247995+02:00", 6 | "magic": "fdff5d115ed98f71a6d1a039669a8634e8e3e1fb9e810720cb957e73b3c02d81", 7 | "version": "v3.121.0" 8 | }, 9 | "secrets_providers": { 10 | "type": "passphrase", 11 | "state": { 12 | "salt": "v1:b8g4hK418io=:v1:yvpLPIh9Tq0ppgvj:/1IMuvDpfiypmeskdCjx4eX7Wn+tCw==" 13 | } 14 | }, 15 | "resources": [ 16 | { 17 | "urn": "urn:pulumi:test::command-random::pulumi:pulumi:Stack::command-random-test", 18 | "custom": false, 19 | "type": "pulumi:pulumi:Stack", 20 | "outputs": { 21 | "output": "the quick brown fox" 22 | }, 23 | "created": "2024-06-28T15:09:48.007562Z", 24 | "modified": "2024-06-28T15:09:48.007562Z", 25 | "sourcePosition": "project:///node_modules/@pulumi/runtime/stack.ts#36,23" 26 | }, 27 | { 28 | "urn": "urn:pulumi:test::command-random::pulumi:providers:command::default_0_11_1", 29 | "custom": true, 30 | "id": "da5f149c-ad5d-47d1-950a-84ce47984797", 31 | "type": "pulumi:providers:command", 32 | "inputs": { 33 | "version": "0.11.1" 34 | }, 35 | "outputs": { 36 | "version": "0.11.1" 37 | }, 38 | "created": "2024-06-28T15:09:49.186853Z", 39 | "modified": "2024-06-28T15:09:49.186853Z" 40 | }, 41 | { 42 | "urn": "urn:pulumi:test::command-random::command:local:Command::stdin", 43 | "custom": true, 44 | "id": "stdin30aceb66", 45 | "type": "command:local:Command", 46 | "inputs": { 47 | "create": "head -n 1", 48 | "stdin": "the quick brown fox\njumped over\nthe lazy dog" 49 | }, 50 | "outputs": { 51 | "create": "head -n 1", 52 | "stderr": "", 53 | "stdin": "the quick brown fox\njumped over\nthe lazy dog", 54 | "stdout": "the quick brown fox" 55 | }, 56 | "parent": "urn:pulumi:test::command-random::pulumi:pulumi:Stack::command-random-test", 57 | "provider": "urn:pulumi:test::command-random::pulumi:providers:command::default_0_11_1::da5f149c-ad5d-47d1-950a-84ce47984797", 58 | "propertyDependencies": { 59 | "create": [], 60 | "stdin": [] 61 | }, 62 | "created": "2024-06-28T15:09:49.216982Z", 63 | "modified": "2024-06-28T15:09:49.216982Z", 64 | "sourcePosition": "project:///index.ts#4,16" 65 | } 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/release_command.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This file is autogenerated - changes will be overwritten when regenerated by https://github.com/pulumi/ci-mgmt 2 | 3 | name: release-command 4 | on: 5 | repository_dispatch: 6 | types: 7 | - release-command 8 | jobs: 9 | should_release: 10 | name: Should release PR 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 15 | with: 16 | persist-credentials: false 17 | - env: 18 | ESC_ACTION_ENVIRONMENT: github-secrets/${{ github.repository_owner }}-${{ github.event.repository.name }} 19 | ESC_ACTION_EXPORT_ENVIRONMENT_VARIABLES: "false" 20 | ESC_ACTION_OIDC_AUTH: "true" 21 | ESC_ACTION_OIDC_ORGANIZATION: pulumi 22 | ESC_ACTION_OIDC_REQUESTED_TOKEN_TYPE: urn:pulumi:token-type:access_token:organization 23 | id: esc-secrets 24 | name: Fetch secrets from ESC 25 | uses: pulumi/esc-action@9eb774255b1a4afb7855678ae8d4a77359da0d9b 26 | - name: Should release PR 27 | uses: pulumi/action-release-by-pr-label@main 28 | with: 29 | command: "should-release" 30 | repo: ${{ github.repository }} 31 | pr: ${{ github.event.client_payload.pull_request.number }} 32 | version: ${{ github.event.client_payload.slash_command.args.all }} 33 | slack_channel: ${{ steps.esc-secrets.outputs.RELEASE_OPS_STAGING_SLACK_CHANNEL }} 34 | env: 35 | RELEASE_BOT_ENDPOINT: ${{ steps.esc-secrets.outputs.RELEASE_BOT_ENDPOINT }} 36 | RELEASE_BOT_KEY: ${{ steps.esc-secrets.outputs.RELEASE_BOT_KEY }} 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | - if: failure() 39 | name: Notify failure 40 | uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 41 | with: 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | repository: ${{ github.event.client_payload.github.payload.repository.full_name }} 44 | issue-number: ${{ github.event.client_payload.github.payload.issue.number }} 45 | body: | 46 | "release command failed: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" 47 | - if: success() 48 | name: Notify success 49 | uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 50 | with: 51 | token: ${{ secrets.GITHUB_TOKEN }} 52 | repository: ${{ github.event.client_payload.github.payload.repository.full_name }} 53 | comment-id: ${{ github.event.client_payload.github.payload.comment.id }} 54 | reaction-type: hooray 55 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/copy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "os" 19 | "path/filepath" 20 | "testing" 21 | 22 | "github.com/pulumi/pulumi-go-provider/infer/types" 23 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func TestAssetSourcePath(t *testing.T) { 28 | assetPath, input := createAssetInput(t) 29 | require.NotNil(t, input.Source.Asset) 30 | require.Equal(t, assetPath, input.Source.Asset.Path) 31 | require.Equal(t, assetPath, input.sourcePath()) 32 | } 33 | 34 | func TestArchiveSourcePath(t *testing.T) { 35 | archivePath, input := createArchiveInput(t) 36 | require.NotNil(t, input.Source.Archive) 37 | require.Equal(t, archivePath, input.Source.Archive.Path) 38 | require.Equal(t, archivePath, input.sourcePath()) 39 | } 40 | 41 | func TestAssetHash(t *testing.T) { 42 | _, input := createAssetInput(t) 43 | require.NotNil(t, input.Source.Asset) 44 | require.Equal(t, input.Source.Asset.Hash, input.hash()) 45 | } 46 | 47 | func TestArchiveHash(t *testing.T) { 48 | _, input := createArchiveInput(t) 49 | require.NotNil(t, input.Source.Archive) 50 | require.Equal(t, input.Source.Archive.Hash, input.hash()) 51 | } 52 | 53 | func createArchiveInput(t *testing.T) (string, *CopyToRemoteInputs) { 54 | archivePath := filepath.Join(t.TempDir(), "archive.zip") 55 | require.NoError(t, os.WriteFile(archivePath, []byte("hello, world"), 0644)) 56 | archive, err := resource.NewPathArchive(archivePath) 57 | require.NoError(t, err) 58 | 59 | c := &CopyToRemoteInputs{ 60 | Source: types.AssetOrArchive{ 61 | Archive: archive, 62 | }, 63 | } 64 | return archivePath, c 65 | } 66 | 67 | func createAssetInput(t *testing.T) (string, *CopyToRemoteInputs) { 68 | assetPath := filepath.Join(t.TempDir(), "asset") 69 | require.NoError(t, os.WriteFile(assetPath, []byte("hello, world"), 0644)) 70 | asset, err := resource.NewPathAsset(assetPath) 71 | require.NoError(t, err) 72 | 73 | c := &CopyToRemoteInputs{ 74 | Source: types.AssetOrArchive{ 75 | Asset: asset, 76 | }, 77 | } 78 | return assetPath, c 79 | } 80 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/copyfileController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "context" 19 | "os" 20 | 21 | "github.com/pkg/sftp" 22 | 23 | p "github.com/pulumi/pulumi-go-provider" 24 | "github.com/pulumi/pulumi-go-provider/infer" 25 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 26 | ) 27 | 28 | // These are not required. They indicate to Go that Command implements the following interfaces. 29 | // If the function signature doesn't match or isn't implemented, we get nice compile time errors in this file. 30 | var _ = (infer.CustomResource[CopyFileInputs, CopyFileOutputs])((*CopyFile)(nil)) 31 | 32 | // This is the Create method. This will be run on every CopyFile resource creation. 33 | func (*CopyFile) Create(ctx context.Context, req infer.CreateRequest[CopyFileInputs]) (infer.CreateResponse[CopyFileOutputs], error) { 34 | input := req.Inputs 35 | preview := req.DryRun 36 | if preview { 37 | return infer.CreateResponse[CopyFileOutputs]{ID: "", Output: CopyFileOutputs{input}}, nil 38 | } 39 | 40 | p.GetLogger(ctx).Debugf("Creating file: %s:%s from local file %s", 41 | *input.Connection.Host, input.RemotePath, input.LocalPath) 42 | 43 | src, err := os.Open(input.LocalPath) 44 | if err != nil { 45 | return infer.CreateResponse[CopyFileOutputs]{ID: "", Output: CopyFileOutputs{input}}, err 46 | } 47 | defer src.Close() 48 | 49 | client, err := input.Connection.Dial(ctx) 50 | if err != nil { 51 | return infer.CreateResponse[CopyFileOutputs]{ID: "", Output: CopyFileOutputs{input}}, err 52 | } 53 | defer client.Close() 54 | 55 | sftp, err := sftp.NewClient(client) 56 | if err != nil { 57 | return infer.CreateResponse[CopyFileOutputs]{}, err 58 | } 59 | defer sftp.Close() 60 | 61 | dst, err := sftp.Create(input.RemotePath) 62 | if err != nil { 63 | return infer.CreateResponse[CopyFileOutputs]{}, err 64 | } 65 | 66 | _, err = dst.ReadFrom(src) 67 | if err != nil { 68 | return infer.CreateResponse[CopyFileOutputs]{}, err 69 | } 70 | 71 | id, err := resource.NewUniqueHex("", 8, 0) 72 | return infer.CreateResponse[CopyFileOutputs]{ID: id, Output: CopyFileOutputs{input}}, err 73 | } 74 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report something that's not working correctly 3 | labels: ["kind/bug", "needs-triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | You can also ask questions on our [Community Slack](https://slack.pulumi.com/). 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: Describe what happened 14 | description: Please summarize what happened, including what Pulumi commands you ran, as well as 15 | an inline snippet of any relevant error or console output. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: sample-program 20 | attributes: 21 | label: Sample program 22 | description: | 23 |
Provide a reproducible sample program 24 | If this is a bug you encountered while running a Pulumi command, please provide us with a minimal, 25 | self-contained Pulumi program that reproduces this behavior so that we can investigate on our end. 26 | Without a functional reproduction, we will not be able to prioritize this bug. 27 | **Note:** If the program output is more than a few lines, please send us a Gist or a link to a file. 28 |
29 | validations: 30 | required: true 31 | - type: textarea 32 | id: log-output 33 | attributes: 34 | label: Log output 35 | description: | 36 |
How to Submit Logs 37 | If this is something that is dependent on your environment, please also provide us with the output of 38 | `pulumi up --logtostderr --logflow -v=10` from the root of your project. 39 | We may also ask you to supply us with debug output following [these steps](https://www.pulumi.com/docs/using-pulumi/pulumi-packages/debugging-provider-packages/). 40 | **Note:** If the log output is more than a few lines, please send us a Gist or a link to a file. 41 |
42 | - type: textarea 43 | id: resources 44 | attributes: 45 | label: Affected Resource(s) 46 | description: Please list the affected Pulumi Resource(s) or Function(s). 47 | validations: 48 | required: false 49 | - type: textarea 50 | id: versions 51 | attributes: 52 | label: Output of `pulumi about` 53 | description: Provide the output of `pulumi about` from the root of your project. 54 | validations: 55 | required: true 56 | - type: textarea 57 | id: ctx 58 | attributes: 59 | label: Additional context 60 | description: Anything else you would like to add? 61 | validations: 62 | required: false 63 | - type: textarea 64 | id: voting 65 | attributes: 66 | label: Contributing 67 | value: | 68 | Vote on this issue by adding a 👍 reaction. 69 | To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already). -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/local/LocalFunctions.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command.local; 5 | 6 | import com.pulumi.command.Utilities; 7 | import com.pulumi.command.local.inputs.RunArgs; 8 | import com.pulumi.command.local.inputs.RunPlainArgs; 9 | import com.pulumi.command.local.outputs.RunResult; 10 | import com.pulumi.core.Output; 11 | import com.pulumi.core.TypeShape; 12 | import com.pulumi.deployment.Deployment; 13 | import com.pulumi.deployment.InvokeOptions; 14 | import com.pulumi.deployment.InvokeOutputOptions; 15 | import java.util.concurrent.CompletableFuture; 16 | 17 | public final class LocalFunctions { 18 | /** 19 | * A local command to be executed unconditionally. 20 | * This command will always be run on any preview or deployment. Use `local.Command` to conditionally execute commands as part of the resource lifecycle. 21 | * 22 | */ 23 | public static Output run(RunArgs args) { 24 | return run(args, InvokeOptions.Empty); 25 | } 26 | /** 27 | * A local command to be executed unconditionally. 28 | * This command will always be run on any preview or deployment. Use `local.Command` to conditionally execute commands as part of the resource lifecycle. 29 | * 30 | */ 31 | public static CompletableFuture runPlain(RunPlainArgs args) { 32 | return runPlain(args, InvokeOptions.Empty); 33 | } 34 | /** 35 | * A local command to be executed unconditionally. 36 | * This command will always be run on any preview or deployment. Use `local.Command` to conditionally execute commands as part of the resource lifecycle. 37 | * 38 | */ 39 | public static Output run(RunArgs args, InvokeOptions options) { 40 | return Deployment.getInstance().invoke("command:local:run", TypeShape.of(RunResult.class), args, Utilities.withVersion(options)); 41 | } 42 | /** 43 | * A local command to be executed unconditionally. 44 | * This command will always be run on any preview or deployment. Use `local.Command` to conditionally execute commands as part of the resource lifecycle. 45 | * 46 | */ 47 | public static Output run(RunArgs args, InvokeOutputOptions options) { 48 | return Deployment.getInstance().invoke("command:local:run", TypeShape.of(RunResult.class), args, Utilities.withVersion(options)); 49 | } 50 | /** 51 | * A local command to be executed unconditionally. 52 | * This command will always be run on any preview or deployment. Use `local.Command` to conditionally execute commands as part of the resource lifecycle. 53 | * 54 | */ 55 | public static CompletableFuture runPlain(RunPlainArgs args, InvokeOptions options) { 56 | return Deployment.getInstance().invokeAsync("command:local:run", TypeShape.of(RunResult.class), args, Utilities.withVersion(options)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/gcp-py-compute-command/__main__.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import pulumi 3 | import pulumi_gcp as gcp 4 | import pulumi_command as command 5 | 6 | # Get the config ready to go. 7 | config = pulumi.Config() 8 | 9 | # If keyName is provided, an existing KeyPair is used, else if publicKey is provided a new KeyPair 10 | # derived from the publicKey is created. 11 | key_name = config.get('keyName') 12 | public_key = config.get('publicKey') 13 | 14 | 15 | # The privateKey associated with the selected key must be provided (either directly or base64 encoded), 16 | # along with an optional passphrase if needed. 17 | def decode_key(key): 18 | try: 19 | key = base64.b64decode(key.encode('ascii')).decode('ascii') 20 | except: 21 | pass 22 | 23 | if key.startswith('-----BEGIN RSA PRIVATE KEY-----'): 24 | return key 25 | 26 | return key.encode('ascii') 27 | 28 | 29 | private_key = config.require_secret('privateKey').apply(decode_key) 30 | 31 | svcacct = gcp.serviceaccount.Account("my-service-account", 32 | account_id="service-account", 33 | display_name="Service Account for Ansible") 34 | 35 | svckey = gcp.serviceaccount.Key("my-service-key", 36 | service_account_id=svcacct.name, 37 | public_key_type="TYPE_X509_PEM_FILE") 38 | 39 | 40 | addr = gcp.compute.address.Address('my-address') 41 | compute_instance = gcp.compute.Instance( 42 | "my-instance", 43 | machine_type="f1-micro", 44 | boot_disk=gcp.compute.InstanceBootDiskArgs( 45 | initialize_params=gcp.compute.InstanceBootDiskInitializeParamsArgs( 46 | image="ubuntu-os-cloud/ubuntu-2004-lts" 47 | ) 48 | ), 49 | network_interfaces=[gcp.compute.InstanceNetworkInterfaceArgs( 50 | network='default', 51 | access_configs=[gcp.compute.InstanceNetworkInterfaceAccessConfigArgs( 52 | nat_ip=addr.address 53 | )], 54 | )], 55 | service_account=gcp.compute.InstanceServiceAccountArgs( 56 | scopes=["https://www.googleapis.com/auth/cloud-platform"], 57 | email=svcacct.email 58 | ), 59 | metadata={ 60 | 'ssh-keys': f'user:{public_key}' 61 | }, 62 | ) 63 | 64 | conn = command.remote.ConnectionArgs( 65 | host=addr.address, 66 | private_key=private_key, 67 | user='user' 68 | ) 69 | 70 | # Copy a config file to our server. 71 | cp_config = command.remote.Copy( 72 | 'config', 73 | connection=conn, 74 | local_asset='myapp.conf', 75 | remote_path='myapp.conf', 76 | opts=pulumi.ResourceOptions(depends_on=[compute_instance]) 77 | ) 78 | 79 | # Execute a basic command on our server. 80 | cat_config = command.remote.Command( 81 | 'cat-config', 82 | connection=conn, 83 | create='cat myapp.conf', 84 | opts=pulumi.ResourceOptions(depends_on=[cp_config]) 85 | ) 86 | 87 | # Export the server's IP and stdout from the command. 88 | pulumi.export('publicIp', addr.address) 89 | pulumi.export('catConfigStdout', cat_config.stdout) 90 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/commandController_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/gliderlabs/ssh" 24 | "github.com/pulumi/pulumi-command/provider/pkg/provider/common" 25 | "github.com/pulumi/pulumi-command/provider/pkg/provider/util/testutil" 26 | "github.com/pulumi/pulumi-go-provider/infer" 27 | "github.com/pulumi/pulumi/sdk/v3/go/pulumi" 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | func TestOptionalLogging(t *testing.T) { 32 | for _, sharedLogMode := range Logging.Values(LogStdoutAndStderr) { 33 | // Get a copy of the log mode that doesn't get changed by the next iteration in the for loop, 34 | // to allow running in parallel. 35 | // See 'The long-standing "for" loop gotcha' here: https://go.dev/blog/go1.22 36 | // This can be removed after upgrading to go 1.22. 37 | logMode := sharedLogMode 38 | t.Run(logMode.Name, func(t *testing.T) { 39 | t.Parallel() 40 | 41 | // This SSH server always writes "foo" to stdout and "bar" to stderr, no matter the command. 42 | server := testutil.NewTestSshServer(t, func(s ssh.Session) { 43 | _, err := io.WriteString(s, "foo") 44 | require.NoError(t, err) 45 | _, err = io.WriteString(s.Stderr(), "bar") 46 | require.NoError(t, err) 47 | }) 48 | 49 | cmd := Command{} 50 | 51 | ctx := &testutil.TestContext{Context: context.Background()} 52 | input := CommandInputs{ 53 | Logging: &logMode.Value, 54 | ResourceInputs: common.ResourceInputs{ 55 | Create: pulumi.StringRef("ignored"), 56 | }, 57 | Connection: &Connection{ 58 | connectionBase: connectionBase{ 59 | Host: pulumi.StringRef(server.Host), 60 | Port: pulumi.Float64Ref(float64(server.Port)), 61 | User: pulumi.StringRef("user"), // unused but prevents nil panic 62 | PerDialTimeout: pulumi.IntRef(1), // unused but prevents nil panic 63 | }, 64 | }, 65 | } 66 | 67 | _, err := cmd.Create(ctx, infer.CreateRequest[CommandInputs]{Name: "name", Inputs: input, DryRun: false}) 68 | require.NoError(t, err) 69 | 70 | log := ctx.Output.String() 71 | 72 | // When logging both stdout and stderr, the output could be foobar or barfoo. 73 | require.Equal(t, logMode.Value.ShouldLogStdout(), strings.Contains(log, "foo")) 74 | require.Equal(t, logMode.Value.ShouldLogStderr(), strings.Contains(log, "bar")) 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/copy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | _ "embed" 19 | 20 | "github.com/pulumi/pulumi-go-provider/infer" 21 | "github.com/pulumi/pulumi-go-provider/infer/types" 22 | ) 23 | 24 | //go:embed copyToRemote.md 25 | var copyResourceDoc string 26 | 27 | type CopyToRemote struct{} 28 | 29 | var _ = (infer.Annotated)((*CopyToRemote)(nil)) 30 | 31 | // Copy implements Annotate which allows you to attach descriptions to the Copy resource. 32 | func (c *CopyToRemote) Annotate(a infer.Annotator) { 33 | a.Describe(&c, copyResourceDoc) 34 | } 35 | 36 | type CopyToRemoteInputs struct { 37 | Connection *Connection `pulumi:"connection" provider:"secret"` 38 | Triggers *[]interface{} `pulumi:"triggers,optional" provider:"replaceOnChanges"` 39 | Source types.AssetOrArchive `pulumi:"source"` 40 | RemotePath string `pulumi:"remotePath"` 41 | } 42 | 43 | func (c *CopyToRemoteInputs) Annotate(a infer.Annotator) { 44 | a.Describe(&c.Connection, "The parameters with which to connect to the remote host.") 45 | a.Describe(&c.Triggers, "Trigger replacements on changes to this input.") 46 | a.Describe(&c.Source, "An [asset or an archive](https://www.pulumi.com/docs/concepts/assets-archives/) "+ 47 | "to upload as the source of the copy. It must be path-based, i.e., be a `FileAsset` or a `FileArchive`. "+ 48 | "The item will be copied as-is; archives like .tgz will not be unpacked. "+ 49 | "Directories are copied recursively, overwriting existing files.") 50 | a.Describe(&c.RemotePath, "The destination path on the remote host. "+ 51 | "The last element of the path will be created if it doesn't exist but it's an error when additional elements don't exist. "+ 52 | "When the remote path is an existing directory, the source file or directory will be copied into that directory. "+ 53 | "When the source is a file and the remote path is an existing file, that file will be overwritten. "+ 54 | "When the source is a directory and the remote path an existing file, the copy will fail.") 55 | } 56 | 57 | func (c *CopyToRemoteInputs) sourcePath() string { 58 | if c.Source.Asset != nil { 59 | return c.Source.Asset.Path 60 | } 61 | return c.Source.Archive.Path 62 | } 63 | 64 | func (c *CopyToRemoteInputs) hash() string { 65 | if c.Source.Archive != nil { 66 | return c.Source.Archive.Hash 67 | } 68 | return c.Source.Asset.Hash 69 | } 70 | 71 | type CopyToRemoteOutputs struct { 72 | CopyToRemoteInputs 73 | } 74 | -------------------------------------------------------------------------------- /examples/aws-py-ec2-command/__main__.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import pulumi 3 | import pulumi_aws as aws 4 | import pulumi_command as command 5 | 6 | # Get the config ready to go. 7 | config = pulumi.Config() 8 | 9 | # If keyName is provided, an existing KeyPair is used, else if publicKey is provided a new KeyPair 10 | # derived from the publicKey is created. 11 | key_name = config.get('keyName') 12 | public_key = config.get('publicKey') 13 | 14 | 15 | # The privateKey associated with the selected key must be provided (either directly or base64 encoded), 16 | # along with an optional passphrase if needed. 17 | def decode_key(key): 18 | try: 19 | key = base64.b64decode(key.encode('ascii')).decode('ascii') 20 | except: 21 | pass 22 | 23 | if key.startswith('-----BEGIN RSA PRIVATE KEY-----'): 24 | return key 25 | 26 | return key.encode('ascii') 27 | 28 | 29 | private_key = config.require_secret('privateKey').apply(decode_key) 30 | 31 | # Create a new security group that permits SSH and web access. 32 | secgrp = aws.ec2.SecurityGroup('secgrp', 33 | description='Foo', 34 | ingress=[ 35 | aws.ec2.SecurityGroupIngressArgs(protocol='tcp', from_port=22, to_port=22, 36 | cidr_blocks=['0.0.0.0/0']), 37 | aws.ec2.SecurityGroupIngressArgs(protocol='tcp', from_port=80, to_port=80, 38 | cidr_blocks=['0.0.0.0/0']), 39 | ], 40 | ) 41 | 42 | # Get the AMI 43 | ami = aws.ec2.get_ami( 44 | owners=['amazon'], 45 | most_recent=True, 46 | filters=[aws.ec2.GetAmiFilterArgs( 47 | name='name', 48 | values=['amzn2-ami-hvm-2.0.????????-x86_64-gp2'], 49 | )], 50 | ) 51 | 52 | # Create an EC2 server that we'll then provision stuff onto. 53 | size = 't2.micro' 54 | if key_name is None: 55 | key = aws.ec2.KeyPair('key', public_key=public_key) 56 | key_name = key.key_name 57 | server = aws.ec2.Instance('server', 58 | instance_type=size, 59 | ami=ami.id, 60 | key_name=key_name, 61 | vpc_security_group_ids=[secgrp.id], 62 | ) 63 | 64 | 65 | conn = command.remote.ConnectionArgs( 66 | host=server.public_ip, 67 | private_key=private_key, 68 | user='ec2-user' 69 | ) 70 | 71 | # Copy a config file to our server. 72 | cp_config = command.remote.Copy( 73 | 'config', 74 | connection=conn, 75 | local_asset='myapp.conf', 76 | remote_path='myapp.conf', 77 | opts=pulumi.ResourceOptions(depends_on=[server]) 78 | ) 79 | 80 | # Execute a basic command on our server. 81 | cat_config = command.remote.Command( 82 | 'cat-config', 83 | connection=conn, 84 | create='cat myapp.conf', 85 | opts=pulumi.ResourceOptions(depends_on=[cp_config]) 86 | ) 87 | 88 | # Export the server's IP/host and stdout from the command. 89 | pulumi.export('publicIp', server.public_ip) 90 | pulumi.export('publicHostName', server.public_dns) 91 | pulumi.export('catConfigStdout', cat_config.stdout) 92 | -------------------------------------------------------------------------------- /sdk/python/pulumi_command/provider.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # *** WARNING: this file was generated by pulumi-language-python. *** 3 | # *** Do not edit by hand unless you're certain you know what you are doing! *** 4 | 5 | import builtins as _builtins 6 | import warnings 7 | import sys 8 | import pulumi 9 | import pulumi.runtime 10 | from typing import Any, Mapping, Optional, Sequence, Union, overload 11 | if sys.version_info >= (3, 11): 12 | from typing import NotRequired, TypedDict, TypeAlias 13 | else: 14 | from typing_extensions import NotRequired, TypedDict, TypeAlias 15 | from . import _utilities 16 | 17 | __all__ = ['ProviderArgs', 'Provider'] 18 | 19 | @pulumi.input_type 20 | class ProviderArgs: 21 | def __init__(__self__): 22 | """ 23 | The set of arguments for constructing a Provider resource. 24 | """ 25 | pass 26 | 27 | 28 | @pulumi.type_token("pulumi:providers:command") 29 | class Provider(pulumi.ProviderResource): 30 | @overload 31 | def __init__(__self__, 32 | resource_name: str, 33 | opts: Optional[pulumi.ResourceOptions] = None, 34 | __props__=None): 35 | """ 36 | Create a Command resource with the given unique name, props, and options. 37 | :param str resource_name: The name of the resource. 38 | :param pulumi.ResourceOptions opts: Options for the resource. 39 | """ 40 | ... 41 | @overload 42 | def __init__(__self__, 43 | resource_name: str, 44 | args: Optional[ProviderArgs] = None, 45 | opts: Optional[pulumi.ResourceOptions] = None): 46 | """ 47 | Create a Command resource with the given unique name, props, and options. 48 | :param str resource_name: The name of the resource. 49 | :param ProviderArgs args: The arguments to use to populate this resource's properties. 50 | :param pulumi.ResourceOptions opts: Options for the resource. 51 | """ 52 | ... 53 | def __init__(__self__, resource_name: str, *args, **kwargs): 54 | resource_args, opts = _utilities.get_resource_args_opts(ProviderArgs, pulumi.ResourceOptions, *args, **kwargs) 55 | if resource_args is not None: 56 | __self__._internal_init(resource_name, opts, **resource_args.__dict__) 57 | else: 58 | __self__._internal_init(resource_name, *args, **kwargs) 59 | 60 | def _internal_init(__self__, 61 | resource_name: str, 62 | opts: Optional[pulumi.ResourceOptions] = None, 63 | __props__=None): 64 | opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) 65 | if not isinstance(opts, pulumi.ResourceOptions): 66 | raise TypeError('Expected resource options to be a ResourceOptions instance') 67 | if opts.id is None: 68 | if __props__ is not None: 69 | raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') 70 | __props__ = ProviderArgs.__new__(ProviderArgs) 71 | 72 | super(Provider, __self__).__init__( 73 | 'command', 74 | resource_name, 75 | __props__, 76 | opts) 77 | 78 | -------------------------------------------------------------------------------- /sdk/nodejs/utilities.ts: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-nodejs. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | 5 | import * as runtime from "@pulumi/pulumi/runtime"; 6 | import * as pulumi from "@pulumi/pulumi"; 7 | 8 | export function getEnv(...vars: string[]): string | undefined { 9 | for (const v of vars) { 10 | const value = process.env[v]; 11 | if (value) { 12 | return value; 13 | } 14 | } 15 | return undefined; 16 | } 17 | 18 | export function getEnvBoolean(...vars: string[]): boolean | undefined { 19 | const s = getEnv(...vars); 20 | if (s !== undefined) { 21 | // NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what 22 | // Terraform uses internally when parsing boolean values. 23 | if (["1", "t", "T", "true", "TRUE", "True"].find(v => v === s) !== undefined) { 24 | return true; 25 | } 26 | if (["0", "f", "F", "false", "FALSE", "False"].find(v => v === s) !== undefined) { 27 | return false; 28 | } 29 | } 30 | return undefined; 31 | } 32 | 33 | export function getEnvNumber(...vars: string[]): number | undefined { 34 | const s = getEnv(...vars); 35 | if (s !== undefined) { 36 | const f = parseFloat(s); 37 | if (!isNaN(f)) { 38 | return f; 39 | } 40 | } 41 | return undefined; 42 | } 43 | 44 | export function getVersion(): string { 45 | let version = require('./package.json').version; 46 | // Node allows for the version to be prefixed by a "v", while semver doesn't. 47 | // If there is a v, strip it off. 48 | if (version.indexOf('v') === 0) { 49 | version = version.slice(1); 50 | } 51 | return version; 52 | } 53 | 54 | /** @internal */ 55 | export function resourceOptsDefaults(): any { 56 | return { version: getVersion() }; 57 | } 58 | 59 | /** @internal */ 60 | export function lazyLoad(exports: any, props: string[], loadModule: any) { 61 | for (let property of props) { 62 | Object.defineProperty(exports, property, { 63 | enumerable: true, 64 | get: function() { 65 | return loadModule()[property]; 66 | }, 67 | }); 68 | } 69 | } 70 | 71 | /** @internal */ 72 | export async function callAsync( 73 | tok: string, 74 | props: pulumi.Inputs, 75 | res?: pulumi.Resource, 76 | opts?: {property?: string}, 77 | ): Promise { 78 | const o: any = runtime.call(tok, props, res); 79 | const value = await o.promise(true /*withUnknowns*/); 80 | const isKnown = await o.isKnown; 81 | const isSecret = await o.isSecret; 82 | const problem: string|undefined = 83 | !isKnown ? "an unknown value" 84 | : isSecret ? "a secret value" 85 | : undefined; 86 | // Ingoring o.resources silently. They are typically non-empty, r.f() calls include r as a dependency. 87 | if (problem) { 88 | throw new Error(`Plain resource method "${tok}" incorrectly returned ${problem}. ` + 89 | "This is an error in the provider, please report this to the provider developer."); 90 | } 91 | // Extract a single property if requested. 92 | if (opts && opts.property) { 93 | return value[opts.property]; 94 | } 95 | return value; 96 | } 97 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/commandController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/pulumi/pulumi-go-provider/infer" 21 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 22 | ) 23 | 24 | // These are not required. They indicate to Go that Command implements the following interfaces. 25 | // If the function signature doesn't match or isn't implemented, we get nice compile time errors in this file. 26 | var _ = (infer.CustomResource[CommandInputs, CommandOutputs])((*Command)(nil)) 27 | var _ = (infer.CustomUpdate[CommandInputs, CommandOutputs])((*Command)(nil)) 28 | var _ = (infer.CustomDelete[CommandOutputs])((*Command)(nil)) 29 | 30 | // This is the Create method. This will be run on every Command resource creation. 31 | func (*Command) Create(ctx context.Context, req infer.CreateRequest[CommandInputs]) (infer.CreateResponse[CommandOutputs], error) { 32 | name := req.Name 33 | input := req.Inputs 34 | preview := req.DryRun 35 | state := CommandOutputs{CommandInputs: input} 36 | var err error 37 | id, err := resource.NewUniqueHex(name, 8, 0) 38 | if err != nil { 39 | return infer.CreateResponse[CommandOutputs]{ID: "", Output: state}, err 40 | } 41 | if preview { 42 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, nil 43 | } 44 | 45 | if state.Create == nil { 46 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, nil 47 | } 48 | cmd := "" 49 | if state.Create != nil { 50 | cmd = *state.Create 51 | } 52 | 53 | if !preview { 54 | err = state.run(ctx, cmd, input.Logging) 55 | } 56 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, err 57 | } 58 | 59 | // The Update method will be run on every update. 60 | func (*Command) Update(ctx context.Context, req infer.UpdateRequest[CommandInputs, CommandOutputs]) (infer.UpdateResponse[CommandOutputs], error) { 61 | olds := req.State 62 | news := req.Inputs 63 | preview := req.DryRun 64 | state := CommandOutputs{CommandInputs: news, BaseOutputs: olds.BaseOutputs} 65 | if preview { 66 | return infer.UpdateResponse[CommandOutputs]{Output: state}, nil 67 | } 68 | var err error 69 | if !preview { 70 | if news.Update != nil { 71 | err = state.run(ctx, *news.Update, news.Logging) 72 | } else if news.Create != nil { 73 | err = state.run(ctx, *news.Create, news.Logging) 74 | } 75 | } 76 | return infer.UpdateResponse[CommandOutputs]{Output: state}, err 77 | } 78 | 79 | // The Delete method will run when the resource is deleted. 80 | func (*Command) Delete(ctx context.Context, req infer.DeleteRequest[CommandOutputs]) (infer.DeleteResponse, error) { 81 | props := req.State 82 | if props.Delete == nil { 83 | return infer.DeleteResponse{}, nil 84 | } 85 | return infer.DeleteResponse{}, props.run(ctx, *props.Delete, props.Logging) 86 | } 87 | -------------------------------------------------------------------------------- /sdk/dotnet/Remote/Outputs/ProxyConnection.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Immutable; 7 | using System.Threading.Tasks; 8 | using Pulumi.Serialization; 9 | 10 | namespace Pulumi.Command.Remote.Outputs 11 | { 12 | 13 | /// 14 | /// Instructions for how to connect to a remote endpoint via a bastion host. 15 | /// 16 | [OutputType] 17 | public sealed class ProxyConnection 18 | { 19 | /// 20 | /// SSH Agent socket path. Default to environment variable SSH_AUTH_SOCK if present. 21 | /// 22 | public readonly string? AgentSocketPath; 23 | /// 24 | /// Max allowed errors on trying to dial the remote host. -1 set count to unlimited. Default value is 10. 25 | /// 26 | public readonly int? DialErrorLimit; 27 | /// 28 | /// The address of the bastion host to connect to. 29 | /// 30 | public readonly string Host; 31 | /// 32 | /// The expected host key to verify the server's identity. If not provided, the host key will be ignored. 33 | /// 34 | public readonly string? HostKey; 35 | /// 36 | /// The password we should use for the connection to the bastion host. 37 | /// 38 | public readonly string? Password; 39 | /// 40 | /// Max number of seconds for each dial attempt. 0 implies no maximum. Default value is 15 seconds. 41 | /// 42 | public readonly int? PerDialTimeout; 43 | /// 44 | /// The port of the bastion host to connect to. 45 | /// 46 | public readonly double? Port; 47 | /// 48 | /// The contents of an SSH key to use for the connection. This takes preference over the password if provided. 49 | /// 50 | public readonly string? PrivateKey; 51 | /// 52 | /// The password to use in case the private key is encrypted. 53 | /// 54 | public readonly string? PrivateKeyPassword; 55 | /// 56 | /// The user that we should use for the connection to the bastion host. 57 | /// 58 | public readonly string? User; 59 | 60 | [OutputConstructor] 61 | private ProxyConnection( 62 | string? agentSocketPath, 63 | 64 | int? dialErrorLimit, 65 | 66 | string host, 67 | 68 | string? hostKey, 69 | 70 | string? password, 71 | 72 | int? perDialTimeout, 73 | 74 | double? port, 75 | 76 | string? privateKey, 77 | 78 | string? privateKeyPassword, 79 | 80 | string? user) 81 | { 82 | AgentSocketPath = agentSocketPath; 83 | DialErrorLimit = dialErrorLimit; 84 | Host = host; 85 | HostKey = hostKey; 86 | Password = password; 87 | PerDialTimeout = perDialTimeout; 88 | Port = port; 89 | PrivateKey = privateKey; 90 | PrivateKeyPassword = privateKeyPassword; 91 | User = user; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/ec2_remote/index.ts: -------------------------------------------------------------------------------- 1 | import { interpolate, Config, asset } from "@pulumi/pulumi"; 2 | import { local, remote, types } from "@pulumi/command"; 3 | import * as aws from "@pulumi/aws"; 4 | import * as fs from "fs"; 5 | import * as os from "os"; 6 | import * as path from "path"; 7 | import { size } from "./size"; 8 | 9 | const config = new Config(); 10 | const keyName = config.get("keyName") ?? 11 | new aws.ec2.KeyPair("key", { publicKey: config.require("publicKey") }).keyName; 12 | const privateKeyBase64 = config.get("privateKeyBase64"); 13 | const privateKey = privateKeyBase64 ? 14 | Buffer.from(privateKeyBase64, 'base64').toString('ascii') : 15 | fs.readFileSync(path.join(os.homedir(), ".ssh", "id_rsa")).toString("utf8"); 16 | 17 | const secgrp = new aws.ec2.SecurityGroup("secgrp", { 18 | description: "Foo", 19 | ingress: [ 20 | { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }, 21 | { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, 22 | ], 23 | }); 24 | 25 | const ami = aws.ec2.getAmiOutput({ 26 | owners: ["amazon"], 27 | mostRecent: true, 28 | filters: [{ 29 | name: "name", 30 | values: ["amzn2-ami-hvm-*-x86_64-ebs"], 31 | }], 32 | }); 33 | 34 | const server = new aws.ec2.Instance("server", { 35 | instanceType: size, 36 | ami: ami.id, 37 | keyName: keyName, 38 | vpcSecurityGroupIds: [secgrp.id], 39 | }, { replaceOnChanges: ["instanceType"] }); 40 | 41 | const connection: types.input.remote.ConnectionArgs = { 42 | host: server.publicIp, 43 | user: "ec2-user", 44 | privateKey: privateKey, 45 | }; 46 | 47 | const connectionNoDialRetry: types.input.remote.ConnectionArgs = { 48 | ...connection, 49 | dialErrorLimit: 1, 50 | }; 51 | 52 | // We poll the server until it responds. 53 | // 54 | // Because other commands depend on this command, other commands are guaranteed 55 | // to hit an already booted server. 56 | const poll = new remote.Command("poll", { 57 | connection: { ...connection, dialErrorLimit: -1 }, 58 | create: "echo 'Connection established'", 59 | }, { customTimeouts: { create: "12m" } }) 60 | 61 | const hostname = new remote.Command("hostname", { 62 | connection, 63 | create: "hostname", 64 | }, { dependsOn: poll }); 65 | 66 | new remote.Command("remotePrivateIP", { 67 | connection, 68 | create: interpolate`echo ${server.privateIp} > private_ip.txt`, 69 | delete: `rm private_ip.txt`, 70 | }, { deleteBeforeReplace: true, dependsOn: poll }); 71 | 72 | new remote.Command("remoteWithNoDialRetryPrivateIP", { 73 | connection: connectionNoDialRetry, 74 | create: interpolate`echo ${server.privateIp} > private_ip_on_no_dial_retry.txt`, 75 | delete: `rm private_ip_on_no_dial_retry.txt`, 76 | }, { deleteBeforeReplace: true, dependsOn: poll }); 77 | 78 | new local.Command("localPrivateIP", { 79 | create: interpolate`echo ${server.privateIp} > private_ip.txt`, 80 | delete: `rm private_ip.txt`, 81 | }, { deleteBeforeReplace: true }); 82 | 83 | const sizeFile = new remote.CopyToRemote("size", { 84 | connection, 85 | source: new asset.FileAsset("./size.ts"), 86 | remotePath: "size.ts", 87 | }, { dependsOn: poll }) 88 | 89 | const catSize = new remote.Command("checkSize", { 90 | connection, 91 | create: "cat size.ts", 92 | }, { dependsOn: sizeFile }) 93 | 94 | export const connectionSecret = hostname.connection; 95 | export const confirmSize = catSize.stdout; 96 | export const publicIp = server.publicIp; 97 | export const publicHostName = server.publicDns; 98 | export const hostnameStdout = hostname.stdout; 99 | -------------------------------------------------------------------------------- /provider/pkg/provider/remote/command.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package remote 16 | 17 | import ( 18 | _ "embed" 19 | 20 | "github.com/pulumi/pulumi-go-provider/infer" 21 | 22 | "github.com/pulumi/pulumi-command/provider/pkg/provider/common" 23 | ) 24 | 25 | //go:embed command.md 26 | var resourceDoc string 27 | 28 | type Command struct{} 29 | 30 | // Implementing Annotate lets you provide descriptions for resources and they will 31 | // be visible in the provider's schema and the generated SDKs. 32 | func (c *Command) Annotate(a infer.Annotator) { 33 | a.Describe(&c, resourceDoc) 34 | } 35 | 36 | // The arguments for a remote Command resource. 37 | type CommandInputs struct { 38 | common.ResourceInputs 39 | // the pulumi-go-provider library uses field tags to dictate behavior. 40 | // pulumi:"connection" specifies the name of the field in the schema 41 | // pulumi:"optional" specifies that a field is optional. This must be a pointer. 42 | // provider:"replaceOnChanges" specifies that the resource will be replaced if the field changes. 43 | // provider:"secret" specifies that a field should be marked secret. 44 | Stdin *string `pulumi:"stdin,optional"` 45 | Logging *Logging `pulumi:"logging,optional"` 46 | Connection *Connection `pulumi:"connection" provider:"secret"` 47 | Environment map[string]string `pulumi:"environment,optional"` 48 | AddPreviousOutputInEnv *bool `pulumi:"addPreviousOutputInEnv,optional"` 49 | } 50 | 51 | // Implementing Annotate lets you provide descriptions and default values for arguments and they will 52 | // be visible in the provider's schema and the generated SDKs. 53 | func (c *CommandInputs) Annotate(a infer.Annotator) { 54 | a.Describe(&c.Stdin, "Pass a string to the command's process as standard in") 55 | a.Describe(&c.Logging, `If the command's stdout and stderr should be logged. This doesn't affect the capturing of 56 | stdout and stderr as outputs. If there might be secrets in the output, you can disable logging here and mark the 57 | outputs as secret via 'additionalSecretOutputs'. Defaults to logging both stdout and stderr.`) 58 | a.Describe(&c.Connection, "The parameters with which to connect to the remote host.") 59 | a.Describe(&c.Environment, `Additional environment variables available to the command's process. 60 | Note that this only works if the SSH server is configured to accept these variables via AcceptEnv. 61 | Alternatively, if a Bash-like shell runs the command on the remote host, you could prefix the command itself 62 | with the variables in the form 'VAR=value command'.`) 63 | a.Describe(&c.AddPreviousOutputInEnv, 64 | `If the previous command's stdout and stderr (as generated by the prior create/update) is 65 | injected into the environment of the next run as PULUMI_COMMAND_STDOUT and PULUMI_COMMAND_STDERR. 66 | Defaults to true.`) 67 | } 68 | 69 | // The properties for a remote Command resource. 70 | type CommandOutputs struct { 71 | CommandInputs 72 | BaseOutputs 73 | } 74 | -------------------------------------------------------------------------------- /sdk/dotnet/Utilities.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | namespace Pulumi.Command 5 | { 6 | static class Utilities 7 | { 8 | public static string? GetEnv(params string[] names) 9 | { 10 | foreach (var n in names) 11 | { 12 | var value = global::System.Environment.GetEnvironmentVariable(n); 13 | if (value != null) 14 | { 15 | return value; 16 | } 17 | } 18 | return null; 19 | } 20 | 21 | static string[] trueValues = { "1", "t", "T", "true", "TRUE", "True" }; 22 | static string[] falseValues = { "0", "f", "F", "false", "FALSE", "False" }; 23 | public static bool? GetEnvBoolean(params string[] names) 24 | { 25 | var s = GetEnv(names); 26 | if (s != null) 27 | { 28 | if (global::System.Array.IndexOf(trueValues, s) != -1) 29 | { 30 | return true; 31 | } 32 | if (global::System.Array.IndexOf(falseValues, s) != -1) 33 | { 34 | return false; 35 | } 36 | } 37 | return null; 38 | } 39 | 40 | public static int? GetEnvInt32(params string[] names) => int.TryParse(GetEnv(names), out int v) ? (int?)v : null; 41 | 42 | public static double? GetEnvDouble(params string[] names) => double.TryParse(GetEnv(names), out double v) ? (double?)v : null; 43 | 44 | [global::System.Obsolete("Please use WithDefaults instead")] 45 | public static global::Pulumi.InvokeOptions WithVersion(this global::Pulumi.InvokeOptions? options) 46 | { 47 | var dst = options ?? new global::Pulumi.InvokeOptions{}; 48 | dst.Version = options?.Version ?? Version; 49 | return dst; 50 | } 51 | 52 | public static global::Pulumi.InvokeOptions WithDefaults(this global::Pulumi.InvokeOptions? src) 53 | { 54 | var dst = src ?? new global::Pulumi.InvokeOptions{}; 55 | dst.Version = src?.Version ?? Version; 56 | return dst; 57 | } 58 | 59 | public static global::Pulumi.InvokeOutputOptions WithDefaults(this global::Pulumi.InvokeOutputOptions? src) 60 | { 61 | var dst = src ?? new global::Pulumi.InvokeOutputOptions{}; 62 | dst.Version = src?.Version ?? Version; 63 | return dst; 64 | } 65 | 66 | private readonly static string version; 67 | public static string Version => version; 68 | 69 | static Utilities() 70 | { 71 | var assembly = global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Utilities)).Assembly; 72 | using var stream = assembly.GetManifestResourceStream("Pulumi.Command.version.txt"); 73 | using var reader = new global::System.IO.StreamReader(stream ?? throw new global::System.NotSupportedException("Missing embedded version.txt file")); 74 | version = reader.ReadToEnd().Trim(); 75 | var parts = version.Split("\n"); 76 | if (parts.Length == 2) 77 | { 78 | // The first part is the provider name. 79 | version = parts[1].Trim(); 80 | } 81 | } 82 | } 83 | 84 | internal sealed class CommandResourceTypeAttribute : global::Pulumi.ResourceTypeAttribute 85 | { 86 | public CommandResourceTypeAttribute(string type) : base(type, Utilities.Version) 87 | { 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /provider/pkg/provider/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package provider 16 | 17 | import ( 18 | p "github.com/pulumi/pulumi-go-provider" 19 | "github.com/pulumi/pulumi-go-provider/infer" 20 | "github.com/pulumi/pulumi-go-provider/middleware/schema" 21 | 22 | "github.com/pulumi/pulumi-command/provider/pkg/provider/local" 23 | "github.com/pulumi/pulumi-command/provider/pkg/provider/remote" 24 | ) 25 | 26 | const ( 27 | Name = "command" 28 | ) 29 | 30 | // This provider uses the `pulumi-go-provider` library to produce a code-first provider definition. 31 | func NewProvider() p.Provider { 32 | return infer.Provider(infer.Options{ 33 | // This is the metadata for the provider 34 | Metadata: schema.Metadata{ 35 | DisplayName: "Command", 36 | Description: "The Pulumi Command Provider enables you to execute commands and scripts either locally or remotely as part of the Pulumi resource model.", 37 | Keywords: []string{ 38 | "pulumi", 39 | "command", 40 | "category/utility", 41 | "kind/native", 42 | }, 43 | Homepage: "https://pulumi.com", 44 | License: "Apache-2.0", 45 | Repository: "https://github.com/pulumi/pulumi-command", 46 | Publisher: "Pulumi", 47 | LogoURL: "https://raw.githubusercontent.com/pulumi/pulumi-command/master/assets/logo.svg", 48 | // This contains language specific details for generating the provider's SDKs 49 | LanguageMap: map[string]any{ 50 | "csharp": map[string]any{ 51 | "respectSchemaVersion": true, 52 | "packageReferences": map[string]string{ 53 | "Pulumi": "3.*", 54 | }, 55 | }, 56 | "go": map[string]any{ 57 | "respectSchemaVersion": true, 58 | "generateResourceContainerTypes": true, 59 | "importBasePath": "github.com/pulumi/pulumi-command/sdk/go/command", 60 | }, 61 | "nodejs": map[string]any{ 62 | "respectSchemaVersion": true, 63 | }, 64 | "python": map[string]any{ 65 | "respectSchemaVersion": true, 66 | "pyproject": map[string]bool{ 67 | "enabled": true, 68 | }, 69 | }, 70 | "java": map[string]any{ 71 | "buildFiles": "gradle", 72 | "gradleNexusPublishPluginVersion": "2.0.0", 73 | "dependencies": map[string]any{ 74 | "com.pulumi:pulumi": "1.0.0", 75 | "com.google.code.gson:gson": "2.8.9", 76 | "com.google.code.findbugs:jsr305": "3.0.2", 77 | }, 78 | }, 79 | }, 80 | }, 81 | // A list of `infer.Resource` that are provided by the provider. 82 | Resources: []infer.InferredResource{ 83 | // The Command resource implementation is commented extensively for new pulumi-go-provider developers. 84 | infer.Resource(&local.Command{}), 85 | infer.Resource(&remote.Command{}), 86 | infer.Resource(&remote.CopyToRemote{}), 87 | infer.Resource(&remote.CopyFile{}), 88 | }, 89 | // Functions or invokes that are provided by the provider. 90 | Functions: []infer.InferredFunction{ 91 | // The Run function is commented extensively for new pulumi-go-provider developers. 92 | infer.Function(&local.Run{}), 93 | }, 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /sdk/dotnet/Remote/Outputs/Connection.cs: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-dotnet. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Immutable; 7 | using System.Threading.Tasks; 8 | using Pulumi.Serialization; 9 | 10 | namespace Pulumi.Command.Remote.Outputs 11 | { 12 | 13 | /// 14 | /// Instructions for how to connect to a remote endpoint. 15 | /// 16 | [OutputType] 17 | public sealed class Connection 18 | { 19 | /// 20 | /// SSH Agent socket path. Default to environment variable SSH_AUTH_SOCK if present. 21 | /// 22 | public readonly string? AgentSocketPath; 23 | /// 24 | /// Max allowed errors on trying to dial the remote host. -1 set count to unlimited. Default value is 10. 25 | /// 26 | public readonly int? DialErrorLimit; 27 | /// 28 | /// The address of the resource to connect to. 29 | /// 30 | public readonly string Host; 31 | /// 32 | /// The expected host key to verify the server's identity. If not provided, the host key will be ignored. 33 | /// 34 | public readonly string? HostKey; 35 | /// 36 | /// The password we should use for the connection. 37 | /// 38 | public readonly string? Password; 39 | /// 40 | /// Max number of seconds for each dial attempt. 0 implies no maximum. Default value is 15 seconds. 41 | /// 42 | public readonly int? PerDialTimeout; 43 | /// 44 | /// The port to connect to. Defaults to 22. 45 | /// 46 | public readonly double? Port; 47 | /// 48 | /// The contents of an SSH key to use for the connection. This takes preference over the password if provided. 49 | /// 50 | public readonly string? PrivateKey; 51 | /// 52 | /// The password to use in case the private key is encrypted. 53 | /// 54 | public readonly string? PrivateKeyPassword; 55 | /// 56 | /// The connection settings for the bastion/proxy host. 57 | /// 58 | public readonly Outputs.ProxyConnection? Proxy; 59 | /// 60 | /// The user that we should use for the connection. 61 | /// 62 | public readonly string? User; 63 | 64 | [OutputConstructor] 65 | private Connection( 66 | string? agentSocketPath, 67 | 68 | int? dialErrorLimit, 69 | 70 | string host, 71 | 72 | string? hostKey, 73 | 74 | string? password, 75 | 76 | int? perDialTimeout, 77 | 78 | double? port, 79 | 80 | string? privateKey, 81 | 82 | string? privateKeyPassword, 83 | 84 | Outputs.ProxyConnection? proxy, 85 | 86 | string? user) 87 | { 88 | AgentSocketPath = agentSocketPath; 89 | DialErrorLimit = dialErrorLimit; 90 | Host = host; 91 | HostKey = hostKey; 92 | Password = password; 93 | PerDialTimeout = perDialTimeout; 94 | Port = port; 95 | PrivateKey = privateKey; 96 | PrivateKeyPassword = privateKeyPassword; 97 | Proxy = proxy; 98 | User = user; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/ec2_dir_copy/index.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from "@pulumi/pulumi"; 2 | import { remote, types } from "@pulumi/command"; 3 | import * as aws from "@pulumi/aws"; 4 | import * as fs from "fs"; 5 | import * as os from "os"; 6 | import * as path from "path"; 7 | import { hashElement } from "folder-hash"; 8 | import { size } from "./size"; 9 | 10 | export = async () => { 11 | // Get a key pair to connect to the EC2 instance. If the name of an existing key pair is 12 | // provided, use it, otherwise create one. We get the private key from config, or default to 13 | // the default id_rsa SSH key. 14 | const config = new pulumi.Config(); 15 | const keyName = config.get("keyName") ?? 16 | new aws.ec2.KeyPair("key", { publicKey: config.require("publicKey") }).keyName; 17 | const privateKeyBase64 = config.get("privateKeyBase64"); 18 | const privateKey = privateKeyBase64 ? 19 | Buffer.from(privateKeyBase64, 'base64').toString('ascii') : 20 | fs.readFileSync(path.join(os.homedir(), ".ssh", "id_rsa")).toString("utf8"); 21 | 22 | // Create a security group that allows SSH traffic. 23 | const secgrp = new aws.ec2.SecurityGroup("secgrp", { 24 | description: "Foo", 25 | ingress: [ 26 | { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }, 27 | ], 28 | }); 29 | 30 | // Get the latest Amazon Linux AMI (image) for the region we're using. 31 | const ami = aws.ec2.getAmiOutput({ 32 | owners: ["amazon"], 33 | mostRecent: true, 34 | filters: [{ 35 | name: "name", 36 | values: ["al2023-ami-2023.*-kernel-*-x86_64"], 37 | }], 38 | }); 39 | 40 | // Create the EC2 instance we will copy files to. 41 | const server = new aws.ec2.Instance("server", { 42 | instanceType: size, 43 | ami: ami.id, 44 | keyName: keyName, 45 | vpcSecurityGroupIds: [secgrp.id], 46 | }, { 47 | replaceOnChanges: ["instanceType"], 48 | }); 49 | 50 | // The configuration of our SSH connection to the instance. 51 | const connection: types.input.remote.ConnectionArgs = { 52 | host: server.publicIp, 53 | user: "ec2-user", 54 | privateKey: privateKey, 55 | }; 56 | 57 | // Poll the server until it responds. 58 | // 59 | // Because other commands depend on this command, other commands are guaranteed 60 | // to hit an already booted server. 61 | const poll = new remote.Command("poll", { 62 | connection: { ...connection, dialErrorLimit: -1 }, 63 | create: "echo 'Connection established'", 64 | }, { customTimeouts: { create: "10m" } }) 65 | 66 | ////// Start of the actual test ////// 67 | 68 | const from = path.join(__dirname, "src/"); 69 | const to = config.get("destDir")!; 70 | 71 | const archive = new pulumi.asset.FileArchive(from); 72 | const copy = new remote.CopyToRemote("copy", { 73 | connection, 74 | source: archive, 75 | remotePath: to, 76 | }, { dependsOn: poll }); 77 | 78 | // Verify that the expected files were copied to the remote. 79 | // We want to run this after each copy, i.e., when something changed, but not otherwise to avoid unclean refreshes. 80 | // We use the hash of the source directory as a trigger to achieve this, since the trigger needs to be a primitive 81 | // value and we cannot use the Copy resource itself. 82 | // The FileArchive already a hash calculated, but it's not exposed. 83 | const hash = await hashElement(from); 84 | const ls = new remote.Command("ls", { 85 | connection, 86 | create: `find ${to} | sort`, 87 | triggers: [hash], 88 | }, { dependsOn: copy }); 89 | 90 | return { 91 | destDir: to, 92 | lsRemote: ls.stdout 93 | } 94 | } -------------------------------------------------------------------------------- /examples/ec2_copyfile/index.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from "@pulumi/pulumi"; 2 | import { remote, types } from "@pulumi/command"; 3 | import * as aws from "@pulumi/aws"; 4 | import * as fs from "fs"; 5 | import * as os from "os"; 6 | import * as path from "path"; 7 | import { hashElement } from "folder-hash"; 8 | import { size } from "./size"; 9 | 10 | // This test for remote.CopyFile duplicates a bunch of code from ec2_dir_copy, but since 11 | // CopyFile is deprecated and will be removed, we don't need to spend time refactoring it. 12 | 13 | export = async () => { 14 | // Get a key pair to connect to the EC2 instance. If the name of an existing key pair is 15 | // provided, use it, otherwise create one. We get the private key from config, or default to 16 | // the default id_rsa SSH key. 17 | const config = new pulumi.Config(); 18 | const keyName = config.get("keyName") ?? 19 | new aws.ec2.KeyPair("key", { publicKey: config.require("publicKey") }).keyName; 20 | const privateKeyBase64 = config.get("privateKeyBase64"); 21 | const privateKey = privateKeyBase64 ? 22 | Buffer.from(privateKeyBase64, 'base64').toString('ascii') : 23 | fs.readFileSync(path.join(os.homedir(), ".ssh", "id_rsa")).toString("utf8"); 24 | 25 | // Create a security group that allows SSH traffic. 26 | const secgrp = new aws.ec2.SecurityGroup("secgrp", { 27 | description: "Foo", 28 | ingress: [ 29 | { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }, 30 | ], 31 | }); 32 | 33 | // Get the latest Amazon Linux AMI (image) for the region we're using. 34 | const ami = aws.ec2.getAmiOutput({ 35 | owners: ["amazon"], 36 | mostRecent: true, 37 | filters: [{ 38 | name: "name", 39 | values: ["al2023-ami-2023.*-kernel-*-x86_64"], 40 | }], 41 | }); 42 | 43 | // Create the EC2 instance we will copy files to. 44 | const server = new aws.ec2.Instance("server", { 45 | instanceType: size, 46 | ami: ami.id, 47 | keyName: keyName, 48 | vpcSecurityGroupIds: [secgrp.id], 49 | }, { 50 | replaceOnChanges: ["instanceType"], 51 | }); 52 | 53 | // The configuration of our SSH connection to the instance. 54 | const connection: types.input.remote.ConnectionArgs = { 55 | host: server.publicIp, 56 | user: "ec2-user", 57 | privateKey: privateKey, 58 | }; 59 | 60 | // Poll the server until it responds. 61 | // 62 | // Because other commands depend on this command, other commands are guaranteed 63 | // to hit an already booted server. 64 | const poll = new remote.Command("poll", { 65 | connection: { ...connection, dialErrorLimit: -1 }, 66 | create: "echo 'Connection established'", 67 | }, { customTimeouts: { create: "10m" } }) 68 | 69 | ////// Start of the actual test ////// 70 | 71 | const from = path.join(__dirname, "src/file1"); 72 | const to = config.get("destDir")!; 73 | 74 | const copy = new remote.CopyFile("copy", { 75 | connection, 76 | localPath: from, 77 | remotePath: to, 78 | }, { dependsOn: poll }); 79 | 80 | // Verify that the expected files were copied to the remote. 81 | // We want to run this after each copy, i.e., when something changed, but not otherwise to avoid unclean refreshes. 82 | // We use the hash of the source directory as a trigger to achieve this, since the trigger needs to be a primitive 83 | // value and we cannot use the Copy resource itself. 84 | // The FileArchive already a hash calculated, but it's not exposed. 85 | const hash = await hashElement(from); 86 | const ls = new remote.Command("ls", { 87 | connection, 88 | create: `find ${to} | sort`, 89 | triggers: [hash], 90 | }, { dependsOn: copy }); 91 | 92 | return { 93 | destDir: to, 94 | lsRemote: ls.stdout 95 | } 96 | } -------------------------------------------------------------------------------- /sdk/java/src/main/java/com/pulumi/command/Utilities.java: -------------------------------------------------------------------------------- 1 | // *** WARNING: this file was generated by pulumi-language-java. *** 2 | // *** Do not edit by hand unless you're certain you know what you are doing! *** 3 | 4 | package com.pulumi.command; 5 | 6 | 7 | 8 | 9 | 10 | import java.io.BufferedReader; 11 | import java.io.InputStreamReader; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | import javax.annotation.Nullable; 15 | import com.pulumi.core.internal.Environment; 16 | import com.pulumi.deployment.InvokeOptions; 17 | import com.pulumi.deployment.InvokeOutputOptions; 18 | 19 | public class Utilities { 20 | 21 | public static Optional getEnv(java.lang.String... names) { 22 | for (var n : names) { 23 | var value = Environment.getEnvironmentVariable(n); 24 | if (value.isValue()) { 25 | return Optional.of(value.value()); 26 | } 27 | } 28 | return Optional.empty(); 29 | } 30 | 31 | public static Optional getEnvBoolean(java.lang.String... names) { 32 | for (var n : names) { 33 | var value = Environment.getBooleanEnvironmentVariable(n); 34 | if (value.isValue()) { 35 | return Optional.of(value.value()); 36 | } 37 | } 38 | return Optional.empty(); 39 | } 40 | 41 | public static Optional getEnvInteger(java.lang.String... names) { 42 | for (var n : names) { 43 | var value = Environment.getIntegerEnvironmentVariable(n); 44 | if (value.isValue()) { 45 | return Optional.of(value.value()); 46 | } 47 | } 48 | return Optional.empty(); 49 | } 50 | 51 | public static Optional getEnvDouble(java.lang.String... names) { 52 | for (var n : names) { 53 | var value = Environment.getDoubleEnvironmentVariable(n); 54 | if (value.isValue()) { 55 | return Optional.of(value.value()); 56 | } 57 | } 58 | return Optional.empty(); 59 | } 60 | 61 | public static InvokeOptions withVersion(@Nullable InvokeOptions options) { 62 | if (options != null && options.getVersion().isPresent()) { 63 | return options; 64 | } 65 | return new InvokeOptions( 66 | options == null ? null : options.getParent().orElse(null), 67 | options == null ? null : options.getProvider().orElse(null), 68 | getVersion() 69 | ); 70 | } 71 | 72 | public static InvokeOutputOptions withVersion(@Nullable InvokeOutputOptions options) { 73 | if (options != null && options.getVersion().isPresent()) { 74 | return options; 75 | } 76 | return new InvokeOutputOptions( 77 | options == null ? null : options.getParent().orElse(null), 78 | options == null ? null : options.getProvider().orElse(null), 79 | getVersion(), 80 | options == null ? null : options.getDependsOn() 81 | ); 82 | } 83 | 84 | private static final java.lang.String version; 85 | public static java.lang.String getVersion() { 86 | return version; 87 | } 88 | 89 | static { 90 | var resourceName = "com/pulumi/command/version.txt"; 91 | var versionFile = Utilities.class.getClassLoader().getResourceAsStream(resourceName); 92 | if (versionFile == null) { 93 | throw new IllegalStateException( 94 | java.lang.String.format("expected resource '%s' on Classpath, not found", resourceName) 95 | ); 96 | } 97 | version = new BufferedReader(new InputStreamReader(versionFile)) 98 | .lines() 99 | .collect(Collectors.joining("\n")) 100 | .trim(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /provider/pkg/provider/local/commandController.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package local 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/pulumi/pulumi-go-provider/infer" 21 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 22 | ) 23 | 24 | // The following statements are not required. They are type assertions to indicate to Go that Command implements the following interfaces. 25 | // If the function signature doesn't match or isn't implemented, we get nice compile time errors at this location. 26 | 27 | // They would normally be included in the commandController.go file, but they're located here for instructive purposes. 28 | var _ = (infer.CustomResource[CommandInputs, CommandOutputs])((*Command)(nil)) 29 | var _ = (infer.CustomUpdate[CommandInputs, CommandOutputs])((*Command)(nil)) 30 | var _ = (infer.CustomDelete[CommandOutputs])((*Command)(nil)) 31 | 32 | // This is the Create method. This will be run on every Command resource creation. 33 | func (c *Command) Create(ctx context.Context, req infer.CreateRequest[CommandInputs]) (infer.CreateResponse[CommandOutputs], error) { 34 | name := req.Name 35 | input := req.Inputs 36 | preview := req.DryRun 37 | state := CommandOutputs{CommandInputs: input} 38 | id, err := resource.NewUniqueHex(name, 8, 0) 39 | if err != nil { 40 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, err 41 | } 42 | 43 | // If in preview, don't run the command. 44 | if preview { 45 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, nil 46 | } 47 | if input.Create == nil { 48 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, nil 49 | } 50 | cmd := *input.Create 51 | err = run(ctx, cmd, state.BaseInputs, &state.BaseOutputs, input.Logging) 52 | return infer.CreateResponse[CommandOutputs]{ID: id, Output: state}, err 53 | } 54 | 55 | // WireDependencies controls how secrets and unknowns flow through a resource. 56 | // 57 | // var _ = (infer.ExplicitDependencies[CommandInputs, CommandOutputs])((*Command)(nil)) 58 | // func (r *Command) WireDependencies(f infer.FieldSelector, args *CommandInputs, state *CommandOutputs) { .. } 59 | // 60 | // Because we want every output to depend on every input, we can leave the default behavior. 61 | 62 | // The Update method will be run on every update. 63 | func (c *Command) Update(ctx context.Context, req infer.UpdateRequest[CommandInputs, CommandOutputs]) (infer.UpdateResponse[CommandOutputs], error) { 64 | olds := req.State 65 | news := req.Inputs 66 | preview := req.DryRun 67 | state := CommandOutputs{CommandInputs: news, BaseOutputs: olds.BaseOutputs} 68 | // If in preview, don't run the command. 69 | if preview { 70 | return infer.UpdateResponse[CommandOutputs]{Output: state}, nil 71 | } 72 | // Use Create command if Update is unspecified. 73 | cmd := news.Create 74 | if news.Update != nil { 75 | cmd = news.Update 76 | } 77 | // If neither are specified, do nothing. 78 | if cmd == nil { 79 | return infer.UpdateResponse[CommandOutputs]{Output: state}, nil 80 | } 81 | err := run(ctx, *cmd, state.BaseInputs, &state.BaseOutputs, news.Logging) 82 | return infer.UpdateResponse[CommandOutputs]{Output: state}, err 83 | } 84 | 85 | // The Delete method will run when the resource is deleted. 86 | func (c *Command) Delete(ctx context.Context, req infer.DeleteRequest[CommandOutputs]) (infer.DeleteResponse, error) { 87 | props := req.State 88 | if props.Delete == nil { 89 | return infer.DeleteResponse{}, nil 90 | } 91 | return infer.DeleteResponse{}, run(ctx, *props.Delete, props.BaseInputs, &props.BaseOutputs, props.Logging) 92 | } 93 | -------------------------------------------------------------------------------- /examples/ec2_remote_proxy/index.ts: -------------------------------------------------------------------------------- 1 | import { interpolate, Config, asset } from "@pulumi/pulumi"; 2 | import { local, remote, types } from "@pulumi/command"; 3 | import * as aws from "@pulumi/aws"; 4 | import * as fs from "fs"; 5 | import * as os from "os"; 6 | import * as path from "path"; 7 | import { size } from "./size"; 8 | 9 | const config = new Config(); 10 | const keyName = config.get("keyName") ?? new aws.ec2.KeyPair("key", { publicKey: config.require("publicKey") }).keyName; 11 | const privateKeyBase64 = config.get("privateKeyBase64"); 12 | const privateKey = privateKeyBase64 ? Buffer.from(privateKeyBase64, 'base64').toString('ascii') : fs.readFileSync(path.join(os.homedir(), ".ssh", "id_rsa")).toString("utf8"); 13 | 14 | const ingress = new aws.ec2.SecurityGroup("ingress", { 15 | description: "A security group that will accept SSH connections from the outside world.", 16 | ingress: [ 17 | { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }, 18 | { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, 19 | ], 20 | egress: [ 21 | { fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"], }, 22 | ], 23 | }); 24 | 25 | const validated = new aws.ec2.SecurityGroup("validated", { 26 | description: "A security group that will only accept connections that have already been validated.", 27 | ingress: [ 28 | { protocol: "tcp", fromPort: 22, toPort: 22, securityGroups: [ingress.id] }, 29 | { protocol: "tcp", fromPort: 80, toPort: 80, securityGroups: [ingress.id] }, 30 | ], 31 | egress: [ 32 | { fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"], }, 33 | ], 34 | }); 35 | 36 | const ami = aws.ec2.getAmiOutput({ 37 | owners: ["amazon"], 38 | mostRecent: true, 39 | filters: [{ 40 | name: "name", 41 | values: ["amzn2-ami-hvm-*-x86_64-ebs"], 42 | }], 43 | }); 44 | 45 | const server = new aws.ec2.Instance("server", { 46 | instanceType: size, 47 | ami: ami.id, 48 | keyName: keyName, 49 | vpcSecurityGroupIds: [validated.id], 50 | }, { replaceOnChanges: ["instanceType"] }); 51 | 52 | const proxy = new aws.ec2.Instance("proxy", { 53 | instanceType: size, 54 | ami: ami.id, 55 | keyName: keyName, 56 | vpcSecurityGroupIds: [ingress.id], 57 | }, { replaceOnChanges: ["instanceType"] }); 58 | 59 | const connection: types.input.remote.ConnectionArgs = { 60 | host: server.privateDns, 61 | user: "ec2-user", 62 | privateKey: privateKey, 63 | proxy: { 64 | host: proxy.publicIp, 65 | user: "ec2-user", 66 | privateKey: privateKey, 67 | }, 68 | }; 69 | 70 | const hostname = new remote.Command("hostname", { 71 | connection: { 72 | ...connection, 73 | dialErrorLimit: -1, 74 | proxy: { 75 | ...connection.proxy, 76 | dialErrorLimit: -1, 77 | } 78 | }, 79 | create: "hostname", 80 | }, { customTimeouts: { create: "12m" } }); 81 | 82 | new remote.Command("remotePrivateIP", { 83 | connection, 84 | create: interpolate`echo ${server.privateIp} > private_ip.txt`, 85 | delete: `rm private_ip.txt`, 86 | }, { deleteBeforeReplace: true, dependsOn: hostname }); 87 | 88 | new local.Command("localPrivateIP", { 89 | create: interpolate`echo ${server.privateIp} > private_ip.txt`, 90 | delete: `rm private_ip.txt`, 91 | }, { deleteBeforeReplace: true, dependsOn: hostname }); 92 | 93 | const sizeFile = new remote.CopyToRemote("size", { 94 | connection: connection, 95 | source: new asset.FileAsset("./size.ts"), 96 | remotePath: "size.ts", 97 | }, { dependsOn: hostname }) 98 | 99 | const catSize = new remote.Command("checkSize", { 100 | connection: connection, 101 | create: "cat size.ts", 102 | }, { dependsOn: sizeFile }) 103 | 104 | export const connectionSecret = hostname.connection; 105 | export const confirmSize = catSize.stdout; 106 | export const publicIp = server.publicIp; 107 | export const publicHostName = server.publicDns; 108 | export const hostnameStdout = hostname.stdout; 109 | --------------------------------------------------------------------------------