Skip to content

Add extension appservice with swap command#6687

Open
Copilot wants to merge 12 commits intomainfrom
copilot/add-service-tasks-command
Open

Add extension appservice with swap command#6687
Copilot wants to merge 12 commits intomainfrom
copilot/add-service-tasks-command

Conversation

Copy link
Contributor

Copilot AI commented Feb 2, 2026

Adds azd service-tasks command to execute service-specific operations. Initial implementation provides slot swap functionality for App Service deployments.

Interface Changes

  • Extended ServiceTarget with Tasks() []ServiceTask and Task(...) error methods
  • All service targets implement placeholders returning empty task lists
  • appServiceTarget returns swap task

Command Implementation

Flags:

  • --service <name>: Direct service selection
  • --task <task;key=value;...>: Task name and arguments

Behavior:

  • Prompts for service/task selection when flags omitted
  • Returns error if no services exist or task unsupported

App Service Swap Task

Executes slot swap operations with smart defaults:

0 slots: Error - swap requires ≥1 slot
1 slot: Auto-selects slot ↔ production
2+ slots: Prompts for source and destination

Task arguments:

azd service-tasks --service api --task "swap;src=staging;dst=production"

Arguments are optional - prompts fill missing values. Unknown arguments ignored.

Validation:

  • Source ≠ destination
  • Slot names must exist
  • Confirmation prompt (default: yes)

Azure API

