Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support install-only workflow. #834

Merged
merged 9 commits into from Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/workflow.yml
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
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
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
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
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
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
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