github.com/jenkins-x/test-infra@v0.0.7/kubetest/azure.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"log"
    27  	"net/url"
    28  	"os"
    29  	"os/exec"
    30  	"path"
    31  	"path/filepath"
    32  	"strings"
    33  	"time"
    34  
    35  	"github.com/pelletier/go-toml"
    36  	"k8s.io/test-infra/kubetest/util"
    37  
    38  	"github.com/Azure/azure-storage-blob-go/2016-05-31/azblob"
    39  	"github.com/Azure/go-autorest/autorest/azure"
    40  	"github.com/satori/go.uuid"
    41  )
    42  
    43  var (
    44  	// azure specific flags
    45  	acsResourceName        = flag.String("acsengine-resource-name", "", "Azure Resource Name")
    46  	acsResourceGroupName   = flag.String("acsengine-resourcegroup-name", "", "Azure Resource Group Name")
    47  	acsLocation            = flag.String("acsengine-location", "westus2", "Azure ACS location")
    48  	acsMasterVmSize        = flag.String("acsengine-mastervmsize", "Standard_D2s_v3", "Azure Master VM size")
    49  	acsAgentVmSize         = flag.String("acsengine-agentvmsize", "Standard_D2s_v3", "Azure Agent VM size")
    50  	acsAdminUsername       = flag.String("acsengine-admin-username", "", "Admin username")
    51  	acsAdminPassword       = flag.String("acsengine-admin-password", "", "Admin password")
    52  	acsAgentPoolCount      = flag.Int("acsengine-agentpoolcount", 2, "Azure Agent Pool Count")
    53  	acsAgentOSType         = flag.String("acsengine-agentOSType", "Windows", "OS Type of Agent Nodes. Options: Windows|Linux")
    54  	acsTemplatePath        = flag.String("acsengine-template", "", "Azure Template Name")
    55  	acsDnsPrefix           = flag.String("acsengine-dnsprefix", "", "Azure K8s Master DNS Prefix")
    56  	acsEngineURL           = flag.String("acsengine-download-url", "", "Download URL for ACS engine")
    57  	acsEngineMD5           = flag.String("acsengine-md5-sum", "", "Checksum for acs engine download")
    58  	acsSSHPublicKeyPath    = flag.String("acsengine-public-key", "", "Path to SSH Public Key")
    59  	acsWinBinariesURL      = flag.String("acsengine-win-binaries-url", "", "Path to get the zip file containing kubelet and kubeproxy binaries for Windows")
    60  	acsHyperKubeURL        = flag.String("acsengine-hyperkube-url", "", "Path to get the kyberkube image for the deployment")
    61  	acsCredentialsFile     = flag.String("acsengine-creds", "", "Path to credential file for Azure")
    62  	acsOrchestratorRelease = flag.String("acsengine-orchestratorRelease", "1.11", "Orchestrator Profile for acs-engine")
    63  	acsWinZipBuildScript   = flag.String("acsengine-winZipBuildScript", "https://raw.githubusercontent.com/Azure/acs-engine/master/scripts/build-windows-k8s.sh", "Build script to create custom zip containing win binaries for acs-engine")
    64  	acsNetworkPlugin       = flag.String("acsengine-networkPlugin", "azure", "Network pluging to use with acs-engine")
    65  )
    66  
    67  type Creds struct {
    68  	ClientID           string
    69  	ClientSecret       string
    70  	TenantID           string
    71  	SubscriptionID     string
    72  	StorageAccountName string
    73  	StorageAccountKey  string
    74  }
    75  
    76  type Config struct {
    77  	Creds Creds
    78  }
    79  
    80  type Cluster struct {
    81  	ctx                     context.Context
    82  	credentials             *Creds
    83  	location                string
    84  	resourceGroup           string
    85  	name                    string
    86  	apiModelPath            string
    87  	dnsPrefix               string
    88  	templateJSON            map[string]interface{}
    89  	parametersJSON          map[string]interface{}
    90  	outputDir               string
    91  	sshPublicKey            string
    92  	adminUsername           string
    93  	adminPassword           string
    94  	masterVMSize            string
    95  	agentVMSize             string
    96  	acsCustomHyperKubeURL   string
    97  	acsCustomWinBinariesURL string
    98  	acsEngineBinaryPath     string
    99  	azureClient             *AzureClient
   100  }
   101  
   102  func (c *Cluster) getAzCredentials() error {
   103  	content, err := ioutil.ReadFile(*acsCredentialsFile)
   104  	log.Printf("Reading credentials file %v", *acsCredentialsFile)
   105  	if err != nil {
   106  		return fmt.Errorf("error reading credentials file %v %v", *acsCredentialsFile, err)
   107  	}
   108  	config := Config{}
   109  	err = toml.Unmarshal(content, &config)
   110  	c.credentials = &config.Creds
   111  	if err != nil {
   112  		return fmt.Errorf("error parsing credentials file %v %v", *acsCredentialsFile, err)
   113  	}
   114  	return nil
   115  }
   116  
   117  func checkParams() error {
   118  	if *acsCredentialsFile == "" {
   119  		return fmt.Errorf("no credentials file path specified")
   120  	}
   121  	if *acsResourceName == "" {
   122  		*acsResourceName = "kubetest-" + uuid.NewV1().String()
   123  	}
   124  	if *acsResourceGroupName == "" {
   125  		*acsResourceGroupName = *acsResourceName + "-rg"
   126  	}
   127  	if *acsDnsPrefix == "" {
   128  		*acsDnsPrefix = *acsResourceName
   129  	}
   130  	if *acsSSHPublicKeyPath == "" {
   131  		*acsSSHPublicKeyPath = os.Getenv("HOME") + "/.ssh/id_rsa.pub"
   132  	}
   133  	if *acsAdminUsername == "" {
   134  		return fmt.Errorf("error parsing flags. No admin username specified")
   135  	}
   136  	if *acsAdminPassword == "" {
   137  		return fmt.Errorf("error parting flags. No admin password specified.")
   138  	}
   139  	return nil
   140  }
   141  
   142  func newAcsEngine() (*Cluster, error) {
   143  	if err := checkParams(); err != nil {
   144  		return nil, fmt.Errorf("error creating Azure K8S cluster: %v", err)
   145  	}
   146  
   147  	tempdir, _ := ioutil.TempDir(os.Getenv("HOME"), "acs")
   148  	sshKey, err := ioutil.ReadFile(*acsSSHPublicKeyPath)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("error reading SSH Key %v %v", *acsSSHPublicKeyPath, err)
   151  	}
   152  	c := Cluster{
   153  		ctx:                     context.Background(),
   154  		apiModelPath:            *acsTemplatePath,
   155  		name:                    *acsResourceName,
   156  		dnsPrefix:               *acsDnsPrefix,
   157  		location:                *acsLocation,
   158  		resourceGroup:           *acsResourceGroupName,
   159  		outputDir:               tempdir,
   160  		sshPublicKey:            fmt.Sprintf("%s", sshKey),
   161  		credentials:             &Creds{},
   162  		acsCustomHyperKubeURL:   "",
   163  		acsCustomWinBinariesURL: "",
   164  		acsEngineBinaryPath:     "acs-engine", // use the one in path by default
   165  	}
   166  	c.getAzCredentials()
   167  	err = c.getARMClient(c.ctx)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("failed to generate ARM client: %v", err)
   170  	}
   171  	// like kops and gke set KUBERNETES_CONFORMANCE_TEST so the auth is picked up
   172  	// from kubectl instead of bash inference.
   173  	if err := os.Setenv("KUBERNETES_CONFORMANCE_TEST", "yes"); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	return &c, nil
   178  }
   179  
   180  func (c *Cluster) generateTemplate() error {
   181  	v := &AcsEngineAPIModel{
   182  		APIVersion: "vlabs",
   183  		Location:   c.location,
   184  		Name:       c.name,
   185  		Tags: map[string]string{
   186  			"date": time.Now().String(),
   187  		},
   188  		Properties: &Properties{
   189  			OrchestratorProfile: &OrchestratorProfile{
   190  				OrchestratorType:    "Kubernetes",
   191  				OrchestratorRelease: *acsOrchestratorRelease,
   192  				KubernetesConfig: &KubernetesConfig{
   193  					NetworkPlugin: *acsNetworkPlugin,
   194  				},
   195  			},
   196  			MasterProfile: &MasterProfile{
   197  				Count:          1,
   198  				DNSPrefix:      c.dnsPrefix,
   199  				VMSize:         *acsMasterVmSize,
   200  				IPAddressCount: 200,
   201  				Extensions: []map[string]string{
   202  					{
   203  						"name": "win-e2e-master-extension",
   204  					},
   205  				},
   206  			},
   207  			AgentPoolProfiles: []*AgentPoolProfile{
   208  				{
   209  					Name:                "agentpool0",
   210  					VMSize:              *acsAgentVmSize,
   211  					Count:               *acsAgentPoolCount,
   212  					OSType:              *acsAgentOSType,
   213  					AvailabilityProfile: "AvailabilitySet",
   214  					IPAddressCount:      200,
   215  					PreProvisionExtension: map[string]string{
   216  						"name":        "node_setup",
   217  						"singleOrAll": "all",
   218  					},
   219  					Extensions: []map[string]string{
   220  						{
   221  							"name": "winrm",
   222  						},
   223  					},
   224  				},
   225  			},
   226  			LinuxProfile: &LinuxProfile{
   227  				AdminUsername: *acsAdminUsername,
   228  				SSHKeys: &SSH{
   229  					PublicKeys: []PublicKey{{
   230  						KeyData: c.sshPublicKey,
   231  					},
   232  					},
   233  				},
   234  			},
   235  			WindowsProfile: &WindowsProfile{
   236  				AdminUsername: *acsAdminUsername,
   237  				AdminPassword: *acsAdminPassword,
   238  			},
   239  			ServicePrincipalProfile: &ServicePrincipalProfile{
   240  				ClientID: c.credentials.ClientID,
   241  				Secret:   c.credentials.ClientSecret,
   242  			},
   243  			ExtensionProfiles: []map[string]string{
   244  				{
   245  					/* Agent node preprovision template
   246  					   Used to setup windows node for e2e tests: i.e creates c:\tmp folder that some
   247  					   tests expect
   248  
   249  					   Extension source:
   250  					   https://github.com/e2e-win/e2e-win-prow-deployment/blob/master/extensions/agent_preprovision_extension/node_setup.ps1
   251  					*/
   252  					"name":    "node_setup",
   253  					"version": "v1",
   254  					"rootURL": "https://k8swin.blob.core.windows.net/k8s-windows/preprovision_extensions/",
   255  					"script":  "node_setup.ps1",
   256  				},
   257  				{
   258  					/*
   259  					   WinRM template used for accessing windows nodes for debugging and logs collection.
   260  					*/
   261  					"name":    "winrm",
   262  					"version": "v1",
   263  				},
   264  				{
   265  					/*
   266  						Master node custom script. Runs after provisioning.
   267  
   268  						Taints master node as not schedulable for tests. As this is the only
   269  						Linux node in the deployment, we need to wait until kube-system pods
   270  						start before tainting master
   271  
   272  						Extension source:
   273  						https://github.com/e2e-win/e2e-win-prow-deployment/blob/master/extensions/master_extension/win-e2e-master-extension.sh
   274  					*/
   275  					"name":                "win-e2e-master-extension",
   276  					"version":             "v1",
   277  					"extensionParameters": "parameters",
   278  					"rootURL":             "https://k8swin.blob.core.windows.net/k8s-windows/extensions/",
   279  					"script":              "win-e2e-master-extension.sh",
   280  				},
   281  			},
   282  		},
   283  	}
   284  	if *acsHyperKubeURL != "" {
   285  		v.Properties.OrchestratorProfile.KubernetesConfig.CustomHyperkubeImage = *acsHyperKubeURL
   286  	} else if c.acsCustomHyperKubeURL != "" {
   287  		v.Properties.OrchestratorProfile.KubernetesConfig.CustomHyperkubeImage = c.acsCustomHyperKubeURL
   288  	}
   289  
   290  	if *acsWinBinariesURL != "" {
   291  		v.Properties.OrchestratorProfile.KubernetesConfig.CustomWindowsPackageURL = *acsWinBinariesURL
   292  	} else if c.acsCustomWinBinariesURL != "" {
   293  		v.Properties.OrchestratorProfile.KubernetesConfig.CustomWindowsPackageURL = c.acsCustomWinBinariesURL
   294  	}
   295  	apiModel, _ := json.Marshal(v)
   296  	c.apiModelPath = path.Join(c.outputDir, "kubernetes.json")
   297  	err := ioutil.WriteFile(c.apiModelPath, apiModel, 0644)
   298  	if err != nil {
   299  		return fmt.Errorf("cannot write apimodel to file: %v", err)
   300  	}
   301  	return nil
   302  }
   303  
   304  func (c *Cluster) getAcsEngine(retry int) error {
   305  	downloadPath := path.Join(os.Getenv("HOME"), "acs-engine.tar.gz")
   306  	f, err := os.Create(downloadPath)
   307  	if err != nil {
   308  		return err
   309  	}
   310  	defer f.Close()
   311  
   312  	for i := 0; i < retry; i++ {
   313  		log.Printf("downloading %v from %v.", downloadPath, *acsEngineURL)
   314  		if err := httpRead(*acsEngineURL, f); err == nil {
   315  			break
   316  		}
   317  		err = fmt.Errorf("url=%s failed get %v: %v.", *acsEngineURL, downloadPath, err)
   318  		if i == retry-1 {
   319  			return err
   320  		}
   321  		log.Println(err)
   322  		sleep(time.Duration(i) * time.Second)
   323  	}
   324  
   325  	f.Close()
   326  	if *acsEngineMD5 != "" {
   327  		o, err := control.Output(exec.Command("md5sum", f.Name()))
   328  		if err != nil {
   329  			return err
   330  		}
   331  		if strings.Split(string(o), " ")[0] != *acsEngineMD5 {
   332  			return fmt.Errorf("wrong md5 sum for acs-engine.")
   333  		}
   334  	}
   335  
   336  	cwd, err := os.Getwd()
   337  	if err != nil {
   338  		return fmt.Errorf("unable to get current directory: %v .", err)
   339  	}
   340  	log.Printf("Extracting tar file %v into directory %v .", f.Name(), cwd)
   341  
   342  	if err = control.FinishRunning(exec.Command("tar", "-xzf", f.Name(), "--strip", "1")); err != nil {
   343  		return err
   344  	}
   345  	c.acsEngineBinaryPath = path.Join(cwd, "acs-engine")
   346  	return nil
   347  
   348  }
   349  
   350  func (c Cluster) generateARMTemplates() error {
   351  	if err := control.FinishRunning(exec.Command(c.acsEngineBinaryPath, "generate", c.apiModelPath, "--output-directory", c.outputDir)); err != nil {
   352  		return fmt.Errorf("failed to generate ARM templates: %v.", err)
   353  	}
   354  	return nil
   355  }
   356  
   357  func (c *Cluster) loadARMTemplates() error {
   358  	var err error
   359  	template, err := ioutil.ReadFile(path.Join(c.outputDir, "azuredeploy.json"))
   360  	if err != nil {
   361  		return fmt.Errorf("error reading ARM template file: %v.", err)
   362  	}
   363  	c.templateJSON = make(map[string]interface{})
   364  	err = json.Unmarshal(template, &c.templateJSON)
   365  	if err != nil {
   366  		return fmt.Errorf("error unmarshall template %v", err.Error())
   367  	}
   368  	parameters, err := ioutil.ReadFile(path.Join(c.outputDir, "azuredeploy.parameters.json"))
   369  	if err != nil {
   370  		return fmt.Errorf("error reading ARM parameters file: %v", err)
   371  	}
   372  	c.parametersJSON = make(map[string]interface{})
   373  	err = json.Unmarshal(parameters, &c.parametersJSON)
   374  	if err != nil {
   375  		return fmt.Errorf("error unmarshall parameters %v", err.Error())
   376  	}
   377  	c.parametersJSON = c.parametersJSON["parameters"].(map[string]interface{})
   378  
   379  	return nil
   380  }
   381  
   382  func (c *Cluster) getARMClient(ctx context.Context) error {
   383  	// instantiate Azure Resource Manager Client
   384  	env, err := azure.EnvironmentFromName("AzurePublicCloud")
   385  	var client *AzureClient
   386  	if client, err = getAzureClient(env,
   387  		c.credentials.SubscriptionID,
   388  		c.credentials.ClientID,
   389  		c.credentials.TenantID,
   390  		c.credentials.ClientSecret); err != nil {
   391  		return fmt.Errorf("error trying to get Azure Client: %v", err)
   392  	}
   393  	c.azureClient = client
   394  	return nil
   395  }
   396  
   397  func (c *Cluster) createCluster() error {
   398  	var err error
   399  	kubecfgDir, _ := ioutil.ReadDir(path.Join(c.outputDir, "kubeconfig"))
   400  	kubecfg := path.Join(c.outputDir, "kubeconfig", kubecfgDir[0].Name())
   401  	log.Printf("Setting kubeconfig env variable: kubeconfig path: %v.", kubecfg)
   402  	os.Setenv("KUBECONFIG", kubecfg)
   403  	log.Printf("Creating resource group: %v.", c.resourceGroup)
   404  
   405  	log.Printf("Creating Azure resource group: %v for cluster deployment.", c.resourceGroup)
   406  	_, err = c.azureClient.EnsureResourceGroup(c.ctx, c.resourceGroup, c.location, nil)
   407  	if err != nil {
   408  		return fmt.Errorf("could not ensure resource group: %v", err)
   409  	}
   410  	log.Printf("Validating deployment ARM templates.")
   411  	if _, err := c.azureClient.ValidateDeployment(
   412  		c.ctx, c.resourceGroup, c.name, &c.templateJSON, &c.parametersJSON,
   413  	); err != nil {
   414  		return fmt.Errorf("ARM template invalid: %v", err)
   415  	}
   416  	log.Printf("Deploying cluster %v in resource group %v.", c.name, c.resourceGroup)
   417  	if _, err := c.azureClient.DeployTemplate(
   418  		c.ctx, c.resourceGroup, c.name, &c.templateJSON, &c.parametersJSON,
   419  	); err != nil {
   420  		return fmt.Errorf("cannot deploy: %v", err)
   421  	}
   422  	return nil
   423  
   424  }
   425  
   426  func (c *Cluster) buildHyperKube() error {
   427  
   428  	os.Setenv("VERSION", fmt.Sprintf("win-e2e-%v", os.Getenv("BUILD_ID")))
   429  
   430  	cwd, _ := os.Getwd()
   431  	log.Printf("CWD %v", cwd)
   432  	log.Printf("Attempt docker gcloud login")
   433  	prepareDocker := util.K8s("gcloud", "auth", "configure-docker")
   434  	if err := control.FinishRunning(exec.Command(prepareDocker)); err != nil {
   435  		return err
   436  	}
   437  	pushHyperkube := util.K8s("kubernetes", "hack", "dev-push-hyperkube.sh")
   438  	if err1 := control.FinishRunning(exec.Command(pushHyperkube)); err1 != nil {
   439  		return err1
   440  	}
   441  	c.acsCustomHyperKubeURL = fmt.Sprintf("%s/hyperkube-amd64:%s", os.Getenv("REGISTRY"), os.Getenv("VERSION"))
   442  
   443  	log.Printf("Custom hyperkube url: %v", c.acsCustomHyperKubeURL)
   444  	return nil
   445  }
   446  
   447  func (c *Cluster) uploadZip(zipPath string) error {
   448  
   449  	credential := azblob.NewSharedKeyCredential(c.credentials.StorageAccountName, c.credentials.StorageAccountKey)
   450  	p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
   451  
   452  	var containerName string = os.Getenv("AZ_STORAGE_CONTAINER_NAME")
   453  
   454  	URL, _ := url.Parse(
   455  		fmt.Sprintf("https://%s.blob.core.windows.net/%s", c.credentials.StorageAccountName, containerName))
   456  
   457  	containerURL := azblob.NewContainerURL(*URL, p)
   458  	file, err := os.Open(zipPath)
   459  	if err != nil {
   460  		return fmt.Errorf("failed to open file %v . Error %v", zipPath, err)
   461  	}
   462  	blobURL := containerURL.NewBlockBlobURL(filepath.Base(file.Name()))
   463  	_, err1 := azblob.UploadFileToBlockBlob(context.Background(), file, blobURL, azblob.UploadToBlockBlobOptions{})
   464  	file.Close()
   465  	if err1 != nil {
   466  		return err1
   467  	}
   468  	blobURLString := blobURL.URL()
   469  	c.acsCustomWinBinariesURL = blobURLString.String()
   470  	log.Printf("Custom win binaries url: %v", c.acsCustomWinBinariesURL)
   471  	return nil
   472  }
   473  
   474  func getZipBuildScript(buildScriptURL string, retry int) (string, error) {
   475  	downloadPath := path.Join(os.Getenv("HOME"), "build-win-zip.sh")
   476  	f, err := os.Create(downloadPath)
   477  	if err != nil {
   478  		return "", err
   479  	}
   480  	defer f.Close()
   481  
   482  	for i := 0; i < retry; i++ {
   483  		log.Printf("downloading %v from %v.", downloadPath, buildScriptURL)
   484  		if err := httpRead(buildScriptURL, f); err == nil {
   485  			break
   486  		}
   487  		err = fmt.Errorf("url=%s failed get %v: %v.", buildScriptURL, downloadPath, err)
   488  		if i == retry-1 {
   489  			return "", err
   490  		}
   491  		log.Println(err)
   492  		sleep(time.Duration(i) * time.Second)
   493  	}
   494  	f.Chmod(0744)
   495  	return downloadPath, nil
   496  }
   497  
   498  func (c *Cluster) buildWinZip() error {
   499  
   500  	zipName := fmt.Sprintf("%s.zip", os.Getenv("BUILD_ID"))
   501  	buildFolder := path.Join(os.Getenv("HOME"), "winbuild")
   502  	zipPath := path.Join(os.Getenv("HOME"), zipName)
   503  	log.Printf("Building %s", zipName)
   504  	buildScriptPath, err := getZipBuildScript(*acsWinZipBuildScript, 2)
   505  	if err != nil {
   506  		return err
   507  	}
   508  	if err := control.FinishRunning(exec.Command(buildScriptPath, "-u", zipName, "-z", buildFolder)); err != nil {
   509  		return err
   510  	}
   511  	log.Printf("Uploading %s", zipPath)
   512  	if err := c.uploadZip(zipPath); err != nil {
   513  		return err
   514  	}
   515  	return nil
   516  }
   517  
   518  func (c Cluster) Up() error {
   519  
   520  	var err error
   521  	if *acsHyperKubeURL == "" {
   522  		err = c.buildHyperKube()
   523  		if err != nil {
   524  			return fmt.Errorf("error building hyperkube %v", err)
   525  		}
   526  	}
   527  	if *acsWinBinariesURL == "" {
   528  		err = c.buildWinZip()
   529  		if err != nil {
   530  			return fmt.Errorf("error building windowsZipFile %v", err)
   531  		}
   532  	}
   533  	if c.apiModelPath == "" {
   534  		err = c.generateTemplate()
   535  		if err != nil {
   536  			return fmt.Errorf("failed to generate acs-engine apimodel template: %v", err)
   537  		}
   538  	}
   539  	if *acsEngineURL != "" {
   540  		err = c.getAcsEngine(2)
   541  		if err != nil {
   542  			return fmt.Errorf("failed to get ACS Engine binary: %v", err)
   543  		}
   544  	}
   545  	err = c.generateARMTemplates()
   546  	if err != nil {
   547  		return fmt.Errorf("failed to generate ARM templates: %v", err)
   548  	}
   549  	err = c.loadARMTemplates()
   550  	if err != nil {
   551  		return fmt.Errorf("error loading ARM templates: %v", err)
   552  	}
   553  	err = c.createCluster()
   554  	if err != nil {
   555  		return fmt.Errorf("error creating cluster: %v", err)
   556  	}
   557  	return nil
   558  }
   559  
   560  func (c Cluster) Down() error {
   561  	log.Printf("Deleting resource group: %v.", c.resourceGroup)
   562  	return c.azureClient.DeleteResourceGroup(c.ctx, c.resourceGroup)
   563  }
   564  
   565  func (c Cluster) DumpClusterLogs(localPath, gcsPath string) error {
   566  	return nil
   567  }
   568  
   569  func (c Cluster) GetClusterCreated(clusterName string) (time.Time, error) {
   570  	return time.Time{}, errors.New("not implemented")
   571  }
   572  
   573  func (c Cluster) TestSetup() error {
   574  
   575  	// Download repo-list that defines repositories for Windows test images.
   576  
   577  	downloadUrl, ok := os.LookupEnv("KUBE_TEST_REPO_LIST_DOWNLOAD_LOCATION")
   578  	if !ok {
   579  		// Env value for downloadUrl is not set, nothing to do
   580  		log.Printf("KUBE_TEST_REPO_LIST_DOWNLOAD_LOCATION not set. Using default test image repos.")
   581  		return nil
   582  	}
   583  
   584  	downloadPath := path.Join(os.Getenv("HOME"), "repo-list")
   585  	f, err := os.Create(downloadPath)
   586  	if err != nil {
   587  		return err
   588  	}
   589  	defer f.Close()
   590  
   591  	log.Printf("downloading %v from %v.", downloadPath, downloadUrl)
   592  	err = httpRead(downloadUrl, f)
   593  
   594  	if err != nil {
   595  		return fmt.Errorf("url=%s failed get %v: %v.", downloadUrl, downloadPath, err)
   596  	}
   597  	f.Chmod(0744)
   598  	if err := os.Setenv("KUBE_TEST_REPO_LIST", downloadPath); err != nil {
   599  		return err
   600  	}
   601  	return nil
   602  }
   603  
   604  func (c Cluster) IsUp() error {
   605  	return isUp(c)
   606  }
   607  
   608  func (_ Cluster) KubectlCommand() (*exec.Cmd, error) { return nil, nil }