Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azurecr/registry management #383

Merged

Conversation

estebanreyl
Copy link

@estebanreyl estebanreyl commented Aug 16, 2018

Adds ACR management capabilities to the extension, including:

Internally provides additional functionality for further development including:
Quick picks for azure related items

  • Subscription
  • ACR Registry
  • ACR Repository
  • ACR Image
  • Resource Group
  • ACR SKU
  • Locations

Additional functionality for ACR

  • Refresh Token acquisition for docker login
  • Access Token acquisition for v2 api
  • Acquire images, repositories
  • Acquire subscription from registry

Other changes:

  • Unified client now lists only non classic acr registries

Esteban Rey and others added 30 commits July 25, 2018 10:21
Update dev to match Microsoft/vscode-docker/master
* Added Azure Credentials Manager Singleton
* Added getResourceManagementClient
Added try catch loop and awaited for resource group list again to check for duplicates with ResourceManagementClient
* Added Azure Credentials Manager Singleton

* Small Style Fixes

* Further Style fixes, added getResourceManagementClient

* Lazy Initialization Patches
-Changed order of questions asked to user for better UX (location of new res group & location of new registry)
-Placeholder of location is display name view
* first fully functional version of delete through input bar AND right click

* refactored code to make it prettier!

* comments

* comments, added subscription function

* fixed to style guide

* style fixes, refactoring

* delete image after reviews

put my functions from azureCredentialsManager into two new files: utils/azure/acrTools.ts, and commands/utils/quick-pick-azure.ts

Edited code based on Esteban's and Bin's reviews

* One last little change to delete image

* moved repository, azureimage, and getsubscriptions to the correct places within deleteImage

* changes from PR reviews on delete image

* fixed authentication issue, got rid of azureAccount property for repository and image

**on constructor for repository, azurecredentialsmanager was being recreated and thus couldn't find the azureAccount. For this reason, I got rid of the azureAccount property of the classes Repository and AzureImage. This bug may lead to future problems (Esteban and I couldn't see why it was happening)

* minor fixes deleteImage

* delete a parentheses
Master merge maintain fork updated
* Merge fixes to acquire latest telemetry items

* Updated to master AzureUtilityManager
* tslint updates, transfered from old branch

* updated icon

* Addressed PR comments- mostly styling/code efficiency

* Changed url to aka.ms link

* changed Error window to Info message for no build tasks in your registry

* Changed default sku and unified parsing resource group into a new method, getResourceGroup in acrTools.ts

* Changed build task icon
* deleteRepo moved over to branch off dev

* Got rid of unnecessary code, fully functioning!

* deleteRepo moved over to branch off dev

* Got rid of unnecessary code, fully functioning!

* spacing

* final commit

* Cleaned code

* Added Telemetry
Delete azure registry functionality added

Delete azure registry moved to branch off dev

Reorganized stye
package.json Outdated
@@ -491,6 +511,21 @@
"description": "Restarts a composition of containers",
"category": "Docker"
},
{
"command": "vscode-docker.create-ACR-Registry",
"title": "Create Registry",
Copy link
Contributor

Choose a reason for hiding this comment

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

"title": "Create Azure Registry..."

The "Azure" part is important because from the command palette the context would otherwise be unclear. The "..." lets the user know we will be asking for more information.

Copy link
Author

Choose a reason for hiding this comment

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

One thing my team suggested was should we maybe instead change the category to ACR ? And do the same for the rest?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say it should probably match the registry node name, which is currently 'Azure'. You thinking that should change?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, our pm suggested that we could just leave it so the commands would show up as ACR : Create Registry, ACR : Delete Repository, etc …

Copy link
Contributor

Choose a reason for hiding this comment

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

@fiveisprime Matt, what are your thoughts on having the ACR commands use a different category than the rest? Personally I'd rather they all start with "Docker: ".

(Esteban - I misspoke earlier about the category matching the node - I was referring to the command title, not the category).

I.e., I'd suggest:
Docker: Create ACR Registry
Docker: Delete ACR Repository
...

Copy link
Member

Choose a reason for hiding this comment

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

I would rather than start with "Docker:" as well; I like the suggestion of "Docker: Create ACR Registry" and so on. Is that clear enough?

Should we still abbreviate in this case? For example, would it make more sense to say "Docker: Create Azure Container Registry"?

Copy link
Author

Choose a reason for hiding this comment

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

I think either would be fine, I'll try to message my team's PM to see what his opinion is.

resourceGroupNames.push(resGroupName.name);
}

let resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, {
Copy link
Contributor

Choose a reason for hiding this comment

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

ext.ui.showQuickPick().

With your current code, the user can press cancel on this step, but the code keeps going and eventually ends up with "Error: Cannot read property 'name' of undefined"

let resourceGroupNames: string[] = [];

if (canCreateNew) { resourceGroupNames.push('+ Create new resource group'); }
for (let resGroupName of resourceGroups) {
Copy link
Contributor

Choose a reason for hiding this comment

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

resGroupName -> resGroup (it's a ResourceGroup, not a string)

} else {
resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; });
}
return resourceGroup;
Copy link
Contributor

@StephenWeatherford StephenWeatherford Aug 22, 2018

Choose a reason for hiding this comment

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

This method has some possible bug points that result from that fact that:

  1. You're treating the first item as special and indexing resourceGroupNames[0] directly
  2. You're assuming you will find the resourceGroup by name

These are fine as currently written, but they're fragile. E.g., what would you theoretically do you do if the find returns undefined? You can avoid these issues completely by rewriting this way:

export async function quickPickResourceGroup(canCreateNew?: boolean, subscription?: Subscription): Promise<ResourceGroup> {
    let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription);
    let resourceGroupItems: QuickPickItemWithData<ResourceGroup | undefined>[] = [];
    let createNewItem: QuickPickItemWithData<ResourceGroup | undefined> = { label: '+ Create new resource group', data: undefined };

    if (canCreateNew) { resourceGroupItems.push(createNewItem); }
    for (let resGroup of resourceGroups) {
        resourceGroupItems.push({ label: resGroup.name, data: resGroup });
    }

    let selectedItem = await vscode.window.showQuickPick(resourceGroupItems, {
        'canPickMany': false,
        'placeHolder': 'Choose a resource group to use'
    });

    let resourceGroup: ResourceGroup;
    if (selectedItem === createNewItem) {
        if (!subscription) {
            subscription = await quickPickSubscription();
        }
        const loc = await quickPickLocation(subscription);
        resourceGroup = await createNewResourceGroup(loc, subscription);
    } else {
        resourceGroup = selectedItem.data;
    }

    return resourceGroup;
}

Copy link
Author

Choose a reason for hiding this comment

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

Thanks! that looks a lot better and is more stable, I also implemented it for the optional can create part of quickPickACRRegistry. As a note I used IAzureQuickPickItem instead of QuickPickWithData as the IAzure one keeps track of recently used items.

});
});
}
/** Obtains refresh tokens for Azure Container Registry. */
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: It's easier to read if you separate each function with a blank line


//Credential management
/** Obtains registry username and password compatible with docker login */
export async function loginCredentials(registry: Registry): Promise<{ password: string, username: string }> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to be unused.

Copy link
Author

Choose a reason for hiding this comment

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

It is as of this PR but I included it given that all the other token management items are included in this one. It is used in an upcoming feature we will PR after this one to enable pull.

Copy link
Contributor

Choose a reason for hiding this comment

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

okay.

@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to be largely a duplicate of code in azureRegistryNodes.ts. Duplicate code is bad - what if a bug is fixed in one copy but left unfixed in another? If you want to pull out the code from azureRegistryNodes.ts into shared functions and call it from multiple places, that's fine, as long the actual logic exists only in one place.

Copy link
Member

Choose a reason for hiding this comment

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

Agree

Copy link
Author

Choose a reason for hiding this comment

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

Seems reasonable, Ill get to that too.


