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: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ src/
TALXIS.CLI.Logging # Structured logging infrastructure

TALXIS.CLI.Features.Config # txc config: profiles, auth, connections, settings
TALXIS.CLI.Features.Environment # txc environment: solution/package/deployment commands
TALXIS.CLI.Features.Environment # txc environment: env list/create, solution/package/deployment commands
TALXIS.CLI.Features.Data # txc data: model conversion, data packages, transforms
TALXIS.CLI.Features.Docs # txc docs (placeholder)
TALXIS.CLI.Features.Workspace # txc workspace: scaffolding, templates, validation
Expand Down
165 changes: 165 additions & 0 deletions docs/environment-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Environment Lifecycle

`txc env list`, `txc env create`, `txc env update`, and `txc env delete` manage Power Platform environments at the **tenant level** — they use the active profile's credential and cloud for admin authority, not a target environment URL.

## Listing environments

```sh
txc env list [--filter <substring>] [--type <type>] [--format json|text]
```

Returns every Dataverse-backed environment visible to the caller. Results include environment id, display name, URL, unique name, and lifecycle type.

| Option | Description |
|--------|-------------|
| `--filter` | Case-insensitive substring match against display name, unique name, or URL. |
| `--type`, `-t` | Filter to a single lifecycle type: `Production`, `Sandbox`, `Trial`, `Developer`, `Default`, `Teams`, `SubscriptionBasedTrial`. |
| `--profile`, `-p` | Profile supplying the admin identity and cloud. Falls back to the active profile. |
| `--format`, `-f` | `json` or `text` (auto-detected when omitted). |

### Examples

```sh
# List all environments
txc env list

# Only sandboxes, as JSON
txc env list --type Sandbox -f json

# Search by name
txc env list --filter "contoso"
```

## Creating environments

```sh
txc env create --type <type> [options]
```

Provisions a new Power Platform environment. By default the command returns immediately after the request is accepted (fire-and-forget); pass `--wait` to block until provisioning completes.

| Option | Alias | Required | Default | Description |
|--------|-------|----------|---------|-------------|
| `--type` | `-t` | **Yes** | — | `Production`, `Sandbox`, `Trial`, `Developer`, `Teams`, or `SubscriptionBasedTrial`. |
| `--name` | `-n` | Yes* | — | Display name. Required for all types except `Teams`. |
| `--region` | `-r` | No | `unitedstates` | Azure geo region slug (e.g. `europe`, `asia`, `unitedstates`). |
| `--currency` | `-c` | No | `USD` | ISO currency code, validated against the region's catalog. |
| `--language` | `-l` | No | `1033` | LCID integer or localized name (e.g. `English (United States)`). |
| `--domain` | `-d` | No | auto | Subdomain for the environment URL (2-32 chars). |
| `--templates` | — | No | — | Comma-separated Dynamics 365 app template names. |
| `--security-group-id` | `-sg` | Teams: yes | — | Entra security group id. Required for `Teams` environments. |
| `--user` | `-u` | No | — | Owning user's Entra object id. Only valid for `Developer` environments. |
| `--wait` | — | No | `false` | Block until provisioning completes. |
| `--profile` | `-p` | No | active | Profile supplying the admin identity and cloud. |

> \* `--name` is ignored for `Teams` environments (the name derives from the linked group).

### Examples

```sh
# Quick sandbox — returns immediately
txc env create --type Sandbox --name "Feature Branch 42" --region europe

# Developer environment owned by a specific user, wait for completion
txc env create --type Developer --name "Jan's Dev Box" --user 00000000-0000-0000-0000-000000000001 --wait

# Trial with a Dynamics 365 Sales template
txc env create --type Trial --name "Sales Demo" --templates D365_Sales
```

### Type-specific rules

| Type | Notes |
|------|-------|
| `Default` | **Not creatable** — this is the tenant's auto-provisioned environment. |
| `Teams` | Requires `--security-group-id`. Name is derived from the group; `--name` is ignored. |
| `Developer` | Only type that accepts `--user`. When omitted, owned by the caller. |
| `SubscriptionBasedTrial` | Behaves like `Trial` but tied to a subscription. |

### Known limitations

- **`--user` accepts only Entra object ids (GUIDs).** UPN-to-objectId resolution (which PAC CLI supports via Microsoft Graph) is not implemented. Use `az ad user show --id user@contoso.com --query id -o tsv` to look up the id.
- **No `--description` option.** The platform does not support setting a description during creation.
- **Currency, language, and template validation is region-specific.** The CLI fetches the per-region catalog and fails fast with the valid values when a mismatch is detected.

