-
Notifications
You must be signed in to change notification settings - Fork 4.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New data source: azurerm_storage_account_blob_container_sas #4195
Changes from 10 commits
44cc35f
c771f5b
b2f50f9
c6038e8
8d0879b
738024a
53e71cc
43518d0
6484896
37447b3
7274112
dd2c3ca
24eec92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,222 @@ | ||||||||
package azurerm | ||||||||
|
||||||||
import ( | ||||||||
"crypto/sha256" | ||||||||
"encoding/hex" | ||||||||
|
||||||||
"github.com/hashicorp/go-azure-helpers/storage" | ||||||||
"github.com/hashicorp/terraform/helper/schema" | ||||||||
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" | ||||||||
) | ||||||||
|
||||||||
func dataSourceArmStorageAccountBlobContainerSharedAccessSignature() *schema.Resource { | ||||||||
return &schema.Resource{ | ||||||||
Read: dataSourceArmStorageContainerSasRead, | ||||||||
|
||||||||
Schema: map[string]*schema.Schema{ | ||||||||
"connection_string": { | ||||||||
Type: schema.TypeString, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
Sensitive: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add:
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"container_name": { | ||||||||
Type: schema.TypeString, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we remove ForceNew/add:
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"https_only": { | ||||||||
Type: schema.TypeBool, | ||||||||
Optional: true, | ||||||||
Default: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"ip": { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 on reflection could we make this |
||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
ValidateFunc: validate.SharedAccessSignatureIP, | ||||||||
}, | ||||||||
|
||||||||
"start": { | ||||||||
Type: schema.TypeString, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
ValidateFunc: validate.ISO8601DateTime, | ||||||||
}, | ||||||||
|
||||||||
"expiry": { | ||||||||
Type: schema.TypeString, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
ValidateFunc: validate.ISO8601DateTime, | ||||||||
}, | ||||||||
|
||||||||
"permissions": { | ||||||||
Type: schema.TypeList, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
MaxItems: 1, | ||||||||
Elem: &schema.Resource{ | ||||||||
Schema: map[string]*schema.Schema{ | ||||||||
"read": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"add": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"create": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
}, | ||||||||
|
||||||||
"write": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"delete": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"list": { | ||||||||
Type: schema.TypeBool, | ||||||||
Required: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
}, | ||||||||
}, | ||||||||
}, | ||||||||
|
||||||||
"cache_control": { | ||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"content_disposition": { | ||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"content_encoding": { | ||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"content_language": { | ||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)
Suggested change
|
||||||||
}, | ||||||||
|
||||||||
"content_type": { | ||||||||
Type: schema.TypeString, | ||||||||
Optional: true, | ||||||||
ForceNew: true, | ||||||||
r0bnet marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
}, | ||||||||
|
||||||||
"sas": { | ||||||||
Type: schema.TypeString, | ||||||||
Computed: true, | ||||||||
Sensitive: true, | ||||||||
}, | ||||||||
}, | ||||||||
} | ||||||||
|
||||||||
} | ||||||||
|
||||||||
func dataSourceArmStorageContainerSasRead(d *schema.ResourceData, _ interface{}) error { | ||||||||
|
||||||||
connString := d.Get("connection_string").(string) | ||||||||
containerName := d.Get("container_name").(string) | ||||||||
httpsOnly := d.Get("https_only").(bool) | ||||||||
ip := d.Get("ip").(string) | ||||||||
start := d.Get("start").(string) | ||||||||
expiry := d.Get("expiry").(string) | ||||||||
permissionsIface := d.Get("permissions").([]interface{}) | ||||||||
|
||||||||
// response headers | ||||||||
cacheControl := d.Get("cache_control").(string) | ||||||||
contentDisposition := d.Get("content_disposition").(string) | ||||||||
contentEncoding := d.Get("content_encoding").(string) | ||||||||
contentLanguage := d.Get("content_language").(string) | ||||||||
contentType := d.Get("content_type").(string) | ||||||||
|
||||||||
permissions := buildContainerPermissionsString(permissionsIface[0].(map[string]interface{})) | ||||||||
|
||||||||
// Parse the connection string | ||||||||
kvp, err := storage.ParseAccountSASConnectionString(connString) | ||||||||
if err != nil { | ||||||||
return err | ||||||||
} | ||||||||
|
||||||||
// Create the string to sign with the key... | ||||||||
accountName := kvp[connStringAccountNameKey] | ||||||||
accountKey := kvp[connStringAccountKeyKey] | ||||||||
var signedProtocol = "https,http" | ||||||||
if httpsOnly { | ||||||||
signedProtocol = "https" | ||||||||
} | ||||||||
signedIp := ip | ||||||||
signedIdentifier := "" | ||||||||
signedSnapshotTime := "" | ||||||||
|
||||||||
sasToken, err := storage.ComputeContainerSASToken(permissions, start, expiry, accountName, accountKey, | ||||||||
containerName, signedIdentifier, signedIp, signedProtocol, signedSnapshotTime, cacheControl, | ||||||||
contentDisposition, contentEncoding, contentLanguage, contentType) | ||||||||
if err != nil { | ||||||||
return err | ||||||||
} | ||||||||
|
||||||||
d.Set("sas", sasToken) | ||||||||
tokenHash := sha256.Sum256([]byte(sasToken)) | ||||||||
d.SetId(hex.EncodeToString(tokenHash[:])) | ||||||||
|
||||||||
return nil | ||||||||
} | ||||||||
|
||||||||
func buildContainerPermissionsString(perms map[string]interface{}) string { | ||||||||
retVal := "" | ||||||||
|
||||||||
if val, pres := perms["read"].(bool); pres && val { | ||||||||
retVal += "r" | ||||||||
} | ||||||||
|
||||||||
if val, pres := perms["add"].(bool); pres && val { | ||||||||
retVal += "a" | ||||||||
} | ||||||||
|
||||||||
if val, pres := perms["create"].(bool); pres && val { | ||||||||
retVal += "c" | ||||||||
} | ||||||||
|
||||||||
if val, pres := perms["write"].(bool); pres && val { | ||||||||
retVal += "w" | ||||||||
} | ||||||||
|
||||||||
if val, pres := perms["delete"].(bool); pres && val { | ||||||||
retVal += "d" | ||||||||
} | ||||||||
|
||||||||
if val, pres := perms["list"].(bool); pres && val { | ||||||||
retVal += "l" | ||||||||
} | ||||||||
|
||||||||
return retVal | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package azurerm | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform/helper/acctest" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" | ||
) | ||
|
||
func TestAccDataSourceArmStorageAccountBlobContainerSas_basic(t *testing.T) { | ||
dataSourceName := "data.azurerm_storage_account_blob_container_sas.test" | ||
rInt := tf.AccRandTimeInt() | ||
rString := acctest.RandString(4) | ||
location := testLocation() | ||
utcNow := time.Now().UTC() | ||
startDate := utcNow.Format(time.RFC3339) | ||
endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339) | ||
|
||
resource.ParallelTest(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccDataSourceAzureRMStorageAccountBlobContainerSas_basic(rInt, rString, location, startDate, endDate), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(dataSourceName, "https_only", "true"), | ||
resource.TestCheckResourceAttr(dataSourceName, "start", startDate), | ||
resource.TestCheckResourceAttr(dataSourceName, "expiry", endDate), | ||
resource.TestCheckResourceAttr(dataSourceName, "ip", "168.1.5.65"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.#", "1"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.read", "true"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.add", "true"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.create", "false"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.write", "false"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.delete", "true"), | ||
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.list", "true"), | ||
resource.TestCheckResourceAttr(dataSourceName, "cache_control", "max-age=5"), | ||
resource.TestCheckResourceAttr(dataSourceName, "content_disposition", "inline"), | ||
resource.TestCheckResourceAttr(dataSourceName, "content_encoding", "deflate"), | ||
resource.TestCheckResourceAttr(dataSourceName, "content_language", "en-US"), | ||
resource.TestCheckResourceAttr(dataSourceName, "content_type", "application/json"), | ||
resource.TestCheckResourceAttrSet(dataSourceName, "sas"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccDataSourceAzureRMStorageAccountBlobContainerSas_basic(rInt int, rString string, location string, startDate string, endDate string) string { | ||
return fmt.Sprintf(` | ||
resource "azurerm_resource_group" "rg" { | ||
name = "acctestsa-%d" | ||
location = "%s" | ||
} | ||
|
||
resource "azurerm_storage_account" "storage" { | ||
name = "acctestsads%s" | ||
resource_group_name = "${azurerm_resource_group.rg.name}" | ||
|
||
location = "${azurerm_resource_group.rg.location}" | ||
account_tier = "Standard" | ||
account_replication_type = "LRS" | ||
} | ||
|
||
resource "azurerm_storage_container" "container" { | ||
name = "sas-test" | ||
resource_group_name = "${azurerm_resource_group.rg.name}" | ||
storage_account_name = "${azurerm_storage_account.storage.name}" | ||
container_access_type = "private" | ||
} | ||
|
||
data "azurerm_storage_account_blob_container_sas" "test" { | ||
connection_string = "${azurerm_storage_account.storage.primary_connection_string}" | ||
container_name = "${azurerm_storage_container.container.name}" | ||
https_only = true | ||
|
||
ip = "168.1.5.65" | ||
|
||
start = "%s" | ||
expiry = "%s" | ||
|
||
permissions { | ||
read = true | ||
add = true | ||
create = false | ||
write = false | ||
delete = true | ||
list = true | ||
} | ||
|
||
cache_control = "max-age=5" | ||
content_disposition = "inline" | ||
content_encoding = "deflate" | ||
content_language = "en-US" | ||
content_type = "application/json" | ||
} | ||
`, rInt, location, rString, startDate, endDate) | ||
} | ||
|
||
func TestAccDataSourceArmStorageAccountBlobContainerSas_permissionsString(t *testing.T) { | ||
testCases := []struct { | ||
input map[string]interface{} | ||
expected string | ||
}{ | ||
{map[string]interface{}{"read": true}, "r"}, | ||
{map[string]interface{}{"add": true}, "a"}, | ||
{map[string]interface{}{"create": true}, "c"}, | ||
{map[string]interface{}{"write": true}, "w"}, | ||
{map[string]interface{}{"delete": true}, "d"}, | ||
{map[string]interface{}{"list": true}, "l"}, | ||
{map[string]interface{}{"add": true, "write": true, "read": true, "delete": true}, "rawd"}, | ||
} | ||
|
||
for _, test := range testCases { | ||
result := buildContainerPermissionsString(test.input) | ||
if test.expected != result { | ||
t.Fatalf("Failed to build resource type string: expected: %s, result: %s", test.expected, result) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package validate | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strings" | ||
) | ||
|
||
func SharedAccessSignatureIP(v interface{}, k string) (warnings []string, errors []error) { | ||
value := v.(string) | ||
|
||
if net.ParseIP(value) != nil { | ||
return warnings, errors | ||
} | ||
|
||
ipRange := strings.Split(value, "-") | ||
|
||
if len(ipRange) != 2 || net.ParseIP(ipRange[0]) == nil || net.ParseIP(ipRange[1]) == nil { | ||
errors = append(errors, fmt.Errorf("%q must be a valid ipv4 address or a range of ipv4 addresses separated by a hyphen", k)) | ||
return warnings, errors | ||
} | ||
|
||
ip1 := ipRange[0] | ||
ip2 := ipRange[1] | ||
|
||
if ip1 == ip2 { | ||
errors = append(errors, fmt.Errorf("IP addresses in a range for %q must be not be identical", k)) | ||
return warnings, errors | ||
} | ||
|
||
return warnings, errors | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this is a Data Source we don't need the ForceNew here (since this'll be recomputed every time)