azure: delete keyvault deployment

This commit is contained in:
Christopher Boumenot 2017-11-30 00:11:17 -08:00
parent aa2bbf2aec
commit da0c13f622
7 changed files with 178 additions and 57 deletions

View File

@ -48,6 +48,7 @@ type AzureClient struct {
InspectorMaxLength int
Template *CaptureTemplate
LastError azureErrorResponse
VaultClientDelete common.VaultClient
}
func getCaptureResponse(body string) *CaptureTemplate {
@ -209,6 +210,20 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClient.UserAgent += packerUserAgent
// TODO(boumenot) - SDK still does not have a full KeyVault client.
// There are two ways that KeyVault has to be accessed, and each one has their own SPN. An authenticated SPN
// is tied to the URL, and the URL associated with getting the secret is different than the URL
// associated with deleting the KeyVault. As a result, I need to have *two* different clients to
// access KeyVault. I did not want to split it into two separate files, so I am starting with this.
//
// I do not like this implementation. It is getting long in the tooth, and should be re-examined now
// that we have a "working" solution.
azureClient.VaultClientDelete = common.NewVaultClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VaultClientDelete.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClientDelete.UserAgent += packerUserAgent
// If this is a managed disk build, this should be ignored.
if resourceGroupName != "" && storageAccountName != "" {
accountKeys, err := azureClient.AccountsClient.ListKeys(resourceGroupName, storageAccountName)

View File

@ -127,11 +127,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.setImageParameters(b.stateBag)
var steps []multistep.Step
deploymentName := b.stateBag.Get(constants.ArmDeploymentName).(string)
if b.config.OSType == constants.Target_Linux {
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
&communicator.StepConnectSSH{
Config: &b.config.Comm,
@ -146,14 +148,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
NewStepDeleteOSDisk(azureClient, ui),
}
} else if b.config.OSType == constants.Target_Windows {
keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string)
steps = []multistep.Step{
NewStepCreateResourceGroup(azureClient, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
NewStepGetCertificate(azureClient, ui),
NewStepSetCertificate(b.config, ui),
NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
NewStepGetIPAddress(azureClient, ui, endpointConnectType),
&communicator.StepConnectWinRM{
Config: &b.config.Comm,
@ -283,6 +286,9 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.ArmTags, &b.config.AzureTags)
stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
if b.config.OSType == constants.Target_Windows {
stateBag.Put(constants.ArmKeyVaultDeploymentName, fmt.Sprintf("kv%s", b.config.tmpDeploymentName))
}
stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
stateBag.Put(constants.ArmLocation, b.config.Location)
stateBag.Put(constants.ArmNicName, DefaultNicName)

View File

@ -37,54 +37,19 @@ func (s *StepDeleteResourceGroup) deleteResourceGroup(state multistep.StateBag,
if state.Get(constants.ArmIsExistingResourceGroup).(bool) {
s.say("\nThe resource group was not created by Packer, only deleting individual resources ...")
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
if deploymentName != "" {
maxResources := int32(maxResourcesToDelete)
deploymentOperations, err := s.client.DeploymentOperationsClient.List(resourceGroupName, deploymentName, &maxResources)
err = s.deleteDeploymentResources(deploymentName, resourceGroupName)
if err != nil {
return err
}
if keyVaultDeploymentName, ok := state.GetOk(constants.ArmKeyVaultDeploymentName); ok {
err = s.deleteDeploymentResources(keyVaultDeploymentName.(string), resourceGroupName)
if err != nil {
s.say(fmt.Sprintf("Error deleting resources. Please delete them manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceGroupName, err))
s.error(err)
}
for _, deploymentOperation := range *deploymentOperations.Value {
// Sometimes an empty operation is added to the list by Azure
if deploymentOperation.Properties.TargetResource == nil {
continue
}
s.say(fmt.Sprintf(" -> %s : '%s'",
*deploymentOperation.Properties.TargetResource.ResourceType,
*deploymentOperation.Properties.TargetResource.ResourceName))
var networkDeleteFunction func(string, string, <-chan struct{}) (<-chan autorest.Response, <-chan error)
switch *deploymentOperation.Properties.TargetResource.ResourceType {
case "Microsoft.Compute/virtualMachines":
_, errChan := s.client.VirtualMachinesClient.Delete(resourceGroupName, *deploymentOperation.Properties.TargetResource.ResourceName, nil)
err := <-errChan
if err != nil {
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err.Error()))
s.error(err)
}
case "Microsoft.Network/networkInterfaces":
networkDeleteFunction = s.client.InterfacesClient.Delete
case "Microsoft.Network/virtualNetworks":
networkDeleteFunction = s.client.VirtualNetworksClient.Delete
case "Microsoft.Network/publicIPAddresses":
networkDeleteFunction = s.client.PublicIPAddressesClient.Delete
}
if networkDeleteFunction != nil {
_, errChan := networkDeleteFunction(resourceGroupName, *deploymentOperation.Properties.TargetResource.ResourceName, nil)
err := <-errChan
if err != nil {
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", *deploymentOperation.Properties.TargetResource.ResourceName, err.Error()))
s.error(err)
}
}
return err
}
}
return err
return nil
} else {
s.say("\nThe resource group was created by Packer, deleting ...")
_, errChan := s.client.GroupsClient.Delete(resourceGroupName, cancelCh)
@ -97,6 +62,61 @@ func (s *StepDeleteResourceGroup) deleteResourceGroup(state multistep.StateBag,
}
}
func (s *StepDeleteResourceGroup) deleteDeploymentResources(deploymentName, resourceGroupName string) error {
maxResources := int32(maxResourcesToDelete)
deploymentOperations, err := s.client.DeploymentOperationsClient.List(resourceGroupName, deploymentName, &maxResources)
if err != nil {
s.reportIfError(err, resourceGroupName)
return err
}
for _, deploymentOperation := range *deploymentOperations.Value {
// Sometimes an empty operation is added to the list by Azure
if deploymentOperation.Properties.TargetResource == nil {
continue
}
s.say(fmt.Sprintf(" -> %s : '%s'",
*deploymentOperation.Properties.TargetResource.ResourceType,
*deploymentOperation.Properties.TargetResource.ResourceName))
var networkDeleteFunction func(string, string, <-chan struct{}) (<-chan autorest.Response, <-chan error)
resourceName := *deploymentOperation.Properties.TargetResource.ResourceName
switch *deploymentOperation.Properties.TargetResource.ResourceType {
case "Microsoft.Compute/virtualMachines":
_, errChan := s.client.VirtualMachinesClient.Delete(resourceGroupName, resourceName, nil)
err := <-errChan
s.reportIfError(err, resourceName)
case "Microsoft.KeyVault/vaults":
_, err := s.client.VaultClientDelete.Delete(resourceGroupName, resourceName)
s.reportIfError(err, resourceName)
case "Microsoft.Network/networkInterfaces":
networkDeleteFunction = s.client.InterfacesClient.Delete
case "Microsoft.Network/virtualNetworks":
networkDeleteFunction = s.client.VirtualNetworksClient.Delete
case "Microsoft.Network/publicIPAddresses":
networkDeleteFunction = s.client.PublicIPAddressesClient.Delete
}
if networkDeleteFunction != nil {
_, errChan := networkDeleteFunction(resourceGroupName, resourceName, nil)
err := <-errChan
s.reportIfError(err, resourceName)
}
}
return nil
}
func (s *StepDeleteResourceGroup) reportIfError(err error, resourceName string) {
if err != nil {
s.say(fmt.Sprintf("Error deleting resource. Please delete manually.\n\n"+
"Name: %s\n"+
"Error: %s", resourceName, err.Error()))
s.error(err)
}
}
func (s *StepDeleteResourceGroup) Run(state multistep.StateBag) multistep.StepAction {
s.say("Deleting resource group ...")

View File

@ -22,15 +22,17 @@ type StepDeployTemplate struct {
error func(e error)
config *Config
factory templateFactoryFunc
name string
}
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, factory templateFactoryFunc) *StepDeployTemplate {
func NewStepDeployTemplate(client *AzureClient, ui packer.Ui, config *Config, deploymentName string, factory templateFactoryFunc) *StepDeployTemplate {
var step = &StepDeployTemplate{
client: client,
say: func(message string) { ui.Say(message) },
error: func(e error) { ui.Error(e.Error()) },
config: config,
factory: factory,
name: deploymentName,
}
step.deploy = step.deployTemplate
@ -59,15 +61,14 @@ func (s *StepDeployTemplate) Run(state multistep.StateBag) multistep.StepAction
s.say("Deploying deployment template ...")
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", deploymentName))
s.say(fmt.Sprintf(" -> DeploymentName : '%s'", s.name))
result := common.StartInterruptibleTask(
func() bool { return common.IsStateCancelled(state) },
func(cancelCh <-chan struct{}) error {
return s.deploy(resourceGroupName, deploymentName, cancelCh)
return s.deploy(resourceGroupName, s.name, cancelCh)
},
)
@ -104,6 +105,9 @@ func (s *StepDeployTemplate) deleteOperationResource(resourceType string, resour
return err
}
case "Microsoft.KeyVault/vaults":
_, err := s.client.VaultClientDelete.Delete(resourceGroupName, resourceName)
return err
case "Microsoft.Network/networkInterfaces":
networkDeleteFunction = s.client.InterfacesClient.Delete
case "Microsoft.Network/virtualNetworks":
@ -142,7 +146,6 @@ func (s *StepDeployTemplate) deleteImage(imageType string, imageName string, res
blob := s.client.BlobStorageClient.GetContainerReference(storageAccountName).GetBlobReference(blobName)
err = blob.Delete(nil)
return err
}
func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
@ -158,7 +161,7 @@ func (s *StepDeployTemplate) Cleanup(state multistep.StateBag) {
var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string)
var computeName = state.Get(constants.ArmComputeName).(string)
var deploymentName = state.Get(constants.ArmDeploymentName).(string)
var deploymentName = s.name
imageType, imageName, err := s.disk(resourceGroupName, computeName)
if err != nil {
ui.Error("Could not retrieve OS Image details")

View File

@ -61,6 +61,7 @@ func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
},
say: func(message string) {},
error: func(e error) {},
name: "--deployment-name--",
}
stateBag := createTestStateBagStepValidateTemplate()
@ -70,10 +71,9 @@ func TestStepDeployTemplateShouldTakeStepArgumentsFromStateBag(t *testing.T) {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
var expectedDeploymentName = stateBag.Get(constants.ArmDeploymentName).(string)
var expectedResourceGroupName = stateBag.Get(constants.ArmResourceGroupName).(string)
if actualDeploymentName != expectedDeploymentName {
if actualDeploymentName != "--deployment-name--" {
t.Fatal("Expected StepValidateTemplate to source 'constants.ArmDeploymentName' from the state bag, but it did not.")
}

View File

@ -15,6 +15,7 @@ const (
ArmComputeName string = "arm.ComputeName"
ArmImageParameters string = "arm.ImageParameters"
ArmCertificateUrl string = "arm.CertificateUrl"
ArmKeyVaultDeploymentName string = "arm.KeyVaultDeploymentName"
ArmDeploymentName string = "arm.DeploymentName"
ArmNicName string = "arm.NicName"
ArmKeyVaultName string = "arm.KeyVaultName"

View File

@ -9,15 +9,18 @@ import (
"net/url"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
)
const (
AzureVaultApiVersion = "2015-06-01"
AzureVaultApiVersion = "2016-10-01"
)
type VaultClient struct {
autorest.Client
keyVaultEndpoint url.URL
SubscriptionID string
baseURI string
}
func NewVaultClient(keyVaultEndpoint url.URL) VaultClient {
@ -26,6 +29,13 @@ func NewVaultClient(keyVaultEndpoint url.URL) VaultClient {
}
}
func NewVaultClientWithBaseURI(baseURI, subscriptionID string) VaultClient {
return VaultClient{
baseURI: baseURI,
SubscriptionID: subscriptionID,
}
}
type Secret struct {
ID *string `json:"id,omitempty"`
Value string `json:"value"`
@ -76,6 +86,72 @@ func (client *VaultClient) GetSecret(vaultName, secretName string) (*Secret, err
return &secret, nil
}
// Delete deletes the specified Azure key vault.
//
// resourceGroupName is the name of the Resource Group to which the vault belongs. vaultName is the name of the vault
// to delete
func (client *VaultClient) Delete(resourceGroupName string, vaultName string) (result autorest.Response, err error) {
req, err := client.DeletePreparer(resourceGroupName, vaultName)
if err != nil {
err = autorest.NewErrorWithError(err, "keyvault.VaultsClient", "Delete", nil, "Failure preparing request")
return
}
resp, err := client.DeleteSender(req)
if err != nil {
result.Response = resp
err = autorest.NewErrorWithError(err, "keyvault.VaultsClient", "Delete", resp, "Failure sending request")
return
}
result, err = client.DeleteResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "keyvault.VaultsClient", "Delete", resp, "Failure responding to request")
}
return
}
// DeletePreparer prepares the Delete request.
func (client *VaultClient) DeletePreparer(resourceGroupName string, vaultName string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"SubscriptionID": autorest.Encode("path", client.SubscriptionID),
"vaultName": autorest.Encode("path", vaultName),
}
queryParameters := map[string]interface{}{
"api-version": AzureVaultApiVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsDelete(),
autorest.WithBaseURL(client.baseURI),
autorest.WithPathParameters("/subscriptions/{SubscriptionID}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{vaultName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare(&http.Request{})
}
// DeleteSender sends the Delete request. The method will close the
// http.Response Body if it receives an error.
func (client *VaultClient) DeleteSender(req *http.Request) (*http.Response, error) {
return autorest.SendWithSender(client,
req,
azure.DoPollForAsynchronous(client.PollingDelay))
}
// DeleteResponder handles the response to the Delete request. The method always
// closes the http.Response Body.
func (client *VaultClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
err = autorest.Respond(
resp,
client.ByInspecting(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())
result.Response = resp
return
}
func (client *VaultClient) getVaultUrl(vaultName string) string {
return fmt.Sprintf("%s://%s.%s/", client.keyVaultEndpoint.Scheme, vaultName, client.keyVaultEndpoint.Host)
}