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": "",