## Updating environments

```sh
txc env update <id> [--name <name>] [--type <type>] [--security-group-id <guid>]
```

Updates properties of an existing environment. Only the supplied options are changed — omitted properties are left as-is.

| Option | Alias | Description |
|--------|-------|-------------|
| `<id>` | — | Environment id (GUID) to update. **Required.** |
| `--name` | `-n` | New display name. |
| `--type` | `-t` | Convert to a different type (e.g. `Sandbox` → `Production`). |
| `--security-group-id` | `-sg` | Entra security group that gates access. Pass `00000000-0000-0000-0000-000000000000` to remove the restriction. |
| `--profile` | `-p` | Profile supplying the admin identity and cloud. |

### Examples

```sh
# Rename an environment
txc env update 11111111-1111-1111-1111-111111111111 --name "Production - Contoso"

# Promote a sandbox to production
txc env update 11111111-1111-1111-1111-111111111111 --type Production

# Restrict access to a security group
txc env update 11111111-1111-1111-1111-111111111111 \
--security-group-id 22222222-2222-2222-2222-222222222222

# Remove the security group restriction
txc env update 11111111-1111-1111-1111-111111111111 \
--security-group-id 00000000-0000-0000-0000-000000000000
```

## Deleting environments

```sh
txc env delete <id> [--yes] [--wait]
```

**This action is irreversible.** Permanently deletes a Power Platform environment and all its data. The platform validates that the environment can be deleted before initiating the operation (e.g. environments with active D365 apps or managed-environment policies may be blocked).

| Option | Required | Default | Description |
|--------|----------|---------|-------------|
| `<id>` | **Yes** | — | Environment id (GUID) to delete. |
| `--yes` | No | — | Skip interactive confirmation prompt. Required in non-interactive (CI) environments. |
| `--wait` | No | `false` | Block until deletion completes. |
| `--profile`, `-p` | No | active | Profile supplying the admin identity and cloud. |
| `--allow-production` | No | — | Required when targeting Production or Default environments (safety guard). |

### Examples

```sh
# Interactive delete with confirmation prompt
txc env delete 11111111-1111-1111-1111-111111111111

# CI/scripting — skip prompt, wait for completion
txc env delete 11111111-1111-1111-1111-111111111111 --yes --wait

# Delete a production environment (requires explicit opt-in)
txc env delete 11111111-1111-1111-1111-111111111111 --yes --allow-production
```

## Authentication

All environment lifecycle commands use the active profile (or `--profile`) to resolve a credential and cloud instance. The credential acquires an admin token — no target environment URL is needed, since these are tenant-level operations.

See [profiles-and-authentication.md](profiles-and-authentication.md) for how profiles work.

## MCP integration

All environment lifecycle commands are automatically exposed as MCP tools:

| CLI command | MCP tool name | Access hint |
|-------------|--------------|-------------|
| `txc env list` | `environment_list` | `ReadOnlyHint` |
| `txc env create` | `environment_create` | `IdempotentHint` |
| `txc env update` | `environment_update` | `IdempotentHint` |
| `txc env delete` | `environment_delete` | `DestructiveHint` |

No special MCP configuration is needed — tool registration is reflection-driven from the CLI command tree.
4 changes: 4 additions & 0 deletions src/TALXIS.CLI.Core/Model/EnvironmentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ public enum EnvironmentType
Developer = 3,
/// <summary>Default environment — auto-provisioned per tenant; treated as Production for safety.</summary>
Default = 4,
/// <summary>Microsoft Teams-linked environment — backs a Teams team; not destructive by default.</summary>
Teams = 5,
/// <summary>Subscription-based trial environment — time-limited, convertible to production; no destructive guard.</summary>
SubscriptionBasedTrial = 6,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using TALXIS.CLI.Core.Model;

namespace TALXIS.CLI.Core.Platforms.PowerPlatform;

/// <summary>
/// A Power Platform environment as surfaced by <c>txc env list</c>. A
/// provider-agnostic projection so the management-plane CLI never depends on
/// control-plane implementation types.
/// </summary>
public sealed record EnvironmentInfo(
Guid EnvironmentId,
string DisplayName,
Uri EnvironmentUrl,
string? UniqueName,
Guid? OrganizationId,
EnvironmentType? EnvironmentType);

