Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ config*.json
yarn-error.log
*.pem
*.pub
dry-run-diff-log*.json
Makefile
.vscode/*
.claude/*
Expand All @@ -21,3 +22,4 @@ CLAUDE.md
.github/agents/*
.github/skills/*
package-lock.json
.github/hooks/*
224 changes: 224 additions & 0 deletions docs/using-dry-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Using Dry Run

The Auth0 Deploy CLI supports a "dry run" mode that allows you to preview all potential changes to your Auth0 tenant without actually applying them. This feature provides a safety net for validating configurations and understanding the impact of deployments before they occur.

[Discussions thread](https://github.com/auth0/auth0-deploy-cli/discussions/1149)

## Usage

### Command Line Interface

Add the `--dry-run` flag to your import command:

```bash
a0deploy import --config_file=config.json --input_file=./tenant.yaml --dry-run
```

Or use the shorter form:

```bash
a0deploy import -c config.json -i ./tenant-directory --dry-run
```

### Node Module

Set the `AUTH0_DRY_RUN` configuration property to `true`:

```javascript
import { deploy } from 'auth0-deploy-cli';

deploy({
input_file: './local/tenant.yaml',
config: {
AUTH0_DOMAIN: process.env.AUTH0_DOMAIN,
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
AUTH0_DRY_RUN: true,
},
})
.then(() => {
console.log('Dry run completed successfully');
})
.catch((err) => {
console.log('Error during dry run:', err);
});
```

**Note**: To get the interactive menu when using the Node module, also set `AUTH0_DRY_RUN_INTERACTIVE: true`.

To show the preview and then apply without prompting when using the Node module, set `AUTH0_DRY_RUN_APPLY: true` alongside `AUTH0_DRY_RUN: true`.

### Environment Variable

You can also enable dry run mode using an environment variable:

```bash
export AUTH0_DRY_RUN=true
a0deploy import -c config.json -i ./tenant.yaml
```

## CI/CD Usage

By default, `--dry-run` is non-interactive and safe for CI pipelines.

### Preview Only

Shows the plan and exits without applying changes:

```bash
a0deploy import -c config.json -i tenant.yaml --dry-run
```

GitHub Actions:

```yaml
- name: Preview Auth0 changes
run: a0deploy import -c config.json -i tenant.yaml --dry-run
```

### Deployment with Visibility

Show the plan and apply without prompting:

```bash
a0deploy import -c config.json -i tenant.yaml --dry-run --apply
```

### Interactive Review (Manual Deployments)

Add `--interactive` to get the menu (apply / export to file / exit):

```bash
a0deploy import -c config.json -i tenant.yaml --dry-run --interactive
```

## Understanding the Output

Dry run mode provides a detailed preview of all proposed changes in a table format:

```text
Auth0 Deploy CLI - Dry Run Preview

Tenant: example-tenant.auth0.com
Input: local/tenant.yaml

Simulating deployment... The following changes are proposed:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Resource β”‚ Status β”‚ Name / Identifier β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Actions β”‚ CREATE β”‚ Post-Login User Enrichment β”‚
β”‚ β”‚ CREATE β”‚ Pre-Registration Validation β”‚
β”‚ β”‚ DELETE* β”‚ Deprecated Action β”‚
β”‚ Clients β”‚ CREATE β”‚ New SPA Application β”‚
β”‚ β”‚ UPDATE β”‚ Existing M2M Application β”‚
β”‚ Connections β”‚ UPDATE β”‚ Username-Password-Authentication β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

* Requires AUTH0_ALLOW_DELETE to be enabled

Dry Run completed successfully. No changes have been made to your Auth0 tenant.

β”Œ dry-run
β”‚
β—† What would you like to do?
β”‚ β—‹ Apply changes
β”‚ β—‹ Export changes in a file (No Apply)
β”‚ β—‹ Exit
β””
```

## Interactive Options

After displaying the dry run preview, the CLI presents interactive options:

- **Apply changes**: Proceed to apply all the changes shown in the preview
- **Export changes in a file**: Save the changes to a `dry-run-diff-log.json` file without applying them
- **Exit**: Cancel the operation without making any changes

## Resource Deletion Preview

When `AUTH0_ALLOW_DELETE` is enabled, dry run will show which resources would be deleted:

```bash
# Enable deletions in your config
export AUTH0_ALLOW_DELETE=true
a0deploy import -c config.json -i ./tenant.yaml --dry-run
```

Resources marked for deletion will appear in the output with `DELETE*` status, and the asterisk note will explain that `AUTH0_ALLOW_DELETE` must be enabled for deletions to occur.

## Validation and Error Handling

Dry run performs the same validation as a regular deployment:

- **Schema Validation**: Ensures all resources conform to Auth0's expected structure
- **Business Rule Validation**: Checks for conflicts, required fields, and logical constraints
- **Configuration Validation**: Validates keyword replacements and file references

If validation errors are found, dry run will report them without making any changes:

```text
Validation Error: Rule "My Rule" - Names must be unique
Error: Configuration validation failed. No changes made.
```

## No Changes Detected

If no changes are detected between your configuration and the current tenant state, dry run will display:

```text
Auth0 Deploy CLI - Dry Run Preview

Tenant: example-tenant.auth0.com
Input: ./tenant-config/

Simulating deployment... The following changes are proposed:

No changes detected.

Dry Run completed successfully. No changes have been made to your Auth0 tenant.
```

## Best Practices

1. **Always Dry Run First**: Make dry run a standard part of your deployment workflow, especially for production environments.

2. **Review All Changes**: Carefully examine the proposed changes, paying special attention to deletions and updates to critical resources.

3. **Validate in Stages**: For large configuration changes, consider breaking them into smaller deployments and dry running each stage.

4. **Check Dependencies**: Ensure that resource dependencies (like client grants referencing specific clients) are properly configured.

5. **Environment Consistency**: Use dry run to verify that keyword replacements produce the expected values for your target environment.

## Limitations and Considerations

- **State Changes**: The actual tenant state may change between running a dry run and the actual deployment. The dry run reflects the state at the time it was executed.

- **API Limitations**: Some validation can only be performed during actual API calls. Dry run performs as much validation as possible without making changes.

- **Resource Dependencies**: Complex dependencies between resources might only be fully validated during actual deployment.

- **External Factors**: Changes made by other users or processes to the Auth0 tenant are not reflected in the dry run results.

## Troubleshooting

### Common Issues

**Configuration Errors**: If you see configuration-related errors, verify your keyword replacement mappings and file paths.

**Authentication Issues**: Ensure your Auth0 client has the necessary scopes to read all resources you're trying to manage.

**File Not Found**: Check that all referenced files (scripts, templates, etc.) exist and are accessible.

**Format Mismatches**: Verify that your input format matches your configuration. Using YAML format with JSON-specific dry run configurations may cause issues, and vice versa. Ensure consistency between your input file format and configuration settings.

### Getting Help

If you encounter issues with dry run mode:

1. Enable debug logging: `--debug` flag or `AUTH0_DEBUG=true`
2. Check the [troubleshooting guide](./troubleshooting.md)
3. Review your configuration against the [documentation](./configuring-the-deploy-cli.md)
4. Open an issue on the [GitHub repository](https://github.com/auth0/auth0-deploy-cli/issues)
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
"readme": "README.md",
"homepage": "https://github.com/auth0/auth0-deploy-cli#readme",
"dependencies": {
"@clack/prompts": "1.1.0",
"ajv": "^6.12.6",
"auth0": "^5.4.0",
"chalk": "5.6.2",
"dot-prop": "^5.3.0",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.1",
Expand All @@ -48,6 +50,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/chai": "^5.2.3",
"@types/fs-extra": "^9.0.13",
"@types/lodash": "^4.17.24",
"@types/mocha": "^10.0.10",
Expand Down
34 changes: 34 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ type SharedParams = {
experimental_ea?: boolean;
};

export type DryRunMode = 'preview';

type ImportSpecificParams = {
input_file: string;
dry_run?: boolean | '' | DryRunMode;
interactive?: boolean;
apply?: boolean;
};

type ExportSpecificParams = {
Expand Down Expand Up @@ -73,6 +78,23 @@ function getParams(): CliParams {
'The client secret, this allows you to encrypt the secret in your build configuration instead of storing it in a config file',
type: 'string',
},
dry_run: {
alias: 'dry-run',
describe:
'Dry-run mode. Show plan without applying changes. Use --interactive for the menu or --apply to show the plan and apply without prompting.',
type: 'string',
},
interactive: {
describe:
'Use with --dry-run to enable the interactive menu (apply / export to file / exit). Not available in non-TTY environments.',
type: 'boolean',
default: false,
},
apply: {
describe: 'Use with --dry-run to show the plan and then apply without prompting.',
type: 'boolean',
default: false,
},
})
.command(['export', 'dump'], 'Export Auth0 Tenant Configuration', {
output_folder: {
Expand Down Expand Up @@ -121,6 +143,18 @@ function getParams(): CliParams {
)
.example('$0 import -c config.json -i tenant.yaml', 'Deploy Auth0 via YAML')
.example('$0 import -c config.json -i path/to/files', 'Deploy Auth0 via Path')
.example(
'$0 import -c config.json -i tenant.yaml --dry-run',
'Preview changes and exit without applying changes'
)
.example(
'$0 import -c config.json -i tenant.yaml --dry-run --apply',
'Show plan and apply without prompting (CI deployment)'
)
.example(
'$0 import -c config.json -i tenant.yaml --dry-run --interactive',
'Show plan with interactive menu (apply / export / exit)'
)
.example(
'$0 dump -c config.json -f yaml -o path/to/export',
'Dump Auth0 config to folder in YAML format'
Expand Down
42 changes: 40 additions & 2 deletions src/commands/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { deploy as toolsDeploy } from '../tools';
import log from '../logger';
import { setupContext } from '../context';
import { ImportParams } from '../args';
import { isTruthy } from '../utils';

export default async function importCMD(params: ImportParams) {
const {
Expand All @@ -14,8 +15,28 @@ export default async function importCMD(params: ImportParams) {
env: shouldInheritEnv = false,
secret: clientSecret,
experimental_ea: experimentalEA,
dry_run: dryRun,
interactive = false,
apply = false,
} = params;

const normalizedDryRun = dryRun === true || dryRun === '' ? 'preview' : dryRun;
let effectiveDryRun: 'preview' | undefined;

if (!normalizedDryRun) {
effectiveDryRun = undefined;
} else if (normalizedDryRun !== 'preview') {
throw new Error(
`Invalid value for --dry-run: ${normalizedDryRun}. Use --dry-run or --dry-run=preview.`
);
} else {
effectiveDryRun = normalizedDryRun;
}

if (apply && !effectiveDryRun) {
throw new Error('--apply must be used with --dry-run.');
}

if (shouldInheritEnv) {
nconf.env().use('memory');

Expand Down Expand Up @@ -48,6 +69,21 @@ export default async function importCMD(params: ImportParams) {
nconf.set('AUTH0_EXPERIMENTAL_EA', experimentalEA);
}

// Override AUTH0_DRY_RUN if dry_run passed in command line
if (effectiveDryRun) {
overrides.AUTH0_DRY_RUN = effectiveDryRun;
nconf.set('AUTH0_DRY_RUN', effectiveDryRun);
}
if (interactive) {
overrides.AUTH0_DRY_RUN_INTERACTIVE = interactive;
nconf.set('AUTH0_DRY_RUN_INTERACTIVE', interactive);
}
const existingDryRunApply = nconf.get('AUTH0_DRY_RUN_APPLY');
if (apply || isTruthy(existingDryRunApply)) {
overrides.AUTH0_DRY_RUN_APPLY = true;
nconf.set('AUTH0_DRY_RUN_APPLY', true);
}

nconf.overrides(overrides);

// Setup context and load
Expand All @@ -57,8 +93,10 @@ export default async function importCMD(params: ImportParams) {
const config = configFactory();
config.setProvider((key) => nconf.get(key));

//@ts-ignore because context and assets still need to be typed TODO: type assets and type context
// @ts-ignore because context and assets still need to be typed TODO: type assets and type context
await toolsDeploy(context.assets, context.mgmtClient, config);

log.info('Import Successful');
if (!effectiveDryRun) {
log.info('Import Successful');
}
}
Loading