Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/cli/cli-subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ To list currently active subscriptions:
planet subscriptions list --status running
```

To list suspended subscriptions:
```sh
planet subscriptions list --status suspended
```

To list subscriptions with the `catalog` source type:
```sh
planet subscriptions list --source-type catalog
Expand Down Expand Up @@ -313,6 +318,102 @@ planet subscriptions cancel cb817760-1f07-4ee7-bba6-bcac5346343f
That will stop the subscription from producing any more results, but it will stay in the system so you can
continue to list and get it.

### Suspend Subscription

You can temporarily pause a subscription without canceling it. This is useful when you want to stop processing
and delivery for a period of time but plan to resume later. Suspended subscriptions can be reactivated at any time.

To suspend a single subscription:

```sh
planet subscriptions suspend cb817760-1f07-4ee7-bba6-bcac5346343f
```

You can optionally include details explaining why the subscription is being suspended:

```sh
planet subscriptions suspend cb817760-1f07-4ee7-bba6-bcac5346343f \
--details "Pausing during maintenance window"
```

The suspend command returns the updated subscription JSON, showing the new status.

### Bulk Suspend Subscriptions

The `bulk-suspend` command provides flexible options for suspending multiple subscriptions at once.

To suspend all of your subscriptions (default behavior):

```sh
planet subscriptions bulk-suspend
```

To suspend specific subscriptions:

```sh
planet subscriptions bulk-suspend \
--subscription-ids cb817760-1f07-4ee7-bba6-bcac5346343f,a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6
```

To suspend all subscriptions across your organization (requires organization admin permissions):

```sh
planet subscriptions bulk-suspend --all
```

You can include details explaining the reason for suspension:

```sh
planet subscriptions bulk-suspend --details "System maintenance scheduled"
```

Or combine with specific subscription IDs:

```sh
planet subscriptions bulk-suspend \
--subscription-ids cb817760-1f07-4ee7-bba6-bcac5346343f,a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6 \
--details "Pausing during data migration"
```

**Note:** The `--subscription-ids` and `--all` flags are mutually exclusive. If neither flag is provided,
all of your subscriptions will be suspended.

### Reactivate Subscription

To resume a suspended subscription, use the reactivate command:

```sh
planet subscriptions reactivate cb817760-1f07-4ee7-bba6-bcac5346343f
```

The subscription will resume processing and delivering imagery based on its original configuration.

### Bulk Reactivate Subscriptions

The `bulk-reactivate` command provides flexible options for reactivating multiple suspended subscriptions.

To reactivate all of your suspended subscriptions (default behavior):

```sh
planet subscriptions bulk-reactivate
```

To reactivate specific subscriptions:

```sh
planet subscriptions bulk-reactivate \
--subscription-ids cb817760-1f07-4ee7-bba6-bcac5346343f,a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6
```

To reactivate all suspended subscriptions across your organization (requires organization admin permissions):

```sh
planet subscriptions bulk-reactivate --all
```

**Note:** The `--subscription-ids` and `--all` flags are mutually exclusive. If neither flag is provided,
all of your suspended subscriptions will be reactivated.

## Subscription Request Conveniences

There are a couple of commands that can assist in creating the subscription JSON, used for creation and updates.
Expand Down
85 changes: 85 additions & 0 deletions planet/cli/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,91 @@ async def cancel_subscription_cmd(ctx, subscription_id, pretty):
_ = await client.cancel_subscription(subscription_id)


@subscriptions.command(name='suspend') # type: ignore
@click.argument('subscription_id')
@click.option('--details',
help='Optional details explaining suspension reason')
@pretty
@click.pass_context
@translate_exceptions
@coro
async def suspend_subscription_cmd(ctx, subscription_id, details, pretty):
"""Suspends a subscription and prints the suspended subscription."""
async with subscriptions_client(ctx) as client:
sub = await client.suspend_subscription(subscription_id, details)
echo_json(sub, pretty)


@subscriptions.command(name='reactivate') # type: ignore
@click.argument('subscription_id')
@pretty
@click.pass_context
@translate_exceptions
@coro
async def reactivate_subscription_cmd(ctx, subscription_id, pretty):
"""Reactivates a subscription."""
async with subscriptions_client(ctx) as client:
_ = await client.reactivate_subscription(subscription_id)


@subscriptions.command(name='bulk-suspend') # type: ignore
@click.option('--subscription-ids',
type=types.CommaSeparatedString(),
help='Comma-separated list of subscription IDs to suspend')
@click.option('--all',
'all_flag',
is_flag=True,
help='Suspend all organization subscriptions (admin only)')
@click.option('--details',
help='Optional details explaining suspension reason')
@pretty
@click.pass_context
@translate_exceptions
@coro
async def bulk_suspend_subscriptions_cmd(ctx,
subscription_ids,
all_flag,
details,
pretty):
"""Suspends multiple subscriptions.

By default (no flags), suspends all of the user's subscriptions.
Use --subscription-ids to suspend specific subscriptions.
Use --all to suspend all organization subscriptions (requires admin).
"""
async with subscriptions_client(ctx) as client:
_ = await client.bulk_suspend_subscriptions(subscription_ids,
details,
all_flag)


@subscriptions.command(name='bulk-reactivate') # type: ignore
@click.option('--subscription-ids',
type=types.CommaSeparatedString(),
help='Comma-separated list of subscription IDs to reactivate')
@click.option('--all',
'all_flag',
is_flag=True,
help='Reactivate all organization subscriptions (admin only)')
@pretty
@click.pass_context
@translate_exceptions
@coro
async def bulk_reactivate_subscriptions_cmd(ctx,
subscription_ids,
all_flag,
pretty):
"""Reactivates multiple subscriptions.

By default (no flags), reactivates all of the user's subscriptions.
Use --subscription-ids to reactivate specific subscriptions.
Use --all to reactivate all organization subscriptions (requires admin).
"""
async with subscriptions_client(ctx) as client:
_ = await client.bulk_reactivate_subscriptions(subscription_ids,
all_flag)


