diff --git a/azurerm/internal/authorizers/authorizer_shared_access_signature.go b/azurerm/internal/authorizers/authorizer_shared_access_signature.go new file mode 100644 index 0000000000000..8393daaf211b1 --- /dev/null +++ b/azurerm/internal/authorizers/authorizer_shared_access_signature.go @@ -0,0 +1,47 @@ +package authorizers + +import ( + "net/http" + "net/url" + + "github.com/Azure/go-autorest/autorest" +) + +// SharedAccessSignatureAuthorizer implements an authorization for Shared Access Signature +// this can be used for interaction with Blob, File and Queue Storage Endpoints +type SharedAccessSignatureAuthorizer struct { + sasQuery map[string]interface{} +} + +// NewSharedAccessSignatureAuthorizer creates a SharedAccessSignatureAuthorizer using sasToken +func NewSharedAccessSignatureAuthorizer(sasToken string) *SharedAccessSignatureAuthorizer { + m, _ := url.ParseQuery(sasToken) + query := make(map[string]interface{}, len(m)) + for key, value := range m { + for i, v := range value { + value[i] = url.QueryEscape(v) + } + query[key] = value + } + return &SharedAccessSignatureAuthorizer{ + sasQuery: query, + } +} + +// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose +// value is "SharedKey " followed by the computed key. +// This can be used for the Blob, Queue, and File Services +// +// from: https://docs.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature +func (skl *SharedAccessSignatureAuthorizer) WithAuthorization() autorest.PrepareDecorator { + return func(p autorest.Preparer) autorest.Preparer { + return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { + r, err := p.Prepare(r) + if err != nil { + return r, err + } + + return autorest.Prepare(r, autorest.WithQueryParameters(skl.sasQuery)) + }) + } +} diff --git a/azurerm/internal/services/appplatform/client/client.go b/azurerm/internal/services/appplatform/client/client.go index db4af5abedd2b..0874e7a6e0c57 100644 --- a/azurerm/internal/services/appplatform/client/client.go +++ b/azurerm/internal/services/appplatform/client/client.go @@ -6,14 +6,24 @@ import ( ) type Client struct { - ServicesClient *appplatform.ServicesClient + ServicesClient *appplatform.ServicesClient + AppsClient *appplatform.AppsClient + DeploymentsClient *appplatform.DeploymentsClient } func NewClient(o *common.ClientOptions) *Client { servicesClient := appplatform.NewServicesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&servicesClient.Client, o.ResourceManagerAuthorizer) + appsClient := appplatform.NewAppsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&appsClient.Client, o.ResourceManagerAuthorizer) + + deploymentsClient := appplatform.NewDeploymentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&deploymentsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - ServicesClient: &servicesClient, + AppsClient: &appsClient, + DeploymentsClient: &deploymentsClient, + ServicesClient: &servicesClient, } } diff --git a/azurerm/internal/services/appplatform/parse/spring_cloud_app.go b/azurerm/internal/services/appplatform/parse/spring_cloud_app.go new file mode 100644 index 0000000000000..75e8fe0577c18 --- /dev/null +++ b/azurerm/internal/services/appplatform/parse/spring_cloud_app.go @@ -0,0 +1,38 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type SpringCloudAppId struct { + ResourceGroup string + ServiceName string + Name string +} + +func SpringCloudAppID(input string) (*SpringCloudAppId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("unable to parse Spring Cloud App ID %q: %+v", input, err) + } + + app := SpringCloudAppId{ + ResourceGroup: id.ResourceGroup, + } + + if app.ServiceName, err = id.PopSegment("Spring"); err != nil { + return nil, err + } + + if app.Name, err = id.PopSegment("apps"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &app, nil +} diff --git a/azurerm/internal/services/appplatform/parse/spring_cloud_app_test.go b/azurerm/internal/services/appplatform/parse/spring_cloud_app_test.go new file mode 100644 index 0000000000000..3f783629fad8e --- /dev/null +++ b/azurerm/internal/services/appplatform/parse/spring_cloud_app_test.go @@ -0,0 +1,88 @@ +package parse + +import ( + "testing" +) + +func TestSpringCloudAppID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *SpringCloudAppId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "No Spring Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/", + Expected: nil, + }, + { + Name: "Missing Apps Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/", + Expected: nil, + }, + { + Name: "Missing Apps Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/", + Expected: nil, + }, + { + Name: "Spring Cloud App ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1", + Expected: &SpringCloudAppId{ + ResourceGroup: "resGroup1", + ServiceName: "spring1", + Name: "app1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/Apps/app1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := SpringCloudAppID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.ServiceName != v.Expected.ServiceName { + t.Fatalf("Expected %q but got %q for ServiceName", v.Expected.ServiceName, actual.ServiceName) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/appplatform/parse/spring_cloud_deployment.go b/azurerm/internal/services/appplatform/parse/spring_cloud_deployment.go new file mode 100644 index 0000000000000..98fa563ba40e8 --- /dev/null +++ b/azurerm/internal/services/appplatform/parse/spring_cloud_deployment.go @@ -0,0 +1,43 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type SpringCloudDeploymentId struct { + ResourceGroup string + ServiceName string + AppName string + Name string +} + +func SpringCloudDeploymentID(input string) (*SpringCloudDeploymentId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("unable to parse Spring Cloud Deployment ID %q: %+v", input, err) + } + + deployment := SpringCloudDeploymentId{ + ResourceGroup: id.ResourceGroup, + } + + if deployment.ServiceName, err = id.PopSegment("Spring"); err != nil { + return nil, err + } + + if deployment.AppName, err = id.PopSegment("apps"); err != nil { + return nil, err + } + + if deployment.Name, err = id.PopSegment("deployments"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &deployment, nil +} diff --git a/azurerm/internal/services/appplatform/parse/spring_cloud_deployment_test.go b/azurerm/internal/services/appplatform/parse/spring_cloud_deployment_test.go new file mode 100644 index 0000000000000..64f777dcc555d --- /dev/null +++ b/azurerm/internal/services/appplatform/parse/spring_cloud_deployment_test.go @@ -0,0 +1,103 @@ +package parse + +import ( + "testing" +) + +func TestSpringCloudDeploymentID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *SpringCloudDeploymentId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "No Spring Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/", + Expected: nil, + }, + { + Name: "Missing Apps Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/", + Expected: nil, + }, + { + Name: "Missing Apps Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/", + Expected: nil, + }, + { + Name: "Missing Deployment Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1", + Expected: nil, + }, + { + Name: "Missing Deployment Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1/deployments", + Expected: nil, + }, + { + Name: "Spring Cloud Deployment ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1/deployments/deployment1", + Expected: &SpringCloudDeploymentId{ + ResourceGroup: "resGroup1", + ServiceName: "spring1", + AppName: "app1", + Name: "deployment1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1/Deployments/deployment1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := SpringCloudDeploymentID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.ServiceName != v.Expected.ServiceName { + t.Fatalf("Expected %q but got %q for ServiceName", v.Expected.ServiceName, actual.ServiceName) + } + + if actual.AppName != v.Expected.AppName { + t.Fatalf("Expected %q but got %q for AppName", v.Expected.AppName, actual.AppName) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/appplatform/registration.go b/azurerm/internal/services/appplatform/registration.go index 0a6b19ad9b3ba..8f9d0203ba676 100644 --- a/azurerm/internal/services/appplatform/registration.go +++ b/azurerm/internal/services/appplatform/registration.go @@ -28,6 +28,9 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_spring_cloud_service": resourceArmSpringCloudService(), + "azurerm_spring_cloud_active_deployment": resourceArmSpringCloudActiveDeployment(), + "azurerm_spring_cloud_app": resourceArmSpringCloudApp(), + "azurerm_spring_cloud_deployment": resourceArmSpringCloudDeployment(), + "azurerm_spring_cloud_service": resourceArmSpringCloudService(), } } diff --git a/azurerm/internal/services/appplatform/resource_arm_spring_cloud_active_deployment.go b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_active_deployment.go new file mode 100644 index 0000000000000..dc488ed822c55 --- /dev/null +++ b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_active_deployment.go @@ -0,0 +1,288 @@ +package appplatform + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSpringCloudActiveDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSpringCloudActiveDeploymentCreateUpdate, + Read: resourceArmSpringCloudActiveDeploymentRead, + Update: resourceArmSpringCloudActiveDeploymentCreateUpdate, + Delete: resourceArmSpringCloudActiveDeploymentDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SpringCloudAppID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "service_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudServiceName, + }, + + "app_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudAppName, + }, + + "deployment_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudDeploymentName, + }, + + "public": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "persistent_disk": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mount_path": { + Type: schema.TypeString, + Optional: true, + Default: "/persistent", + ValidateFunc: validate.MountPath, + }, + "size_in_gb": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntInSlice([]int{0, 50}), + }, + }, + }, + }, + + "temporary_disk": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mount_path": { + Type: schema.TypeString, + Optional: true, + Default: "/tmp", + ValidateFunc: validate.MountPath, + }, + "size_in_gb": { + Type: schema.TypeInt, + Optional: true, + Default: 5, + ValidateFunc: validation.IntBetween(0, 5), + }, + }, + }, + }, + + "url": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceArmSpringCloudActiveDeploymentCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + serviceName := d.Get("service_name").(string) + appName := d.Get("app_name").(string) + deploymentName := d.Get("deployment_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, serviceName, appName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("[DEBUG] Spring Cloud App %q (Spring Cloud service %q / resource group %q) was not found.", appName, serviceName, resourceGroup) + } + return fmt.Errorf("failure making Read request on AzureRM Spring Cloud App %q (Spring Cloud service %q / resource group %q): %+v", appName, serviceName, resourceGroup, err) + } + + if d.IsNewResource() { + if resp.Properties != nil && resp.Properties.ActiveDeploymentName != nil { + return tf.ImportAsExistsError("azurerm_spring_cloud_active_deployment", *resp.ID) + } + } + + resp.Properties = &appplatform.AppResourceProperties{ + ActiveDeploymentName: &deploymentName, + Public: utils.Bool(d.Get("public").(bool)), + PersistentDisk: expandArmSpringCloudAppPersistentDisk(d.Get("persistent_disk").([]interface{})), + TemporaryDisk: expandArmSpringCloudAppTemporaryDisk(d.Get("temporary_disk").([]interface{})), + } + + future, err := client.Update(ctx, resourceGroup, serviceName, appName, resp) + if err != nil { + return fmt.Errorf("failure setting active deployment %q (Spring Cloud Service %q / Spring Cloud App %q / Resource Group %q): %+v", deploymentName, serviceName, appName, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failure setting active deployment %q (Spring Cloud Service %q / Spring Cloud App %q / Resource Group %q): %+v", deploymentName, serviceName, appName, resourceGroup, err) + } + + resp, err = client.Get(ctx, resourceGroup, serviceName, appName, "") + if err != nil { + return fmt.Errorf("failure retrieving Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", appName, serviceName, resourceGroup, err) + } + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("cannot read Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q) ID", appName, serviceName, resourceGroup) + } + d.SetId(*resp.ID) + + return resourceArmSpringCloudActiveDeploymentRead(d, meta) +} + +func resourceArmSpringCloudActiveDeploymentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudAppID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ServiceName, id.Name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Spring Cloud App %q was not found - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("failure making Read request on AzureRM Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", id.Name, id.ServiceName, id.ResourceGroup, err) + } + + d.Set("resource_group_name", id.ResourceGroup) + d.Set("service_name", id.ServiceName) + d.Set("app_name", resp.Name) + + if appResourceProperties := resp.Properties; appResourceProperties != nil { + if err := d.Set("persistent_disk", flattenArmSpringCloudAppPersistentDisk(appResourceProperties.PersistentDisk)); err != nil { + return fmt.Errorf("failure setting `persistent_disk`: %+v", err) + } + if err := d.Set("temporary_disk", flattenArmSpringCloudAppTemporaryDisk(appResourceProperties.TemporaryDisk)); err != nil { + return fmt.Errorf("failure setting `temporary_disk`: %+v", err) + } + d.Set("public", appResourceProperties.Public) + d.Set("url", appResourceProperties.URL) + d.Set("deployment_name", appResourceProperties.ActiveDeploymentName) + } + + return nil +} + +func resourceArmSpringCloudActiveDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + // the server side can not update app active_deployment_name to nil + // so return nil + return nil +} + +func expandArmSpringCloudAppPersistentDisk(input []interface{}) *appplatform.PersistentDisk { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + return &appplatform.PersistentDisk{ + MountPath: utils.String(v["mount_path"].(string)), + SizeInGB: utils.Int32(int32(v["size_in_gb"].(int))), + } +} + +func expandArmSpringCloudAppTemporaryDisk(input []interface{}) *appplatform.TemporaryDisk { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + return &appplatform.TemporaryDisk{ + MountPath: utils.String(v["mount_path"].(string)), + SizeInGB: utils.Int32(int32(v["size_in_gb"].(int))), + } +} + +func flattenArmSpringCloudAppPersistentDisk(input *appplatform.PersistentDisk) []interface{} { + if input == nil { + return []interface{}{} + } + + mountPath := "" + if input.MountPath != nil { + mountPath = *input.MountPath + } + + sizeInGb := 0 + if input.SizeInGB != nil { + sizeInGb = int(*input.SizeInGB) + } + + return []interface{}{ + map[string]interface{}{ + "mount_path": mountPath, + "size_in_gb": sizeInGb, + }, + } +} + +func flattenArmSpringCloudAppTemporaryDisk(input *appplatform.TemporaryDisk) []interface{} { + if input == nil { + return []interface{}{} + } + + mountPath := "" + if input.MountPath != nil { + mountPath = *input.MountPath + } + + sizeInGb := 0 + if input.SizeInGB != nil { + sizeInGb = int(*input.SizeInGB) + } + + return []interface{}{ + map[string]interface{}{ + "mount_path": mountPath, + "size_in_gb": sizeInGb, + }, + } +} diff --git a/azurerm/internal/services/appplatform/resource_arm_spring_cloud_app.go b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_app.go new file mode 100644 index 0000000000000..431cc3ebc6fb5 --- /dev/null +++ b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_app.go @@ -0,0 +1,143 @@ +package appplatform + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +// for some limitations of rest api: +// 1. app can not update without active deployment, rest api will throw errors +// 2. create app doesn't take care parameters, for example: TemporaryDisk +// so move these field to resource `azurerm_spring_cloud_app_active_deployment` and make SpringCloudApp unmodifiable +func resourceArmSpringCloudApp() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSpringCloudAppCreate, + Read: resourceArmSpringCloudAppRead, + Update: nil, + Delete: resourceArmSpringCloudAppDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SpringCloudAppID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudAppName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "service_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudServiceName, + }, + }, + } +} + +func resourceArmSpringCloudAppCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + serviceName := d.Get("service_name").(string) + + existing, err := client.Get(ctx, resourceGroup, serviceName, name, "") + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("failure checking for present of existing Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_spring_cloud_app", *existing.ID) + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, serviceName, name, appplatform.AppResource{}) + if err != nil { + return fmt.Errorf("failure creating Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failure waiting for creation of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, serviceName, name, "") + if err != nil { + return fmt.Errorf("failure retrieving Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + } + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("cannot read Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q) ID", name, serviceName, resourceGroup) + } + d.SetId(*resp.ID) + + return resourceArmSpringCloudAppRead(d, meta) +} + +func resourceArmSpringCloudAppRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudAppID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ServiceName, id.Name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Spring Cloud App %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("failure reading Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", id.Name, id.ServiceName, id.ResourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("service_name", id.ServiceName) + + return nil +} + +func resourceArmSpringCloudAppDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudAppID(d.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.ResourceGroup, id.ServiceName, id.Name); err != nil { + return fmt.Errorf("failure deleting Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", id.Name, id.ServiceName, id.ResourceGroup, err) + } + + return nil +} diff --git a/azurerm/internal/services/appplatform/resource_arm_spring_cloud_deployment.go b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_deployment.go new file mode 100644 index 0000000000000..77a21df7022e6 --- /dev/null +++ b/azurerm/internal/services/appplatform/resource_arm_spring_cloud_deployment.go @@ -0,0 +1,348 @@ +package appplatform + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSpringCloudDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSpringCloudDeploymentCreate, + Read: resourceArmSpringCloudDeploymentRead, + Update: resourceArmSpringCloudDeploymentUpdate, + Delete: resourceArmSpringCloudDeploymentDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SpringCloudDeploymentID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudDeploymentName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "service_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudServiceName, + }, + + "app_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudAppName, + }, + + "cpu": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntBetween(1, 4), + }, + + "memory_in_gb": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntBetween(1, 8), + }, + + "instance_count": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntBetween(1, 20), + }, + + "jvm_options": { + Type: schema.TypeString, + Optional: true, + }, + + "env": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "runtime_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(appplatform.Java8), + string(appplatform.Java11), + }, true), + Default: string(appplatform.Java8), + }, + + "jar_file": { + Type: schema.TypeString, + Optional: true, + }, + + "jar_file_md5": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceArmSpringCloudDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + appsClient := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + serviceName := d.Get("service_name").(string) + appName := d.Get("app_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + existing, err := client.Get(ctx, resourceGroup, serviceName, appName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("failure checking for present of existing Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", appName, serviceName, appName, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_spring_cloud_deployment", *existing.ID) + } + + jarFile := d.Get("jar_file").(string) + userSourceInfo, err := expandSpringCloudDeploymentUserSourceInfo(meta, ctx, appsClient, resourceGroup, serviceName, appName, jarFile) + if err != nil { + return err + } + + deployment := appplatform.DeploymentResource{ + Properties: &appplatform.DeploymentResourceProperties{ + AppName: utils.String(appName), + DeploymentSettings: &appplatform.DeploymentSettings{ + CPU: utils.Int32(int32(d.Get("cpu").(int))), + MemoryInGB: utils.Int32(int32(d.Get("memory_in_gb").(int))), + JvmOptions: utils.String(d.Get("jvm_options").(string)), + InstanceCount: utils.Int32(int32(d.Get("instance_count").(int))), + EnvironmentVariables: expandSpringCloudDeploymentEnv(d.Get("env").(map[string]interface{})), + RuntimeVersion: appplatform.RuntimeVersion(d.Get("runtime_version").(string)), + }, + Source: userSourceInfo, + }, + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, serviceName, appName, name, deployment) + if err != nil { + return fmt.Errorf("failure creating Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failure waiting for creation of Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + + resp, err := future.Result(*client) + if err != nil { + return fmt.Errorf("failure retrieving Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("cannot get Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + d.SetId(*resp.ID) + + return resourceArmSpringCloudDeploymentRead(d, meta) +} + +func resourceArmSpringCloudDeploymentUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + appsClient := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + serviceName := d.Get("service_name").(string) + appName := d.Get("app_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + deployment := appplatform.DeploymentResource{ + Properties: &appplatform.DeploymentResourceProperties{ + AppName: utils.String(appName), + DeploymentSettings: &appplatform.DeploymentSettings{ + CPU: utils.Int32(int32(d.Get("cpu").(int))), + MemoryInGB: utils.Int32(int32(d.Get("memory_in_gb").(int))), + JvmOptions: utils.String(d.Get("jvm_options").(string)), + InstanceCount: utils.Int32(int32(d.Get("instance_count").(int))), + EnvironmentVariables: expandSpringCloudDeploymentEnv(d.Get("env").(map[string]interface{})), + RuntimeVersion: appplatform.RuntimeVersion(d.Get("runtime_version").(string)), + }, + }, + } + + if d.HasChanges("jar_file", "jar_file_md5") { + jarFile := d.Get("jar_file").(string) + userSourceInfo, err := expandSpringCloudDeploymentUserSourceInfo(meta, ctx, appsClient, resourceGroup, serviceName, appName, jarFile) + if err != nil { + return err + } + deployment.Properties.Source = userSourceInfo + } + + future, err := client.Update(ctx, resourceGroup, serviceName, appName, name, deployment) + if err != nil { + return fmt.Errorf("failure updating Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failure waiting for creation of Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + + resp, err := future.Result(*client) + if err != nil { + return fmt.Errorf("failure retrieving Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("cannot get Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", name, serviceName, appName, resourceGroup, err) + } + d.SetId(*resp.ID) + + return resourceArmSpringCloudDeploymentRead(d, meta) +} + +func resourceArmSpringCloudDeploymentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudDeploymentID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ServiceName, id.AppName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Spring Cloud Deployments %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("failure reading Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", id.Name, id.ServiceName, id.AppName, id.ResourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("service_name", id.ServiceName) + d.Set("app_name", id.AppName) + if deploymentSettings := resp.Properties.DeploymentSettings; deploymentSettings != nil { + d.Set("cpu", deploymentSettings.CPU) + d.Set("memory_in_gb", deploymentSettings.MemoryInGB) + d.Set("jvm_options", deploymentSettings.JvmOptions) + d.Set("instance_count", deploymentSettings.InstanceCount) + d.Set("runtime_version", deploymentSettings.RuntimeVersion) + if err := d.Set("env", flattenSpringCloudDeploymentEnv(deploymentSettings.EnvironmentVariables)); err != nil { + return fmt.Errorf("failure setting `env`: %+v", err) + } + } + + return nil +} + +func resourceArmSpringCloudDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + appsClient := meta.(*clients.Client).AppPlatform.AppsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudDeploymentID(d.Id()) + if err != nil { + return err + } + + appResp, err := appsClient.Get(ctx, id.ResourceGroup, id.ServiceName, id.AppName, "") + if err != nil { + return fmt.Errorf("failure getting Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", id.AppName, id.ServiceName, id.ResourceGroup, err) + } + + // active deployment can not be deleted, rest api will return err. + // so just return nil + if appResp.Properties != nil && appResp.Properties.ActiveDeploymentName != nil && *appResp.Properties.ActiveDeploymentName == id.Name { + log.Printf("active deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q) can not be deleted", id.Name, id.ServiceName, id.AppName, id.ResourceGroup) + return nil + } + + if _, err := client.Delete(ctx, id.ResourceGroup, id.ServiceName, id.AppName, id.Name); err != nil { + return fmt.Errorf("failure deleting Spring Cloud Deployment %q (Spring Cloud Service %q / App Name %q / Resource Group %q): %+v", id.Name, id.AppName, id.ServiceName, id.ResourceGroup, err) + } + + return nil +} + +// if users don't specify jar_file, there will be a default spring cloud hello world jar running +func expandSpringCloudDeploymentUserSourceInfo(meta interface{}, ctx context.Context, appsClient *appplatform.AppsClient, resourceGroup, serviceName, appName, jarFile string) (*appplatform.UserSourceInfo, error) { + userSourceInfo := appplatform.UserSourceInfo{ + Type: appplatform.Jar, + RelativePath: utils.String(""), + } + if len(jarFile) != 0 { + resourceUploadDefinition, err := appsClient.GetResourceUploadURL(ctx, resourceGroup, serviceName, appName) + if err != nil { + return nil, fmt.Errorf("failure getting uploading URL of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", appName, serviceName, resourceGroup, err) + } + + if err := uploadDeploymentJar(meta, ctx, resourceUploadDefinition, jarFile); err != nil { + return nil, fmt.Errorf("failure uploading file %s, %+v", jarFile, err) + } + + userSourceInfo.RelativePath = resourceUploadDefinition.RelativePath + } + return &userSourceInfo, nil +} + +func expandSpringCloudDeploymentEnv(input map[string]interface{}) map[string]*string { + output := map[string]*string{} + + if len(input) == 0 { + return output + } + + for k, v := range input { + output[k] = utils.String(v.(string)) + } + + return output +} + +func flattenSpringCloudDeploymentEnv(input map[string]*string) map[string]string { + output := make(map[string]string) + for k, v := range input { + output[k] = *v + } + + return output +} diff --git a/azurerm/internal/services/appplatform/spring_cloud_deployment_jar.go b/azurerm/internal/services/appplatform/spring_cloud_deployment_jar.go new file mode 100644 index 0000000000000..55a51c245adaf --- /dev/null +++ b/azurerm/internal/services/appplatform/spring_cloud_deployment_jar.go @@ -0,0 +1,51 @@ +package appplatform + +import ( + "context" + "fmt" + "log" + "net/url" + "os" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files" +) + +func uploadDeploymentJar(meta interface{}, ctx context.Context, resourceUploadDefinition appplatform.ResourceUploadDefinition, jarFile string) error { + u, err := url.Parse(*resourceUploadDefinition.UploadURL) + if err != nil { + return fmt.Errorf("failure parsing uploading URL %s: %+v", *resourceUploadDefinition.UploadURL, err) + } + accountName := strings.Split(u.Host, ".")[0] + shareName := strings.Split(u.Path, "/")[1] + sasToken := u.RawQuery + + index := strings.LastIndex(*resourceUploadDefinition.RelativePath, "/") + path := (*resourceUploadDefinition.RelativePath)[:index] + filename := (*resourceUploadDefinition.RelativePath)[index+1:] + + filesClient := meta.(*clients.Client).Storage.FileFilesClientWithSASToken(sasToken) + + file, err := os.Open(jarFile) + if err != nil { + return fmt.Errorf("failure opening: %+v", err) + } + + log.Printf("[DEBUG] Creating Top Level File... accountName: %s shareName: %s, path: %s, filename: %s", accountName, shareName, path, filename) + info, err := file.Stat() + if err != nil { + return fmt.Errorf("failure 'stat'-ing: %+v", err) + } + createFileInput := files.CreateInput{ + ContentLength: info.Size(), + } + if _, err := filesClient.Create(ctx, accountName, shareName, path, filename, createFileInput); err != nil { + return fmt.Errorf("failure creating Top-Level File: %+v", err) + } + if err := filesClient.PutFile(ctx, accountName, shareName, path, filename, file, 4); err != nil { + return fmt.Errorf("failure uploading file: %+v", err) + } + return nil +} diff --git a/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_active_deployment_test.go b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_active_deployment_test.go new file mode 100644 index 0000000000000..c2a9dc028b825 --- /dev/null +++ b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_active_deployment_test.go @@ -0,0 +1,220 @@ +package tests + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSpringCloudActiveDeployment_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_active_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudActiveDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSpringCloudActiveDeployment_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_active_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudActiveDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMSpringCloudActiveDeployment_requiresImport), + }, + }) +} + +func TestAccAzureRMSpringCloudActiveDeployment_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_active_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudActiveDeployment_complete(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSpringCloudActiveDeployment_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_active_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudActiveDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSpringCloudActiveDeployment_complete(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSpringCloudActiveDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMSpringCloudActiveDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMSpringCloudActiveDeploymentExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Spring Cloud Active Deployment not found: %s", resourceName) + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serviceName := rs.Primary.Attributes["service_name"] + appName := rs.Primary.Attributes["app_name"] + deploymentName := rs.Primary.Attributes["deployment_name"] + + client := acceptance.AzureProvider.Meta().(*clients.Client).AppPlatform.AppsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + resp, err := client.Get(ctx, resourceGroup, serviceName, appName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Spring Cloud App %q (Spring Cloud Name %q / Resource Group %q) does not exist", appName, serviceName, resourceGroup) + } + return fmt.Errorf("Bad: Get on AppPlatform.AppsClient: %+v", err) + } + if resp.Properties == nil || resp.Properties.ActiveDeploymentName == nil || + *resp.Properties.ActiveDeploymentName != deploymentName { + return fmt.Errorf("Active deployment is not current deployment %q", deploymentName) + } + + return nil + } +} + +func testAccAzureRMSpringCloudActiveDeployment_basic(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudActiveDeployment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_active_deployment" "test" { + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name + deployment_name = azurerm_spring_cloud_deployment.test.name +} +`, template) +} + +func testAccAzureRMSpringCloudActiveDeployment_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudActiveDeployment_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_active_deployment" "import" { + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name + deployment_name = azurerm_spring_cloud_deployment.test.name +} +`, template) +} + +func testAccAzureRMSpringCloudActiveDeployment_complete(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudActiveDeployment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_active_deployment" "test" { + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name + deployment_name = azurerm_spring_cloud_deployment.test.name + + temporary_disk { + mount_path = "/tmp" + size_in_gb = 3 + } + + persistent_disk { + mount_path = "/persistent" + size_in_gb = 50 + } + + public = true +} +`, template) +} + +func testAccAzureRMSpringCloudActiveDeployment_template(data acceptance.TestData) string { + lowerRandomString := strings.ToLower(data.RandomString) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-spring-%d" + location = "%s" +} + +resource "azurerm_spring_cloud_service" "test" { + name = "acctest-sc-%s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_spring_cloud_app" "test" { + name = "acctest-sca-%s" + resource_group_name = azurerm_spring_cloud_service.test.resource_group_name + service_name = azurerm_spring_cloud_service.test.name +} + +resource "azurerm_spring_cloud_deployment" "test" { + name = "acctest-scd-%s" + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name +} +`, data.RandomInteger, data.Locations.Primary, lowerRandomString, lowerRandomString, lowerRandomString) +} diff --git a/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_app_test.go b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_app_test.go new file mode 100644 index 0000000000000..178114c2e8242 --- /dev/null +++ b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_app_test.go @@ -0,0 +1,145 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSpringCloudApp_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudApp_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSpringCloudApp_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudApp_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMSpringCloudApp_requiresImport), + }, + }) +} + +func testCheckAzureRMSpringCloudAppExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Spring Cloud App not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serviceName := rs.Primary.Attributes["service_name"] + + client := acceptance.AzureProvider.Meta().(*clients.Client).AppPlatform.AppsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + if resp, err := client.Get(ctx, resourceGroup, serviceName, name, ""); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Spring Cloud App %q (Spring Cloud Name %q / Resource Group %q) does not exist", name, serviceName, resourceGroup) + } + return fmt.Errorf("Bad: Get on AppPlatform.AppsClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMSpringCloudAppDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).AppPlatform.AppsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_spring_cloud_app" { + continue + } + + name := rs.Primary.Attributes["name"] + resGroup := rs.Primary.Attributes["resource_group_name"] + serviceName := rs.Primary.Attributes["service_name"] + + if resp, err := client.Get(ctx, resGroup, serviceName, name, ""); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on AppPlatform.AppsClient: %+v", err) + } + } + + return nil + } + + return nil +} + +func testAccAzureRMSpringCloudApp_basic(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudApp_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_app" "test" { + name = "acctest-sca-%d" + resource_group_name = azurerm_spring_cloud_service.test.resource_group_name + service_name = azurerm_spring_cloud_service.test.name +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSpringCloudApp_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudApp_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_app" "import" { + name = azurerm_spring_cloud_app.test.name + resource_group_name = azurerm_spring_cloud_app.test.resource_group_name + service_name = azurerm_spring_cloud_app.test.service_name +} +`, template) +} + +func testAccAzureRMSpringCloudApp_template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-spring-%d" + location = "%s" +} + +resource "azurerm_spring_cloud_service" "test" { + name = "acctest-sc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_deployment_test.go b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_deployment_test.go new file mode 100644 index 0000000000000..2f56799754e24 --- /dev/null +++ b/azurerm/internal/services/appplatform/tests/resource_arm_spring_cloud_deployment_test.go @@ -0,0 +1,236 @@ +package tests + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSpringCloudDeployment_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSpringCloudDeployment_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMSpringCloudDeployment_requiresImport), + }, + }) +} + +func TestAccAzureRMSpringCloudDeployment_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudDeployment_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep("jar_file", "jar_file_md5"), + }, + }) +} + +func TestAccAzureRMSpringCloudDeployment_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_deployment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSpringCloudDeployment_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep("jar_file", "jar_file_md5"), + { + Config: testAccAzureRMSpringCloudDeployment_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudDeploymentExists(data.ResourceName), + ), + }, + data.ImportStep("jar_file", "jar_file_md5"), + }, + }) +} + +func testCheckAzureRMSpringCloudDeploymentExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Spring Cloud Deployment not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serviceName := rs.Primary.Attributes["service_name"] + appName := rs.Primary.Attributes["app_name"] + + client := acceptance.AzureProvider.Meta().(*clients.Client).AppPlatform.DeploymentsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + if resp, err := client.Get(ctx, resourceGroup, serviceName, appName, name); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Spring Cloud Deployment %q (Spring Cloud Name %q / Spring Cloud App %q / Resource Group %q) does not exist", name, serviceName, appName, resourceGroup) + } + return fmt.Errorf("Bad: Get on AppPlatform.DeploymentsClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMSpringCloudDeploymentDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).AppPlatform.DeploymentsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_spring_cloud_deployment" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serviceName := rs.Primary.Attributes["service_name"] + appName := rs.Primary.Attributes["app_name"] + + if resp, err := client.Get(ctx, resourceGroup, serviceName, appName, name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on AppPlatform.DeploymentsClient: %+v", err) + } + } + + return nil + } + + return nil +} + +func testAccAzureRMSpringCloudDeployment_basic(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudDeployment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_deployment" "test" { + name = "acctest-scd-%d" + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSpringCloudDeployment_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudDeployment_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_deployment" "import" { + name = "acctest-scd-%d" + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSpringCloudDeployment_complete(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudDeployment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_deployment" "test" { + name = "acctest-scd-%d" + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name + app_name = azurerm_spring_cloud_app.test.name + + cpu = 2 + memory_in_gb = 2 + instance_count = 2 + runtime_version = "Java_11" + jar_file = "testdata/gateway.jar" + jar_file_md5 = filemd5("testdata/gateway.jar") + jvm_options = "-Xms1G -Xmx1G" + + env = { + name1 = "value1" + name2 = "value2" + } +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSpringCloudDeployment_template(data acceptance.TestData) string { + lowerRandomString := strings.ToLower(data.RandomString) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-spring-%d" + location = "%s" +} + +resource "azurerm_spring_cloud_service" "test" { + name = "acctest-sc-%s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_spring_cloud_app" "test" { + name = "acctest-sca-%s" + resource_group_name = azurerm_resource_group.test.name + service_name = azurerm_spring_cloud_service.test.name +} +`, data.RandomInteger, data.Locations.Primary, lowerRandomString, lowerRandomString) +} diff --git a/azurerm/internal/services/appplatform/validate/spring_cloud_app.go b/azurerm/internal/services/appplatform/validate/spring_cloud_app.go new file mode 100644 index 0000000000000..6b184d051513e --- /dev/null +++ b/azurerm/internal/services/appplatform/validate/spring_cloud_app.go @@ -0,0 +1,25 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func SpringCloudAppName(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, append(errors, fmt.Errorf("expected type of %s to be string", k)) + } + + // The name attribute rules are : + // 1. can contain only lowercase letters, numbers and hyphens. + // 2. The first character must be a letter. + // 3. The last character must be a letter or number + // 3. The value must be between 4 and 32 characters long + + if !regexp.MustCompile(`^([a-z])([a-z\d-]{2,30})([a-z\d])$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%s must begin with a letter, end with a letter or number, contain only lowercase letters, numbers and hyphens. The value must be between 4 and 32 characters long.", k)) + } + + return nil, errors +} diff --git a/azurerm/internal/services/appplatform/validate/spring_cloud_deployment.go b/azurerm/internal/services/appplatform/validate/spring_cloud_deployment.go new file mode 100644 index 0000000000000..e3e72e546f9d4 --- /dev/null +++ b/azurerm/internal/services/appplatform/validate/spring_cloud_deployment.go @@ -0,0 +1,40 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func SpringCloudDeploymentName(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, append(errors, fmt.Errorf("expected type of %s to be string", k)) + } + + // The name attribute rules are : + // 1. can contain only lowercase letters, numbers and hyphens. + // 2. The first character must be a letter. + // 3. The last character must be a letter or number + // 3. The value must be between 4 and 32 characters long + + if !regexp.MustCompile(`^([a-z])([a-z\d-]{2,30})([a-z\d])$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%s must begin with a letter, end with a letter or number, contain only lowercase letters, numbers and hyphens. The value must be between 4 and 32 characters long.", k)) + } + + return nil, errors +} + +func MountPath(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, append(errors, fmt.Errorf("expected type of %s to be string", k)) + } + if len(v) <= 2 || len(v) >= 255 { + errors = append(errors, fmt.Errorf("%s should great than 2 and less than 255", k)) + } + + if !regexp.MustCompile(`^(?:\/(?:[a-zA-Z][a-zA-Z0-9]*))+$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%s is not valid, must match the regular expression ^(?:\\/(?:[a-zA-Z][a-zA-Z0-9]*))+$", k)) + } + return nil, errors +} diff --git a/azurerm/internal/services/storage/client/client.go b/azurerm/internal/services/storage/client/client.go index 25a2e40f2e709..0e9a73ee96287 100644 --- a/azurerm/internal/services/storage/client/client.go +++ b/azurerm/internal/services/storage/client/client.go @@ -8,12 +8,14 @@ import ( "github.com/Azure/azure-sdk-for-go/services/storagecache/mgmt/2019-11-01/storagecache" "github.com/Azure/go-autorest/autorest" az "github.com/Azure/go-autorest/autorest/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/authorizers" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/accounts" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/datalakestore/filesystems" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/directories" + "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/queue/queues" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/entities" @@ -156,6 +158,13 @@ func (client Client) FileShareDirectoriesClient(ctx context.Context, account acc return &directoriesClient, nil } +func (client Client) FileFilesClientWithSASToken(sasToken string) *files.Client { + fileClient := files.NewWithEnvironment(client.environment) + sasAuth := authorizers.NewSharedAccessSignatureAuthorizer(sasToken) + fileClient.Client.Authorizer = sasAuth + return &fileClient +} + func (client Client) FileSharesClient(ctx context.Context, account accountDetails) (*shares.Client, error) { // NOTE: Files do not support AzureAD Authentication diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/README.md b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/README.md new file mode 100644 index 0000000000000..19b7af7e0f04f --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/README.md @@ -0,0 +1,43 @@ +## File Storage Files SDK for API version 2018-11-09 + +This package allows you to interact with the Files File Storage API + +### Supported Authorizers + +* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`) +* SharedKeyLite (Blob, File & Queue) + +### Example Usage + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files" +) + +func Example() error { + accountName := "storageaccount1" + storageAccountKey := "ABC123...." + shareName := "myshare" + directoryName := "myfiles" + fileName := "example.txt" + + storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey) + filesClient := files.New() + filesClient.Client.Authorizer = storageAuth + + ctx := context.TODO() + input := files.CreateInput{} + if _, err := filesClient.Create(ctx, accountName, shareName, directoryName, fileName, input); err != nil { + return fmt.Errorf("Error creating File: %s", err) + } + + return nil +} +``` \ No newline at end of file diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/client.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/client.go new file mode 100644 index 0000000000000..ecca81586b5a7 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/client.go @@ -0,0 +1,25 @@ +package files + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +// Client is the base client for File Storage Shares. +type Client struct { + autorest.Client + BaseURI string +} + +// New creates an instance of the Client client. +func New() Client { + return NewWithEnvironment(azure.PublicCloud) +} + +// NewWithEnvironment creates an instance of the Client client. +func NewWithEnvironment(environment azure.Environment) Client { + return Client{ + Client: autorest.NewClientWithUserAgent(UserAgent()), + BaseURI: environment.StorageEndpointSuffix, + } +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy.go new file mode 100644 index 0000000000000..31768b3d52b7f --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy.go @@ -0,0 +1,132 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" + "github.com/tombuildsstuff/giovanni/storage/internal/metadata" +) + +type CopyInput struct { + // Specifies the URL of the source file or blob, up to 2 KB in length. + // + // To copy a file to another file within the same storage account, you may use Shared Key to authenticate + // the source file. If you are copying a file from another storage account, or if you are copying a blob from + // the same storage account or another storage account, then you must authenticate the source file or blob using a + // shared access signature. If the source is a public blob, no authentication is required to perform the copy + // operation. A file in a share snapshot can also be specified as a copy source. + CopySource string + + MetaData map[string]string +} + +type CopyResult struct { + autorest.Response + + // The CopyID, which can be passed to AbortCopy to abort the copy. + CopyID string + + // Either `success` or `pending` + CopySuccess string +} + +// Copy copies a blob or file to a destination file within the storage account asynchronously. +func (client Client) Copy(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (result CopyResult, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "Copy", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "Copy", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "Copy", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "Copy", "`fileName` cannot be an empty string.") + } + if input.CopySource == "" { + return result, validation.NewError("files.Client", "Copy", "`input.CopySource` cannot be an empty string.") + } + if err := metadata.Validate(input.MetaData); err != nil { + return result, validation.NewError("files.Client", "Copy", fmt.Sprintf("`input.MetaData` is not valid: %s.", err)) + } + + req, err := client.CopyPreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Copy", nil, "Failure preparing request") + return + } + + resp, err := client.CopySender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure sending request") + return + } + + result, err = client.CopyResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure responding to request") + return + } + + return +} + +// CopyPreparer prepares the Copy request. +func (client Client) CopyPreparer(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-copy-source": input.CopySource, + } + + headers = metadata.SetIntoHeaders(headers, input.MetaData) + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// CopySender sends the Copy request. The method will close the +// http.Response Body if it receives an error. +func (client Client) CopySender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// CopyResponder handles the response to the Copy request. The method always +// closes the http.Response Body. +func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) { + if resp != nil && resp.Header != nil { + result.CopyID = resp.Header.Get("x-ms-copy-id") + result.CopySuccess = resp.Header.Get("x-ms-copy-status") + } + + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusAccepted), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_abort.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_abort.go new file mode 100644 index 0000000000000..2f0913185888b --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_abort.go @@ -0,0 +1,104 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +// AbortCopy aborts a pending Copy File operation, and leaves a destination file with zero length and full metadata +func (client Client) AbortCopy(ctx context.Context, accountName, shareName, path, fileName, copyID string) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "AbortCopy", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "AbortCopy", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "AbortCopy", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "AbortCopy", "`fileName` cannot be an empty string.") + } + if copyID == "" { + return result, validation.NewError("files.Client", "AbortCopy", "`copyID` cannot be an empty string.") + } + + req, err := client.AbortCopyPreparer(ctx, accountName, shareName, path, fileName, copyID) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", nil, "Failure preparing request") + return + } + + resp, err := client.AbortCopySender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure sending request") + return + } + + result, err = client.AbortCopyResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure responding to request") + return + } + + return +} + +// AbortCopyPreparer prepares the AbortCopy request. +func (client Client) AbortCopyPreparer(ctx context.Context, accountName, shareName, path, fileName, copyID string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "copy"), + "copyid": autorest.Encode("query", copyID), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-copy-action": "abort", + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithQueryParameters(queryParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// AbortCopySender sends the AbortCopy request. The method will close the +// http.Response Body if it receives an error. +func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// AbortCopyResponder handles the response to the AbortCopy request. The method always +// closes the http.Response Body. +func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusNoContent), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_wait.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_wait.go new file mode 100644 index 0000000000000..e6a646b1017be --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/copy_wait.go @@ -0,0 +1,55 @@ +package files + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/Azure/go-autorest/autorest" +) + +type CopyAndWaitResult struct { + autorest.Response + + CopyID string +} + +const DefaultCopyPollDuration = 15 * time.Second + +// CopyAndWait is a convenience method which doesn't exist in the API, which copies the file and then waits for the copy to complete +func (client Client) CopyAndWait(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput, pollDuration time.Duration) (result CopyResult, err error) { + copy, e := client.Copy(ctx, accountName, shareName, path, fileName, input) + if err != nil { + result.Response = copy.Response + err = fmt.Errorf("Error copying: %s", e) + return + } + + result.CopyID = copy.CopyID + + // since the API doesn't return a LRO, this is a hack which also polls every 10s, but should be sufficient + for true { + props, e := client.GetProperties(ctx, accountName, shareName, path, fileName) + if e != nil { + result.Response = copy.Response + err = fmt.Errorf("Error waiting for copy: %s", e) + return + } + + switch strings.ToLower(props.CopyStatus) { + case "pending": + time.Sleep(pollDuration) + continue + + case "success": + return + + default: + err = fmt.Errorf("Unexpected CopyState %q", e) + return + } + } + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/create.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/create.go new file mode 100644 index 0000000000000..85d4b0b21271e --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/create.go @@ -0,0 +1,146 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" + "github.com/tombuildsstuff/giovanni/storage/internal/metadata" +) + +type CreateInput struct { + // This header specifies the maximum size for the file, up to 1 TiB. + ContentLength int64 + + // The MIME content type of the file + // If not specified, the default type is application/octet-stream. + ContentType *string + + // Specifies which content encodings have been applied to the file. + // This value is returned to the client when the Get File operation is performed + // on the file resource and can be used to decode file content. + ContentEncoding *string + + // Specifies the natural languages used by this resource. + ContentLanguage *string + + // The File service stores this value but does not use or modify it. + CacheControl *string + + // Sets the file's MD5 hash. + ContentMD5 *string + + // Sets the file’s Content-Disposition header. + ContentDisposition *string + + MetaData map[string]string +} + +// Create creates a new file or replaces a file. +func (client Client) Create(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "Create", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "Create", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "Create", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "Create", "`fileName` cannot be an empty string.") + } + if err := metadata.Validate(input.MetaData); err != nil { + return result, validation.NewError("files.Client", "Create", "`input.MetaData` cannot be an empty string.") + } + + req, err := client.CreatePreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Create", nil, "Failure preparing request") + return + } + + resp, err := client.CreateSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure sending request") + return + } + + result, err = client.CreateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure responding to request") + return + } + + return +} + +// CreatePreparer prepares the Create request. +func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-content-length": input.ContentLength, + "x-ms-type": "file", + } + + if input.ContentDisposition != nil { + headers["x-ms-content-disposition"] = *input.ContentDisposition + } + + if input.ContentEncoding != nil { + headers["x-ms-content-encoding"] = *input.ContentEncoding + } + + if input.ContentMD5 != nil { + headers["x-ms-content-md5"] = *input.ContentMD5 + } + + if input.ContentType != nil { + headers["x-ms-content-type"] = *input.ContentType + } + + headers = metadata.SetIntoHeaders(headers, input.MetaData) + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// CreateSender sends the Create request. The method will close the +// http.Response Body if it receives an error. +func (client Client) CreateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// CreateResponder handles the response to the Create request. The method always +// closes the http.Response Body. +func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusCreated), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/delete.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/delete.go new file mode 100644 index 0000000000000..5debd767d1fdb --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/delete.go @@ -0,0 +1,94 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +// Delete immediately deletes the file from the File Share. +func (client Client) Delete(ctx context.Context, accountName, shareName, path, fileName string) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "Delete", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "Delete", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "Delete", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "Delete", "`fileName` cannot be an empty string.") + } + + req, err := client.DeletePreparer(ctx, accountName, shareName, path, fileName) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Delete", nil, "Failure preparing request") + return + } + + resp, err := client.DeleteSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure sending request") + return + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure responding to request") + return + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsDelete(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client Client) DeleteSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusAccepted), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_get.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_get.go new file mode 100644 index 0000000000000..fd62f90aec8fc --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_get.go @@ -0,0 +1,111 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" + "github.com/tombuildsstuff/giovanni/storage/internal/metadata" +) + +type GetMetaDataResult struct { + autorest.Response + + MetaData map[string]string +} + +// GetMetaData returns the MetaData for the specified File. +func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path, fileName string) (result GetMetaDataResult, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "GetMetaData", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "GetMetaData", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "GetMetaData", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "GetMetaData", "`fileName` cannot be an empty string.") + } + + req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path, fileName) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", nil, "Failure preparing request") + return + } + + resp, err := client.GetMetaDataSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure sending request") + return + } + + result, err = client.GetMetaDataResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure responding to request") + return + } + + return +} + +// GetMetaDataPreparer prepares the GetMetaData request. +func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "metadata"), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithQueryParameters(queryParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetMetaDataSender sends the GetMetaData request. The method will close the +// http.Response Body if it receives an error. +func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// GetMetaDataResponder handles the response to the GetMetaData request. The method always +// closes the http.Response Body. +func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) { + if resp != nil && resp.Header != nil { + result.MetaData = metadata.ParseFromHeaders(resp.Header) + } + + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + //metadata.ByParsingFromHeaders(&result.MetaData), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_set.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_set.go new file mode 100644 index 0000000000000..41e3ffcb8ff9c --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/metadata_set.go @@ -0,0 +1,105 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" + "github.com/tombuildsstuff/giovanni/storage/internal/metadata" +) + +// SetMetaData updates the specified File to have the specified MetaData. +func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "SetMetaData", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "SetMetaData", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "SetMetaData", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "SetMetaData", "`fileName` cannot be an empty string.") + } + if err := metadata.Validate(metaData); err != nil { + return result, validation.NewError("files.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err)) + } + + req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, fileName, metaData) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", nil, "Failure preparing request") + return + } + + resp, err := client.SetMetaDataSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure sending request") + return + } + + result, err = client.SetMetaDataResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure responding to request") + return + } + + return +} + +// SetMetaDataPreparer prepares the SetMetaData request. +func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "metadata"), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + } + + headers = metadata.SetIntoHeaders(headers, metaData) + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithQueryParameters(queryParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// SetMetaDataSender sends the SetMetaData request. The method will close the +// http.Response Body if it receives an error. +func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// SetMetaDataResponder handles the response to the SetMetaData request. The method always +// closes the http.Response Body. +func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_get.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_get.go new file mode 100644 index 0000000000000..c6a0c399d2d75 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_get.go @@ -0,0 +1,144 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" + "github.com/tombuildsstuff/giovanni/storage/internal/metadata" +) + +type GetResult struct { + autorest.Response + + CacheControl string + ContentDisposition string + ContentEncoding string + ContentLanguage string + ContentLength *int64 + ContentMD5 string + ContentType string + CopyID string + CopyStatus string + CopySource string + CopyProgress string + CopyStatusDescription string + CopyCompletionTime string + Encrypted bool + + MetaData map[string]string +} + +// GetProperties returns the Properties for the specified file +func (client Client) GetProperties(ctx context.Context, accountName, shareName, path, fileName string) (result GetResult, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "GetProperties", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "GetProperties", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "GetProperties", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "GetProperties", "`fileName` cannot be an empty string.") + } + + req, err := client.GetPropertiesPreparer(ctx, accountName, shareName, path, fileName) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", nil, "Failure preparing request") + return + } + + resp, err := client.GetPropertiesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure sending request") + return + } + + result, err = client.GetPropertiesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure responding to request") + return + } + + return +} + +// GetPropertiesPreparer prepares the GetProperties request. +func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsHead(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetPropertiesSender sends the GetProperties request. The method will close the +// http.Response Body if it receives an error. +func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// GetPropertiesResponder handles the response to the GetProperties request. The method always +// closes the http.Response Body. +func (client Client) GetPropertiesResponder(resp *http.Response) (result GetResult, err error) { + if resp != nil && resp.Header != nil { + result.CacheControl = resp.Header.Get("Cache-Control") + result.ContentDisposition = resp.Header.Get("Content-Disposition") + result.ContentEncoding = resp.Header.Get("Content-Encoding") + result.ContentLanguage = resp.Header.Get("Content-Language") + result.ContentMD5 = resp.Header.Get("x-ms-content-md5") + result.ContentType = resp.Header.Get("Content-Type") + result.CopyID = resp.Header.Get("x-ms-copy-id") + result.CopyProgress = resp.Header.Get("x-ms-copy-progress") + result.CopySource = resp.Header.Get("x-ms-copy-source") + result.CopyStatus = resp.Header.Get("x-ms-copy-status") + result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description") + result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time") + result.Encrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true") + result.MetaData = metadata.ParseFromHeaders(resp.Header) + + contentLengthRaw := resp.Header.Get("Content-Length") + if contentLengthRaw != "" { + contentLength, err := strconv.Atoi(contentLengthRaw) + if err != nil { + return result, fmt.Errorf("Error parsing %q for Content-Length as an integer: %s", contentLengthRaw, err) + } + contentLengthI64 := int64(contentLength) + result.ContentLength = &contentLengthI64 + } + } + + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_set.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_set.go new file mode 100644 index 0000000000000..79fffc2b23c0d --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/properties_set.go @@ -0,0 +1,160 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +type SetPropertiesInput struct { + // Resizes a file to the specified size. + // If the specified byte value is less than the current size of the file, + // then all ranges above the specified byte value are cleared. + ContentLength *int64 + + // Modifies the cache control string for the file. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentControl *string + + // Sets the file’s Content-Disposition header. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentDisposition *string + + // Sets the file's content encoding. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentEncoding *string + + // Sets the file's content language. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentLanguage *string + + // Sets the file's MD5 hash. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentMD5 *string + + // Sets the file's content type. + // If this property is not specified on the request, then the property will be cleared for the file. + // Subsequent calls to Get File Properties will not return this property, + // unless it is explicitly set on the file again. + ContentType *string +} + +// SetProperties sets the specified properties on the specified File +func (client Client) SetProperties(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "SetProperties", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "SetProperties", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "SetProperties", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "SetProperties", "`fileName` cannot be an empty string.") + } + + req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", nil, "Failure preparing request") + return + } + + resp, err := client.SetPropertiesSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure sending request") + return + } + + result, err = client.SetPropertiesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure responding to request") + return + } + + return +} + +// SetPropertiesPreparer prepares the SetProperties request. +func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-type": "file", + } + + if input.ContentControl != nil { + headers["x-ms-cache-control"] = *input.ContentControl + } + if input.ContentDisposition != nil { + headers["x-ms-content-disposition"] = *input.ContentDisposition + } + if input.ContentEncoding != nil { + headers["x-ms-content-encoding"] = *input.ContentEncoding + } + if input.ContentLanguage != nil { + headers["x-ms-content-language"] = *input.ContentLanguage + } + if input.ContentLength != nil { + headers["x-ms-content-length"] = *input.ContentLength + } + if input.ContentMD5 != nil { + headers["x-ms-content-md5"] = *input.ContentMD5 + } + if input.ContentType != nil { + headers["x-ms-content-type"] = *input.ContentType + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// SetPropertiesSender sends the SetProperties request. The method will close the +// http.Response Body if it receives an error. +func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// SetPropertiesResponder handles the response to the SetProperties request. The method always +// closes the http.Response Body. +func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusCreated), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_clear.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_clear.go new file mode 100644 index 0000000000000..5d8145fae4229 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_clear.go @@ -0,0 +1,112 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +type ClearByteRangeInput struct { + StartBytes int64 + EndBytes int64 +} + +// ClearByteRange clears the specified Byte Range from within the specified File +func (client Client) ClearByteRange(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "ClearByteRange", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "ClearByteRange", "`fileName` cannot be an empty string.") + } + if input.StartBytes < 0 { + return result, validation.NewError("files.Client", "ClearByteRange", "`input.StartBytes` must be greater or equal to 0.") + } + if input.EndBytes <= 0 { + return result, validation.NewError("files.Client", "ClearByteRange", "`input.EndBytes` must be greater than 0.") + } + + req, err := client.ClearByteRangePreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", nil, "Failure preparing request") + return + } + + resp, err := client.ClearByteRangeSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure sending request") + return + } + + result, err = client.ClearByteRangeResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure responding to request") + return + } + + return +} + +// ClearByteRangePreparer prepares the ClearByteRange request. +func (client Client) ClearByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "range"), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-write": "clear", + "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes), + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ClearByteRangeSender sends the ClearByteRange request. The method will close the +// http.Response Body if it receives an error. +func (client Client) ClearByteRangeSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// ClearByteRangeResponder handles the response to the ClearByteRange request. The method always +// closes the http.Response Body. +func (client Client) ClearByteRangeResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusCreated), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get.go new file mode 100644 index 0000000000000..733d3f525105f --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get.go @@ -0,0 +1,121 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +type GetByteRangeInput struct { + StartBytes int64 + EndBytes int64 +} + +type GetByteRangeResult struct { + autorest.Response + + Contents []byte +} + +// GetByteRange returns the specified Byte Range from the specified File. +func (client Client) GetByteRange(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (result GetByteRangeResult, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "GetByteRange", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "GetByteRange", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "GetByteRange", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "GetByteRange", "`fileName` cannot be an empty string.") + } + if input.StartBytes < 0 { + return result, validation.NewError("files.Client", "GetByteRange", "`input.StartBytes` must be greater or equal to 0.") + } + if input.EndBytes <= 0 { + return result, validation.NewError("files.Client", "GetByteRange", "`input.EndBytes` must be greater than 0.") + } + expectedBytes := input.EndBytes - input.StartBytes + if expectedBytes < (4 * 1024) { + return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at least 4KB.") + } + if expectedBytes > (4 * 1024 * 1024) { + return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at most 4MB.") + } + + req, err := client.GetByteRangePreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", nil, "Failure preparing request") + return + } + + resp, err := client.GetByteRangeSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure sending request") + return + } + + result, err = client.GetByteRangeResponder(resp, expectedBytes) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure responding to request") + return + } + + return +} + +// GetByteRangePreparer prepares the GetByteRange request. +func (client Client) GetByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1), + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetByteRangeSender sends the GetByteRange request. The method will close the +// http.Response Body if it receives an error. +func (client Client) GetByteRangeSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// GetByteRangeResponder handles the response to the GetByteRange request. The method always +// closes the http.Response Body. +func (client Client) GetByteRangeResponder(resp *http.Response, length int64) (result GetByteRangeResult, err error) { + result.Contents = make([]byte, length) + + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent), + autorest.ByUnmarshallingBytes(&result.Contents), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get_file.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get_file.go new file mode 100644 index 0000000000000..9e5be17f85fc1 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_get_file.go @@ -0,0 +1,128 @@ +package files + +import ( + "context" + "fmt" + "log" + "math" + "runtime" + "sync" + + "github.com/Azure/go-autorest/autorest" +) + +// GetFile is a helper method to download a file by chunking it automatically +func (client Client) GetFile(ctx context.Context, accountName, shareName, path, fileName string, parallelism int) (result autorest.Response, outputBytes []byte, err error) { + + // first look up the file and check out how many bytes it is + file, e := client.GetProperties(ctx, accountName, shareName, path, fileName) + if err != nil { + result = file.Response + err = e + return + } + + if file.ContentLength == nil { + err = fmt.Errorf("Content-Length was nil!") + return + } + + length := int64(*file.ContentLength) + chunkSize := int64(4 * 1024 * 1024) // 4MB + + if chunkSize > length { + chunkSize = length + } + + // then split that up into chunks and retrieve it retrieve it into the 'results' set + chunks := int(math.Ceil(float64(length) / float64(chunkSize))) + workerCount := parallelism * runtime.NumCPU() + if workerCount > chunks { + workerCount = chunks + } + + var waitGroup sync.WaitGroup + waitGroup.Add(workerCount) + + results := make([]*downloadFileChunkResult, chunks) + errors := make(chan error, chunkSize) + + for i := 0; i < chunks; i++ { + go func(i int) { + log.Printf("[DEBUG] Downloading Chunk %d of %d", i+1, chunks) + + dfci := downloadFileChunkInput{ + thisChunk: i, + chunkSize: chunkSize, + fileSize: length, + } + + result, err := client.downloadFileChunk(ctx, accountName, shareName, path, fileName, dfci) + if err != nil { + errors <- err + waitGroup.Done() + return + } + + // if there's no error, we should have bytes, so this is safe + results[i] = result + + waitGroup.Done() + }(i) + } + waitGroup.Wait() + + // TODO: we should switch to hashicorp/multi-error here + if len(errors) > 0 { + err = fmt.Errorf("Error downloading file: %s", <-errors) + return + } + + // then finally put it all together, in order and return it + output := make([]byte, length) + for _, v := range results { + copy(output[v.startBytes:v.endBytes], v.bytes) + } + + outputBytes = output + return +} + +type downloadFileChunkInput struct { + thisChunk int + chunkSize int64 + fileSize int64 +} + +type downloadFileChunkResult struct { + startBytes int64 + endBytes int64 + bytes []byte +} + +func (client Client) downloadFileChunk(ctx context.Context, accountName, shareName, path, fileName string, input downloadFileChunkInput) (*downloadFileChunkResult, error) { + startBytes := input.chunkSize * int64(input.thisChunk) + endBytes := startBytes + input.chunkSize + + // the last chunk may exceed the size of the file + remaining := input.fileSize - startBytes + if input.chunkSize > remaining { + endBytes = startBytes + remaining + } + + getInput := GetByteRangeInput{ + StartBytes: startBytes, + EndBytes: endBytes, + } + result, err := client.GetByteRange(ctx, accountName, shareName, path, fileName, getInput) + if err != nil { + return nil, fmt.Errorf("Error putting bytes: %s", err) + } + + output := downloadFileChunkResult{ + startBytes: startBytes, + endBytes: endBytes, + bytes: result.Contents, + } + return &output, nil +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put.go new file mode 100644 index 0000000000000..208becc34bd1e --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put.go @@ -0,0 +1,130 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +type PutByteRangeInput struct { + StartBytes int64 + EndBytes int64 + + // Content is the File Contents for the specified range + // which can be at most 4MB + Content []byte +} + +// PutByteRange puts the specified Byte Range in the specified File. +func (client Client) PutByteRange(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (result autorest.Response, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "PutByteRange", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "PutByteRange", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "PutByteRange", "`shareName` must be a lower-cased string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "PutByteRange", "`fileName` cannot be an empty string.") + } + if input.StartBytes < 0 { + return result, validation.NewError("files.Client", "PutByteRange", "`input.StartBytes` must be greater or equal to 0.") + } + if input.EndBytes <= 0 { + return result, validation.NewError("files.Client", "PutByteRange", "`input.EndBytes` must be greater than 0.") + } + + expectedBytes := input.EndBytes - input.StartBytes + actualBytes := len(input.Content) + if expectedBytes != int64(actualBytes) { + return result, validation.NewError("files.Client", "PutByteRange", fmt.Sprintf("The specified byte-range (%d) didn't match the content size (%d).", expectedBytes, actualBytes)) + } + if expectedBytes < (4 * 1024) { + return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at least 4KB.") + } + + if expectedBytes > (4 * 1024 * 1024) { + return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at most 4MB.") + } + + req, err := client.PutByteRangePreparer(ctx, accountName, shareName, path, fileName, input) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", nil, "Failure preparing request") + return + } + + resp, err := client.PutByteRangeSender(req) + if err != nil { + result = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure sending request") + return + } + + result, err = client.PutByteRangeResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure responding to request") + return + } + + return +} + +// PutByteRangePreparer prepares the PutByteRange request. +func (client Client) PutByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "range"), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + "x-ms-write": "update", + "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1), + "Content-Length": int(len(input.Content)), + } + + preparer := autorest.CreatePreparer( + autorest.AsPut(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers), + autorest.WithQueryParameters(queryParameters), + autorest.WithBytes(&input.Content)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// PutByteRangeSender sends the PutByteRange request. The method will close the +// http.Response Body if it receives an error. +func (client Client) PutByteRangeSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// PutByteRangeResponder handles the response to the PutByteRange request. The method always +// closes the http.Response Body. +func (client Client) PutByteRangeResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusCreated), + autorest.ByClosing()) + result = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put_file.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put_file.go new file mode 100644 index 0000000000000..a39cd377cee89 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/range_put_file.go @@ -0,0 +1,107 @@ +package files + +import ( + "context" + "fmt" + "io" + "log" + "math" + "os" + "runtime" + "sync" + + "github.com/Azure/go-autorest/autorest" +) + +// PutFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself +func (client Client) PutFile(ctx context.Context, accountName, shareName, path, fileName string, file *os.File, parallelism int) error { + fileInfo, err := file.Stat() + if err != nil { + return fmt.Errorf("Error loading file info: %s", err) + } + + fileSize := fileInfo.Size() + chunkSize := 4 * 1024 * 1024 // 4MB + if chunkSize > int(fileSize) { + chunkSize = int(fileSize) + } + chunks := int(math.Ceil(float64(fileSize) / float64(chunkSize*1.0))) + + workerCount := parallelism * runtime.NumCPU() + if workerCount > chunks { + workerCount = chunks + } + + var waitGroup sync.WaitGroup + waitGroup.Add(workerCount) + errors := make(chan error, chunkSize) + + for i := 0; i < chunks; i++ { + go func(i int) { + log.Printf("[DEBUG] Chunk %d of %d", i+1, chunks) + + uci := uploadChunkInput{ + thisChunk: i, + chunkSize: chunkSize, + fileSize: fileSize, + } + + _, err := client.uploadChunk(ctx, accountName, shareName, path, fileName, uci, file) + if err != nil { + errors <- err + waitGroup.Done() + return + } + + waitGroup.Done() + return + }(i) + } + waitGroup.Wait() + + // TODO: we should switch to hashicorp/multi-error here + if len(errors) > 0 { + return fmt.Errorf("Error uploading file: %s", <-errors) + } + + return nil +} + +type uploadChunkInput struct { + thisChunk int + chunkSize int + fileSize int64 +} + +func (client Client) uploadChunk(ctx context.Context, accountName, shareName, path, fileName string, input uploadChunkInput, file *os.File) (result autorest.Response, err error) { + startBytes := int64(input.chunkSize * input.thisChunk) + endBytes := startBytes + int64(input.chunkSize) + + // the last size may exceed the size of the file + remaining := input.fileSize - startBytes + if int64(input.chunkSize) > remaining { + endBytes = startBytes + remaining + } + + bytesToRead := int(endBytes) - int(startBytes) + bytes := make([]byte, bytesToRead) + + _, err = file.ReadAt(bytes, startBytes) + if err != nil { + if err != io.EOF { + return result, fmt.Errorf("Error reading bytes: %s", err) + } + } + + putBytesInput := PutByteRangeInput{ + StartBytes: startBytes, + EndBytes: endBytes, + Content: bytes, + } + result, err = client.PutByteRange(ctx, accountName, shareName, path, fileName, putBytesInput) + if err != nil { + return result, fmt.Errorf("Error putting bytes: %s", err) + } + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/ranges_list.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/ranges_list.go new file mode 100644 index 0000000000000..ea309f97ddb2e --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/ranges_list.go @@ -0,0 +1,114 @@ +package files + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +type ListRangesResult struct { + autorest.Response + + Ranges []Range `xml:"Range"` +} + +type Range struct { + Start string `xml:"Start"` + End string `xml:"End"` +} + +// ListRanges returns the list of valid ranges for the specified File. +func (client Client) ListRanges(ctx context.Context, accountName, shareName, path, fileName string) (result ListRangesResult, err error) { + if accountName == "" { + return result, validation.NewError("files.Client", "ListRanges", "`accountName` cannot be an empty string.") + } + if shareName == "" { + return result, validation.NewError("files.Client", "ListRanges", "`shareName` cannot be an empty string.") + } + if strings.ToLower(shareName) != shareName { + return result, validation.NewError("files.Client", "ListRanges", "`shareName` must be a lower-cased string.") + } + if path == "" { + return result, validation.NewError("files.Client", "ListRanges", "`path` cannot be an empty string.") + } + if fileName == "" { + return result, validation.NewError("files.Client", "ListRanges", "`fileName` cannot be an empty string.") + } + + req, err := client.ListRangesPreparer(ctx, accountName, shareName, path, fileName) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", nil, "Failure preparing request") + return + } + + resp, err := client.ListRangesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure sending request") + return + } + + result, err = client.ListRangesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure responding to request") + return + } + + return +} + +// ListRangesPreparer prepares the ListRanges request. +func (client Client) ListRangesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) { + if path != "" { + path = fmt.Sprintf("%s/", path) + } + pathParameters := map[string]interface{}{ + "shareName": autorest.Encode("path", shareName), + "directory": autorest.Encode("path", path), + "fileName": autorest.Encode("path", fileName), + } + + queryParameters := map[string]interface{}{ + "comp": autorest.Encode("query", "rangelist"), + } + + headers := map[string]interface{}{ + "x-ms-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/xml; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)), + autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters), + autorest.WithHeaders(headers), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListRangesSender sends the ListRanges request. The method will close the +// http.Response Body if it receives an error. +func (client Client) ListRangesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req, + azure.DoRetryWithRegistration(client.Client)) +} + +// ListRangesResponder handles the response to the ListRanges request. The method always +// closes the http.Response Body. +func (client Client) ListRangesResponder(resp *http.Response) (result ListRangesResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingXML(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + + return +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/resource_id.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/resource_id.go new file mode 100644 index 0000000000000..f18e702e81d95 --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/resource_id.go @@ -0,0 +1,64 @@ +package files + +import ( + "fmt" + "net/url" + "strings" + + "github.com/tombuildsstuff/giovanni/storage/internal/endpoints" +) + +// GetResourceID returns the Resource ID for the given File +// This can be useful when, for example, you're using this as a unique identifier +func (client Client) GetResourceID(accountName, shareName, directoryName, filePath string) string { + domain := endpoints.GetFileEndpoint(client.BaseURI, accountName) + return fmt.Sprintf("%s/%s/%s/%s", domain, shareName, directoryName, filePath) +} + +type ResourceID struct { + AccountName string + DirectoryName string + FileName string + ShareName string +} + +// ParseResourceID parses the specified Resource ID and returns an object +// which can be used to interact with Files within a Storage Share. +func ParseResourceID(id string) (*ResourceID, error) { + // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt + // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt + + if id == "" { + return nil, fmt.Errorf("`id` was empty") + } + + uri, err := url.Parse(id) + if err != nil { + return nil, fmt.Errorf("Error parsing ID as a URL: %s", err) + } + + accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host) + if err != nil { + return nil, fmt.Errorf("Error parsing Account Name: %s", err) + } + + path := strings.TrimPrefix(uri.Path, "/") + segments := strings.Split(path, "/") + if len(segments) == 0 { + return nil, fmt.Errorf("Expected the path to contain segments but got none") + } + + shareName := segments[0] + fileName := segments[len(segments)-1] + + directoryName := strings.TrimPrefix(path, shareName) + directoryName = strings.TrimPrefix(directoryName, "/") + directoryName = strings.TrimSuffix(directoryName, fileName) + directoryName = strings.TrimSuffix(directoryName, "/") + return &ResourceID{ + AccountName: *accountName, + ShareName: shareName, + DirectoryName: directoryName, + FileName: fileName, + }, nil +} diff --git a/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/version.go b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/version.go new file mode 100644 index 0000000000000..8d135a3618b9a --- /dev/null +++ b/vendor/github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files/version.go @@ -0,0 +1,14 @@ +package files + +import ( + "fmt" + + "github.com/tombuildsstuff/giovanni/version" +) + +// APIVersion is the version of the API used for all Storage API Operations +const APIVersion = "2018-11-09" + +func UserAgent() string { + return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c54d03cc3bebd..6b7b4bd23e64f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -344,6 +344,7 @@ github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers github.com/tombuildsstuff/giovanni/storage/2018-11-09/datalakestore/filesystems github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/directories +github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares github.com/tombuildsstuff/giovanni/storage/2018-11-09/queue/queues github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/entities diff --git a/website/allowed-subcategories b/website/allowed-subcategories index d1c2c34478fa9..bbb72b2ffb95b 100644 --- a/website/allowed-subcategories +++ b/website/allowed-subcategories @@ -1,7 +1,6 @@ API Management Analysis Services App Configuration -App Platform App Service (Web Apps) Application Insights Authorization @@ -52,6 +51,7 @@ Redis Search Security Center Service Fabric +Spring Cloud Storage Stream Analytics Template diff --git a/website/azurerm.erb b/website/azurerm.erb index 82495facfd3a2..3a8f9d6cc086b 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -790,6 +790,18 @@
  • App Platform