From d3d4b99180a234e6b56d5ed4ad4c18d7b5a719a1 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Wed, 6 Jan 2021 00:05:09 -0500 Subject: [PATCH 1/3] Sync bitbucket and GitHub --- cloudmanager/anf.go | 335 ++++++++++++++++++ cloudmanager/client.go | 2 + cloudmanager/cloudmanager/restapi/client.go | 3 + cloudmanager/config.go | 2 + cloudmanager/occm_aws.go | 3 +- cloudmanager/provider.go | 1 + ...resource_netapp_cloudmanager_anf_volume.go | 287 +++++++++++++++ cloudmanager/volume.go | 11 +- go.mod | 4 + website/docs/r/anf_volume.html.markdown | 93 +++++ 10 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 cloudmanager/anf.go create mode 100644 cloudmanager/resource_netapp_cloudmanager_anf_volume.go create mode 100644 website/docs/r/anf_volume.html.markdown diff --git a/cloudmanager/anf.go b/cloudmanager/anf.go new file mode 100644 index 0000000..25269f7 --- /dev/null +++ b/cloudmanager/anf.go @@ -0,0 +1,335 @@ +package cloudmanager + +import ( + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/fatih/structs" +) + +type anfVolumeRequest struct { + Size float64 `structs:"quotaInBytes"` + Name string `structs:"name"` + VolumePath string `structs:"volumePath"` + ProtocolTypes []string `structs:"protocolTypes"` + ServiceLevel string `structs:"serviceLevel"` + SubnetName string `structs:"subnetName"` + VirtualNetworkName string `structs:"virtualNetworkName"` + Location string `structs:"location"` + Rules []rule `structs:"rules"` + WorkingEnvironmentName string `structs:"workingEnvironmentName"` +} + +// VolumePath is returned as creationToken +type anfVolumeResponse struct { + Size float64 `json:"quotaInBytes"` + Name string `json:"name"` + VolumePath string `json:"creationToken"` + ProtocolTypes []string `json:"protocolTypes"` + ServiceLevel string `json:"serviceLevel"` + SubnetName string `json:"subnet"` + Location string `json:"location"` + Rules map[string][]ruleResponse `json:"exportPolicy"` + WorkingEnvironmentName string `json:"workingEnvironmentName"` +} + +type ruleResponse struct { + AllowedClients string `json:"allowedClients"` + Cifs bool `json:"cifs"` + Nfsv3 bool `json:"nfsv3"` + Nfsv41 bool `json:"nfsv41"` + RuleIndex int `json:"ruleIndex"` + UnixReadOnly bool `json:"unixReadOnly"` + UnixReadWrite bool `json:"unixReadWrite"` +} + +type rule struct { + AllowedClients string `structs:"allowedClients"` + Cifs bool `structs:"cifs"` + Nfsv3 bool `structs:"nfsv3"` + Nfsv41 bool `structs:"nfsv41"` + RuleIndex int `structs:"ruleIndex"` + UnixReadOnly bool `structs:"unixReadOnly"` + UnixReadWrite bool `structs:"unixReadWrite"` +} + +type azureInfo struct { + AccountName string `structs:"accountName"` + AccountID string `structs:"accountID"` + CredentialsID string `structs:"credentialsID"` + SubscriptionName string `structs:"subscriptionName"` + NetAppAccountName string `structs:"netAppAccountName"` + ResourceGroupsName string `structs:"resourceGroupsName"` + CapacityPools string `structs:"capacityPools"` + VirtualNetworkName string `structs:"virtualNetworkName"` + SubnetName string `structs:"subnetName"` +} + +func (c *Client) getAccountByName(name string) (string, error) { + log.Print("getAccount") + + baseURL := "/tenancy/account" + hostType := "CloudManagerHost" + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getAccount request failed ", statusCode) + return "", err + } + responseError := apiResponseChecker(statusCode, response, "getAccount") + if responseError != nil { + return "", responseError + } + + var results []accountIDResult + if err := json.Unmarshal(response, &results); err != nil { + log.Print("Failed to unmarshall response from getAccount ", err) + return "", err + } + if len(results) == 0 { + return "", fmt.Errorf("no account exists") + } + if name != "" { + for _, result := range results { + if name == result.AccountName { + return result.AccountID, nil + } + } + return "", fmt.Errorf("account: %s not found", name) + } + + // return the first account if name is not provided. + return results[0].AccountID, nil +} + +func (c *Client) createANFVolume(vol anfVolumeRequest, info azureInfo) error { + baseURL, err := c.getCVSAPIRoot(info.AccountName, vol.WorkingEnvironmentName) + if err != nil { + return err + } + subscription, err := c.getSubscription(baseURL, info.SubscriptionName) + if err != nil { + return err + } + subnet, err := c.getSubnetID(fmt.Sprintf("%s/subscriptions/%s", baseURL, subscription), info.VirtualNetworkName, info.SubnetName, vol.Location) + if err != nil { + return err + } + baseURL = fmt.Sprintf("%s/subscriptions/%s/resourceGroups/%s/netAppAccounts/%s/capacityPools/%s/volumes", baseURL, subscription, info.ResourceGroupsName, info.NetAppAccountName, info.CapacityPools) + hostType := "CVSHost" + param := structs.Map(vol) + param["subnetId"] = subnet + statusCode, response, _, err := c.CallAPIMethod("POST", baseURL, param, c.Token, hostType) + if err != nil { + log.Print("createANFVolume request failed ", statusCode) + return err + } + responseError := apiResponseChecker(statusCode, response, "createANFVolume") + if responseError != nil { + return responseError + } + + return nil +} + +func (c *Client) getANFVolume(vol anfVolumeRequest, info azureInfo) (anfVolumeResponse, error) { + baseURL, err := c.getCVSAPIRoot(info.AccountName, vol.WorkingEnvironmentName) + if err != nil { + return anfVolumeResponse{}, err + } + subscription, err := c.getSubscription(baseURL, info.SubscriptionName) + if err != nil { + return anfVolumeResponse{}, err + } + subnet, err := c.getSubnetID(fmt.Sprintf("%s/subscriptions/%s", baseURL, subscription), info.VirtualNetworkName, info.SubnetName, vol.Location) + if err != nil { + return anfVolumeResponse{}, err + } + baseURL = fmt.Sprintf("%s/subscriptions/%s/resourceGroups/%s/netAppAccounts/%s/capacityPools/%s/volumes/%s", baseURL, subscription, info.ResourceGroupsName, info.NetAppAccountName, info.CapacityPools, vol.Name) + hostType := "CVSHost" + param := structs.Map(vol) + param["subnetId"] = subnet + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getANFVolume request failed ", statusCode) + return anfVolumeResponse{}, err + } + responseError := apiResponseChecker(statusCode, response, "getANFVolume") + if responseError != nil { + return anfVolumeResponse{}, responseError + } + var result anfVolumeResponse + if err := json.Unmarshal(response, &result); err != nil { + log.Print("Failed to unmarshall response from getANFVolume ", err) + return anfVolumeResponse{}, err + } + + return result, nil +} + +func (c *Client) getCVSWorkingEnvironment(accountID string, WorkingEnvironment string) (string, string, error) { + if c.Token == "" { + accesTokenResult, err := c.getAccessToken() + if err != nil { + log.Print("Not able to get the access token.") + return "", "", err + } + c.Token = accesTokenResult.Token + } + + baseURL := fmt.Sprintf("/cvs/accounts/%s/working-environments", accountID) + hostType := "CVSHost" + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getCVSWorkingEnvironment request failed ", statusCode) + return "", "", err + } + responseError := apiResponseChecker(statusCode, response, "getCVSWorkingEnvironment") + if responseError != nil { + return "", "", responseError + } + var results []map[string]interface{} + if err := json.Unmarshal(response, &results); err != nil { + log.Print("Failed to unmarshall response from getCVSWorkingEnvironment ", err) + return "", "", err + } + + for _, result := range results { + if strings.ToLower(result["name"].(string)) == strings.ToLower(WorkingEnvironment) { + return result["credentialsId"].(string), result["provider"].(string), nil + } + } + + return "", "", fmt.Errorf(" working environment: %s doesn't exist", WorkingEnvironment) +} + +func (c *Client) getCVSAPIRoot(accountName string, workingEnvironment string) (string, error) { + if c.Token == "" { + accesTokenResult, err := c.getAccessToken() + if err != nil { + log.Print("Not able to get the access token.") + return "", err + } + c.Token = accesTokenResult.Token + } + accountID, err := c.getAccountByName(accountName) + if err != nil { + return "", err + } + credentialsID, provider, err := c.getCVSWorkingEnvironment(accountID, workingEnvironment) + if err != nil { + return "", err + } + + if provider == "azure" { + return fmt.Sprintf("/cvs/azure/accounts/%s/credentials/%s", accountID, credentialsID), nil + } else if provider == "gcp" { + return fmt.Sprintf("/cvs/gcp/accounts/%s/credentials/%s", accountID, credentialsID), nil + } else if provider == "aws" { + return fmt.Sprintf("/cvs/aws/accounts/%s/credentials/%s", accountID, credentialsID), nil + } else { + return "", fmt.Errorf("working environment's provider is not supported or not found") + } +} + +func (c *Client) getSubscription(baseURL string, subscription string) (string, error) { + if c.Token == "" { + accesTokenResult, err := c.getAccessToken() + if err != nil { + log.Print("Not able to get the access token.") + return "", err + } + c.Token = accesTokenResult.Token + } + baseURL = fmt.Sprintf("%s/subscriptions", baseURL) + hostType := "CVSHost" + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getSubscriptions request failed ", statusCode) + return "", err + } + responseError := apiResponseChecker(statusCode, response, "getSubscriptions") + if responseError != nil { + return "", responseError + } + + var results []map[string]interface{} + if err := json.Unmarshal(response, &results); err != nil { + log.Print("Failed to unmarshall response from getSubscriptions ", err) + return "", err + } + for _, result := range results { + if strings.ToLower(subscription) == strings.ToLower(result["displayName"].(string)) { + return result["subscriptionId"].(string), nil + } + } + + return "", fmt.Errorf("subscription: %s doesn't exist", subscription) + +} + +func (c *Client) getSubnetID(baseURL string, virtualNetwork string, subnet string, location string) (string, error) { + if c.Token == "" { + accesTokenResult, err := c.getAccessToken() + if err != nil { + log.Print("Not able to get the access token.") + return "", err + } + c.Token = accesTokenResult.Token + } + baseURL = fmt.Sprintf("%s/virtualNetworks?location=%s", baseURL, location) + hostType := "CVSHost" + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getSubnetID request failed ", statusCode) + return "", err + } + responseError := apiResponseChecker(statusCode, response, "getSubnetID") + if responseError != nil { + return "", responseError + } + + var results []interface{} + if err := json.Unmarshal(response, &results); err != nil { + log.Print("Failed to unmarshall response from getSubnetID ", err) + return "", err + } + for _, result := range results { + if strings.ToLower(virtualNetwork) == strings.ToLower(result.(map[string]interface{})["name"].(string)) { + subnetResults := result.(map[string]interface{})["subnets"].([]interface{}) + for _, subnetResult := range subnetResults { + if strings.ToLower(subnet) == strings.ToLower(subnetResult.(map[string]interface{})["name"].(string)) { + return subnetResult.(map[string]interface{})["subnetId"].(string), nil + } + } + } + } + + return "", fmt.Errorf("subnet: %s doesn't exist", subnet) + +} + +func (c *Client) deleteANFVolume(vol anfVolumeRequest, info azureInfo) error { + baseURL, err := c.getCVSAPIRoot(info.AccountName, vol.WorkingEnvironmentName) + if err != nil { + return err + } + subscription, err := c.getSubscription(baseURL, info.SubscriptionName) + if err != nil { + return err + } + baseURL = fmt.Sprintf("%s/subscriptions/%s/resourceGroups/%s/netAppAccounts/%s/capacityPools/%s/volumes/%s", baseURL, subscription, info.ResourceGroupsName, info.NetAppAccountName, info.CapacityPools, vol.Name) + hostType := "CVSHost" + statusCode, response, _, err := c.CallAPIMethod("DELETE", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("deleteANFVolume request failed ", statusCode) + return err + } + responseError := apiResponseChecker(statusCode, response, "deleteANFVolume") + if responseError != nil { + return responseError + } + + return nil +} diff --git a/cloudmanager/client.go b/cloudmanager/client.go index 44c98eb..3d0655a 100644 --- a/cloudmanager/client.go +++ b/cloudmanager/client.go @@ -51,6 +51,7 @@ type Client struct { GCPImageFamily string GCPDeploymentTemplate string GCPServiceAccountPath string + CVSHostName string initOnce sync.Once instanceInput *restapi.Client @@ -643,6 +644,7 @@ func (c *Client) init() { RefreshToken: c.RefreshToken, Audience: c.Audience, GCPDeploymentManager: c.GCPDeploymentManager, + CVSHostName: c.CVSHostName, } } diff --git a/cloudmanager/cloudmanager/restapi/client.go b/cloudmanager/cloudmanager/restapi/client.go index cebdb79..9342fad 100644 --- a/cloudmanager/cloudmanager/restapi/client.go +++ b/cloudmanager/cloudmanager/restapi/client.go @@ -15,6 +15,7 @@ type Client struct { RefreshToken string Audience string GCPDeploymentManager string + CVSHostName string httpClient http.Client } @@ -36,6 +37,8 @@ func (c *Client) Do(baseURL string, hostType string, token string, paramsNil boo } else if hostType == "GCPDeploymentManager" { host = c.GCPDeploymentManager gcpType = true + } else if hostType == "CVSHost" { + host = c.CVSHostName } httpReq, err := req.BuildHTTPReq(host, token, c.Audience, baseURL, paramsNil, accountID, clientID, gcpType) diff --git a/cloudmanager/config.go b/cloudmanager/config.go index bb70a21..9a3bb02 100644 --- a/cloudmanager/config.go +++ b/cloudmanager/config.go @@ -27,6 +27,7 @@ func (c *configStuct) clientFun() (*Client, error) { GCPDeploymentManager: "https://www.googleapis.com", GCPImageProject: "netapp-cloudmanager", GCPImageFamily: "cloudmanager", + CVSHostName: "https://api.services.cloud.netapp.com", } } else if c.Environment == "stage" { log.Print("Stage Environment") @@ -41,6 +42,7 @@ func (c *configStuct) clientFun() (*Client, error) { GCPImageProject: "tlv-automation", GCPImageFamily: "occm-automation", AzureEnvironmentForOCCM: "stage", + CVSHostName: "https://staging.api.services.cloud.netapp.com", } } else { return &Client{}, fmt.Errorf("expected environment to be one of [prod stage], %s", c.Environment) diff --git a/cloudmanager/occm_aws.go b/cloudmanager/occm_aws.go index 174af9b..0fc461e 100644 --- a/cloudmanager/occm_aws.go +++ b/cloudmanager/occm_aws.go @@ -133,7 +133,8 @@ type accountResult struct { // accountIDResult to get the account ID type accountIDResult struct { - AccountID string `json:"accountPublicId"` + AccountID string `json:"accountPublicId"` + AccountName string `json:"accountName"` } // listOCCMResult lists the details for given Client ID diff --git a/cloudmanager/provider.go b/cloudmanager/provider.go index 7319dab..0c38770 100644 --- a/cloudmanager/provider.go +++ b/cloudmanager/provider.go @@ -36,6 +36,7 @@ func Provider() terraform.ResourceProvider { "netapp-cloudmanager_cifs_server": resourceCVOCIFS(), "netapp-cloudmanager_snapmirror": resourceCVOSnapMirror(), "netapp-cloudmanager_nss_account": resourceCVONssAccount(), + "netapp-cloudmanager_anf_volume": resourceCVSANFVolume(), }, DataSourcesMap: map[string]*schema.Resource{ "netapp-cloudmanager_cifs_server": dataSourceCVOCIFS(), diff --git a/cloudmanager/resource_netapp_cloudmanager_anf_volume.go b/cloudmanager/resource_netapp_cloudmanager_anf_volume.go new file mode 100644 index 0000000..7ff455f --- /dev/null +++ b/cloudmanager/resource_netapp_cloudmanager_anf_volume.go @@ -0,0 +1,287 @@ +package cloudmanager + +import ( + "log" + "math" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceCVSANFVolume() *schema.Resource { + return &schema.Resource{ + Create: resourceCVSAzureVolumeCreate, + Read: resourceCVSAzureVolumeRead, + Delete: resourceCVSAzureVolumeDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + CustomizeDiff: resourceVolumeCustomizeDiff, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "working_environment_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "account": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "subscription": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "resource_groups": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "netapp_account": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "capacity_pool": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "size": { + Type: schema.TypeFloat, + Required: true, + ForceNew: true, + }, + "size_unit": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"gb"}, false), + }, + "volume_path": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "protocol_types": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "service_level": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "virtual_network": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "client_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "export_policy": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_clients": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "cifs": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "nfsv3": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "nfsv41": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "unix_read_only": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "unix_read_write": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "rule_index": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceCVSAzureVolumeCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("Creating volume: %#v", d) + + client := meta.(*Client) + client.ClientID = d.Get("client_id").(string) + volume := anfVolumeRequest{} + volume.Name = d.Get("name").(string) + volume.Location = d.Get("location").(string) + volume.ServiceLevel = d.Get("service_level").(string) + volume.Size = math.Round(convertSizeUnit(d.Get("size").(float64), d.Get("size_unit").(string), "B")*10) / 10 + volume.SubnetName = d.Get("subnet").(string) + volume.VolumePath = d.Get("volume_path").(string) + volume.VirtualNetworkName = d.Get("virtual_network").(string) + volume.WorkingEnvironmentName = d.Get("working_environment_name").(string) + if v, ok := d.GetOk("protocol_types"); ok { + protocolTypes := make([]string, 0, v.(*schema.Set).Len()) + for _, x := range v.(*schema.Set).List() { + protocolTypes = append(protocolTypes, x.(string)) + } + volume.ProtocolTypes = protocolTypes + } + if v, ok := d.GetOk("export_policy"); ok { + if len(v.([]interface{})) > 0 { + rules := make([]rule, 0, len(v.([]interface{}))) + for _, v1 := range v.([]interface{}) { + v2 := v1.(map[string]interface{}) + ruleList := v2["rule"].([]interface{}) + for _, v3 := range ruleList { + rule := rule{} + ruleMap := v3.(map[string]interface{}) + rule.AllowedClients = ruleMap["allowed_clients"].(string) + rule.UnixReadOnly = ruleMap["unix_read_only"].(bool) + rule.RuleIndex = ruleMap["rule_index"].(int) + rules = append(rules, rule) + } + } + volume.Rules = rules + } + } + info := azureInfo{} + info.AccountName = d.Get("account").(string) + info.SubscriptionName = d.Get("subscription").(string) + info.VirtualNetworkName = d.Get("virtual_network").(string) + info.SubnetName = d.Get("subnet").(string) + info.ResourceGroupsName = d.Get("resource_groups").(string) + info.NetAppAccountName = d.Get("netapp_account").(string) + info.CapacityPools = d.Get("capacity_pool").(string) + err := client.createANFVolume(volume, info) + if err != nil { + return err + } + // volume Id is not returned, so use name. + d.SetId(volume.Name) + + return resourceCVSAzureVolumeRead(d, meta) +} + +func resourceCVSAzureVolumeRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + client.ClientID = d.Get("client_id").(string) + volume := anfVolumeRequest{} + info := azureInfo{} + volume.Name = d.Get("name").(string) + volume.WorkingEnvironmentName = d.Get("working_environment_name").(string) + volume.Location = d.Get("location").(string) + info.AccountName = d.Get("account").(string) + info.SubscriptionName = d.Get("subscription").(string) + info.VirtualNetworkName = d.Get("virtual_network").(string) + info.SubnetName = d.Get("subnet").(string) + info.ResourceGroupsName = d.Get("resource_groups").(string) + info.NetAppAccountName = d.Get("netapp_account").(string) + info.CapacityPools = d.Get("capacity_pool").(string) + result, err := client.getANFVolume(volume, info) + if err != nil { + log.Print("Error reading volume") + return err + } + d.Set("size", math.Round(convertSizeUnit(result.Size, "B", d.Get("size_unit").(string))*10)/10) + d.Set("volume_path", result.VolumePath) + d.Set("protocol_types", result.ProtocolTypes) + d.Set("service_level", result.ServiceLevel) + d.Set("location", result.Location) + // subnet is returned as empty string in get volume API. + //d.Set("subnet", result.SubnetName) + + rules := make([]map[string]interface{}, 1) + rule := make(map[string]interface{}) + ruleList := make([]map[string]interface{}, len(result.Rules["rules"])) + for _, ruleContent := range result.Rules["rules"] { + ruleDict := make(map[string]interface{}) + ruleDict["allowed_clients"] = ruleContent.AllowedClients + ruleDict["cifs"] = ruleContent.Cifs + ruleDict["nfsv3"] = ruleContent.Nfsv3 + ruleDict["nfsv41"] = ruleContent.Nfsv41 + ruleDict["ruleIndex"] = ruleContent.RuleIndex + ruleDict["unixReadOnly"] = ruleContent.UnixReadOnly + ruleDict["unixReadWrite"] = ruleContent.UnixReadWrite + ruleList = append(ruleList, ruleDict) + } + rule["rule"] = ruleList + rules[0] = rule + d.Set("export_policy", rules) + + return nil +} + +func resourceCVSAzureVolumeDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + client.ClientID = d.Get("client_id").(string) + info := azureInfo{} + volume := anfVolumeRequest{} + volume.Name = d.Get("name").(string) + volume.WorkingEnvironmentName = d.Get("working_environment_name").(string) + info.AccountName = d.Get("account").(string) + info.SubscriptionName = d.Get("subscription").(string) + info.VirtualNetworkName = d.Get("virtual_network").(string) + info.SubnetName = d.Get("subnet").(string) + info.ResourceGroupsName = d.Get("resource_groups").(string) + info.NetAppAccountName = d.Get("netapp_account").(string) + info.CapacityPools = d.Get("capacity_pool").(string) + err := client.deleteANFVolume(volume, info) + if err != nil { + return err + } + + return nil +} diff --git a/cloudmanager/volume.go b/cloudmanager/volume.go index c310d92..b7e52cf 100644 --- a/cloudmanager/volume.go +++ b/cloudmanager/volume.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "github.com/fatih/structs" ) @@ -375,11 +376,17 @@ func (c *Client) checkCifsExists(workingEnvironmentID string, svm string) (bool, } func convertSizeUnit(size float64, from string, to string) float64 { - if from == "GB" && to == "GB" { + if strings.ToUpper(from) == "GB" && strings.ToUpper(to) == "GB" { return size } - if from == "GB" && to == "TB" { + if strings.ToUpper(from) == "GB" && strings.ToUpper(to) == "TB" { size = size / 1024 } + if strings.ToUpper(from) == "GB" && strings.ToUpper(to) == "B" { + size = size * 1073741824 + } + if strings.ToUpper(from) == "B" && strings.ToUpper(to) == "GB" { + size = size / 1073741824 + } return size } diff --git a/go.mod b/go.mod index d38e560..0d9f997 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,13 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 github.com/aws/aws-sdk-go v1.35.5 github.com/fatih/structs v1.1.0 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/hashicorp/terraform v0.13.4 github.com/sirupsen/logrus v1.7.0 + github.com/stretchr/testify v1.5.1 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.0.0-20201008025239-9df69603baec // indirect + google.golang.org/protobuf v1.25.0 // indirect gopkg.in/yaml.v2 v2.2.8 ) diff --git a/website/docs/r/anf_volume.html.markdown b/website/docs/r/anf_volume.html.markdown new file mode 100644 index 0000000..c5e38c2 --- /dev/null +++ b/website/docs/r/anf_volume.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "netapp_cloudmanager" +page_title: "NetApp_CloudManager: netapp_cloudmanager_anf_volume" +sidebar_current: "docs-netapp-cloudmanager-resource-anf-volume" +description: |- + Provides a netapp-cloudmanager_anf_volume resource. This can be used to create, and delete volumes for Azure NetApp Files. +--- + +# netapp-cloudmanager_anf_volume + +Provides a netapp-cloudmanager_anf_volume resource. This can be used to create, and delete volumes for Azure NetApp Files. +Requires existence of a Cloud Manager Connector. + +## Example Usages + +**Create netapp-cloudmanager_volume:** + +``` +resource "netapp-cloudmanager_anf_volume" "test-1" { + provider = netapp-cloudmanager + name = "test_vol" + size = 105 + size_unit = "gb" + volume_path = "volume-path" + protocol_types = ["NFSv3"] + location = "eastus" + client_id = netapp-cloudmanager_connector_azure.cm-azure.client_id + service_level = "Standard" + subnet = "default" + virtual_network = "mynetwork" + working_environment_name = "ANF_environment" + account = "Demo_SIM" + netapp_account = "test" + subscription = "My Subscription" + resource_groups = "myRG-eastus" + capacity_pool = "ANFPool" + rules { + rule { + allowed_clients = "1.0.0.1" + rule_index = 1 + nfsv3 = true + unix_read_only = true + } + rule { + allowed_clients = "1.0.0.2" + rule_index = 2 + nfsv3 = true + unix_read_only = true + unix_read_write = false + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the volume. +* `size` - (Required) The volume size, supported with decimal numbers. +* `size_unit` - (Required) [ 'GB' ]. +* `volume_path` - (Required) The volume path. +* `protocol_types` (Required) [ 'NFSv3' ]. +* `location` - (Required) The location of the account. +* `service_level` - (Required) ['Premium' or 'Standard' or 'Ultra']. +* `subnet` - (Required) The name of the subnet. +* `virtual_network` - (Required) The name of the virtual network. +* `account` - (Required) The name of the account. +* `netapp_account` - (Required) The name of the netapp account. +* `subscription` - (Required) The name of the subscription. +* `resource_groups` - (Required) The name of the resource group in Azure where the volume will be created. +* `capacity_pool` - (Required) The name of the capacity pool. +* `client_id` - (Required) The client ID of the Cloud Manager Connector. You can find the ID from a previous create Connector action as shown in the example, or from the Connector tab on [https://cloudmanager.netapp.com](https://cloudmanager.netapp.com). +* `working_environment_name` - (Required) The working environment name. +* `export_policy` - (Optional) The rules of the export policy. + + +The `export_policy` block supports: +* `rule` - (Optional) The rule of the export policy. + +The `rule` block supports: +* `allowed_clients` - (Optional) allowed clients. +* `rule_index` - (Optional) rule index. +* `nfsv3` - (Optional) Boolean. +* `unix_read_only` - (Optional) Boolean. +* `unix_read_wrtie` - (Optional) Boolean. + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `id` - The name of the volume. + From 453152402fb3ccbe7a4dbafeba6ffa98588e300f Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Wed, 6 Jan 2021 12:27:29 -0500 Subject: [PATCH 2/3] Sync bitbucket and GitHub --- cloudmanager/helper.go | 69 ++++++++++++++++++- ...resource_netapp_cloudmanager_snapmirror.go | 2 +- cloudmanager/snapmirror.go | 59 +++++++++------- cloudmanager/volume.go | 26 ++++++- 4 files changed, 128 insertions(+), 28 deletions(-) diff --git a/cloudmanager/helper.go b/cloudmanager/helper.go index 68e1a70..cd1c005 100644 --- a/cloudmanager/helper.go +++ b/cloudmanager/helper.go @@ -152,6 +152,17 @@ func findWE(name string, weList []workingEnvironmentInfo) (workingEnvironmentInf return workingEnvironmentInfo{}, fmt.Errorf("Cannot find working environment %s in the list", name) } +func findWEForID(id string, weList []workingEnvironmentInfo) (workingEnvironmentInfo, error) { + + for i := range weList { + if weList[i].PublicID == id { + log.Printf("Found working environment: %v", weList[i]) + return weList[i], nil + } + } + return workingEnvironmentInfo{}, fmt.Errorf("Cannot find working environment %s in the list", id) +} + func (c *Client) findWorkingEnvironmentByName(name string) (workingEnvironmentInfo, error) { // check working environment exists or not baseURL := fmt.Sprintf("/occm/api/working-environments/exists/%s", name) @@ -217,6 +228,7 @@ func (c *Client) findWorkingEnvironmentByName(name string) (workingEnvironmentIn return workingEnvironmentInfo{}, err } +// get WE directly from REST API using a given ID func (c *Client) findWorkingEnvironmentByID(id string) (workingEnvironmentInfo, error) { workingEnvInfo, err := c.getWorkingEnvironmentInfo(id) if err != nil { @@ -317,7 +329,7 @@ func (c *Client) getWorkingEnvironmentDetailForSnapMirror(d *schema.ResourceData if a, ok := d.GetOk("source_working_environment_id"); ok { WorkingEnvironmentID := a.(string) - sourceWorkingEnvDetail, err = c.findWorkingEnvironmentByID(WorkingEnvironmentID) + sourceWorkingEnvDetail, err = c.findWorkingEnvironmentForID(WorkingEnvironmentID) if err != nil { return workingEnvironmentInfo{}, workingEnvironmentInfo{}, fmt.Errorf("Cannot find working environment by source_working_environment_id %s", WorkingEnvironmentID) } @@ -333,10 +345,11 @@ func (c *Client) getWorkingEnvironmentDetailForSnapMirror(d *schema.ResourceData if a, ok := d.GetOk("destination_working_environment_id"); ok { WorkingEnvironmentID := a.(string) - destWorkingEnvDetail, err = c.findWorkingEnvironmentByID(WorkingEnvironmentID) + destWorkingEnvDetail, err = c.findWorkingEnvironmentForID(WorkingEnvironmentID) if err != nil { return workingEnvironmentInfo{}, workingEnvironmentInfo{}, fmt.Errorf("Cannot find working environment by destination_working_environment_id %s", WorkingEnvironmentID) } + log.Print("findWorkingEnvironmentForID", destWorkingEnvDetail) } else if a, ok = d.GetOk("destination_working_environment_name"); ok { destWorkingEnvDetail, err = c.findWorkingEnvironmentByName(a.(string)) if err != nil { @@ -348,3 +361,55 @@ func (c *Client) getWorkingEnvironmentDetailForSnapMirror(d *schema.ResourceData } return sourceWorkingEnvDetail, destWorkingEnvDetail, nil } + +// get all WE from REST API and then using a given ID get the WE +func (c *Client) findWorkingEnvironmentForID(id string) (workingEnvironmentInfo, error) { + hostType := "CloudManagerHost" + + if c.Token == "" { + accesTokenResult, err := c.getAccessToken() + if err != nil { + return workingEnvironmentInfo{}, err + } + c.Token = accesTokenResult.Token + } + baseURL := "/occm/api/working-environments" + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Printf("findWorkingEnvironmentForId %s request failed (%d)", id, statusCode) + return workingEnvironmentInfo{}, err + } + + responseError := apiResponseChecker(statusCode, response, "findWorkingEnvironmentByName") + if responseError != nil { + return workingEnvironmentInfo{}, responseError + } + + var workingEnvironments workingEnvironmentResult + if err := json.Unmarshal(response, &workingEnvironments); err != nil { + log.Print("Failed to unmarshall response from findWorkingEnvironmentByName") + return workingEnvironmentInfo{}, err + } + + var workingEnvironment workingEnvironmentInfo + workingEnvironment, err = findWEForID(id, workingEnvironments.VsaWorkingEnvironment) + if err == nil { + return workingEnvironment, nil + } + workingEnvironment, err = findWEForID(id, workingEnvironments.OnPremWorkingEnvironments) + if err == nil { + return workingEnvironment, nil + } + workingEnvironment, err = findWEForID(id, workingEnvironments.AzureVsaWorkingEnvironments) + if err == nil { + return workingEnvironment, nil + } + workingEnvironment, err = findWEForID(id, workingEnvironments.GcpVsaWorkingEnvironments) + if err == nil { + return workingEnvironment, nil + } + + log.Printf("Cannot find the working environment %s", id) + + return workingEnvironmentInfo{}, err +} diff --git a/cloudmanager/resource_netapp_cloudmanager_snapmirror.go b/cloudmanager/resource_netapp_cloudmanager_snapmirror.go index edc257e..79a40c6 100644 --- a/cloudmanager/resource_netapp_cloudmanager_snapmirror.go +++ b/cloudmanager/resource_netapp_cloudmanager_snapmirror.go @@ -144,7 +144,7 @@ func resourceCVOSnapMirrorCreate(d *schema.ResourceData, meta interface{}) error snapMirror.ReplicationVolume.DestinationSvmName = destWEInfo.SvmName } - res, err := client.buildSnapMirrorCreate(snapMirror) + res, err := client.buildSnapMirrorCreate(snapMirror, sourceWEInfo.WorkingEnvironmentType, destWEInfo.WorkingEnvironmentType) if err != nil { log.Print("Error creating SnapMirrorCreate") return err diff --git a/cloudmanager/snapmirror.go b/cloudmanager/snapmirror.go index c9483c8..fca2e5f 100644 --- a/cloudmanager/snapmirror.go +++ b/cloudmanager/snapmirror.go @@ -74,7 +74,7 @@ func (c *Client) getInterclusterlifs(snapMirror snapMirrorRequest) (intercluster return interclusterlifsResponse, nil } -func (c *Client) buildSnapMirrorCreate(snapMirror snapMirrorRequest) (snapMirrorRequest, error) { +func (c *Client) buildSnapMirrorCreate(snapMirror snapMirrorRequest, sourceWorkingEnvironmentType string, destWorkingEnvironmentType string) (snapMirrorRequest, error) { accessTokenResult, err := c.getAccessToken() if err != nil { @@ -89,14 +89,23 @@ func (c *Client) buildSnapMirrorCreate(snapMirror snapMirrorRequest) (snapMirror return snapMirrorRequest{}, err } + var volumeSource []volumeResponse volumeS := volumeRequest{} volumeS.WorkingEnvironmentID = snapMirror.ReplicationRequest.SourceWorkingEnvironmentID volumeS.Name = snapMirror.ReplicationVolume.SourceVolumeName - volumeSource, err := c.getVolume(volumeS) - if err != nil { - log.Print("Error reading source volume") - return snapMirrorRequest{}, err + if sourceWorkingEnvironmentType != "ON_PREM" { + volumeSource, err = c.getVolume(volumeS) + if err != nil { + log.Print("Error reading source volume") + return snapMirrorRequest{}, err + } + } else { + volumeSource, err = c.getVolumeForOnPrem(volumeS) + if err != nil { + log.Print("Error reading source volume") + return snapMirrorRequest{}, err + } } if len(volumeSource) == 0 { @@ -122,12 +131,21 @@ func (c *Client) buildSnapMirrorCreate(snapMirror snapMirrorRequest) (snapMirror return snapMirrorRequest{}, fmt.Errorf("source volume not found") } - quote := c.buildQuoteRequest(snapMirror, volDestQuote, snapMirror.ReplicationVolume.DestinationVolumeName, snapMirror.ReplicationVolume.DestinationSvmName, snapMirror.ReplicationRequest.DestinationWorkingEnvironmentID) + if destWorkingEnvironmentType != "ON_PREM" { + quote := c.buildQuoteRequest(snapMirror, volDestQuote, snapMirror.ReplicationVolume.DestinationVolumeName, snapMirror.ReplicationVolume.DestinationSvmName, snapMirror.ReplicationRequest.DestinationWorkingEnvironmentID) - quoteResponse, err := c.quoteVolume(quote) - if err != nil { - log.Printf("Error quoting destination volume") - return snapMirrorRequest{}, err + quoteResponse, err := c.quoteVolume(quote) + if err != nil { + log.Printf("Error quoting destination volume") + return snapMirrorRequest{}, err + } + snapMirror.ReplicationVolume.NumOfDisksApprovedToAdd = quoteResponse["numOfDisks"].(float64) + if snapMirror.ReplicationVolume.DestinationAggregateName != "" { + snapMirror.ReplicationVolume.AdvancedMode = true + } else { + snapMirror.ReplicationVolume.AdvancedMode = false + snapMirror.ReplicationVolume.DestinationAggregateName = quoteResponse["aggregateName"].(string) + } } var sourceInterclusterLifIps []string @@ -139,20 +157,11 @@ func (c *Client) buildSnapMirrorCreate(snapMirror snapMirrorRequest) (snapMirror snapMirror.ReplicationVolume.SourceSvmName = sourceVolume.SvmName snapMirror.ReplicationVolume.SourceVolumeName = sourceVolume.Name - snapMirror.ReplicationVolume.NumOfDisksApprovedToAdd = quoteResponse["numOfDisks"].(float64) - if snapMirror.ReplicationVolume.DestinationProviderVolumeType == "" { snapMirror.ReplicationVolume.DestinationProviderVolumeType = sourceVolume.ProviderVolumeType } - if snapMirror.ReplicationVolume.DestinationAggregateName != "" { - snapMirror.ReplicationVolume.AdvancedMode = true - } else { - snapMirror.ReplicationVolume.AdvancedMode = false - snapMirror.ReplicationVolume.DestinationAggregateName = quoteResponse["aggregateName"].(string) - } - - err = c.createSnapMirror(snapMirror) + err = c.createSnapMirror(snapMirror, destWorkingEnvironmentType) if err != nil { log.Printf("Error creating snapmirror") return snapMirrorRequest{}, err @@ -168,7 +177,6 @@ func (c *Client) buildQuoteRequest(snapMirror snapMirrorRequest, vol volumeRespo quote.Size.Size = vol.Size.Size quote.Size.Unit = vol.Size.Unit quote.SnapshotPolicyName = vol.SnapshotPolicyName - quote.ProviderVolumeType = vol.ProviderVolumeType quote.EnableDeduplication = vol.EnableDeduplication quote.EnableThinProvisioning = vol.EnableThinProvisioning quote.EnableCompression = vol.EnableCompression @@ -188,8 +196,13 @@ func (c *Client) buildQuoteRequest(snapMirror snapMirrorRequest, vol volumeRespo return quote } -func (c *Client) createSnapMirror(sm snapMirrorRequest) error { - baseURL := "/occm/api/replication/vsa" +func (c *Client) createSnapMirror(sm snapMirrorRequest, destWorkingEnvironmentType string) error { + var baseURL string + if destWorkingEnvironmentType != "ON_PREM" { + baseURL = "/occm/api/replication/vsa" + } else { + baseURL = "/occm/api/replication/onprem" + } hostType := "CloudManagerHost" params := structs.Map(sm) diff --git a/cloudmanager/volume.go b/cloudmanager/volume.go index b7e52cf..7481f57 100644 --- a/cloudmanager/volume.go +++ b/cloudmanager/volume.go @@ -174,8 +174,8 @@ func (c *Client) deleteVolume(vol volumeRequest) error { if responseError != nil { return responseError } - - log.Print("Wait for volume deletion.") + + log.Print("Wait for volume deletion.") err = c.waitOnCompletion(onCloudRequestID, "volume", "delete", 10, 60) if err != nil { log.Print("deleteVolume request failed ", statusCode) @@ -210,6 +210,28 @@ func (c *Client) getVolume(vol volumeRequest) ([]volumeResponse, error) { return result, nil } +func (c *Client) getVolumeForOnPrem(vol volumeRequest) ([]volumeResponse, error) { + var result []volumeResponse + hostType := "CloudManagerHost" + baseURL := fmt.Sprintf("/occm/api/onprem/volumes?workingEnvironmentId=%s", vol.WorkingEnvironmentID) + + statusCode, response, _, err := c.CallAPIMethod("GET", baseURL, nil, c.Token, hostType) + if err != nil { + log.Print("getVolumeForOnPrem request failed ", statusCode) + return result, err + } + responseError := apiResponseChecker(statusCode, response, "getVolumeForOnPrem") + if responseError != nil { + return result, responseError + } + if err := json.Unmarshal(response, &result); err != nil { + log.Print("Failed to unmarshall response from getVolumeForOnPrem ", err) + return result, err + } + + return result, nil +} + func (c *Client) getVolumeByID(request volumeRequest) (volumeResponse, error) { res, err := c.getVolume(request) if err != nil { From acdd38aa0310b1e26d90530e1ef1fa1d1899752f Mon Sep 17 00:00:00 2001 From: Wenjun Shen Date: Mon, 11 Jan 2021 16:18:14 -0800 Subject: [PATCH 3/3] run go mod tidy --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index 0d9f997..d38e560 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,9 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 github.com/aws/aws-sdk-go v1.35.5 github.com/fatih/structs v1.1.0 - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/hashicorp/terraform v0.13.4 github.com/sirupsen/logrus v1.7.0 - github.com/stretchr/testify v1.5.1 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.0.0-20201008025239-9df69603baec // indirect - google.golang.org/protobuf v1.25.0 // indirect gopkg.in/yaml.v2 v2.2.8 )