Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -20,3 +21,4 @@ Makefile
.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 @@ -33,7 +33,9 @@
"readme": "README.md",
"homepage": "https://github.com/auth0/auth0-deploy-cli#readme",
"dependencies": {
"@clack/prompts": "1.1.0",
"ajv": "^6.12.6",
"chalk": "5.6.2",
"auth0": "^5.6.0",
"dot-prop": "^5.3.0",
"fs-extra": "^10.1.0",
Expand All @@ -49,6 +51,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 @@ -74,6 +79,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 @@ -127,6 +149,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
50 changes: 48 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,36 @@ 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 (interactive && !effectiveDryRun) {
throw new Error('--interactive must be used with --dry-run.');
}

if (interactive && apply) {
throw new Error('--interactive and --apply cannot be used together.');
}

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

Expand Down Expand Up @@ -48,6 +77,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 +101,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