From e235fdedb3990b01beef02cf0e383d611c0a9a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Mon, 27 Apr 2020 12:07:16 +0200 Subject: [PATCH] tests/image: boot the vhd images on Azure Previously, vhd images were tested using QEMU. This commit changes that to boot them in the actual Azure infrastructure. Azure VMs have quite a lot of dependencies - a network interface, a virtual network, a network security group, a public ip address and a disk. Azure CLI and Azure Portal handle the creation of all these resources internally. However, when using the API, the caller is responsible to create all these resources before creating an actual VM. To handle the creation of all the resources in the right order, a deployment is used. A deployment is a set of resources defined in a JSON document. It can optionally take parameters to customize each deployment. After the deployment is finished, the VM is up and ready to be tested using SSH. Sadly, the deployments are a bit hard to clean-up. One would expect that deleting a deployment removes all the deployed resources. However, it doesn't work this way and therefore it's needed to clean up all resources "manually". For this reason, our deployment sets a unique tag on all the resources created by the deployment. After this test is finished, the API is queried for all the resources with the tag and then, they're deleted in the right order. --- cmd/osbuild-image-tests/azuretest/azure.go | 328 ++++++++++++++++++ .../azuretest/deployment.go | 52 +++ .../constants/constants-travis.go | 22 +- .../constants/constants.go | 22 +- cmd/osbuild-image-tests/main_test.go | 41 +++ golang-github-osbuild-composer.spec | 4 + osbuild-composer.spec | 4 + test/azure-deployment-template.json | 211 +++++++++++ test/cases/fedora_30-x86_64-vhd-boot.json | 4 +- test/cases/fedora_31-x86_64-vhd-boot.json | 4 +- .../format-request-map.json | 2 +- 11 files changed, 669 insertions(+), 25 deletions(-) create mode 100644 cmd/osbuild-image-tests/azuretest/azure.go create mode 100644 cmd/osbuild-image-tests/azuretest/deployment.go create mode 100644 test/azure-deployment-template.json diff --git a/cmd/osbuild-image-tests/azuretest/azure.go b/cmd/osbuild-image-tests/azuretest/azure.go new file mode 100644 index 000000000..1f3322bfa --- /dev/null +++ b/cmd/osbuild-image-tests/azuretest/azure.go @@ -0,0 +1,328 @@ +// +build integration + +package azuretest + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "net/url" + "os" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-05-01/resources" + "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/Azure/go-autorest/autorest/azure/auth" + + "github.com/osbuild/osbuild-composer/internal/upload/azure" +) + +// wrapErrorf returns error constructed using fmt.Errorf from format and any +// other args. If innerError != nil, it's appended at the end of the new +// error. +func wrapErrorf(innerError error, format string, a ...interface{}) error { + if innerError != nil { + a = append(a, innerError) + return fmt.Errorf(format+"\n\ninner error: %#s", a...) + } + + return fmt.Errorf(format, a...) +} + +type azureCredentials struct { + azure.Credentials + ContainerName string + SubscriptionID string + ClientID string + ClientSecret string + TenantID string + Location string + ResourceGroup string +} + +// getAzureCredentialsFromEnv gets the credentials from environment variables +// If none of the environment variables is set, it returns nil. +// If some but not all environment variables are set, it returns an error. +func GetAzureCredentialsFromEnv() (*azureCredentials, error) { + storageAccount, saExists := os.LookupEnv("AZURE_STORAGE_ACCOUNT") + storageAccessKey, sakExists := os.LookupEnv("AZURE_STORAGE_ACCESS_KEY") + containerName, cExists := os.LookupEnv("AZURE_CONTAINER_NAME") + subscriptionId, siExists := os.LookupEnv("AZURE_SUBSCRIPTION_ID") + clientId, ciExists := os.LookupEnv("AZURE_CLIENT_ID") + clientSecret, csExists := os.LookupEnv("AZURE_CLIENT_SECRET") + tenantId, tiExists := os.LookupEnv("AZURE_TENANT_ID") + location, lExists := os.LookupEnv("AZURE_LOCATION") + resourceGroup, rgExists := os.LookupEnv("AZURE_RESOURCE_GROUP") + + // Workaround Travis security feature. If non of the variables is set, just ignore the test + if !saExists && !sakExists && !cExists && !siExists && !ciExists && !csExists && !tiExists && !lExists && !rgExists { + return nil, nil + } + // If only one/two of them are not set, then fail + if !saExists || !sakExists || !cExists || !siExists || !ciExists || !csExists || !tiExists || !lExists || !rgExists { + return nil, errors.New("not all required env variables were set") + } + + return &azureCredentials{ + Credentials: azure.Credentials{ + StorageAccount: storageAccount, + StorageAccessKey: storageAccessKey, + }, + ContainerName: containerName, + SubscriptionID: subscriptionId, + ClientID: clientId, + ClientSecret: clientSecret, + TenantID: tenantId, + Location: location, + ResourceGroup: resourceGroup, + }, nil +} + +// UploadImageToAzure mimics the upload feature of osbuild-composer. +func UploadImageToAzure(c *azureCredentials, imagePath string, imageName string) error { + metadata := azure.ImageMetadata{ + ContainerName: c.ContainerName, + ImageName: imageName, + } + err := azure.UploadImage(c.Credentials, metadata, imagePath, 16) + if err != nil { + return fmt.Errorf("upload to azure failed: %v", err) + } + + return nil +} + +// DeleteImageFromAzure deletes the image uploaded by osbuild-composer +// (or UpluadImageToAzure method). +func DeleteImageFromAzure(c *azureCredentials, imageName string) error { + // Create a default request pipeline using your storage account name and account key. + credential, err := azblob.NewSharedKeyCredential(c.StorageAccount, c.StorageAccessKey) + if err != nil { + return err + } + + p := azblob.NewPipeline(credential, azblob.PipelineOptions{}) + + // get storage account blob service URL endpoint. + URL, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s", c.StorageAccount, c.ContainerName)) + + // Create a ContainerURL object that wraps the container URL and a request + // pipeline to make requests. + containerURL := azblob.NewContainerURL(*URL, p) + + // Create the container, use a never-expiring context + ctx := context.Background() + + blobURL := containerURL.NewPageBlobURL(imageName) + + _, err = blobURL.Delete(ctx, azblob.DeleteSnapshotsOptionInclude, azblob.BlobAccessConditions{}) + + if err != nil { + return fmt.Errorf("cannot delete the image: %v", err) + } + + return nil +} + +type resourceType struct { + resType string + apiVersion string +} + +// resourcesTypesToDelete serves two purposes: +// 1) The WithBootedImageInAzure method tags all the created resources and +// we can get the list of resources with that tag. However, it's needed to +// delete them in right order because of inner dependencies. +// 2) The resources.Client.DeleteByID method requires the API version to be +// passed in. Therefore we need to way to get API version for a given +// resource type. +var resourcesTypesToDelete = []resourceType{ + { + resType: "Microsoft.Compute/virtualMachines", + apiVersion: "2019-07-01", + }, + { + resType: "Microsoft.Network/networkInterfaces", + apiVersion: "2019-09-01", + }, + { + resType: "Microsoft.Network/publicIPAddresses", + apiVersion: "2019-09-01", + }, + { + resType: "Microsoft.Network/networkSecurityGroups", + apiVersion: "2019-09-01", + }, + { + resType: "Microsoft.Network/virtualNetworks", + apiVersion: "2019-09-01", + }, + { + resType: "Microsoft.Compute/disks", + apiVersion: "2019-07-01", + }, + { + resType: "Microsoft.Compute/images", + apiVersion: "2019-07-01", + }, +} + +// readPublicKey reads the public key from a file and returns it as a string +func readPublicKey(publicKeyFile string) (string, error) { + publicKey, err := ioutil.ReadFile(publicKeyFile) + if err != nil { + return "", fmt.Errorf("cannot read the public key file: %v", err) + } + + return string(publicKey), nil +} + +// deleteResource is a convenient wrapper around Azure SDK to delete a resource +func deleteResource(client resources.Client, id string, apiVersion string) error { + deleteFuture, err := client.DeleteByID(context.Background(), id, apiVersion) + if err != nil { + return fmt.Errorf("cannot delete the resourceType %s: %v", id, err) + } + + err = deleteFuture.WaitForCompletionRef(context.Background(), client.BaseClient.Client) + if err != nil { + return fmt.Errorf("waiting for the resourceType %s deletion failed: %v", id, err) + } + + _, err = deleteFuture.Result(client) + if err != nil { + return fmt.Errorf("cannot retrieve the result of %s deletion: %v", id, err) + } + + return nil +} + +// withBootedImageInAzure runs the function f in the context of booted +// image in Azure +func WithBootedImageInAzure(creds *azureCredentials, imageName, testId, publicKeyFile string, f func(address string) error) (retErr error) { + publicKey, err := readPublicKey(publicKeyFile) + if err != nil { + return err + } + + clientCredentialsConfig := auth.NewClientCredentialsConfig(creds.ClientID, creds.ClientSecret, creds.TenantID) + authorizer, err := clientCredentialsConfig.Authorizer() + if err != nil { + return fmt.Errorf("cannot create the authorizer: %v", err) + } + + template, err := loadDeploymentTemplate() + if err != nil { + return err + } + + // Azure requires a lot of names - for a virtual machine, a virtual network, + // a virtual interface and so on and so forth. + // In the Go code, it's just need to know the public IP address name, + // the tag name and the deployment name. Let's set these here to names + // based on the test id. + // The rest of the names are set from the test id inside the deployment + // template because they're irrelevant to the Go code. + deploymentName := testId + tag := "tag-" + testId + publicIPAddressName := "address-" + testId + imagePath := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", creds.StorageAccount, creds.ContainerName, imageName) + + parameters := deploymentParameters{ + Location: newDeploymentParameter(creds.Location), + TestId: newDeploymentParameter(testId), + Tag: newDeploymentParameter(tag), + PublicIPAddressName: newDeploymentParameter(publicIPAddressName), + ImagePath: newDeploymentParameter(imagePath), + AdminUsername: newDeploymentParameter("redhat"), + AdminPublicKey: newDeploymentParameter(publicKey), + } + + deploymentsClient := resources.NewDeploymentsClient(creds.SubscriptionID) + deploymentsClient.Authorizer = authorizer + + deploymentFuture, err := deploymentsClient.CreateOrUpdate(context.Background(), creds.ResourceGroup, deploymentName, resources.Deployment{ + Properties: &resources.DeploymentProperties{ + Mode: resources.Incremental, + Template: template, + Parameters: parameters, + }, + }) + + // Let's registed the clean-up function as soon as possible. + defer func() { + resourcesClient := resources.NewClient(creds.SubscriptionID) + resourcesClient.Authorizer = authorizer + + // find all the resources we marked with a tag during the deployment + filter := fmt.Sprintf("tagName eq 'osbuild-composer-image-test' and tagValue eq '%s'", tag) + resourceList, err := resourcesClient.ListByResourceGroup(context.Background(), creds.ResourceGroup, filter, "", nil) + if err != nil { + retErr = wrapErrorf(retErr, "listing of resources failed: %v", err) + } else { + + // delete all the found resources + for _, resourceType := range resourcesTypesToDelete { + for _, resource := range resourceList.Values() { + if *resource.Type != resourceType.resType { + continue + } + + err := deleteResource(resourcesClient, *resource.ID, resourceType.apiVersion) + if err != nil { + retErr = wrapErrorf(retErr, "cannot delete the resource %s: %v", *resource.ID, err) + // do not return here, try deleting as much as possible + } + } + } + } + + // Delete the deployment + // This actually does not delete any resources created by the + // deployment as one might think. Therefore the code above + // and the tagging are needed. + result, err := deploymentsClient.Delete(context.Background(), creds.ResourceGroup, deploymentName) + if err != nil { + retErr = wrapErrorf(retErr, "cannot create the request for the deployment deletion: %v", err) + return + } + + err = result.WaitForCompletionRef(context.Background(), deploymentsClient.Client) + if err != nil { + retErr = wrapErrorf(retErr, "waiting for the deployment deletion failed: %v", err) + return + } + + _, err = result.Result(deploymentsClient) + if err != nil { + retErr = wrapErrorf(retErr, "cannot retrieve the deployment deletion result: %v", err) + return + } + }() + + if err != nil { + return fmt.Errorf("creating a deployment failed: %v", err) + } + + err = deploymentFuture.WaitForCompletionRef(context.Background(), deploymentsClient.Client) + if err != nil { + return fmt.Errorf("waiting for deployment completion failed: %v", err) + } + + _, err = deploymentFuture.Result(deploymentsClient) + if err != nil { + return fmt.Errorf("retrieving the deployment result failed: %v", err) + } + + // get the IP address + publicIPAddressClient := network.NewPublicIPAddressesClient(creds.SubscriptionID) + publicIPAddressClient.Authorizer = authorizer + + publicIPAddress, err := publicIPAddressClient.Get(context.Background(), creds.ResourceGroup, publicIPAddressName, "") + if err != nil { + return fmt.Errorf("cannot get the ip address details: %v", err) + } + + return f(*publicIPAddress.IPAddress) +} diff --git a/cmd/osbuild-image-tests/azuretest/deployment.go b/cmd/osbuild-image-tests/azuretest/deployment.go new file mode 100644 index 000000000..179baa5c8 --- /dev/null +++ b/cmd/osbuild-image-tests/azuretest/deployment.go @@ -0,0 +1,52 @@ +// +build integration + +package azuretest + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants" +) + +// loadDeploymentTemplate loads the deployment template from the specified +// path and return it as a "dynamically" typed object +func loadDeploymentTemplate() (interface{}, error) { + f, err := os.Open(constants.TestPaths.AzureDeploymentTemplate) + if err != nil { + return nil, fmt.Errorf("cannot open the deployment file: %v", err) + } + + defer f.Close() + + var result interface{} + + err = json.NewDecoder(f).Decode(&result) + + if err != nil { + return nil, fmt.Errorf("cannot decode the deployment file: %v", err) + } + + return result, nil +} + +// struct for encoding a deployment parameter +type deploymentParameter struct { + Value interface{} `json:"value"` +} + +func newDeploymentParameter(value interface{}) deploymentParameter { + return deploymentParameter{Value: value} +} + +// struct for encoding deployment parameters +type deploymentParameters struct { + Location deploymentParameter `json:"location"` + TestId deploymentParameter `json:"testId"` + Tag deploymentParameter `json:"tag"` + PublicIPAddressName deploymentParameter `json:"publicIPAddressName"` + ImagePath deploymentParameter `json:"imagePath"` + AdminUsername deploymentParameter `json:"adminUsername"` + AdminPublicKey deploymentParameter `json:"adminPublicKey"` +} diff --git a/cmd/osbuild-image-tests/constants/constants-travis.go b/cmd/osbuild-image-tests/constants/constants-travis.go index 22eda6bce..48872e450 100644 --- a/cmd/osbuild-image-tests/constants/constants-travis.go +++ b/cmd/osbuild-image-tests/constants/constants-travis.go @@ -18,15 +18,17 @@ func GetOsbuildCommand(store string) *exec.Cmd { } var TestPaths = struct { - ImageInfo string - PrivateKey string - TestCasesDirectory string - UserData string - MetaData string + ImageInfo string + PrivateKey string + TestCasesDirectory string + UserData string + MetaData string + AzureDeploymentTemplate string }{ - ImageInfo: "tools/image-info", - PrivateKey: "test/keyring/id_rsa", - TestCasesDirectory: "test/cases", - UserData: "test/cloud-init/user-data", - MetaData: "test/cloud-init/meta-data", + ImageInfo: "tools/image-info", + PrivateKey: "test/keyring/id_rsa", + TestCasesDirectory: "test/cases", + UserData: "test/cloud-init/user-data", + MetaData: "test/cloud-init/meta-data", + AzureDeploymentTemplate: "test/azure-deployment-template.json", } diff --git a/cmd/osbuild-image-tests/constants/constants.go b/cmd/osbuild-image-tests/constants/constants.go index 17f594614..73d986709 100644 --- a/cmd/osbuild-image-tests/constants/constants.go +++ b/cmd/osbuild-image-tests/constants/constants.go @@ -14,15 +14,17 @@ func GetOsbuildCommand(store string) *exec.Cmd { } var TestPaths = struct { - ImageInfo string - PrivateKey string - TestCasesDirectory string - UserData string - MetaData string + ImageInfo string + PrivateKey string + TestCasesDirectory string + UserData string + MetaData string + AzureDeploymentTemplate string }{ - ImageInfo: "/usr/libexec/osbuild-composer/image-info", - PrivateKey: "/usr/share/tests/osbuild-composer/keyring/id_rsa", - TestCasesDirectory: "/usr/share/tests/osbuild-composer/cases", - UserData: "/usr/share/tests/osbuild-composer/cloud-init/user-data", - MetaData: "/usr/share/tests/osbuild-composer/cloud-init/meta-data", + ImageInfo: "/usr/libexec/osbuild-composer/image-info", + PrivateKey: "/usr/share/tests/osbuild-composer/keyring/id_rsa", + TestCasesDirectory: "/usr/share/tests/osbuild-composer/cases", + UserData: "/usr/share/tests/osbuild-composer/cloud-init/user-data", + MetaData: "/usr/share/tests/osbuild-composer/cloud-init/meta-data", + AzureDeploymentTemplate: "/usr/share/tests/osbuild-composer/azure-deployment-template.json", } diff --git a/cmd/osbuild-image-tests/main_test.go b/cmd/osbuild-image-tests/main_test.go index f404d1bea..f8f88c46f 100644 --- a/cmd/osbuild-image-tests/main_test.go +++ b/cmd/osbuild-image-tests/main_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/azuretest" "github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants" "github.com/osbuild/osbuild-composer/internal/common" ) @@ -262,6 +263,43 @@ func testBootUsingAWS(t *testing.T, imagePath string) { require.NoError(t, err) } +func testBootUsingAzure(t *testing.T, imagePath string) { + creds, err := azuretest.GetAzureCredentialsFromEnv() + require.NoError(t, err) + + // if no credentials are given, fall back to qemu + if creds == nil { + log.Print("no Azure credentials given, falling back to booting using qemu") + testBootUsingQemu(t, imagePath) + return + } + + // create a random test id to name all the resources used in this test + testId, err := generateRandomString("") + require.NoError(t, err) + + imageName := "image-" + testId + ".vhd" + + // the following line should be done by osbuild-composer at some point + err = azuretest.UploadImageToAzure(creds, imagePath, imageName) + require.NoErrorf(t, err, "upload to azure failed, resources could have been leaked") + + // delete the image after the test is over + defer func() { + err = azuretest.DeleteImageFromAzure(creds, imageName) + require.NoErrorf(t, err, "cannot delete the azure image, resources could have been leaked") + }() + + // boot the uploaded image and try to connect to it + err = withSSHKeyPair(func(privateKey, publicKey string) error { + return azuretest.WithBootedImageInAzure(creds, imageName, testId, publicKey, func(address string) error { + testSSH(t, address, privateKey, nil) + return nil + }) + }) + require.NoError(t, err) +} + // testBoot tests if the image is able to successfully boot // Before the test it boots the image respecting the specified bootType. // The test passes if the function is able to connect to the image via ssh @@ -281,6 +319,9 @@ func testBoot(t *testing.T, imagePath string, bootType string, outputID string) case "aws": testBootUsingAWS(t, imagePath) + case "azure": + testBootUsingAzure(t, imagePath) + default: panic("unknown boot type!") } diff --git a/golang-github-osbuild-composer.spec b/golang-github-osbuild-composer.spec index 1b98363a2..c44ada3ee 100644 --- a/golang-github-osbuild-composer.spec +++ b/golang-github-osbuild-composer.spec @@ -26,6 +26,7 @@ BuildRequires: systemd BuildRequires: systemd-rpm-macros BuildRequires: git BuildRequires: golang(github.com/aws/aws-sdk-go) +BuildRequires: golang(github.com/Azure/azure-sdk-for-go) BuildRequires: golang(github.com/Azure/azure-storage-blob-go/azblob) BuildRequires: golang(github.com/BurntSushi/toml) BuildRequires: golang(github.com/coreos/go-semver/semver) @@ -113,6 +114,9 @@ install -m 0755 -vp _bin/osbuild-image-tests %{buildroot}%{_libex install -m 0755 -vp _bin/osbuild-rcm-tests %{buildroot}%{_libexecdir}/tests/osbuild-composer/ install -m 0755 -vp tools/image-info %{buildroot}%{_libexecdir}/osbuild-composer/ +install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer +install -m 0644 -vp test/azure-deployment-template.json %{buildroot}%{_datadir}/tests/osbuild-composer/ + install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer/cases install -m 0644 -vp test/cases/* %{buildroot}%{_datadir}/tests/osbuild-composer/cases/ install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer/keyring diff --git a/osbuild-composer.spec b/osbuild-composer.spec index 478b9a889..deff3369d 100644 --- a/osbuild-composer.spec +++ b/osbuild-composer.spec @@ -30,6 +30,7 @@ BuildRequires: systemd BuildRequires: systemd-rpm-macros BuildRequires: git BuildRequires: golang(github.com/aws/aws-sdk-go) +BuildRequires: golang(github.com/Azure/azure-sdk-for-go) BuildRequires: golang(github.com/Azure/azure-storage-blob-go/azblob) BuildRequires: golang(github.com/BurntSushi/toml) BuildRequires: golang(github.com/coreos/go-semver/semver) @@ -127,6 +128,9 @@ install -m 0755 -vp _bin/osbuild-image-tests %{buildroot}%{_libex install -m 0755 -vp _bin/osbuild-rcm-tests %{buildroot}%{_libexecdir}/tests/osbuild-composer/ install -m 0755 -vp tools/image-info %{buildroot}%{_libexecdir}/osbuild-composer/ +install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer +install -m 0644 -vp test/azure-deployment-template.json %{buildroot}%{_datadir}/tests/osbuild-composer/ + install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer/cases install -m 0644 -vp test/cases/* %{buildroot}%{_datadir}/tests/osbuild-composer/cases/ install -m 0755 -vd %{buildroot}%{_datadir}/tests/osbuild-composer/keyring diff --git a/test/azure-deployment-template.json b/test/azure-deployment-template.json new file mode 100644 index 000000000..9189b46b8 --- /dev/null +++ b/test/azure-deployment-template.json @@ -0,0 +1,211 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "testId": { + "type": "string" + }, + "publicIPAddressName": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "location": { + "type": "string" + }, + "imagePath": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "adminPublicKey": { + "type": "secureString" + } + }, + "variables": { + "subnetRef": "[concat(variables('vnetId'), '/subnets/default')]", + "networkInterfaceName": "[concat('iface-', parameters('testId'))]", + "networkSecurityGroupName": "[concat('nsg-', parameters('testId'))]", + "virtualNetworkName": "[concat('vnet-', parameters('testId'))]", + "publicIPAddressName": "[concat('ip-', parameters('testId'))]", + "virtualMachineName": "[concat('vm-', parameters('testId'))]", + "diskName": "[concat('disk-', parameters('testId'))]", + "imageName": "[concat('image-', parameters('testId'))]", + "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]", + "vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + }, + "resources": [ + { + "name": "[variables('networkInterfaceName')]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2019-07-01", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]", + "[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIPAddressName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[variables('subnetRef')]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIpAddress": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIPAddressName'))]" + } + } + } + ], + "networkSecurityGroup": { + "id": "[variables('nsgId')]" + } + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + }, + { + "name": "[variables('networkSecurityGroupName')]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2019-02-01", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "SSH", + "properties": { + "priority": 300, + "protocol": "TCP", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "22" + } + } + ] + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + }, + { + "name": "[variables('virtualNetworkName')]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2019-09-01", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "10.0.5.0/24" + ] + }, + "subnets": [ + { + "name": "default", + "properties": { + "addressPrefix": "10.0.5.0/24" + } + } + ] + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + }, + { + "name": "[parameters('publicIPAddressName')]", + "type": "Microsoft.Network/publicIpAddresses", + "apiVersion": "2019-02-01", + "location": "[parameters('location')]", + "properties": { + "publicIpAllocationMethod": "Dynamic" + }, + "sku": { + "name": "Basic" + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + }, + { + "name": "[variables('imageName')]", + "type": "Microsoft.Compute/images", + "apiVersion": "2019-07-01", + "location": "[parameters('location')]", + "properties": { + "hyperVGeneration": "V1", + "storageProfile": { + "osDisk": { + "osType": "Linux", + "blobUri": "[parameters('imagePath')]", + "osState": "Generalized" + } + } + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + }, + { + "name": "[variables('virtualMachineName')]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2019-07-01", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', variables('networkInterfaceName'))]", + "[concat('Microsoft.Compute/images/', variables('imageName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "Standard_B1s" + }, + "storageProfile": { + "imageReference": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.Compute/images', variables('imageName'))]" + }, + "osDisk": { + "caching": "ReadWrite", + "managedDisk": { + "storageAccountType": "Standard_LRS" + }, + "name": "[variables('diskName')]", + "createOption": "FromImage" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[variables('virtualMachineName')]", + "adminUsername": "[parameters('adminUsername')]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]", + "keyData": "[parameters('adminPublicKey')]" + } + ] + } + } + } + }, + "tags": { + "osbuild-composer-image-test": "[parameters('tag')]" + } + } + ] +} diff --git a/test/cases/fedora_30-x86_64-vhd-boot.json b/test/cases/fedora_30-x86_64-vhd-boot.json index 229ee90f6..da856b87b 100644 --- a/test/cases/fedora_30-x86_64-vhd-boot.json +++ b/test/cases/fedora_30-x86_64-vhd-boot.json @@ -1,6 +1,6 @@ { "boot": { - "type": "qemu" + "type": "azure" }, "compose-request": { "distro": "fedora-30", @@ -7427,4 +7427,4 @@ ], "timezone": "UTC" } -} \ No newline at end of file +} diff --git a/test/cases/fedora_31-x86_64-vhd-boot.json b/test/cases/fedora_31-x86_64-vhd-boot.json index f6699dc33..b7c635e42 100644 --- a/test/cases/fedora_31-x86_64-vhd-boot.json +++ b/test/cases/fedora_31-x86_64-vhd-boot.json @@ -1,6 +1,6 @@ { "boot": { - "type": "qemu" + "type": "azure" }, "compose-request": { "distro": "fedora-31", @@ -9051,4 +9051,4 @@ "waagent.service" ] } -} \ No newline at end of file +} diff --git a/tools/test-case-generators/format-request-map.json b/tools/test-case-generators/format-request-map.json index cf12f4d22..1f999c5e5 100644 --- a/tools/test-case-generators/format-request-map.json +++ b/tools/test-case-generators/format-request-map.json @@ -159,7 +159,7 @@ }, "vhd": { "boot": { - "type": "qemu" + "type": "azure" }, "compose-request": { "distro": "",