@subscriptions.command(name='update') # type: ignore
@click.argument('subscription_id')
@click.argument('request', type=types.JSON())
Expand Down
170 changes: 168 additions & 2 deletions planet/clients/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,177 @@ async def cancel_subscription(self, subscription_id: str) -> None:
except ClientError: # pragma: no cover
raise

async def suspend_subscription(self,
subscription_id: str,
details: Optional[str] = None) -> dict:
"""Suspend a Subscription.

Args:
subscription_id (str): id of subscription to suspend.
details (str): optional details explaining the reason
for suspension.

Returns:
dict: the suspended subscription.

Raises:
APIError: on an API server error.
ClientError: on a client error.
"""
url = f'{self._base_url}/{subscription_id}/suspend'
json_body = {'details': details} if details is not None else None

try:
resp = await self._session.request(method='POST',
url=url,
json=json_body)
# Forward APIError. We don't strictly need this clause, but it
# makes our intent clear.
except APIError:
raise
except ClientError: # pragma: no cover
raise
else:
return resp.json()

async def reactivate_subscription(self, subscription_id: str) -> None:
"""Reactivate a Subscription.

Args:
subscription_id (str): id of subscription to reactivate.

Returns:
None

Raises:
APIError: on an API server error.
ClientError: on a client error.
"""
url = f'{self._base_url}/{subscription_id}/reactivate'

try:
_ = await self._session.request(method='POST', url=url)
# Forward APIError. We don't strictly need this clause, but it
# makes our intent clear.
except APIError:
raise
except ClientError: # pragma: no cover
raise

async def bulk_suspend_subscriptions(
self,
subscription_ids: Optional[List[str]] = None,
details: Optional[str] = None,
all_subscriptions: bool = False) -> None:
"""Suspend multiple Subscriptions.

This method supports three modes of operation:

1. Suspend specific subscriptions: provide subscription_ids
2. Suspend all user's subscriptions: call with no parameters
3. Suspend all organization subscriptions: set all_subscriptions=True
(organization admin only)

Args:
subscription_ids (List[str]): list of subscription ids
to suspend. If not provided and all_subscriptions is False,
suspends all of the user's subscriptions.
details (str): optional details explaining the reason
for suspension.
all_subscriptions (bool): if True, suspend all
subscriptions for the organization (requires organization
admin permissions). Mutually exclusive with subscription_ids.

Returns:
None

Raises:
ClientError: if both subscription_ids and all_subscriptions are
provided.
APIError: on an API server error.
"""
if subscription_ids and all_subscriptions:
raise ClientError(
'Cannot specify both subscription_ids and all_subscriptions')

url = f'{self._base_url}/suspend'
params = {'user_id': 'all'} if all_subscriptions else None
payload: Dict[str, Any] = {}
if subscription_ids is not None:
payload["subscription_ids"] = subscription_ids
if details is not None:
payload["details"] = details
json_body: Optional[Dict[str, Any]] = payload or None

try:
_ = await self._session.request(method='POST',
url=url,
json=json_body,
params=params)
# Forward APIError. We don't strictly need this clause, but it
# makes our intent clear.
except APIError:
raise
except ClientError: # pragma: no cover
raise

async def bulk_reactivate_subscriptions(
self,
subscription_ids: Optional[List[str]] = None,
all_subscriptions: bool = False) -> None:
"""Reactivate multiple Subscriptions.

This method supports three modes of operation:

1. Reactivate specific subscriptions: provide subscription_ids
2. Reactivate all user's subscriptions: call with no parameters
3. Reactivate all organization subscriptions: set all_subscriptions=True
(organization admin only)

Args:
subscription_ids (List[str]): list of subscription ids
to reactivate. If not provided and all_subscriptions is False,
reactivates all of the user's subscriptions.
all_subscriptions (bool): if True, reactivate all
subscriptions for the organization (requires organization
admin permissions). Mutually exclusive with subscription_ids.

Returns:
None

Raises:
ClientError: if both subscription_ids and all_subscriptions are
provided.
APIError: on an API server error.
"""
if subscription_ids and all_subscriptions:
raise ClientError(
'Cannot specify both subscription_ids and all_subscriptions')

url = f'{self._base_url}/reactivate'
params = {'user_id': 'all'} if all_subscriptions else None
payload: Dict[str, Any] = {}
if subscription_ids is not None:
payload["subscription_ids"] = subscription_ids
json_body: Optional[Dict[str, Any]] = payload or None

try:
_ = await self._session.request(method='POST',
url=url,
json=json_body,
params=params)
# Forward APIError. We don't strictly need this clause, but it
# makes our intent clear.
except APIError:
raise
except ClientError: # pragma: no cover
raise

async def update_subscription(self, subscription_id: str,
request: dict) -> dict:
"""Update (edit) a Subscription via PUT.

Args
Args:
subscription_id (str): id of the subscription to update.
request (dict): subscription content for update, full
payload is required.
Expand Down Expand Up @@ -293,7 +459,7 @@ async def patch_subscription(self, subscription_id: str,
request: dict) -> dict:
"""Update (edit) a Subscription via PATCH.

Args
Args:
subscription_id (str): id of the subscription to update.
request (dict): subscription content for update, only
attributes to update are required.
Expand Down
Loading