export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise<Registry> {
const placeHolder = prompt ? prompt : 'Choose registry to use';
let registries = await AzureUtilityManager.getInstance().getRegistries();
Copy link
Contributor

Choose a reason for hiding this comment

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

You're not passing in a sort function, there's the list is not sorted. Really this is a problem with AzureUtilityManager.getRegistries - it should either:

  1. require a sort function (not optional)
  2. have a default sort if none is provided
  3. or don't ask for a sort function, just always do a sort by name

The current implementation makes bugs like these likely.

Copy link
Author

Choose a reason for hiding this comment

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

I think option 2 is best. It provides the functionality without limiting options that will avoid double sorting if an alternate sort is necessary. Ill use a default alphabetical sort.

vscode.window.showInformationMessage(`Successfully deleted image ${tag}`);
if (context) {
dockerExplorerProvider.refreshNode(context.parent);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

else refreshRegistries()

vscode.window.showInformationMessage(`Successfully deleted repository ${Repository.name}`);
if (context) {
dockerExplorerProvider.refreshNode(context.parent);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

else refreshRegistries()

Copy link
Contributor

@StephenWeatherford StephenWeatherford left a comment

Choose a reason for hiding this comment

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

See additional comments

export async function confirmUserIntent(yesOrNoPrompt: string): Promise<boolean> {
let opt: vscode.InputBoxOptions = {
ignoreFocusOut: true,
placeHolder: 'No',
Copy link
Contributor

Choose a reason for hiding this comment

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

placeHolder: 'Yes'

(Value is the default value - they'll see that at first - so 'No' is correct for that. placeHolder is a watermark which they'll see if they delete the 'No' default value - it guides them or gives them examples of what to enter.)

Copy link
Author

Choose a reason for hiding this comment

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

Oh that makes sense

@estebanreyl
Copy link
Author

I've updated to match the review items, let me know if there is anything else that looks like it could use some improvement. The only thing that may need a bit of further discussion is if we should change the naming for the acr features to show up as ACR : Create Registry, ACR : Delete Repository, etc …changing the category. For now I just normalized create Registry to Create Azure Registry.

let createNewItem: IAzureQuickPickItem<Registry | undefined> = { label: '+ Create new registry', data: undefined };
if (canCreateNew) { quickPickRegList.unshift(createNewItem); }

let desiredReg: IAzureQuickPickItem<Registry | undefined> = await ext.ui.showQuickPick(quickPickRegList, {
Copy link
Contributor

Choose a reason for hiding this comment

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

ext.ui.showQuickPick will never return undefined (it will throw UserCancelledError instead)

Copy link
Author

Choose a reason for hiding this comment

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

That's for the type of data within IAzureQuickPickItem however. That's only so I can potentially create a new registry if desired, it should have not have an effect on error checking neither is it used towards that effect.

@@ -128,36 +130,30 @@ export async function quickPickResourceGroup(canCreateNew?: boolean, subscriptio
export async function confirmUserIntent(yesOrNoPrompt: string): Promise<boolean> {
let opt: vscode.InputBoxOptions = {
ignoreFocusOut: true,
placeHolder: 'No',
placeHolder: 'Yes',
Copy link
Contributor

Choose a reason for hiding this comment

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

After playing with it, I'm suggesting a change:
placeHolder: 'Enter "Yes"',

Without that, it looks too much like a default value.


const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS);
let subsAndRegistries: { 'subscription': SubscriptionModels.Subscription, 'registries': ContainerModels.RegistryListResult }[] = [];
//Acquire each subscription's data simultaneously
// tslint:disable-next-line:prefer-for-of // Grandfathered in
for (let i = 0; i < subs.length; i++) {
for (let sub of subscriptions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thx. :-)

Copy link
Author

Choose a reason for hiding this comment

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

np :)

@@ -33,8 +35,8 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise<void>
tag = wholeName[1];
}

const shouldDelete = await quickPicks.confirmUserIntent(`Are you sure you want to delete ${repoName}:${tag}? Enter yes to continue: `);
if (shouldDelete) {
const shouldDelete = await ext.ui.showWarningMessage(`Are you sure you want to delete ${repoName}:${tag}? Enter yes to continue: `, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove "Enter yes to continue:"

Copy link
Contributor

Choose a reason for hiding this comment

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

I see you fixed that later...

} else {
registry = await quickPickACRRegistry(false, 'Select the registry you want to delete');
}
const shouldDelete = await confirmUserIntent(`Are you sure you want to delete ${registry.name} and its associated images? Enter yes to continue: `);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: You should let confirmUserIntent() append the 'Enter Yes to continue' portion to the prompt, since it's the one that's expecting that answer from the user anyway.

@@ -160,6 +165,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider()));

if (azureAccount) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Because these commands are specified in the package.json, they will show up in the command palette. If you don't register them and the user tries to use them, you'll get an error from vscode saying it couldn't find the command. You could either:

  1. set it up so they don't show up in the command palette (minor pain)
  2. have them show a message to the user that they require the Azure Account extension to be installed

I'd prefer #2 for better discoverability.

Copy link
Author

Choose a reason for hiding this comment

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

I'll update accordingly then.

Copy link
Author

Choose a reason for hiding this comment

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

I added a wrapper for registerCommand called registerAzureCommand that handles the case that the azure account extension is not installed by letting the user know in the same manner as the deploy to appservices prompt does before. I suspect we may want to gather telemetry on this?

Copy link
Contributor

@StephenWeatherford StephenWeatherford left a comment

Choose a reason for hiding this comment

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

Just a few more minor changes, otherwise looking good!

@StephenWeatherford StephenWeatherford merged commit bc29acd into microsoft:master Aug 27, 2018
@microsoft microsoft locked and limited conversation to collaborators Oct 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow removing a repository from ACR
7 participants