Blame internal/boot/azuretest/azure.go

Packit Service 509fd4
// +build integration
Packit Service 509fd4
Packit Service 509fd4
package azuretest
Packit Service 509fd4
Packit Service 509fd4
import (
Packit Service 509fd4
	"context"
Packit Service 509fd4
	"errors"
Packit Service 509fd4
	"fmt"
Packit Service 509fd4
	"io/ioutil"
Packit Service 509fd4
	"log"
Packit Service 509fd4
	"net/url"
Packit Service 509fd4
	"os"
Packit Service 509fd4
Packit Service 509fd4
	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network"
Packit Service 509fd4
	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-05-01/resources"
Packit Service 509fd4
	"github.com/Azure/azure-storage-blob-go/azblob"
Packit Service 509fd4
	"github.com/Azure/go-autorest/autorest"
Packit Service 509fd4
	"github.com/Azure/go-autorest/autorest/azure/auth"
Packit Service 509fd4
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/upload/azure"
Packit Service 509fd4
)
Packit Service 509fd4
Packit Service 509fd4
// wrapErrorf returns error constructed using fmt.Errorf from format and any
Packit Service 509fd4
// other args. If innerError != nil, it's appended at the end of the new
Packit Service 509fd4
// error.
Packit Service 509fd4
func wrapErrorf(innerError error, format string, a ...interface{}) error {
Packit Service 509fd4
	if innerError != nil {
Packit Service 509fd4
		a = append(a, innerError)
Packit Service 509fd4
		return fmt.Errorf(format+"\n\ninner error: %#s", a...)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return fmt.Errorf(format, a...)
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
type azureCredentials struct {
Packit Service 509fd4
	azure.Credentials
Packit Service 509fd4
	ContainerName  string
Packit Service 509fd4
	SubscriptionID string
Packit Service 509fd4
	ClientID       string
Packit Service 509fd4
	ClientSecret   string
Packit Service 509fd4
	TenantID       string
Packit Service 509fd4
	Location       string
Packit Service 509fd4
	ResourceGroup  string
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// getAzureCredentialsFromEnv gets the credentials from environment variables
Packit Service 509fd4
// If none of the environment variables is set, it returns nil.
Packit Service 509fd4
// If some but not all environment variables are set, it returns an error.
Packit Service 509fd4
func GetAzureCredentialsFromEnv() (*azureCredentials, error) {
Packit Service 509fd4
	storageAccount, saExists := os.LookupEnv("AZURE_STORAGE_ACCOUNT")
Packit Service 509fd4
	storageAccessKey, sakExists := os.LookupEnv("AZURE_STORAGE_ACCESS_KEY")
Packit Service 509fd4
	containerName, cExists := os.LookupEnv("AZURE_CONTAINER_NAME")
Packit Service 509fd4
	subscriptionId, siExists := os.LookupEnv("AZURE_SUBSCRIPTION_ID")
Packit Service 509fd4
	clientId, ciExists := os.LookupEnv("AZURE_CLIENT_ID")
Packit Service 509fd4
	clientSecret, csExists := os.LookupEnv("AZURE_CLIENT_SECRET")
Packit Service 509fd4
	tenantId, tiExists := os.LookupEnv("AZURE_TENANT_ID")
Packit Service 509fd4
	location, lExists := os.LookupEnv("AZURE_LOCATION")
Packit Service 509fd4
	resourceGroup, rgExists := os.LookupEnv("AZURE_RESOURCE_GROUP")
Packit Service 509fd4
Packit Service 509fd4
	// If non of the variables is set, just ignore the test
Packit Service 509fd4
	if !saExists && !sakExists && !cExists && !siExists && !ciExists && !csExists && !tiExists && !lExists && !rgExists {
Packit Service 509fd4
		return nil, nil
Packit Service 509fd4
	}
Packit Service 509fd4
	// If only one/two of them are not set, then fail
Packit Service 509fd4
	if !saExists || !sakExists || !cExists || !siExists || !ciExists || !csExists || !tiExists || !lExists || !rgExists {
Packit Service 509fd4
		return nil, errors.New("not all required env variables were set")
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return &azureCredentials{
Packit Service 509fd4
		Credentials: azure.Credentials{
Packit Service 509fd4
			StorageAccount:   storageAccount,
Packit Service 509fd4
			StorageAccessKey: storageAccessKey,
Packit Service 509fd4
		},
Packit Service 509fd4
		ContainerName:  containerName,
Packit Service 509fd4
		SubscriptionID: subscriptionId,
Packit Service 509fd4
		ClientID:       clientId,
Packit Service 509fd4
		ClientSecret:   clientSecret,
Packit Service 509fd4
		TenantID:       tenantId,
Packit Service 509fd4
		Location:       location,
Packit Service 509fd4
		ResourceGroup:  resourceGroup,
Packit Service 509fd4
	}, nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// UploadImageToAzure mimics the upload feature of osbuild-composer.
Packit Service 509fd4
func UploadImageToAzure(c *azureCredentials, imagePath string, imageName string) error {
Packit Service 509fd4
	metadata := azure.ImageMetadata{
Packit Service 509fd4
		ContainerName: c.ContainerName,
Packit Service 509fd4
		ImageName:     imageName,
Packit Service 509fd4
	}
Packit Service 509fd4
	err := azure.UploadImage(c.Credentials, metadata, imagePath, 16)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("upload to azure failed: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// DeleteImageFromAzure deletes the image uploaded by osbuild-composer
Packit Service 509fd4
// (or UpluadImageToAzure method).
Packit Service 509fd4
func DeleteImageFromAzure(c *azureCredentials, imageName string) error {
Packit Service 509fd4
	// Create a default request pipeline using your storage account name and account key.
Packit Service 509fd4
	credential, err := azblob.NewSharedKeyCredential(c.StorageAccount, c.StorageAccessKey)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
Packit Service 509fd4
Packit Service 509fd4
	// get storage account blob service URL endpoint.
Packit Service 509fd4
	URL, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s", c.StorageAccount, c.ContainerName))
Packit Service 509fd4
Packit Service 509fd4
	// Create a ContainerURL object that wraps the container URL and a request
Packit Service 509fd4
	// pipeline to make requests.
Packit Service 509fd4
	containerURL := azblob.NewContainerURL(*URL, p)
Packit Service 509fd4
Packit Service 509fd4
	// Create the container, use a never-expiring context
Packit Service 509fd4
	ctx := context.Background()
Packit Service 509fd4
Packit Service 509fd4
	blobURL := containerURL.NewPageBlobURL(imageName)
Packit Service 509fd4
Packit Service 509fd4
	_, err = blobURL.Delete(ctx, azblob.DeleteSnapshotsOptionInclude, azblob.BlobAccessConditions{})
Packit Service 509fd4
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot delete the image: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// readPublicKey reads the public key from a file and returns it as a string
Packit Service 509fd4
func readPublicKey(publicKeyFile string) (string, error) {
Packit Service 509fd4
	publicKey, err := ioutil.ReadFile(publicKeyFile)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return "", fmt.Errorf("cannot read the public key file: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return string(publicKey), nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// deleteResource is a convenient wrapper around Azure SDK to delete a resource
Packit Service 509fd4
func deleteResource(client resources.Client, id string, apiVersion string) error {
Packit Service 509fd4
	deleteFuture, err := client.DeleteByID(context.Background(), id, apiVersion)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot delete the resourceType %s: %v", id, err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	err = deleteFuture.WaitForCompletionRef(context.Background(), client.BaseClient.Client)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("waiting for the resourceType %s deletion failed: %v", id, err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	_, err = deleteFuture.Result(client)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot retrieve the result of %s deletion: %v", id, err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func NewDeploymentParameters(creds *azureCredentials, imageName, testId, publicKey string) DeploymentParameters {
Packit Service 509fd4
	// Azure requires a lot of names - for a virtual machine, a virtual network,
Packit Service 509fd4
	// a virtual interface and so on and so forth.
Packit Service 509fd4
	// Let's create all of them here from the test id so we can delete them
Packit Service 509fd4
	// later.
Packit Service 509fd4
Packit Service 509fd4
	imagePath := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", creds.StorageAccount, creds.ContainerName, imageName)
Packit Service 509fd4
Packit Service 509fd4
	return DeploymentParameters{
Packit Service 509fd4
		NetworkInterfaceName:     newDeploymentParameter("iface-" + testId),
Packit Service 509fd4
		NetworkSecurityGroupName: newDeploymentParameter("nsg-" + testId),
Packit Service 509fd4
		VirtualNetworkName:       newDeploymentParameter("vnet-" + testId),
Packit Service 509fd4
		PublicIPAddressName:      newDeploymentParameter("ip-" + testId),
Packit Service 509fd4
		VirtualMachineName:       newDeploymentParameter("vm-" + testId),
Packit Service 509fd4
		DiskName:                 newDeploymentParameter("disk-" + testId),
Packit Service 509fd4
		ImageName:                newDeploymentParameter("image-" + testId),
Packit Service 509fd4
		Location:                 newDeploymentParameter(creds.Location),
Packit Service 509fd4
		ImagePath:                newDeploymentParameter(imagePath),
Packit Service 509fd4
		AdminUsername:            newDeploymentParameter("redhat"),
Packit Service 509fd4
		AdminPublicKey:           newDeploymentParameter(publicKey),
Packit Service 509fd4
	}
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func CleanUpBootedVM(creds *azureCredentials, parameters DeploymentParameters, authorizer autorest.Authorizer, testId string) (retErr error) {
Packit Service 509fd4
	deploymentName := testId
Packit Service 509fd4
Packit Service 509fd4
	deploymentsClient := resources.NewDeploymentsClient(creds.SubscriptionID)
Packit Service 509fd4
	deploymentsClient.Authorizer = authorizer
Packit Service 509fd4
Packit Service 509fd4
	resourcesClient := resources.NewClient(creds.SubscriptionID)
Packit Service 509fd4
	resourcesClient.Authorizer = authorizer
Packit Service 509fd4
Packit Service 509fd4
	// This array specifies all the resources we need to delete. The
Packit Service 509fd4
	// order is important, e.g. one cannot delete a network interface
Packit Service 509fd4
	// that is still attached to a virtual machine.
Packit Service 509fd4
	resourcesToDelete := []struct {
Packit Service 509fd4
		resType    string
Packit Service 509fd4
		name       string
Packit Service 509fd4
		apiVersion string
Packit Service 509fd4
	}{
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Compute/virtualMachines",
Packit Service 509fd4
			name:       parameters.VirtualMachineName.Value,
Packit Service 509fd4
			apiVersion: "2019-07-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Network/networkInterfaces",
Packit Service 509fd4
			name:       parameters.NetworkInterfaceName.Value,
Packit Service 509fd4
			apiVersion: "2019-09-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Network/publicIPAddresses",
Packit Service 509fd4
			name:       parameters.PublicIPAddressName.Value,
Packit Service 509fd4
			apiVersion: "2019-09-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Network/networkSecurityGroups",
Packit Service 509fd4
			name:       parameters.NetworkSecurityGroupName.Value,
Packit Service 509fd4
			apiVersion: "2019-09-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Network/virtualNetworks",
Packit Service 509fd4
			name:       parameters.VirtualNetworkName.Value,
Packit Service 509fd4
			apiVersion: "2019-09-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Compute/disks",
Packit Service 509fd4
			name:       parameters.DiskName.Value,
Packit Service 509fd4
			apiVersion: "2019-07-01",
Packit Service 509fd4
		},
Packit Service 509fd4
		{
Packit Service 509fd4
			resType:    "Microsoft.Compute/images",
Packit Service 509fd4
			name:       parameters.ImageName.Value,
Packit Service 509fd4
			apiVersion: "2019-07-01",
Packit Service 509fd4
		},
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// Delete all the resources
Packit Service 509fd4
	for _, resourceToDelete := range resourcesToDelete {
Packit Service 509fd4
		resourceID := fmt.Sprintf(
Packit Service 509fd4
			"subscriptions/%s/resourceGroups/%s/providers/%s/%s",
Packit Service 509fd4
			creds.SubscriptionID,
Packit Service 509fd4
			creds.ResourceGroup,
Packit Service 509fd4
			resourceToDelete.resType,
Packit Service 509fd4
			resourceToDelete.name,
Packit Service 509fd4
		)
Packit Service 509fd4
Packit Service 509fd4
		err := deleteResource(resourcesClient, resourceID, resourceToDelete.apiVersion)
Packit Service 509fd4
		if err != nil {
Packit Service 509fd4
			log.Printf("deleting the resource %s errored: %v", resourceToDelete.name, err)
Packit Service 509fd4
			retErr = wrapErrorf(retErr, "cannot delete the resource %s: %v", resourceToDelete.name, err)
Packit Service 509fd4
			// do not return here, try deleting as much as possible
Packit Service 509fd4
		}
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// Delete the deployment
Packit Service 509fd4
	// This actually does not delete any resources created by the
Packit Service 509fd4
	// deployment as one might think. Therefore the code above
Packit Service 509fd4
	// is needed.
Packit Service 509fd4
	result, err := deploymentsClient.Delete(context.Background(), creds.ResourceGroup, deploymentName)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		retErr = wrapErrorf(retErr, "cannot create the request for the deployment deletion: %v", err)
Packit Service 509fd4
		return
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	err = result.WaitForCompletionRef(context.Background(), deploymentsClient.Client)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		retErr = wrapErrorf(retErr, "waiting for the deployment deletion failed: %v", err)
Packit Service 509fd4
		return
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	_, err = result.Result(deploymentsClient)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		retErr = wrapErrorf(retErr, "cannot retrieve the deployment deletion result: %v", err)
Packit Service 509fd4
		return
Packit Service 509fd4
	}
Packit Service 509fd4
	return
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// WithBootedImageInAzure runs the function f in the context of booted
Packit Service 509fd4
// image in Azure
Packit Service 509fd4
func WithBootedImageInAzure(creds *azureCredentials, imageName, testId, publicKeyFile string, f func(address string) error) (retErr error) {
Packit Service 509fd4
	publicKey, err := readPublicKey(publicKeyFile)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	clientCredentialsConfig := auth.NewClientCredentialsConfig(creds.ClientID, creds.ClientSecret, creds.TenantID)
Packit Service 509fd4
	authorizer, err := clientCredentialsConfig.Authorizer()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot create the authorizer: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	template, err := loadDeploymentTemplate()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	deploymentsClient := resources.NewDeploymentsClient(creds.SubscriptionID)
Packit Service 509fd4
	deploymentsClient.Authorizer = authorizer
Packit Service 509fd4
Packit Service 509fd4
	deploymentName := testId
Packit Service 509fd4
	parameters := NewDeploymentParameters(creds, imageName, testId, publicKey)
Packit Service 509fd4
Packit Service 509fd4
	deploymentFuture, err := deploymentsClient.CreateOrUpdate(context.Background(), creds.ResourceGroup, deploymentName, resources.Deployment{
Packit Service 509fd4
		Properties: &resources.DeploymentProperties{
Packit Service 509fd4
			Mode:       resources.Incremental,
Packit Service 509fd4
			Template:   template,
Packit Service 509fd4
			Parameters: parameters,
Packit Service 509fd4
		},
Packit Service 509fd4
	})
Packit Service 509fd4
Packit Service 509fd4
	// Let's registed the clean-up function as soon as possible.
Packit Service 509fd4
	defer func() {
Packit Service 509fd4
		retErr = CleanUpBootedVM(creds, parameters, authorizer, testId)
Packit Service 509fd4
	}()
Packit Service 509fd4
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("creating a deployment failed: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	err = deploymentFuture.WaitForCompletionRef(context.Background(), deploymentsClient.Client)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("waiting for deployment completion failed: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	_, err = deploymentFuture.Result(deploymentsClient)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("retrieving the deployment result failed: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// get the IP address
Packit Service 509fd4
	publicIPAddressClient := network.NewPublicIPAddressesClient(creds.SubscriptionID)
Packit Service 509fd4
	publicIPAddressClient.Authorizer = authorizer
Packit Service 509fd4
Packit Service 509fd4
	publicIPAddress, err := publicIPAddressClient.Get(context.Background(), creds.ResourceGroup, parameters.PublicIPAddressName.Value, "")
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("cannot get the ip address details: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return f(*publicIPAddress.IPAddress)
Packit Service 509fd4
}