Skip to content

Commit

Permalink
Support install-only workflow (#834)
Browse files Browse the repository at this point in the history
Adds support for an install-only workflow
by making the `command` and `stack` inputs optional.

If command is unset, we will stop after installing the pulumi CLI.
This will allow us to deprecate the @pulumi/setup-pulumi action
if we so choose.

Co-authored-by: Abhinav Gupta <abhinav@pulumi.com>
  • Loading branch information
RobbieMcKinstry and abhinav authored Mar 9, 2023
1 parent 0eb7d20 commit 3ace4a6
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 11 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,49 @@ jobs:
outputs:
changed: ${{ steps.changes.outputs.dist }}

test-install-only:
needs: install-and-build
if: ${{ needs.install-and-build.outputs.changed == 'true' }}
runs-on: ${{ matrix.os }}
name: Install-only on ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
steps:
- name: Remove pre-installed Pulumi
shell: bash
env:
RUNNER_OS: ${{ matrix.os }}
run: |
EXT=""
if [ "$RUNNER_OS" == "Windows" ]; then
EXT=".exe"
fi
if command -v "pulumi${EXT}"; then
PULUMI_INSTALL_DIR=$(dirname "$(command -v "pulumi${EXT}")")
echo "Deleting Pulumi"
rm -v "$PULUMI_INSTALL_DIR"/pulumi*
fi
- uses: actions/checkout@v3

- name: Download dist artifact
uses: actions/download-artifact@v3
with:
name: dist
path: dist

# If no action is specified, just install.
- uses: ./
env:
PULUMI_CONFIG_PASSPHRASE: not-a-secret
with:
config-map: "{name: {value: my-pet, secret: false}}"

- run: pulumi version

test-dotnet-stack:
needs: install-and-build
if: ${{ needs.install-and-build.outputs.changed == 'true' }}
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

## HEAD (Unreleased)

- feat: Support install-only mode similar to
[setup-pulumi](https://github.com/marketplace/actions/setup-pulumi)
([#834](https://github.com/pulumi/actions/pull/834))

- fix: allow `comment-on-pr-number` to be used for `${{ github.event }}` types
other than `pull_request`
([#803](https://github.com/pulumi/actions/issues/803))

- bug: Fix installation on Windows.
([#851](https://github.com/pulumi/actions/pull/851))
([#851](https://github.com/pulumi/actions/pull/851))

## 4.0.0 (2023-19-01)

Expand Down
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ This will check out the existing directory and run `pulumi preview`.

The action can be configured with the following arguments:

- `command` (required) - The command to run as part of the action. Accepted
values are `up` (update), `refresh`, `destroy` and `preview`.
- `command` (optional) - The command to run as part of the action. Accepted
values are `up` (alias: update), `refresh`, `destroy`, and `preview`. If
unspecified, the action will stop after installing Pulumi.

- `stack-name` (required) - The name of the stack that Pulumi will be operating
- `stack-name` (optional) - The name of the stack that Pulumi will be operating
on. Use the fully quaified org-name/stack-name when operating on a stack
outside of your individual account.
outside of your individual account. This field is required if a `command` was
specified.

- `work-dir` (optional) - The location of your Pulumi files. Defaults to `./`.

Expand Down Expand Up @@ -124,6 +126,17 @@ By default, this action will try to authenticate Pulumi with the
`PULUMI_ACCESS_TOKEN` then you will need to specify an alternative backend via
the `cloud-url` argument.

### Installation Only

If you want to only install the Pulumi CLI, omit the `command` field of the
action.

```yaml
- uses: pulumi/actions@v4
```

This will install Pulumi and exit without performing any other operations.

### Stack Outputs

[Stack outputs](https://www.pulumi.com/docs/intro/concepts/stack/#outputs) are
Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ branding:
inputs:
command:
description: 'Pulumi command to run, eg. up'
required: true
required: false
stack-name:
description: 'Which stack you want to apply to, eg. dev'
required: true
required: false
work-dir:
description: 'Location of your Pulumi files. Defaults to ./'
required: false
Expand Down
51 changes: 51 additions & 0 deletions src/__tests__/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,57 @@ import { login } from '../login';

const spy = jest.spyOn(pulumiCli, 'run');

const installConfig: Record<string, string> = {
command: undefined,
"pulumi-version": "^2", // test with a non-default value
};

describe('Config without a provided command', () => {
let oldWorkspace = '';
beforeEach(() => {
spy.mockClear();
jest.resetModules();
// Save, then restore the current env var for GITHUB_WORKSPACE
oldWorkspace = process.env.GITHUB_WORKSPACE;
process.env.GITHUB_WORKSPACE = 'n/a';
});
afterEach(() => {
process.env.GITHUB_WORKSPACE = oldWorkspace;
});

it('should not be validated by makeConfig', async () => {
jest.mock('@actions/core', () => ({
getInput: jest.fn((name: string) => {
return installConfig[name];
}),
info: jest.fn(),
}));
jest.mock('@actions/github', () => ({
context: {},
}));
const { makeConfig } = require('../config');
await expect(makeConfig())
.rejects
.toThrow();
});

it('should be validated by makeInstallationConfig', async() => {
jest.mock('@actions/core', () => ({
getInput: jest.fn((name: string) => {
return installConfig[name];
}),
info: jest.fn(),
}));
const { makeInstallationConfig } = require('../config');
const conf = makeInstallationConfig()
expect(conf.success).toBeTruthy();
expect(conf.value).toEqual({
command: undefined,
pulumiVersion: "^2",
});
});
});

describe('main.login', () => {
beforeEach(() => {
spy.mockClear();
Expand Down
24 changes: 21 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,33 @@ import * as rt from 'runtypes';
import { parseArray, parseBoolean, parseNumber } from './libs/utils';

export const command = rt.Union(
rt.Literal('up'),
rt.Literal('update'),
rt.Literal('refresh'),
rt.Literal('destroy'),
rt.Literal('preview'),
rt.Literal('refresh'),
rt.Literal('up'),
rt.Literal('update'),
);

export type Commands = rt.Static<typeof command>;

// installationConfig is the expected Action inputs when
// the user intends to download the Pulumi CLI without
// running any other Pulumi operations.
// We expect command NOT to be provided.
export const installationConfig = rt.Record({
command: rt.Undefined,
pulumiVersion: rt.String,
});

export type InstallationConfig = rt.Static<typeof installationConfig>;

export function makeInstallationConfig(): rt.Result<InstallationConfig> {
return installationConfig.validate({
command: getInput('command') || undefined,
pulumiVersion: getInput('pulumi-version') || "^3",
});
}

export const options = rt.Partial({
parallel: rt.Number,
message: rt.String,
Expand Down
20 changes: 19 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,34 @@ import {
} from '@pulumi/pulumi/automation';
import invariant from 'ts-invariant';
import YAML from 'yaml';
import { Commands, makeConfig } from './config';
import { Commands, Config, InstallationConfig, makeConfig, makeInstallationConfig } from './config';
import { environmentVariables } from './libs/envs';
import { handlePullRequestMessage } from './libs/pr';
import * as pulumiCli from './libs/pulumi-cli';
import { login } from './login';

const main = async () => {
const downloadConfig = makeInstallationConfig();
if (downloadConfig.success) {
await installOnly(downloadConfig.value);
core.info("Pulumi has been successfully installed. Exiting.");
return;
}

// If we get here, we're not in install-only mode.
// Attempt to parse the full configuration and run the action.
const config = await makeConfig();
core.debug('Configuration is loaded');
runAction(config);
};

// installOnly is the main entrypoint of the program when the user
// intends to install the Pulumi CLI without running additional commands.
const installOnly = async (config: InstallationConfig): Promise<void> => {
await pulumiCli.downloadCli(config.pulumiVersion);
}

const runAction = async (config: Config): Promise<void> => {
await pulumiCli.downloadCli(config.options.pulumiVersion);
await login(config.cloudUrl, environmentVariables.PULUMI_ACCESS_TOKEN);

Expand Down

0 comments on commit 3ace4a6

Please sign in to comment.