Added SwapSlot(subscriptionId, resourceGroup, appName, sourceSlot, targetSlot) to AzureClient:

  • Uses BeginSwapSlotWithProduction when either slot is production
  • Uses BeginSwapSlot for named slot pairs
  • Polls until completion

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • aka.ms
    • Triggering command: /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd extension source add -n local -t file -l /home/REDACTED/work/azure-dev/azure-dev/cli/azd/extensions/registry.json --64 x_amd64/vet -o st/snapshot/snapshot.go pkg/mod/google.golang.org/protob-ifaceassert x_amd64/vet -p /azure-dev/cli/a-atomic -lang=go1.22 x_amd64/vet (dns block)
    • Triggering command: /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd extension list --source local --output json x_amd64/vet -c /azure-sdk-for-g--norc /tmp/go-build161--noprofile x_amd64/vet 3014�� om/microsoft/azure-devops-go-api/azuredevops/v7@v7.1.0/build/client.go om/microsoft/azure-devops-go-api/azuredevops/v7@v7.1.0/build/models.go x_amd64/vet -p /azure-dev/cli/a-atomic -lang=go1.17 x_amd64/vet (dns block)
    • Triggering command: /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd /home/REDACTED/work/azure-dev/azure-dev/cli/azd/azd extension install azure.ai.agents --source local --version 0.1.8-preview . AD/microsoft-aut--norc x86_64-linux-gnu--noprofile x_amd64/vet -o ify@v1.11.1/mock/doc.go ify@v1.11.1/mock/mock.go x_amd64/vet -p ohnson/clock -lang=go1.22 x_amd64/vet (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>[feature] service-tasks command</issue_title>
<issue_description>Create a new core command in azd called service-tasks.
Define the help and description for the command based on the next spec:

This command provide tasks depending on the service type. When running azd service-tasks, azd starts by listing and asking which service to use. If there are no services listed, return an error.
Include support for a flag --service serviceName to skip the service selection.
Return error if there is a value for the service flag with a serviceName that is not found.
Once the service is selected, check the type of the service.

Update the ServiceTarget interface to add a new method that return an array of ServiceTask.
Define the ServiceTask just with one string Name inside.
Use the same strategy that is on Deploy to create a service_target based on the service config. Then call this new method Tasks() []ServiceTask and display a prompt to user to pick one of the tasks.
If the array of tasks is empty, display a message to the user telling there is currently no tasks for the service type and return nil to finish the run without error.

Add support to define the task with a flag like --task "swap". Same way for how the service flag can skip the prompt for the service, the flag task should skip prompting for which task to run if the value matches to one of the returned tasks listed by the service_target. If there is no match, return an error about the task swap is not supported by the <serviceType>.
The flag type is type string and can support an special syntax to provide more data after the task. It supports many strings separated by a semicolon, like task;data;moreData;otherData. In this syntax, if there is at least one ;, then the task is the first string before the ; and the remaining string is the task arguments. Make sure to handle this and extract the task and task arguments when the task flag is present.

Once the service, the task and the task arguments are known, call a new method in the ServiceTarget called Task. THis is a new method you have to add and implement to all service_targets.
The method should take a context, a ServiceTask and the task arguments (as string) and support returning error.

Implement the Task method in all service_target with a simple placeholder that returns nil. It will be just a placeholder and it should be fine because you will also make the Tasks method to return empty list for all service_targets.

Then update the appServiceTarget to support one task called swap.
In the Task() function, check if the task is swap and if yes, call a new implementation to handle this task.
Create a private method in the appServiceTarget to handle swap.
This operation can take a source and a destination within the task arguments. For example, the user can use `--task "swap;src=foo;dst=bar". The task arguments can define both, one or none, so the function should start by trying to check if all the data is in the task arguments. If something is missing, like if no src or dest were provided, prompt the user to pick the src and dst, starting by the src.
Validate that the src and dst are not the same, otherwise return an error.
To validate and get the right values to prompt for the values, make a REST call to fetch the list of deployment slots from the appServiceTarget. The appServiceTarget should already have the logic to find the app service in Azure. It should be the same logic that is used to deploy the service. This logic help us to find the main app and get the list of slots.
If there are no slots, return an error and mention that the swap operation requires a service with at least one slot.
If there is only one slot, skip the prompt to ask for src and dst and automatically set the main app as the dst and the one only slot as src. Do this if there was not task arguments. If there were arguments, then check if those match to the existing main app and slots. When there is just one slot, the options for the task arguments should only be to use the slot as src or dst and we would honor the request. It would either be a request to roll-back (when the slot is the dst) or a release (when the slot is the src).
Once we know the src and dst, make a final confirmation prompt with a default Y/n (default yes) to approve the swap.
Once approved, implement the code to do the swap and wait for it to be completed.
When there are more than one slot, and there was no task arguments provided, start by prompting for the src and ask the user what is the source slot you want to use, then ask for the dst and make sure not to list the option selected as src to avoid picking the same for both.

Implementing this feature should add support to do swap for services in azd that are using appServiceTarget and set also a foundation for how to integrate task for other services, having a command with jus...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 5 commits February 2, 2026 05:28
Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
… naming

Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
Copilot AI changed the title [WIP] Add service-tasks command to azd Add service-tasks command with App Service slot swap Feb 2, 2026
Copilot AI requested a review from vhvb1989 February 2, 2026 05:37
@vhvb1989 vhvb1989 marked this pull request as ready for review February 2, 2026 21:22
Copilot AI review requested due to automatic review settings February 2, 2026 21:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the azd service-tasks command to execute service-specific operations, with initial support for App Service deployment slot swapping.

Changes:

  • Added ServiceTarget interface methods Tasks() and Task() for service-specific task execution
  • Implemented azd service-tasks command with --service and --task flags for service/task selection
  • Added slot swap functionality for App Service deployments with validation and interactive prompts

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
cli/azd/cmd/service_tasks.go New command implementation with service/task selection logic
cli/azd/cmd/root.go Registers service-tasks command
cli/azd/pkg/project/service_target.go Extended ServiceTarget interface with Tasks/Task methods
cli/azd/pkg/project/service_target_appservice.go Implements swap task with slot validation and Azure API integration
cli/azd/pkg/azapi/webapp.go Added SwapSlot method for Azure App Service slot operations
cli/azd/pkg/project/service_target_*.go Placeholder implementations for all service targets
cli/azd/cmd/testdata/*.snap Updated test snapshots and documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vhvb1989 vhvb1989 changed the title Add service-tasks command with App Service slot swap Add extension appservice with swap command Feb 5, 2026
@vhvb1989 vhvb1989 requested a review from Copilot February 5, 2026 05:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 22 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +343 to +408
// SwapSlot swaps two deployment slots or a slot with production.
// sourceSlot: the source slot name (empty string means production)
// targetSlot: the target slot name (empty string means production)
// The swap operation swaps the content of sourceSlot into targetSlot.
func (cli *AzureClient) SwapSlot(
ctx context.Context,
subscriptionId string,
resourceGroup string,
appName string,
sourceSlot string,
targetSlot string,
) error {
client, err := cli.createWebAppsClient(ctx, subscriptionId)
if err != nil {
return err
}

// Handle the swap based on which slots are involved
var poller interface{}
var swapErr error

if sourceSlot == "" && targetSlot == "" {
return fmt.Errorf("cannot swap production with itself")
} else if sourceSlot == "" {
// Swapping production with a named slot (e.g., production -> staging)
// Use BeginSwapSlotWithProduction with targetSlot as the slot to swap with
swapRequest := armappservice.CsmSlotEntity{
TargetSlot: to.Ptr(targetSlot),
}
poller, swapErr = client.BeginSwapSlotWithProduction(ctx, resourceGroup, appName, swapRequest, nil)
} else if targetSlot == "" {
// Swapping a named slot with production (e.g., staging -> production)
// Use BeginSwapSlot with sourceSlot and production as target
swapRequest := armappservice.CsmSlotEntity{
TargetSlot: to.Ptr("production"),
}
poller, swapErr = client.BeginSwapSlot(ctx, resourceGroup, appName, sourceSlot, swapRequest, nil)
} else {
// Swapping between two named slots
swapRequest := armappservice.CsmSlotEntity{
TargetSlot: to.Ptr(targetSlot),
}
poller, swapErr = client.BeginSwapSlot(ctx, resourceGroup, appName, sourceSlot, swapRequest, nil)
}

if swapErr != nil {
return fmt.Errorf("starting slot swap: %w", swapErr)
}

// Wait for completion
// Type assert to get the PollUntilDone method
switch p := poller.(type) {
case *runtime.Poller[armappservice.WebAppsClientSwapSlotWithProductionResponse]:
_, swapErr = p.PollUntilDone(ctx, nil)
case *runtime.Poller[armappservice.WebAppsClientSwapSlotResponse]:
_, swapErr = p.PollUntilDone(ctx, nil)
default:
return fmt.Errorf("unexpected poller type")
}

if swapErr != nil {
return fmt.Errorf("waiting for slot swap to complete: %w", swapErr)
}

return nil
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added SwapSlot method lacks test coverage. Given that the webapp_slots_test.go file already has tests for other slot-related operations (HasAppServiceDeployments, GetAppServiceSlots, DeployAppServiceSlotZip), this new method should have corresponding test coverage to validate its behavior across different scenarios (production to slot, slot to production, slot to slot, error cases).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] service-tasks command

2 participants