c76ad94492
This commit adds a build step to travis to auto-delete unstable archives older than 14 days (our regular release schedule) from Azure via ci.go purge. The commit also pulls in the latest Azure storage code, also switching over from the old import path (github.com/Azure/azure-sdk-for-go) to the new split one (github.com/Azure/azure-storage-go).
255 lines
6.7 KiB
Go
255 lines
6.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// AzureTable is the typedef of the Azure Table name
|
|
type AzureTable string
|
|
|
|
const (
|
|
tablesURIPath = "/Tables"
|
|
)
|
|
|
|
type createTableRequest struct {
|
|
TableName string `json:"TableName"`
|
|
}
|
|
|
|
// TableAccessPolicy are used for SETTING table policies
|
|
type TableAccessPolicy struct {
|
|
ID string
|
|
StartTime time.Time
|
|
ExpiryTime time.Time
|
|
CanRead bool
|
|
CanAppend bool
|
|
CanUpdate bool
|
|
CanDelete bool
|
|
}
|
|
|
|
func pathForTable(table AzureTable) string { return fmt.Sprintf("%s", table) }
|
|
|
|
func (c *TableServiceClient) getStandardHeaders() map[string]string {
|
|
return map[string]string{
|
|
"x-ms-version": "2015-02-21",
|
|
"x-ms-date": currentTimeRfc1123Formatted(),
|
|
"Accept": "application/json;odata=nometadata",
|
|
"Accept-Charset": "UTF-8",
|
|
"Content-Type": "application/json",
|
|
userAgentHeader: c.client.userAgent,
|
|
}
|
|
}
|
|
|
|
// QueryTables returns the tables created in the
|
|
// *TableServiceClient storage account.
|
|
func (c *TableServiceClient) QueryTables() ([]AzureTable, error) {
|
|
uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{})
|
|
|
|
headers := c.getStandardHeaders()
|
|
headers["Content-Length"] = "0"
|
|
|
|
resp, err := c.client.execInternalJSON(http.MethodGet, uri, headers, nil, c.auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.body.Close()
|
|
|
|
if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
|
|
ioutil.ReadAll(resp.body)
|
|
return nil, err
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
if _, err := buf.ReadFrom(resp.body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var respArray queryTablesResponse
|
|
if err := json.Unmarshal(buf.Bytes(), &respArray); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := make([]AzureTable, len(respArray.TableName))
|
|
for i, elem := range respArray.TableName {
|
|
s[i] = AzureTable(elem.TableName)
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// CreateTable creates the table given the specific
|
|
// name. This function fails if the name is not compliant
|
|
// with the specification or the tables already exists.
|
|
func (c *TableServiceClient) CreateTable(table AzureTable) error {
|
|
uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{})
|
|
|
|
headers := c.getStandardHeaders()
|
|
|
|
req := createTableRequest{TableName: string(table)}
|
|
buf := new(bytes.Buffer)
|
|
|
|
if err := json.NewEncoder(buf).Encode(req); err != nil {
|
|
return err
|
|
}
|
|
|
|
headers["Content-Length"] = fmt.Sprintf("%d", buf.Len())
|
|
|
|
resp, err := c.client.execInternalJSON(http.MethodPost, uri, headers, buf, c.auth)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer readAndCloseBody(resp.body)
|
|
|
|
if err := checkRespCode(resp.statusCode, []int{http.StatusCreated}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteTable deletes the table given the specific
|
|
// name. This function fails if the table is not present.
|
|
// Be advised: DeleteTable deletes all the entries
|
|
// that may be present.
|
|
func (c *TableServiceClient) DeleteTable(table AzureTable) error {
|
|
uri := c.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{})
|
|
uri += fmt.Sprintf("('%s')", string(table))
|
|
|
|
headers := c.getStandardHeaders()
|
|
|
|
headers["Content-Length"] = "0"
|
|
|
|
resp, err := c.client.execInternalJSON(http.MethodDelete, uri, headers, nil, c.auth)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer readAndCloseBody(resp.body)
|
|
|
|
if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil {
|
|
return err
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetTablePermissions sets up table ACL permissions as per REST details https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Table-ACL
|
|
func (c *TableServiceClient) SetTablePermissions(table AzureTable, policies []TableAccessPolicy, timeout uint) (err error) {
|
|
params := url.Values{"comp": {"acl"}}
|
|
|
|
if timeout > 0 {
|
|
params.Add("timeout", fmt.Sprint(timeout))
|
|
}
|
|
|
|
uri := c.client.getEndpoint(tableServiceName, string(table), params)
|
|
headers := c.client.getStandardHeaders()
|
|
|
|
body, length, err := generateTableACLPayload(policies)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
headers["Content-Length"] = fmt.Sprintf("%v", length)
|
|
|
|
resp, err := c.client.execInternalJSON(http.MethodPut, uri, headers, body, c.auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer readAndCloseBody(resp.body)
|
|
|
|
if err := checkRespCode(resp.statusCode, []int{http.StatusNoContent}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func generateTableACLPayload(policies []TableAccessPolicy) (io.Reader, int, error) {
|
|
sil := SignedIdentifiers{
|
|
SignedIdentifiers: []SignedIdentifier{},
|
|
}
|
|
for _, tap := range policies {
|
|
permission := generateTablePermissions(&tap)
|
|
signedIdentifier := convertAccessPolicyToXMLStructs(tap.ID, tap.StartTime, tap.ExpiryTime, permission)
|
|
sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
|
|
}
|
|
return xmlMarshal(sil)
|
|
}
|
|
|
|
// GetTablePermissions gets the table ACL permissions, as per REST details https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-acl
|
|
func (c *TableServiceClient) GetTablePermissions(table AzureTable, timeout int) (permissionResponse []TableAccessPolicy, err error) {
|
|
params := url.Values{"comp": {"acl"}}
|
|
|
|
if timeout > 0 {
|
|
params.Add("timeout", strconv.Itoa(timeout))
|
|
}
|
|
|
|
uri := c.client.getEndpoint(tableServiceName, string(table), params)
|
|
headers := c.client.getStandardHeaders()
|
|
resp, err := c.client.execInternalJSON(http.MethodGet, uri, headers, nil, c.auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.body.Close()
|
|
|
|
if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
|
|
ioutil.ReadAll(resp.body)
|
|
return nil, err
|
|
}
|
|
|
|
var ap AccessPolicy
|
|
err = xmlUnmarshal(resp.body, &ap.SignedIdentifiersList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := updateTableAccessPolicy(ap)
|
|
return out, nil
|
|
}
|
|
|
|
func updateTableAccessPolicy(ap AccessPolicy) []TableAccessPolicy {
|
|
out := []TableAccessPolicy{}
|
|
for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
|
|
tap := TableAccessPolicy{
|
|
ID: policy.ID,
|
|
StartTime: policy.AccessPolicy.StartTime,
|
|
ExpiryTime: policy.AccessPolicy.ExpiryTime,
|
|
}
|
|
tap.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
|
|
tap.CanAppend = updatePermissions(policy.AccessPolicy.Permission, "a")
|
|
tap.CanUpdate = updatePermissions(policy.AccessPolicy.Permission, "u")
|
|
tap.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
|
|
|
|
out = append(out, tap)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func generateTablePermissions(tap *TableAccessPolicy) (permissions string) {
|
|
// generate the permissions string (raud).
|
|
// still want the end user API to have bool flags.
|
|
permissions = ""
|
|
|
|
if tap.CanRead {
|
|
permissions += "r"
|
|
}
|
|
|
|
if tap.CanAppend {
|
|
permissions += "a"
|
|
}
|
|
|
|
if tap.CanUpdate {
|
|
permissions += "u"
|
|
}
|
|
|
|
if tap.CanDelete {
|
|
permissions += "d"
|
|
}
|
|
return permissions
|
|
}
|