/// <summary>
/// User-supplied inputs for <c>txc env create</c>. Raw, human-friendly values
/// (region slug, currency code, language name/LCID, template names) are
/// resolved and validated by the control-plane implementation.
/// </summary>
public sealed record EnvironmentCreateOptions
{
public string? DisplayName { get; init; }
public required EnvironmentType EnvironmentType { get; init; }
public string Region { get; init; } = "unitedstates";
public string CurrencyCode { get; init; } = "USD";
public string Language { get; init; } = "1033";
public string? DomainName { get; init; }
public IReadOnlyList<string> Templates { get; init; } = Array.Empty<string>();
public Guid? SecurityGroupId { get; init; }
public Guid? UserObjectId { get; init; }
public bool Wait { get; init; }
public TimeSpan MaxWait { get; init; } = TimeSpan.FromMinutes(60);
}

/// <summary>
/// Result of an environment creation. When the caller does not wait,
/// <see cref="Completed"/> is <c>false</c> and <see cref="OperationLocation"/>
/// carries the URL that reports provisioning progress.
/// </summary>
public sealed record EnvironmentCreateOutcome(
Guid? EnvironmentId,
string? DisplayName,
Uri? EnvironmentUrl,
EnvironmentType? EnvironmentType,
string Status,
bool Completed,
Uri? OperationLocation);

/// <summary>
/// User-supplied inputs for <c>txc env update</c>. Only non-null properties
/// are patched — omitted fields are left unchanged on the environment.
/// </summary>
public sealed record EnvironmentUpdateOptions
{
public required Guid EnvironmentId { get; init; }
public string? DisplayName { get; init; }
public EnvironmentType? EnvironmentType { get; init; }
public Guid? SecurityGroupId { get; init; }
}

/// <summary>
/// Result of an environment update.
/// </summary>
public sealed record EnvironmentUpdateOutcome(
Guid EnvironmentId,
string? DisplayName,
EnvironmentType? EnvironmentType,
string Status);

/// <summary>
/// Result of an environment deletion. When the caller does not wait,
/// <see cref="Completed"/> is <c>false</c> and <see cref="OperationLocation"/>
/// carries the URL that reports deletion progress.
/// </summary>
public sealed record EnvironmentDeleteOutcome(
Guid EnvironmentId,
string Status,
bool Completed,
Uri? OperationLocation);

/// <summary>
/// Tenant-level environment administration: listing the environments visible
/// to the active profile's identity, creating new ones, and deleting existing
/// ones. Resolves the (Profile, Connection, Credential) triple internally —
/// the credential and cloud supply the admin authority, independent of any
/// single target environment URL.
/// </summary>
public interface IEnvironmentManagementService
{
/// <summary>
/// Lists the Dataverse-backed environments in the tenant visible to the
/// resolved profile's identity.
/// </summary>
Task<IReadOnlyList<EnvironmentInfo>> ListAsync(
string? profileName,
CancellationToken ct);

/// <summary>
/// Creates a new environment using the resolved profile's credential and
/// cloud for admin authority.
/// </summary>
Task<EnvironmentCreateOutcome> CreateAsync(
string? profileName,
EnvironmentCreateOptions options,
CancellationToken ct);

/// <summary>
/// Updates properties of an existing environment. Only the non-null fields
/// in <paramref name="options"/> are changed.
/// </summary>
Task<EnvironmentUpdateOutcome> UpdateAsync(
string? profileName,
EnvironmentUpdateOptions options,
CancellationToken ct);

/// <summary>
/// Permanently deletes an environment from the tenant. The BAP admin API
/// validates that the environment can be deleted before initiating the
/// operation. By default returns immediately; pass <paramref name="wait"/>
/// to block until deletion completes.
/// </summary>
Task<EnvironmentDeleteOutcome> DeleteAsync(
string? profileName,
Guid environmentId,
bool wait,
TimeSpan maxWait,
CancellationToken ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace TALXIS.CLI.Features.Environment;
Name = "environment",
Alias = "env",
Description = "Manage the footprint of your project in a live target environment (packages, solutions, deployment history).",
Children = new[] { typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(OptionSet.OptionSetCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand) },
Children = new[] { typeof(EnvironmentListCliCommand), typeof(EnvironmentCreateCliCommand), typeof(EnvironmentUpdateCliCommand), typeof(EnvironmentDeleteCliCommand), typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(OptionSet.OptionSetCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class EnvironmentCliCommand
Expand Down
Loading