diff --git a/builder/azure/common/client/azure_client_set.go b/builder/azure/common/client/azure_client_set.go new file mode 100644 index 000000000..c0dfb12e8 --- /dev/null +++ b/builder/azure/common/client/azure_client_set.go @@ -0,0 +1,90 @@ +package client + +import ( + "net/http" + "regexp" + "time" + + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi" + "github.com/Azure/go-autorest/autorest" +) + +type AzureClientSet interface { + MetadataClient() MetadataClientAPI + + DisksClient() computeapi.DisksClientAPI + ImagesClient() computeapi.ImagesClientAPI + VirtualMachinesClient() computeapi.VirtualMachinesClientAPI + VirtualMachineImagesClient() VirtualMachineImagesClientAPI + + PollClient() autorest.Client +} + +var subscriptionPathRegex = regexp.MustCompile(`/subscriptions/([[:xdigit:]]{8}(-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12})`) + +var _ AzureClientSet = &azureClientSet{} + +type azureClientSet struct { + sender autorest.Sender + authorizer autorest.Authorizer + subscriptionID string + PollingDelay time.Duration +} + +func New(c Config, say func(string)) (AzureClientSet, error) { + token, err := c.GetServicePrincipalToken(say, c.CloudEnvironment.ResourceManagerEndpoint) + if err != nil { + return nil, err + } + return &azureClientSet{ + authorizer: autorest.NewBearerAuthorizer(token), + subscriptionID: c.SubscriptionID, + sender: http.DefaultClient, + PollingDelay: time.Second, + }, nil +} + +func (s azureClientSet) configureAutorestClient(c *autorest.Client) { + c.Authorizer = s.authorizer + c.Sender = s.sender +} + +func (s azureClientSet) MetadataClient() MetadataClientAPI { + return metadataClient{s.sender} +} + +func (s azureClientSet) DisksClient() computeapi.DisksClientAPI { + c := compute.NewDisksClient(s.subscriptionID) + s.configureAutorestClient(&c.Client) + c.PollingDelay = s.PollingDelay + return c +} + +func (s azureClientSet) ImagesClient() computeapi.ImagesClientAPI { + c := compute.NewImagesClient(s.subscriptionID) + s.configureAutorestClient(&c.Client) + c.PollingDelay = s.PollingDelay + return c +} + +func (s azureClientSet) VirtualMachinesClient() computeapi.VirtualMachinesClientAPI { + c := compute.NewVirtualMachinesClient(s.subscriptionID) + s.configureAutorestClient(&c.Client) + c.PollingDelay = s.PollingDelay + return c +} + +func (s azureClientSet) VirtualMachineImagesClient() VirtualMachineImagesClientAPI { + c := compute.NewVirtualMachineImagesClient(s.subscriptionID) + s.configureAutorestClient(&c.Client) + c.PollingDelay = s.PollingDelay + return virtualMachineImagesClientAPI{c} +} + +func (s azureClientSet) PollClient() autorest.Client { + c := autorest.NewClientWithUserAgent("Packer-Azure-ClientSet") + s.configureAutorestClient(&c) + c.PollingDelay = time.Second / 3 + return c +} diff --git a/builder/azure/common/client/platform_image.go b/builder/azure/common/client/platform_image.go new file mode 100644 index 000000000..ac11ec935 --- /dev/null +++ b/builder/azure/common/client/platform_image.go @@ -0,0 +1,57 @@ +package client + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi" + "github.com/Azure/go-autorest/autorest/to" + "regexp" + "strings" +) + +var platformImageRegex = regexp.MustCompile(`^[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+$`) + +type VirtualMachineImagesClientAPI interface { + computeapi.VirtualMachineImagesClientAPI + // extensions + GetLatest(ctx context.Context, publisher, offer, sku, location string) (*compute.VirtualMachineImageResource, error) +} + +var _ VirtualMachineImagesClientAPI = virtualMachineImagesClientAPI{} + +type virtualMachineImagesClientAPI struct { + computeapi.VirtualMachineImagesClientAPI +} + +func ParsePlatformImageURN(urn string) (image *PlatformImage, err error) { + if !platformImageRegex.Match([]byte(urn)) { + return nil, fmt.Errorf("%q is not a valid platform image specifier", urn) + } + parts := strings.Split(urn, ":") + return &PlatformImage{parts[0], parts[1], parts[2], parts[3]}, nil +} + +func (c virtualMachineImagesClientAPI) GetLatest(ctx context.Context, publisher, offer, sku, location string) (*compute.VirtualMachineImageResource, error) { + result, err := c.List(ctx, location, publisher, offer, sku, "", to.Int32Ptr(1), "name desc") + if err != nil { + return nil, err + } + if result.Value == nil || len(*result.Value) == 0 { + return nil, fmt.Errorf("%s:%s:%s:latest could not be found in location %s", publisher, offer, sku, location) + } + + return &(*result.Value)[0], nil +} + +type PlatformImage struct { + Publisher, Offer, Sku, Version string +} + +func (pi PlatformImage) URN() string { + return fmt.Sprintf("%s:%s:%s:%s", + pi.Publisher, + pi.Offer, + pi.Sku, + pi.Version) +} diff --git a/builder/azure/common/client/platform_image_test.go b/builder/azure/common/client/platform_image_test.go new file mode 100644 index 000000000..981031d45 --- /dev/null +++ b/builder/azure/common/client/platform_image_test.go @@ -0,0 +1,30 @@ +package client + +import ( + "fmt" + "testing" +) + +func Test_platformImageRegex(t *testing.T) { + for i, v := range []string{ + "Publisher:Offer:Sku:Versions", + "Publisher:Offer-name:2.0_alpha:2.0.2019060122", + } { + t.Run(fmt.Sprintf("should_match_%d", i), func(t *testing.T) { + if !platformImageRegex.Match([]byte(v)) { + t.Fatalf("expected %q to match", v) + } + }) + } + + for i, v := range []string{ + "Publ isher:Offer:Sku:Versions", + "Publ/isher:Offer-name:2.0_alpha:2.0.2019060122", + } { + t.Run(fmt.Sprintf("should_not_match_%d", i), func(t *testing.T) { + if platformImageRegex.Match([]byte(v)) { + t.Fatalf("did not expected %q to match", v) + } + }) + } +} diff --git a/builder/azure/common/client/testclient.go b/builder/azure/common/client/testclient.go new file mode 100644 index 000000000..964e04795 --- /dev/null +++ b/builder/azure/common/client/testclient.go @@ -0,0 +1,31 @@ +package client + +import ( + "os" + "testing" + "net/http" + "errors" + + "github.com/Azure/go-autorest/autorest/azure/auth" +) + +func GetTestClientSet(t *testing.T) (AzureClientSet, error) { + if (os.Getenv("AZURE_INTEGRATION_TEST") == "") { + t.Skip("AZURE_INTEGRATION_TEST not set") + } else { + a, err := auth.NewAuthorizerFromEnvironment() + if err == nil { + cli := azureClientSet{} + cli.authorizer = a + cli.subscriptionID = os.Getenv("AZURE_SUBSCRIPTION_ID") + cli.PollingDelay = 0 + cli.sender = http.DefaultClient + return cli, nil + } else { + t.Skipf("Could not create Azure client: %v", err) + } + } + + return nil, errors.New("Couldn't create client set") +} +