<commit_before>package azurerm

import (
	"fmt"
	"log"
	"regexp"

	"time"

	"net"

	"github.com/Azure/azure-sdk-for-go/arm/keyvault"
	"github.com/hashicorp/go-getter/helper/url"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/satori/uuid"
	"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

// As can be seen in the API definition, the Sku Family only supports the value
// `A` and is a required field
// https://github.com/Azure/azure-rest-api-specs/blob/master/arm-keyvault/2015-06-01/swagger/keyvault.json#L239
var armKeyVaultSkuFamily = "A"

func resourceArmKeyVault() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmKeyVaultCreate,
		Read:   resourceArmKeyVaultRead,
		Update: resourceArmKeyVaultCreate,
		Delete: resourceArmKeyVaultDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateKeyVaultName,
			},

			"location": locationSchema(),

			"resource_group_name": resourceGroupNameSchema(),

			"sku": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": {
							Type:     schema.TypeString,
							Required: true,
							ValidateFunc: validation.StringInSlice([]string{
								string(keyvault.Standard),
								string(keyvault.Premium),
							}, false),
						},
					},
				},
			},

			"vault_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tenant_id": {
				Type:         schema.TypeString,
				Required:     true,
				ValidateFunc: validateUUID,
			},

			"access_policy": {
				Type:     schema.TypeList,
				Optional: true,
				MinItems: 1,
				MaxItems: 16,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"tenant_id": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validateUUID,
						},
						"object_id": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validateUUID,
						},
						"application_id": {
							Type:         schema.TypeString,
							Optional:     true,
							ValidateFunc: validateUUID,
						},
						"certificate_permissions": {
							Type:     schema.TypeList,
							Optional: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.All),
									string(keyvault.Create),
									string(keyvault.Delete),
									string(keyvault.Deleteissuers),
									string(keyvault.Get),
									string(keyvault.Getissuers),
									string(keyvault.Import),
									string(keyvault.List),
									string(keyvault.Listissuers),
									string(keyvault.Managecontacts),
									string(keyvault.Manageissuers),
									string(keyvault.Setissuers),
									string(keyvault.Update),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
						"key_permissions": {
							Type:     schema.TypeList,
							Required: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.KeyPermissionsAll),
									string(keyvault.KeyPermissionsBackup),
									string(keyvault.KeyPermissionsCreate),
									string(keyvault.KeyPermissionsDecrypt),
									string(keyvault.KeyPermissionsDelete),
									string(keyvault.KeyPermissionsEncrypt),
									string(keyvault.KeyPermissionsGet),
									string(keyvault.KeyPermissionsImport),
									string(keyvault.KeyPermissionsList),
									string(keyvault.KeyPermissionsRestore),
									string(keyvault.KeyPermissionsSign),
									string(keyvault.KeyPermissionsUnwrapKey),
									string(keyvault.KeyPermissionsUpdate),
									string(keyvault.KeyPermissionsVerify),
									string(keyvault.KeyPermissionsWrapKey),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
						"secret_permissions": {
							Type:     schema.TypeList,
							Required: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.SecretPermissionsAll),
									string(keyvault.SecretPermissionsDelete),
									string(keyvault.SecretPermissionsGet),
									string(keyvault.SecretPermissionsList),
									string(keyvault.SecretPermissionsSet),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
					},
				},
			},

			"enabled_for_deployment": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"enabled_for_disk_encryption": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"enabled_for_template_deployment": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient
	log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string))
	enabledForDeployment := d.Get("enabled_for_deployment").(bool)
	enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool)
	enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool)
	tags := d.Get("tags").(map[string]interface{})

	parameters := keyvault.VaultCreateOrUpdateParameters{
		Location: &location,
		Properties: &keyvault.VaultProperties{
			TenantID:                     &tenantUUID,
			Sku:                          expandKeyVaultSku(d),
			AccessPolicies:               expandKeyVaultAccessPolicies(d),
			EnabledForDeployment:         &enabledForDeployment,
			EnabledForDiskEncryption:     &enabledForDiskEncryption,
			EnabledForTemplateDeployment: &enabledForTemplateDeployment,
		},
		Tags: expandTags(tags),
	}

	_, err := client.CreateOrUpdate(resGroup, name, parameters)
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("Cannot read KeyVault %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	if d.IsNewResource() {
		if props := read.Properties; props != nil {
			if vault := props.VaultURI; vault != nil {
				err := resource.Retry(30*time.Second, checkKeyVaultDNSIsAvailable(*vault))
				if err != nil {
					return err
				}
			}
		}
	}

	return resourceArmKeyVaultRead(d, meta)
}

func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["vaults"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if utils.ResponseWasNotFound(resp.Response) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Azure KeyVault %s: %+v", name, err)
	}

	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("tenant_id", resp.Properties.TenantID.String())
	d.Set("enabled_for_deployment", resp.Properties.EnabledForDeployment)
	d.Set("enabled_for_disk_encryption", resp.Properties.EnabledForDiskEncryption)
	d.Set("enabled_for_template_deployment", resp.Properties.EnabledForTemplateDeployment)
	d.Set("sku", flattenKeyVaultSku(resp.Properties.Sku))
	d.Set("access_policy", flattenKeyVaultAccessPolicies(resp.Properties.AccessPolicies))
	d.Set("vault_uri", resp.Properties.VaultURI)

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["vaults"]

	_, err = client.Delete(resGroup, name)

	return err
}

func expandKeyVaultSku(d *schema.ResourceData) *keyvault.Sku {
	skuSets := d.Get("sku").(*schema.Set).List()
	sku := skuSets[0].(map[string]interface{})

	return &keyvault.Sku{
		Family: &armKeyVaultSkuFamily,
		Name:   keyvault.SkuName(sku["name"].(string)),
	}
}

func expandKeyVaultAccessPolicies(d *schema.ResourceData) *[]keyvault.AccessPolicyEntry {
	policies := d.Get("access_policy").([]interface{})
	result := make([]keyvault.AccessPolicyEntry, 0, len(policies))

	for _, policySet := range policies {
		policyRaw := policySet.(map[string]interface{})

		certificatePermissionsRaw := policyRaw["certificate_permissions"].([]interface{})
		certificatePermissions := []keyvault.CertificatePermissions{}
		for _, permission := range certificatePermissionsRaw {
			certificatePermissions = append(certificatePermissions, keyvault.CertificatePermissions(permission.(string)))
		}

		keyPermissionsRaw := policyRaw["key_permissions"].([]interface{})
		keyPermissions := []keyvault.KeyPermissions{}
		for _, permission := range keyPermissionsRaw {
			keyPermissions = append(keyPermissions, keyvault.KeyPermissions(permission.(string)))
		}

		secretPermissionsRaw := policyRaw["secret_permissions"].([]interface{})
		secretPermissions := []keyvault.SecretPermissions{}
		for _, permission := range secretPermissionsRaw {
			secretPermissions = append(secretPermissions, keyvault.SecretPermissions(permission.(string)))
		}

		policy := keyvault.AccessPolicyEntry{
			Permissions: &keyvault.Permissions{
				Certificates: &certificatePermissions,
				Keys:         &keyPermissions,
				Secrets:      &secretPermissions,
			},
		}

		tenantUUID := uuid.FromStringOrNil(policyRaw["tenant_id"].(string))
		policy.TenantID = &tenantUUID
		objectUUID := policyRaw["object_id"].(string)
		policy.ObjectID = &objectUUID

		if v := policyRaw["application_id"]; v != "" {
			applicationUUID := uuid.FromStringOrNil(v.(string))
			policy.ApplicationID = &applicationUUID
		}

		result = append(result, policy)
	}

	return &result
}

func flattenKeyVaultSku(sku *keyvault.Sku) []interface{} {
	result := map[string]interface{}{
		"name": string(sku.Name),
	}

	return []interface{}{result}
}

func flattenKeyVaultAccessPolicies(policies *[]keyvault.AccessPolicyEntry) []interface{} {
	result := make([]interface{}, 0, len(*policies))

	for _, policy := range *policies {
		policyRaw := make(map[string]interface{})

		keyPermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Keys))
		for _, keyPermission := range *policy.Permissions.Keys {
			keyPermissionsRaw = append(keyPermissionsRaw, string(keyPermission))
		}

		secretPermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Secrets))
		for _, secretPermission := range *policy.Permissions.Secrets {
			secretPermissionsRaw = append(secretPermissionsRaw, string(secretPermission))
		}

		policyRaw["tenant_id"] = policy.TenantID.String()
		policyRaw["object_id"] = *policy.ObjectID
		if policy.ApplicationID != nil {
			policyRaw["application_id"] = policy.ApplicationID.String()
		}
		policyRaw["key_permissions"] = keyPermissionsRaw
		policyRaw["secret_permissions"] = secretPermissionsRaw

		if policy.Permissions.Certificates != nil {
			certificatePermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Certificates))
			for _, certificatePermission := range *policy.Permissions.Certificates {
				certificatePermissionsRaw = append(certificatePermissionsRaw, string(certificatePermission))
			}
			policyRaw["certificate_permissions"] = certificatePermissionsRaw
		}

		result = append(result, policyRaw)
	}

	return result
}

func validateKeyVaultName(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)
	if matched := regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`).Match([]byte(value)); !matched {
		errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and must be between 3-24 chars", k))
	}

	return
}

func checkKeyVaultDNSIsAvailable(vaultUri string) func() *resource.RetryError {
	return func() *resource.RetryError {
		uri, err := url.Parse(vaultUri)
		if err != nil {
			return resource.NonRetryableError(err)
		}

		conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", uri.Host))
		if err != nil {
			return resource.RetryableError(err)
		}

		_ = conn.Close()
		return nil
	}
}
<commit_msg>Updating SKU to be a List rather than a Set<commit_after>package azurerm

import (
	"fmt"
	"log"
	"regexp"

	"time"

	"net"

	"github.com/Azure/azure-sdk-for-go/arm/keyvault"
	"github.com/hashicorp/go-getter/helper/url"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/satori/uuid"
	"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

// As can be seen in the API definition, the Sku Family only supports the value
// `A` and is a required field
// https://github.com/Azure/azure-rest-api-specs/blob/master/arm-keyvault/2015-06-01/swagger/keyvault.json#L239
var armKeyVaultSkuFamily = "A"

func resourceArmKeyVault() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmKeyVaultCreate,
		Read:   resourceArmKeyVaultRead,
		Update: resourceArmKeyVaultCreate,
		Delete: resourceArmKeyVaultDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateKeyVaultName,
			},

			"location": locationSchema(),

			"resource_group_name": resourceGroupNameSchema(),

			"sku": {
				Type:     schema.TypeList,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": {
							Type:     schema.TypeString,
							Required: true,
							ValidateFunc: validation.StringInSlice([]string{
								string(keyvault.Standard),
								string(keyvault.Premium),
							}, false),
						},
					},
				},
			},

			"vault_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tenant_id": {
				Type:         schema.TypeString,
				Required:     true,
				ValidateFunc: validateUUID,
			},

			"access_policy": {
				Type:     schema.TypeList,
				Optional: true,
				MinItems: 1,
				MaxItems: 16,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"tenant_id": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validateUUID,
						},
						"object_id": {
							Type:         schema.TypeString,
							Required:     true,
							ValidateFunc: validateUUID,
						},
						"application_id": {
							Type:         schema.TypeString,
							Optional:     true,
							ValidateFunc: validateUUID,
						},
						"certificate_permissions": {
							Type:     schema.TypeList,
							Optional: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.All),
									string(keyvault.Create),
									string(keyvault.Delete),
									string(keyvault.Deleteissuers),
									string(keyvault.Get),
									string(keyvault.Getissuers),
									string(keyvault.Import),
									string(keyvault.List),
									string(keyvault.Listissuers),
									string(keyvault.Managecontacts),
									string(keyvault.Manageissuers),
									string(keyvault.Setissuers),
									string(keyvault.Update),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
						"key_permissions": {
							Type:     schema.TypeList,
							Required: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.KeyPermissionsAll),
									string(keyvault.KeyPermissionsBackup),
									string(keyvault.KeyPermissionsCreate),
									string(keyvault.KeyPermissionsDecrypt),
									string(keyvault.KeyPermissionsDelete),
									string(keyvault.KeyPermissionsEncrypt),
									string(keyvault.KeyPermissionsGet),
									string(keyvault.KeyPermissionsImport),
									string(keyvault.KeyPermissionsList),
									string(keyvault.KeyPermissionsRestore),
									string(keyvault.KeyPermissionsSign),
									string(keyvault.KeyPermissionsUnwrapKey),
									string(keyvault.KeyPermissionsUpdate),
									string(keyvault.KeyPermissionsVerify),
									string(keyvault.KeyPermissionsWrapKey),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
						"secret_permissions": {
							Type:     schema.TypeList,
							Required: true,
							Elem: &schema.Schema{
								Type: schema.TypeString,
								ValidateFunc: validation.StringInSlice([]string{
									string(keyvault.SecretPermissionsAll),
									string(keyvault.SecretPermissionsDelete),
									string(keyvault.SecretPermissionsGet),
									string(keyvault.SecretPermissionsList),
									string(keyvault.SecretPermissionsSet),
								}, true),
								DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							},
						},
					},
				},
			},

			"enabled_for_deployment": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"enabled_for_disk_encryption": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"enabled_for_template_deployment": {
				Type:     schema.TypeBool,
				Optional: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient
	log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string))
	enabledForDeployment := d.Get("enabled_for_deployment").(bool)
	enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool)
	enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool)
	tags := d.Get("tags").(map[string]interface{})

	parameters := keyvault.VaultCreateOrUpdateParameters{
		Location: &location,
		Properties: &keyvault.VaultProperties{
			TenantID:                     &tenantUUID,
			Sku:                          expandKeyVaultSku(d),
			AccessPolicies:               expandKeyVaultAccessPolicies(d),
			EnabledForDeployment:         &enabledForDeployment,
			EnabledForDiskEncryption:     &enabledForDiskEncryption,
			EnabledForTemplateDeployment: &enabledForTemplateDeployment,
		},
		Tags: expandTags(tags),
	}

	_, err := client.CreateOrUpdate(resGroup, name, parameters)
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("Cannot read KeyVault %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	if d.IsNewResource() {
		if props := read.Properties; props != nil {
			if vault := props.VaultURI; vault != nil {
				err := resource.Retry(30*time.Second, checkKeyVaultDNSIsAvailable(*vault))
				if err != nil {
					return err
				}
			}
		}
	}

	return resourceArmKeyVaultRead(d, meta)
}

func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["vaults"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		if utils.ResponseWasNotFound(resp.Response) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error making Read request on Azure KeyVault %s: %+v", name, err)
	}

	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("tenant_id", resp.Properties.TenantID.String())
	d.Set("enabled_for_deployment", resp.Properties.EnabledForDeployment)
	d.Set("enabled_for_disk_encryption", resp.Properties.EnabledForDiskEncryption)
	d.Set("enabled_for_template_deployment", resp.Properties.EnabledForTemplateDeployment)
	d.Set("sku", flattenKeyVaultSku(resp.Properties.Sku))
	d.Set("access_policy", flattenKeyVaultAccessPolicies(resp.Properties.AccessPolicies))
	d.Set("vault_uri", resp.Properties.VaultURI)

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).keyVaultClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["vaults"]

	_, err = client.Delete(resGroup, name)

	return err
}

func expandKeyVaultSku(d *schema.ResourceData) *keyvault.Sku {
	skuSets := d.Get("sku").([]interface{})
	sku := skuSets[0].(map[string]interface{})

	return &keyvault.Sku{
		Family: &armKeyVaultSkuFamily,
		Name:   keyvault.SkuName(sku["name"].(string)),
	}
}

func expandKeyVaultAccessPolicies(d *schema.ResourceData) *[]keyvault.AccessPolicyEntry {
	policies := d.Get("access_policy").([]interface{})
	result := make([]keyvault.AccessPolicyEntry, 0, len(policies))

	for _, policySet := range policies {
		policyRaw := policySet.(map[string]interface{})

		certificatePermissionsRaw := policyRaw["certificate_permissions"].([]interface{})
		certificatePermissions := []keyvault.CertificatePermissions{}
		for _, permission := range certificatePermissionsRaw {
			certificatePermissions = append(certificatePermissions, keyvault.CertificatePermissions(permission.(string)))
		}

		keyPermissionsRaw := policyRaw["key_permissions"].([]interface{})
		keyPermissions := []keyvault.KeyPermissions{}
		for _, permission := range keyPermissionsRaw {
			keyPermissions = append(keyPermissions, keyvault.KeyPermissions(permission.(string)))
		}

		secretPermissionsRaw := policyRaw["secret_permissions"].([]interface{})
		secretPermissions := []keyvault.SecretPermissions{}
		for _, permission := range secretPermissionsRaw {
			secretPermissions = append(secretPermissions, keyvault.SecretPermissions(permission.(string)))
		}

		policy := keyvault.AccessPolicyEntry{
			Permissions: &keyvault.Permissions{
				Certificates: &certificatePermissions,
				Keys:         &keyPermissions,
				Secrets:      &secretPermissions,
			},
		}

		tenantUUID := uuid.FromStringOrNil(policyRaw["tenant_id"].(string))
		policy.TenantID = &tenantUUID
		objectUUID := policyRaw["object_id"].(string)
		policy.ObjectID = &objectUUID

		if v := policyRaw["application_id"]; v != "" {
			applicationUUID := uuid.FromStringOrNil(v.(string))
			policy.ApplicationID = &applicationUUID
		}

		result = append(result, policy)
	}

	return &result
}

func flattenKeyVaultSku(sku *keyvault.Sku) []interface{} {
	result := map[string]interface{}{
		"name": string(sku.Name),
	}

	return []interface{}{result}
}

func flattenKeyVaultAccessPolicies(policies *[]keyvault.AccessPolicyEntry) []interface{} {
	result := make([]interface{}, 0, len(*policies))

	for _, policy := range *policies {
		policyRaw := make(map[string]interface{})

		keyPermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Keys))
		for _, keyPermission := range *policy.Permissions.Keys {
			keyPermissionsRaw = append(keyPermissionsRaw, string(keyPermission))
		}

		secretPermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Secrets))
		for _, secretPermission := range *policy.Permissions.Secrets {
			secretPermissionsRaw = append(secretPermissionsRaw, string(secretPermission))
		}

		policyRaw["tenant_id"] = policy.TenantID.String()
		policyRaw["object_id"] = *policy.ObjectID
		if policy.ApplicationID != nil {
			policyRaw["application_id"] = policy.ApplicationID.String()
		}
		policyRaw["key_permissions"] = keyPermissionsRaw
		policyRaw["secret_permissions"] = secretPermissionsRaw

		if policy.Permissions.Certificates != nil {
			certificatePermissionsRaw := make([]interface{}, 0, len(*policy.Permissions.Certificates))
			for _, certificatePermission := range *policy.Permissions.Certificates {
				certificatePermissionsRaw = append(certificatePermissionsRaw, string(certificatePermission))
			}
			policyRaw["certificate_permissions"] = certificatePermissionsRaw
		}

		result = append(result, policyRaw)
	}

	return result
}

func validateKeyVaultName(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)
	if matched := regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`).Match([]byte(value)); !matched {
		errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and must be between 3-24 chars", k))
	}

	return
}

func checkKeyVaultDNSIsAvailable(vaultUri string) func() *resource.RetryError {
	return func() *resource.RetryError {
		uri, err := url.Parse(vaultUri)
		if err != nil {
			return resource.NonRetryableError(err)
		}

		conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", uri.Host))
		if err != nil {
			return resource.RetryableError(err)
		}

		_ = conn.Close()
		return nil
	}
}
<|endoftext|>


<commit_before><commit_msg>handlers/github: Include blocker summary in comment<commit_after><|endoftext|>


<commit_before>/*
Package readcache implements a read-through cache where the caller supplies
the means to read-through in the form of a function.
*/
package readcache

import (
	"sync"
	"time"
)

// Type Cache defines a read-through cache.
type Cache interface {
	// Retrieve an item from the cache if available, or from a
	// backing source if it is not.
	// May return an error instead, if the item cannot be fetched.
	Get(key string) (interface{}, error)
}

// Constructs a new cache
func New(getter func(string) (interface{}, time.Time, error)) Cache {
	return &readcache{getter, make(map[string]*cacheable), make(map[string]*readControl), new(sync.RWMutex), new(sync.RWMutex)}
}

// Type cacheable is something that may be stored in a cache
type cacheable struct {
	// The item in the cache
	Value interface{}

	// The time at which this item should expire from the cache.
	ExpiresAt time.Time
}

// Type readControl is a mechanism for controlling concurrent fetches
type readControl struct {
	Controller *sync.Once
	Result     *cacheable
	Error      error
}

// Type readcache implements the Cache interface
type readcache struct {
	// The fetcher of items
	Getter func(string) (interface{}, time.Time, error)

	// The cache of items
	Cache map[string]*cacheable

	// Controls read of items from the getter; prevents multiple concurrent reads of the same item.
	ReadControls map[string]*readControl

	// Locks the entire cache for reads or writes
	CacheLock *sync.RWMutex

	// Locks the read control manifest for reads or writes
	ReadControlsLock *sync.RWMutex
}

// Get an item from the cache, retrieving the item from the getter if necessary.
// This implemention is meant to be goroutine safe.  It assumes that updating a
// map while concurrently reading from it is unsafe, so it uses a read/write mutex
// to synchronize access to its internal maps.
func (c *readcache) Get(key string) (interface{}, error) {
	cachedValue, ok := getFromCache(c, key)
	if ok {
		return cachedValue.Value, nil
	}

	readControl, cachedValue, ok := getReadControl(c, key)
	if ok {
		return cachedValue.Value, nil
	}

	cachedValue, err := doFetch(c, key, readControl)
	if cachedValue != nil {
		return cachedValue.Value, err
	} else {
		return nil, err
	}
}

// Attempt to retrieve an item from the cache, if it exists and hasn't expired.
// Returns somevalue, true if exists or nil, false if it does not.
func getFromCache(c *readcache, key string) (*cacheable, bool) {
	c.CacheLock.RLock()
	cachedValue, ok := c.Cache[key]
	c.CacheLock.RUnlock()
	if ok {
		now := time.Now()
		if cachedValue.ExpiresAt.After(now) {
			return cachedValue, true
		}
		c.CacheLock.Lock()
		// Determine if another goroutine has updated the cache before the lock
		cachedValue, ok = c.Cache[key]
		if ok && cachedValue.ExpiresAt.After(now) {
			c.CacheLock.Unlock()
			return cachedValue, true
		}
		delete(c.Cache, key)
		c.CacheLock.Unlock()
	}
	return nil, false
}

// Get a Once for controlling the read-through on a particular cached item.
// Performs a last-minute check to determine if another goroutine has populated
// the cache before a lock is acquired, so this function may return a cached
// value instead.  If so, the third return value will be true.  Otherwise, a
// read control is returned and the third value is false.
func getReadControl(c *readcache, key string) (control *readControl, cachedItem *cacheable, gotCachedItem bool) {
	gotCachedItem = false

	c.ReadControlsLock.RLock()
	control, ok := c.ReadControls[key]
	c.ReadControlsLock.RUnlock()
	if !ok {
		c.ReadControlsLock.Lock()

		// Another goroutine may have created a read control, fetched an item, updated the
		// cache and cleaned up its read control by the time we reach this point.
		// Therefore, we verify that the cache still does not contain anything for the
		// given key.
		// Warning: possibility of deadlock when dealing with multiple locks.  Make sure
		//          they are always acquired in the same order.
		c.CacheLock.RLock()
		cachedItem, ok = c.Cache[key]
		c.CacheLock.RUnlock()

		if ok {
			c.ReadControlsLock.Unlock()
			gotCachedItem = true
			return
		}

		control, ok = c.ReadControls[key]
		if !ok {
			control = &readControl{new(sync.Once), nil, nil}
			c.ReadControls[key] = control
		}
		c.ReadControlsLock.Unlock()
	}

	return
}

// Use the given read control to fetch a value and store it in the cache.
// The read control may prevent this goroutine from fetching the value if
// some other routine gets to it first.  In either case, the resulting
// fetched value is returned.
func doFetch(c *readcache, key string, readControl *readControl) (cachedValue *cacheable, err error) {
	readControl.Controller.Do(func() {
		defer func() {
			c.ReadControlsLock.Lock()
			delete(c.ReadControls, key)
			c.ReadControlsLock.Unlock()
		}()

		var value interface{}
		var expiresAt time.Time
		value, expiresAt, err = c.Getter(key)
		if err == nil {
			cachedValue = &cacheable{value, expiresAt}
			readControl.Result = cachedValue
			c.CacheLock.Lock()
			c.Cache[key] = cachedValue
			c.CacheLock.Unlock()
		} else {
			readControl.Error = err
		}
	})

	cachedValue = readControl.Result
	err = readControl.Error

	return
}
<commit_msg>Documentation touch-ups<commit_after>/*
Package readcache implements a read-through cache. Items are fetched by a user-provided function.
*/
package readcache

import (
	"sync"
	"time"
)

// Type Cache defines a read-through cache.
type Cache interface {
	// Retrieve an item from the cache if available, or from a
	// backing source if it is not.
	// May return an error instead, if the item cannot be fetched.
	Get(key string) (interface{}, error)
}

// Constructs a new cache.  The item fetcher may return an item of type interface {} with an
// expiration time, or it may return an error.  If an error is returned, then all other return values are ignored.
func New(getter func(string) (interface{}, time.Time, error)) Cache {
	return &readcache{getter, make(map[string]*cacheable), make(map[string]*readControl), new(sync.RWMutex), new(sync.RWMutex)}
}

// Type cacheable is something that may be stored in a cache
type cacheable struct {
	// The item in the cache
	Value interface{}

	// The time at which this item should expire from the cache.
	ExpiresAt time.Time
}

// Type readControl is a mechanism for controlling concurrent fetches
type readControl struct {
	Controller *sync.Once
	Result     *cacheable
	Error      error
}

// Type readcache implements the Cache interface
type readcache struct {
	// The fetcher of items
	Getter func(string) (interface{}, time.Time, error)

	// The cache of items
	Cache map[string]*cacheable

	// Controls read of items from the getter; prevents multiple concurrent reads of the same item.
	ReadControls map[string]*readControl

	// Locks the entire cache for reads or writes
	CacheLock *sync.RWMutex

	// Locks the read control manifest for reads or writes
	ReadControlsLock *sync.RWMutex
}

// Get an item from the cache, retrieving the item from the getter if necessary.
// This implemention is meant to be goroutine safe.  It assumes that updating a
// map while concurrently reading from it is unsafe, so it uses a read/write mutex
// to synchronize access to its internal maps.
func (c *readcache) Get(key string) (interface{}, error) {
	cachedValue, ok := getFromCache(c, key)
	if ok {
		return cachedValue.Value, nil
	}

	readControl, cachedValue, ok := getReadControl(c, key)
	if ok {
		return cachedValue.Value, nil
	}

	cachedValue, err := doFetch(c, key, readControl)
	if cachedValue != nil {
		return cachedValue.Value, err
	} else {
		return nil, err
	}
}

// Attempt to retrieve an item from the cache, if it exists and hasn't expired.
// Returns somevalue, true if exists or nil, false if it does not.
func getFromCache(c *readcache, key string) (*cacheable, bool) {
	c.CacheLock.RLock()
	cachedValue, ok := c.Cache[key]
	c.CacheLock.RUnlock()
	if ok {
		now := time.Now()
		if cachedValue.ExpiresAt.After(now) {
			return cachedValue, true
		}
		c.CacheLock.Lock()
		// Determine if another goroutine has updated the cache before the lock
		cachedValue, ok = c.Cache[key]
		if ok && cachedValue.ExpiresAt.After(now) {
			c.CacheLock.Unlock()
			return cachedValue, true
		}
		delete(c.Cache, key)
		c.CacheLock.Unlock()
	}
	return nil, false
}

// Get a Once for controlling the read-through on a particular cached item.
// Performs a last-minute check to determine if another goroutine has populated
// the cache before a lock is acquired, so this function may return a cached
// value instead.  If so, the third return value will be true.  Otherwise, a
// read control is returned and the third value is false.
func getReadControl(c *readcache, key string) (control *readControl, cachedItem *cacheable, gotCachedItem bool) {
	gotCachedItem = false

	c.ReadControlsLock.RLock()
	control, ok := c.ReadControls[key]
	c.ReadControlsLock.RUnlock()
	if !ok {
		c.ReadControlsLock.Lock()

		// Another goroutine may have created a read control, fetched an item, updated the
		// cache and cleaned up its read control by the time we reach this point.
		// Therefore, we verify that the cache still does not contain anything for the
		// given key.
		// Warning: possibility of deadlock when dealing with multiple locks.  Make sure
		//          they are always acquired in the same order.
		c.CacheLock.RLock()
		cachedItem, ok = c.Cache[key]
		c.CacheLock.RUnlock()

		if ok {
			c.ReadControlsLock.Unlock()
			gotCachedItem = true
			return
		}

		control, ok = c.ReadControls[key]
		if !ok {
			control = &readControl{new(sync.Once), nil, nil}
			c.ReadControls[key] = control
		}
		c.ReadControlsLock.Unlock()
	}

	return
}

// Use the given read control to fetch a value and store it in the cache.
// The read control may prevent this goroutine from fetching the value if
// some other routine gets to it first.  In either case, the resulting
// fetched value is returned.
func doFetch(c *readcache, key string, readControl *readControl) (cachedValue *cacheable, err error) {
	readControl.Controller.Do(func() {
		defer func() {
			c.ReadControlsLock.Lock()
			delete(c.ReadControls, key)
			c.ReadControlsLock.Unlock()
		}()

		var value interface{}
		var expiresAt time.Time
		value, expiresAt, err = c.Getter(key)
		if err == nil {
			cachedValue = &cacheable{value, expiresAt}
			readControl.Result = cachedValue
			c.CacheLock.Lock()
			c.Cache[key] = cachedValue
			c.CacheLock.Unlock()
		} else {
			readControl.Error = err
		}
	})

	cachedValue = readControl.Result
	err = readControl.Error

	return
}
<|endoftext|>


<commit_before>package upload

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)

type AwsUploadF interface {
	Upload(io.Reader) (*s3manager.UploadOutput, error)
}

type UploadParameters struct {
	Action         string `json:"action"`
	AwsAccessKeyId string `json:"AWSAccessKeyId"`
	ContentType    string `json:"Content-Type"`
	Policy         string `json:"policy"`
	Signature      string `json:"signature"`
	Acl            string `json:"acl"`
	Key            string `json:"key"`
	SuccessStatus  string `json:"success_action_status"`
	SignatureUrl   string `json:"signature_url"`
	VideoId        string `json:"video_id"`
	AssetId        string `json:"asset_id"`
}

type V4Request struct {
	Method   string            `json:"method"`
	Action   string            `json:"action"`
	Path     string            `json:"path"`
	RawQuery string            `json:"raw_query"`
	Headers  map[string]string `json:"headers"`
}

type V4Response struct {
	Authorization string `json:"authorization"`
	Date          string `json:"date"`
}

func CreateV4Request(req *request.Request) V4Request {
	r := V4Request{}
	hreq := req.HTTPRequest
	r.Method = req.Method
	r.Action = req.Action
	r.Path = hreq.URL.Path
	r.RawQuery = hreq.URL.RawQuery
	for header, _ := range req.SignedHeaderVals {
		r.Headers[header] = hreq.Header.Get(header)
	}
	return r
}

func (r *V4Request) BuildRequest() *http.Request {
	req, _ := http.NewRequest(r.Method, r.Action, nil)
	req.URL.Path = r.Path
	req.URL.RawQuery = r.RawQuery
	for header, val := range r.Headers {
		req.Header.Set(header, val)
	}
	return req
}

func (r *V4Request) Sign(awsKey, awsSecret string) (resp V4Response) {
	// use the v4 signer automatically
	provider := credentials.StaticProvider{}
	provider.Value.AccessKeyID = awsKey
	provider.Value.SecretAccessKey = awsSecret
	cred := credentials.NewCredentials(&provider)
	signer := v4.NewSigner(cred)
	req := r.BuildRequest()
	_, err := signer.Sign(req, nil, "s3", "us-east-1", time.Now())
	if err != nil {
		log.Printf("could not sign url %s", err.Error())
		return resp
	}
	date := req.Header.Get("X-Amz-Date")
	auth := req.Header.Get("Authorization")
	return V4Response{
		Authorization: auth,
		Date:          date,
	}
}

var CreatorFn func(UploadParameters) (AwsUploadF, error)

func init() {
	CreatorFn = NewAwsUpload
}

// This is the struct that contains all the AWS settings
type AwsUpload struct {
	UploadParams UploadParameters
	Uploader     *s3manager.Uploader
}

// UploadParameters is retrieved from the Unicorn API, so we're creating an AwsUpload from the settings
func NewAwsUpload(params UploadParameters) (AwsUploadF, error) {
	au := &AwsUpload{
		UploadParams: params,
	}
	provider := credentials.StaticProvider{}
	// use dummy values
	provider.Value.AccessKeyID = multipartUploadAwsAccessKeyId
	provider.Value.SecretAccessKey = multipartUploadAwsSecretAccessKey
	credentials := credentials.NewCredentials(&provider)

	region, e := au.GetRegion()
	if e != nil {
		return au, e
	}
	// session
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials,
		Region:      aws.String(region),
	})
	if err != nil {
		return au, err
	}

	svc := s3.New(sess)

	customSigner := true
	if customSigner {
		// sign handler
		signer := au.Signer()
		svc.Handlers.Sign.PushBack(signer)
	}

	// s3manager uploader
	au.Uploader = s3manager.NewUploaderWithClient(svc)
	return au, nil
}

func (a *AwsUpload) Url() string {
	return a.UploadParams.Action
}

func (a *AwsUpload) Key() string {
	return a.UploadParams.Key
}

func (a *AwsUpload) Acl() string {
	return a.UploadParams.Acl
}

func (a *AwsUpload) ContentType() string {
	return a.UploadParams.ContentType
}

func (a *AwsUpload) AwsKeyId() string {
	return a.UploadParams.AwsAccessKeyId
}

func (a *AwsUpload) UploaderSigUrl() string {
	// take the UploadParams signature and append it to the uploader url
	return a.UploadParams.SignatureUrl
}

// bucketOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the bucket name part of that URL.
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         bucket, err := bucketOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(bucket) // prints synqfm
func (a AwsUpload) GetBucket() (string, error) {
	return BucketOfUploadAction(a.Url())
}

// regionOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the region of the bucket that is the URL refers to.
//
// This relies on heuristics, and will not work with certain styles of URLs.
//
// See: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         region, err := regionOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(region) // prints us-east-1
func (a AwsUpload) GetRegion() (string, error) {
	return RegionOfUploadAction(a.Url())
}

func (a *AwsUpload) Upload(body io.Reader) (*s3manager.UploadOutput, error) {
	// upload parameters
	acl := a.Acl()
	bucket, err := a.GetBucket()
	if err != nil {
		return nil, err
	}
	contentType := a.ContentType()
	key := a.Key()
	uploadInput := &s3manager.UploadInput{
		ACL:         &acl,
		Bucket:      &bucket,
		Body:        body,
		ContentType: &contentType,
		Key:         &key,
	}
	return a.Uploader.Upload(uploadInput)
}

// MultipartUploadSigner returns a function that can be added to an s3 client's
// list of handlers. The function will take over signing of requests from
// aws-sdk-go.
//
// The signer function uses SYNQ's embeddable web uploader's remote procedure
// to sign requests.
//
// This function is used by the internal multipartUpload function.
//
// Example:
//         // AWS session.
//         sess := session.Must(session.NewSession())
//
//         // S3 service client.
//         svc := s3.New(sess)
//
//         // Signer function. Determine the arguments somehow.
//         signer := a.Signer()
//
//         // Register handler as the last handler of the signing phase.
//         svc.Handlers.Sign.PushBack(signer)
//
//         // S3 requests are now signed by signer().

func (a AwsUpload) Signer() func(r *request.Request) {
	signer := func(r *request.Request) {
		err := a.SignRequest(r)
		if err != nil {
			return // TODO(mastensg): how to report errors from handlers?
		}
	}

	return signer
}

func (a *AwsUpload) ServerSignV2(r *request.Request) (string, error) {
	v4 := CreateV4Request(r)
	resp, err := a.V4Sig(v4)
	if err != nil {
		return "", err
	}
	if resp.Date != "" {
		// reset the data
		r.HTTPRequest.Header.Set("X-Amz-Date", resp.Date)
	}
	return resp.Authorization, nil
}

func (a *AwsUpload) ServerSignV1(r *http.Request) (string, error) {
	bucket, err := a.GetBucket()
	if err != nil {
		return "", err
	}
	if err := RewriteXAmzDateHeader(r.Header); err != nil {
		return "", err
	}
	headers := ""
	x_amz_date := r.Header.Get("X-Amz-Date")
	if r.URL.RawQuery == "uploads=" {
		// Initiate multi-part upload

		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-acl:%s\nx-amz-date:%s\n/%s%s",
			r.Method,
			a.ContentType(),
			a.Acl(),
			x_amz_date,
			bucket,
			r.URL.Path+"?uploads",
		)
	} else if r.Method == "PUT" {
		// Upload one part
		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
			r.Method,
			"",
			x_amz_date,
			bucket,
			r.URL.Path+"?"+r.URL.RawQuery,
		)
	} else if r.Method == "POST" {
		// Finish multi-part upload

		// TODO(mastensg): the content-type header set by
		// aws-sdk-go is not exactly the one expected by
		// uploader/signature, maybe
		r.Header.Set("Content-Type", "application/xml; charset=UTF-8")

		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
			r.Method,
			r.Header.Get("Content-Type"),
			x_amz_date,
			bucket,
			r.URL.Path+"?"+r.URL.RawQuery,
		)
	} else {
		return "", errors.New("Unknown request type.")
	}
	sig, err := a.UploaderSignature(headers)
	if err != nil {
		return "", err
	}
	delete(r.Header, "X-Amz-Content-Sha256")
	return fmt.Sprintf("AWS %s:%s", a.AwsKeyId(), sig), nil
}

// This runs as a handler within the Sign HandlerList and uses unicorn to sign the request
// This will replace whats in awssdk-go/aws/signer/v4/v4.go and its own "signWithBody" method
// https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
// https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
func (a *AwsUpload) SignRequest(r *request.Request) error {
	// construct "headers" string to send to
	// https://uploader.synq.fm/uploader/signature
	auth, err := a.ServerSignV2(r)
	if err != nil {
		return err
	}

	// rewrite authorization header(s)
	delete(r.HTTPRequest.Header, "Authorization")
	r.HTTPRequest.Header.Set("Authorization", auth)

	return nil
}

// UploaderSignature uses the backend of the embeddable web uploader to sign
// multipart upload requests.
func (a *AwsUpload) UploaderSignature(headers string) ([]byte, error) {
	reqStruct := UploaderSignatureRequest{Headers: headers}
	reqBody, err := json.Marshal(reqStruct)
	if err != nil {
		return nil, err
	}

	respBody, err := a.Request(reqBody)

	// parse response
	respStruct := UploaderSignatureResponse{}
	err = json.Unmarshal(respBody, &respStruct)
	if err != nil {
		log.Println("error unmarshaling", err.Error())
		return nil, err
	}
	return []byte(respStruct.Signature), nil
}

func (a *AwsUpload) V4Sig(req V4Request) (resp V4Response, err error) {
	reqBody, err := json.Marshal(req)
	if err != nil {
		return resp, err
	}
	respBody, err := a.Request(reqBody)
	if err != nil {
		return resp, err
	}
	err = json.Unmarshal(respBody, &resp)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func (a *AwsUpload) Request(body []byte) ([]byte, error) {
	url := a.UploaderSigUrl()

	resp, err := http.Post(url, "application/json", bytes.NewReader(body))
	if err != nil {
		log.Printf("could not call %s : %s\n", url, err.Error())
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		// TODO(mastensg): report status and maybe body
		// TODO(mastensg): handle known error responses specifically
		log.Printf("invalid response code %d from response\n", resp.StatusCode)
		return nil, errors.New("HTTP response status not OK.")
	}
	// read response
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println("error reading response body", err.Error())
		return nil, err
	}
	return respBody, nil
}
<commit_msg>update aws<commit_after>package upload

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)

type AwsUploadF interface {
	Upload(io.Reader) (*s3manager.UploadOutput, error)
}

type UploadParameters struct {
	Action         string `json:"action"`
	AwsAccessKeyId string `json:"AWSAccessKeyId"`
	ContentType    string `json:"Content-Type"`
	Policy         string `json:"policy"`
	Signature      string `json:"signature"`
	Acl            string `json:"acl"`
	Key            string `json:"key"`
	SuccessStatus  string `json:"success_action_status"`
	SignatureUrl   string `json:"signature_url"`
	VideoId        string `json:"video_id"`
	AssetId        string `json:"asset_id"`
}

type V4Request struct {
	Method   string            `json:"method"`
	Action   string            `json:"action"`
	Path     string            `json:"path"`
	RawQuery string            `json:"raw_query"`
	Headers  map[string]string `json:"headers"`
}

type V4Response struct {
	Authorization string `json:"authorization"`
	Date          string `json:"date"`
}

func CreateV4Request(action string, req *request.Request) V4Request {
	r := V4Request{}
	hreq := req.HTTPRequest
	r.Method = hreq.Method
	r.Action = action
	r.Path = hreq.URL.Path
	r.RawQuery = hreq.URL.RawQuery
	for header, _ := range req.SignedHeaderVals {
		r.Headers[header] = hreq.Header.Get(header)
	}
	return r
}

func (r *V4Request) BuildRequest() *http.Request {
	req, _ := http.NewRequest(r.Method, r.Action, nil)
	req.URL.Path = r.Path
	req.URL.RawQuery = r.RawQuery
	for header, val := range r.Headers {
		req.Header.Set(header, val)
	}
	return req
}

func (r *V4Request) Sign(awsKey, awsSecret string) (resp V4Response) {
	// use the v4 signer automatically
	provider := credentials.StaticProvider{}
	provider.Value.AccessKeyID = awsKey
	provider.Value.SecretAccessKey = awsSecret
	cred := credentials.NewCredentials(&provider)
	signer := v4.NewSigner(cred)
	req := r.BuildRequest()
	_, err := signer.Sign(req, nil, "s3", "us-east-1", time.Now())
	if err != nil {
		log.Printf("could not sign url %s", err.Error())
		return resp
	}
	date := req.Header.Get("X-Amz-Date")
	auth := req.Header.Get("Authorization")
	return V4Response{
		Authorization: auth,
		Date:          date,
	}
}

var CreatorFn func(UploadParameters) (AwsUploadF, error)

func init() {
	CreatorFn = NewAwsUpload
}

// This is the struct that contains all the AWS settings
type AwsUpload struct {
	UploadParams UploadParameters
	Uploader     *s3manager.Uploader
}

// UploadParameters is retrieved from the Unicorn API, so we're creating an AwsUpload from the settings
func NewAwsUpload(params UploadParameters) (AwsUploadF, error) {
	au := &AwsUpload{
		UploadParams: params,
	}
	provider := credentials.StaticProvider{}
	// use dummy values
	provider.Value.AccessKeyID = multipartUploadAwsAccessKeyId
	provider.Value.SecretAccessKey = multipartUploadAwsSecretAccessKey
	credentials := credentials.NewCredentials(&provider)

	region, e := au.GetRegion()
	if e != nil {
		return au, e
	}
	// session
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials,
		Region:      aws.String(region),
	})
	if err != nil {
		return au, err
	}

	svc := s3.New(sess)

	customSigner := true
	if customSigner {
		// sign handler
		signer := au.Signer()
		svc.Handlers.Sign.PushBack(signer)
	}

	// s3manager uploader
	au.Uploader = s3manager.NewUploaderWithClient(svc)
	return au, nil
}

func (a *AwsUpload) Url() string {
	return a.UploadParams.Action
}

func (a *AwsUpload) Key() string {
	return a.UploadParams.Key
}

func (a *AwsUpload) Acl() string {
	return a.UploadParams.Acl
}

func (a *AwsUpload) ContentType() string {
	return a.UploadParams.ContentType
}

func (a *AwsUpload) AwsKeyId() string {
	return a.UploadParams.AwsAccessKeyId
}

func (a *AwsUpload) UploaderSigUrl() string {
	// take the UploadParams signature and append it to the uploader url
	return a.UploadParams.SignatureUrl
}

// bucketOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the bucket name part of that URL.
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         bucket, err := bucketOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(bucket) // prints synqfm
func (a AwsUpload) GetBucket() (string, error) {
	return BucketOfUploadAction(a.Url())
}

// regionOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the region of the bucket that is the URL refers to.
//
// This relies on heuristics, and will not work with certain styles of URLs.
//
// See: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         region, err := regionOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(region) // prints us-east-1
func (a AwsUpload) GetRegion() (string, error) {
	return RegionOfUploadAction(a.Url())
}

func (a *AwsUpload) Upload(body io.Reader) (*s3manager.UploadOutput, error) {
	// upload parameters
	acl := a.Acl()
	bucket, err := a.GetBucket()
	if err != nil {
		return nil, err
	}
	contentType := a.ContentType()
	key := a.Key()
	uploadInput := &s3manager.UploadInput{
		ACL:         &acl,
		Bucket:      &bucket,
		Body:        body,
		ContentType: &contentType,
		Key:         &key,
	}
	return a.Uploader.Upload(uploadInput)
}

// MultipartUploadSigner returns a function that can be added to an s3 client's
// list of handlers. The function will take over signing of requests from
// aws-sdk-go.
//
// The signer function uses SYNQ's embeddable web uploader's remote procedure
// to sign requests.
//
// This function is used by the internal multipartUpload function.
//
// Example:
//         // AWS session.
//         sess := session.Must(session.NewSession())
//
//         // S3 service client.
//         svc := s3.New(sess)
//
//         // Signer function. Determine the arguments somehow.
//         signer := a.Signer()
//
//         // Register handler as the last handler of the signing phase.
//         svc.Handlers.Sign.PushBack(signer)
//
//         // S3 requests are now signed by signer().

func (a AwsUpload) Signer() func(r *request.Request) {
	signer := func(r *request.Request) {
		err := a.SignRequest(r)
		if err != nil {
			return // TODO(mastensg): how to report errors from handlers?
		}
	}

	return signer
}

func (a *AwsUpload) ServerSignV2(r *request.Request) (string, error) {
	v4 := CreateV4Request(a.UploadParams.Action, r)
	resp, err := a.V4Sig(v4)
	if err != nil {
		return "", err
	}
	if resp.Date != "" {
		// reset the data
		r.HTTPRequest.Header.Set("X-Amz-Date", resp.Date)
	}
	return resp.Authorization, nil
}

func (a *AwsUpload) ServerSignV1(r *http.Request) (string, error) {
	bucket, err := a.GetBucket()
	if err != nil {
		return "", err
	}
	if err := RewriteXAmzDateHeader(r.Header); err != nil {
		return "", err
	}
	headers := ""
	x_amz_date := r.Header.Get("X-Amz-Date")
	if r.URL.RawQuery == "uploads=" {
		// Initiate multi-part upload

		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-acl:%s\nx-amz-date:%s\n/%s%s",
			r.Method,
			a.ContentType(),
			a.Acl(),
			x_amz_date,
			bucket,
			r.URL.Path+"?uploads",
		)
	} else if r.Method == "PUT" {
		// Upload one part
		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
			r.Method,
			"",
			x_amz_date,
			bucket,
			r.URL.Path+"?"+r.URL.RawQuery,
		)
	} else if r.Method == "POST" {
		// Finish multi-part upload

		// TODO(mastensg): the content-type header set by
		// aws-sdk-go is not exactly the one expected by
		// uploader/signature, maybe
		r.Header.Set("Content-Type", "application/xml; charset=UTF-8")

		headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
			r.Method,
			r.Header.Get("Content-Type"),
			x_amz_date,
			bucket,
			r.URL.Path+"?"+r.URL.RawQuery,
		)
	} else {
		return "", errors.New("Unknown request type.")
	}
	sig, err := a.UploaderSignature(headers)
	if err != nil {
		return "", err
	}
	delete(r.Header, "X-Amz-Content-Sha256")
	return fmt.Sprintf("AWS %s:%s", a.AwsKeyId(), sig), nil
}

// This runs as a handler within the Sign HandlerList and uses unicorn to sign the request
// This will replace whats in awssdk-go/aws/signer/v4/v4.go and its own "signWithBody" method
// https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
// https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
func (a *AwsUpload) SignRequest(r *request.Request) error {
	// construct "headers" string to send to
	// https://uploader.synq.fm/uploader/signature
	auth, err := a.ServerSignV2(r)
	if err != nil {
		return err
	}

	// rewrite authorization header(s)
	delete(r.HTTPRequest.Header, "Authorization")
	r.HTTPRequest.Header.Set("Authorization", auth)

	return nil
}

// UploaderSignature uses the backend of the embeddable web uploader to sign
// multipart upload requests.
func (a *AwsUpload) UploaderSignature(headers string) ([]byte, error) {
	reqStruct := UploaderSignatureRequest{Headers: headers}
	reqBody, err := json.Marshal(reqStruct)
	if err != nil {
		return nil, err
	}

	respBody, err := a.Request(reqBody)

	// parse response
	respStruct := UploaderSignatureResponse{}
	err = json.Unmarshal(respBody, &respStruct)
	if err != nil {
		log.Println("error unmarshaling", err.Error())
		return nil, err
	}
	return []byte(respStruct.Signature), nil
}

func (a *AwsUpload) V4Sig(req V4Request) (resp V4Response, err error) {
	reqBody, err := json.Marshal(req)
	if err != nil {
		return resp, err
	}
	respBody, err := a.Request(reqBody)
	if err != nil {
		return resp, err
	}
	err = json.Unmarshal(respBody, &resp)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func (a *AwsUpload) Request(body []byte) ([]byte, error) {
	url := a.UploaderSigUrl()

	resp, err := http.Post(url, "application/json", bytes.NewReader(body))
	if err != nil {
		log.Printf("could not call %s : %s\n", url, err.Error())
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		// TODO(mastensg): report status and maybe body
		// TODO(mastensg): handle known error responses specifically
		log.Printf("invalid response code %d from response\n", resp.StatusCode)
		return nil, errors.New("HTTP response status not OK.")
	}
	// read response
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println("error reading response body", err.Error())
		return nil, err
	}
	return respBody, nil
}
<|endoftext|>


<commit_before>package service

import (
	"context"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/onsi/ginkgo"
	configv1 "github.com/openshift/api/config/v1"
	configclient "github.com/openshift/client-go/config/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor/backenddisruption"
	"github.com/openshift/origin/test/extended/util/disruption"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	"k8s.io/kubernetes/test/e2e/framework/service"
	"k8s.io/kubernetes/test/e2e/upgrades"
)

// serviceLoadBalancerUpgradeTest tests that a service is available before, during, and
// after a cluster upgrade.
type serviceLoadBalancerUpgradeTest struct {
	// filled in by presetup
	jig                 *service.TestJig
	tcpService          *v1.Service
	unsupportedPlatform bool

	backendDisruptionTest disruption.BackendDisruptionUpgradeTest
}

func NewServiceLoadBalancerWithNewConnectionsTest() upgrades.Test {
	serviceLBTest := &serviceLoadBalancerUpgradeTest{}
	serviceLBTest.backendDisruptionTest =
		disruption.NewBackendDisruptionTest(
			"[sig-network-edge] Application behind service load balancer with PDB remains available using new connections",
			backenddisruption.NewSimpleBackend(
				"", // late binding host
				"service-loadbalancer-with-pdb",
				"/echo?msg=Hello",
				backenddisruption.NewConnectionType).
				WithExpectedBody("Hello"),
		).WithAllowedDisruption(allowedServiceLBDisruption).
			WithPreSetup(serviceLBTest.loadBalancerSetup)

	return serviceLBTest
}

func NewServiceLoadBalancerWithReusedConnectionsTest() upgrades.Test {
	serviceLBTest := &serviceLoadBalancerUpgradeTest{}
	serviceLBTest.backendDisruptionTest =
		disruption.NewBackendDisruptionTest(
			"[sig-network-edge] Application behind service load balancer with PDB remains available using reused connections",
			backenddisruption.NewSimpleBackend(
				"", // late binding host
				"service-loadbalancer-with-pdb",
				"/echo?msg=Hello",
				backenddisruption.ReusedConnectionType).
				WithExpectedBody("Hello"),
		).WithAllowedDisruption(allowedServiceLBDisruption).
			WithPreSetup(serviceLBTest.loadBalancerSetup)

	return serviceLBTest
}

func allowedServiceLBDisruption(f *framework.Framework, totalDuration time.Duration) (*time.Duration, error) {
	toleratedDisruption := 0.02
	allowedDisruptionNanoseconds := int64(float64(totalDuration.Nanoseconds()) * toleratedDisruption)
	allowedDisruption := time.Duration(allowedDisruptionNanoseconds)

	return &allowedDisruption, nil
}

func (t *serviceLoadBalancerUpgradeTest) Name() string { return t.backendDisruptionTest.Name() }
func (t *serviceLoadBalancerUpgradeTest) DisplayName() string {
	return t.backendDisruptionTest.DisplayName()
}

func shouldTestPDBs() bool { return true }

func (t *serviceLoadBalancerUpgradeTest) loadBalancerSetup(f *framework.Framework, backendSampler disruption.BackendSampler) error {
	configClient, err := configclient.NewForConfig(f.ClientConfig())
	framework.ExpectNoError(err)
	infra, err := configClient.ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
	framework.ExpectNoError(err)
	// ovirt does not support service type loadbalancer because it doesn't program a cloud.
	if infra.Status.PlatformStatus.Type == configv1.OvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.KubevirtPlatformType || infra.Status.PlatformStatus.Type == configv1.LibvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.VSpherePlatformType || infra.Status.PlatformStatus.Type == configv1.BareMetalPlatformType {
		t.unsupportedPlatform = true
	}
	// single node clusters are not supported because the replication controller has 2 replicas with anti-affinity for running on the same node.
	if infra.Status.ControlPlaneTopology == configv1.SingleReplicaTopologyMode {
		t.unsupportedPlatform = true
	}
	if t.unsupportedPlatform {
		return nil
	}

	serviceName := "service-test"
	jig := service.NewTestJig(f.ClientSet, f.Namespace.Name, serviceName)

	ns := f.Namespace
	cs := f.ClientSet

	ginkgo.By("creating a TCP service " + serviceName + " with type=LoadBalancer in namespace " + ns.Name)
	tcpService, err := jig.CreateTCPService(func(s *v1.Service) {
		s.Spec.Type = v1.ServiceTypeLoadBalancer
		// ServiceExternalTrafficPolicyTypeCluster performs during disruption, Local does not
		s.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster
		if s.Annotations == nil {
			s.Annotations = make(map[string]string)
		}
		// We tune the LB checks to match the longest intervals available so that interactions between
		// upgrading components and the service are more obvious.
		// - AWS allows configuration, default is 70s (6 failed with 10s interval in 1.17) set to match GCP
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval"] = "8"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold"] = "3"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold"] = "2"
		// - Azure is hardcoded to 15s (2 failed with 5s interval in 1.17) and is sufficient
		// - GCP has a non-configurable interval of 32s (3 failed health checks with 8s interval in 1.17)
		//   - thus pods need to stay up for > 32s, so pod shutdown period will will be 45s
	})
	framework.ExpectNoError(err)
	tcpService, err = jig.WaitForLoadBalancer(service.GetServiceLoadBalancerCreationTimeout(cs))
	framework.ExpectNoError(err)

	// Get info to hit it with
	tcpIngressIP := service.GetIngressPoint(&tcpService.Status.LoadBalancer.Ingress[0])
	svcPort := int(tcpService.Spec.Ports[0].Port)

	ginkgo.By("creating RC to be part of service " + serviceName)
	rc, err := jig.Run(func(rc *v1.ReplicationController) {
		// ensure the pod waits long enough during update for the LB to see the newly ready pod, which
		// must be longer than the worst load balancer above (GCP at 32s)
		rc.Spec.MinReadySeconds = 33
		// ensure the pod waits long enough for most LBs to take it out of rotation, which has to be
		// longer than the LB failed health check duration + 1 cycle
		rc.Spec.Template.Spec.Containers[0].Lifecycle = &v1.Lifecycle{
			PreStop: &v1.Handler{
				Exec: &v1.ExecAction{Command: []string{"sleep", "45"}},
			},
		}
		// ensure the pod is not forcibly deleted at 30s, but waits longer than the graceful sleep
		minute := int64(60)
		rc.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute

		jig.AddRCAntiAffinity(rc)
	})
	framework.ExpectNoError(err)

	if shouldTestPDBs() {
		ginkgo.By("creating a PodDisruptionBudget to cover the ReplicationController")
		_, err = jig.CreatePDB(rc)
		framework.ExpectNoError(err)
	}

	// Hit it once before considering ourselves ready
	ginkgo.By("hitting pods through the service's LoadBalancer")
	timeout := 10 * time.Minute
	// require thirty seconds of passing requests to continue (in case the SLB becomes available and then degrades)
	// TODO this seems weird to @deads2k, why is status not trustworthy
	TestReachableHTTPWithMinSuccessCount(tcpIngressIP, svcPort, 30, timeout)

	backendSampler.SetHost(fmt.Sprintf("http://%s", net.JoinHostPort(tcpIngressIP, strconv.Itoa(svcPort))))

	t.jig = jig
	t.tcpService = tcpService
	return nil
}

// Test runs a connectivity check to the service.
func (t *serviceLoadBalancerUpgradeTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	if t.unsupportedPlatform {
		return
	}

	t.backendDisruptionTest.Test(f, done, upgrade)

	// verify finalizer behavior
	defer func() {
		ginkgo.By("Check that service can be deleted with finalizer")
		service.WaitForServiceDeletedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name)
	}()
	ginkgo.By("Check that finalizer is present on loadBalancer type service")
	service.WaitForServiceUpdatedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name, true)
}

func (t *serviceLoadBalancerUpgradeTest) Teardown(f *framework.Framework) {
	t.backendDisruptionTest.Teardown(f)
}

func (t *serviceLoadBalancerUpgradeTest) Setup(f *framework.Framework) {
	t.backendDisruptionTest.Setup(f)
}

// TestReachableHTTPWithMinSuccessCount tests that the given host serves HTTP on the given port for a minimum of successCount number of
// counts at a given interval. If the service reachability fails, the counter gets reset
func TestReachableHTTPWithMinSuccessCount(host string, port int, successCount int, timeout time.Duration) {
	consecutiveSuccessCnt := 0
	err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) {
		result := e2enetwork.PokeHTTP(host, port, "/echo?msg=hello",
			&e2enetwork.HTTPPokeParams{
				BodyContains:   "hello",
				RetriableCodes: []int{},
			})
		if result.Status == e2enetwork.HTTPSuccess {
			consecutiveSuccessCnt++
			return consecutiveSuccessCnt >= successCount, nil
		}
		consecutiveSuccessCnt = 0
		return false, nil // caller can retry
	})
	framework.ExpectNoError(err)
}
<commit_msg>update service loadbalancer to explicitly bypass SCCs instead of implicitly by test name to namespace name indirection<commit_after>package service

import (
	"context"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/onsi/ginkgo"
	configv1 "github.com/openshift/api/config/v1"
	configclient "github.com/openshift/client-go/config/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor/backenddisruption"
	"github.com/openshift/origin/test/extended/util/disruption"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/util/retry"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	"k8s.io/kubernetes/test/e2e/framework/service"
	"k8s.io/kubernetes/test/e2e/upgrades"
)

// serviceLoadBalancerUpgradeTest tests that a service is available before, during, and
// after a cluster upgrade.
type serviceLoadBalancerUpgradeTest struct {
	// filled in by presetup
	jig                 *service.TestJig
	tcpService          *v1.Service
	unsupportedPlatform bool

	backendDisruptionTest disruption.BackendDisruptionUpgradeTest
}

func NewServiceLoadBalancerWithNewConnectionsTest() upgrades.Test {
	serviceLBTest := &serviceLoadBalancerUpgradeTest{}
	serviceLBTest.backendDisruptionTest =
		disruption.NewBackendDisruptionTest(
			"[sig-network-edge] Application behind service load balancer with PDB remains available using new connections",
			backenddisruption.NewSimpleBackend(
				"", // late binding host
				"service-loadbalancer-with-pdb",
				"/echo?msg=Hello",
				backenddisruption.NewConnectionType).
				WithExpectedBody("Hello"),
		).WithAllowedDisruption(allowedServiceLBDisruption).
			WithPreSetup(serviceLBTest.loadBalancerSetup)

	return serviceLBTest
}

func NewServiceLoadBalancerWithReusedConnectionsTest() upgrades.Test {
	serviceLBTest := &serviceLoadBalancerUpgradeTest{}
	serviceLBTest.backendDisruptionTest =
		disruption.NewBackendDisruptionTest(
			"[sig-network-edge] Application behind service load balancer with PDB remains available using reused connections",
			backenddisruption.NewSimpleBackend(
				"", // late binding host
				"service-loadbalancer-with-pdb",
				"/echo?msg=Hello",
				backenddisruption.ReusedConnectionType).
				WithExpectedBody("Hello"),
		).WithAllowedDisruption(allowedServiceLBDisruption).
			WithPreSetup(serviceLBTest.loadBalancerSetup)

	return serviceLBTest
}

func allowedServiceLBDisruption(f *framework.Framework, totalDuration time.Duration) (*time.Duration, error) {
	toleratedDisruption := 0.02
	allowedDisruptionNanoseconds := int64(float64(totalDuration.Nanoseconds()) * toleratedDisruption)
	allowedDisruption := time.Duration(allowedDisruptionNanoseconds)

	return &allowedDisruption, nil
}

func (t *serviceLoadBalancerUpgradeTest) Name() string { return t.backendDisruptionTest.Name() }
func (t *serviceLoadBalancerUpgradeTest) DisplayName() string {
	return t.backendDisruptionTest.DisplayName()
}

func shouldTestPDBs() bool { return true }

func (t *serviceLoadBalancerUpgradeTest) loadBalancerSetup(f *framework.Framework, backendSampler disruption.BackendSampler) error {
	// we must update our namespace to bypass SCC so that we can avoid default mutation of our pod and SCC evaluation.
	// technically we could also choose to bind an SCC, but I don't see a lot of value in doing that and we have to wait
	// for a secondary cache to fill to reflect that.  If we miss that cache filling, we'll get assigned a restricted on
	// and fail.
	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		ns, err := f.ClientSet.CoreV1().Namespaces().Get(context.Background(), f.Namespace.Name, metav1.GetOptions{})
		if err != nil {
			return err
		}
		if ns.Labels == nil {
			ns.Labels = map[string]string{}
		}
		ns.Labels["security.openshift.io/disable-securitycontextconstraints"] = "true"
		ns, err = f.ClientSet.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
		if err != nil {
			return err
		}
		return nil
	})
	framework.ExpectNoError(err)

	configClient, err := configclient.NewForConfig(f.ClientConfig())
	framework.ExpectNoError(err)
	infra, err := configClient.ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
	framework.ExpectNoError(err)
	// ovirt does not support service type loadbalancer because it doesn't program a cloud.
	if infra.Status.PlatformStatus.Type == configv1.OvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.KubevirtPlatformType || infra.Status.PlatformStatus.Type == configv1.LibvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.VSpherePlatformType || infra.Status.PlatformStatus.Type == configv1.BareMetalPlatformType {
		t.unsupportedPlatform = true
	}
	// single node clusters are not supported because the replication controller has 2 replicas with anti-affinity for running on the same node.
	if infra.Status.ControlPlaneTopology == configv1.SingleReplicaTopologyMode {
		t.unsupportedPlatform = true
	}
	if t.unsupportedPlatform {
		return nil
	}

	serviceName := "service-test"
	jig := service.NewTestJig(f.ClientSet, f.Namespace.Name, serviceName)

	ns := f.Namespace
	cs := f.ClientSet

	ginkgo.By("creating a TCP service " + serviceName + " with type=LoadBalancer in namespace " + ns.Name)
	tcpService, err := jig.CreateTCPService(func(s *v1.Service) {
		s.Spec.Type = v1.ServiceTypeLoadBalancer
		// ServiceExternalTrafficPolicyTypeCluster performs during disruption, Local does not
		s.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster
		if s.Annotations == nil {
			s.Annotations = make(map[string]string)
		}
		// We tune the LB checks to match the longest intervals available so that interactions between
		// upgrading components and the service are more obvious.
		// - AWS allows configuration, default is 70s (6 failed with 10s interval in 1.17) set to match GCP
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval"] = "8"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold"] = "3"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold"] = "2"
		// - Azure is hardcoded to 15s (2 failed with 5s interval in 1.17) and is sufficient
		// - GCP has a non-configurable interval of 32s (3 failed health checks with 8s interval in 1.17)
		//   - thus pods need to stay up for > 32s, so pod shutdown period will will be 45s
	})
	framework.ExpectNoError(err)
	tcpService, err = jig.WaitForLoadBalancer(service.GetServiceLoadBalancerCreationTimeout(cs))
	framework.ExpectNoError(err)

	// Get info to hit it with
	tcpIngressIP := service.GetIngressPoint(&tcpService.Status.LoadBalancer.Ingress[0])
	svcPort := int(tcpService.Spec.Ports[0].Port)

	ginkgo.By("creating RC to be part of service " + serviceName)
	rc, err := jig.Run(func(rc *v1.ReplicationController) {
		// ensure the pod waits long enough during update for the LB to see the newly ready pod, which
		// must be longer than the worst load balancer above (GCP at 32s)
		rc.Spec.MinReadySeconds = 33
		// ensure the pod waits long enough for most LBs to take it out of rotation, which has to be
		// longer than the LB failed health check duration + 1 cycle
		rc.Spec.Template.Spec.Containers[0].Lifecycle = &v1.Lifecycle{
			PreStop: &v1.Handler{
				Exec: &v1.ExecAction{Command: []string{"sleep", "45"}},
			},
		}
		// ensure the pod is not forcibly deleted at 30s, but waits longer than the graceful sleep
		minute := int64(60)
		rc.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute

		jig.AddRCAntiAffinity(rc)
	})
	framework.ExpectNoError(err)

	if shouldTestPDBs() {
		ginkgo.By("creating a PodDisruptionBudget to cover the ReplicationController")
		_, err = jig.CreatePDB(rc)
		framework.ExpectNoError(err)
	}

	// Hit it once before considering ourselves ready
	ginkgo.By("hitting pods through the service's LoadBalancer")
	timeout := 10 * time.Minute
	// require thirty seconds of passing requests to continue (in case the SLB becomes available and then degrades)
	// TODO this seems weird to @deads2k, why is status not trustworthy
	TestReachableHTTPWithMinSuccessCount(tcpIngressIP, svcPort, 30, timeout)

	backendSampler.SetHost(fmt.Sprintf("http://%s", net.JoinHostPort(tcpIngressIP, strconv.Itoa(svcPort))))

	t.jig = jig
	t.tcpService = tcpService
	return nil
}

// Test runs a connectivity check to the service.
func (t *serviceLoadBalancerUpgradeTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	if t.unsupportedPlatform {
		return
	}

	t.backendDisruptionTest.Test(f, done, upgrade)

	// verify finalizer behavior
	defer func() {
		ginkgo.By("Check that service can be deleted with finalizer")
		service.WaitForServiceDeletedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name)
	}()
	ginkgo.By("Check that finalizer is present on loadBalancer type service")
	service.WaitForServiceUpdatedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name, true)
}

func (t *serviceLoadBalancerUpgradeTest) Teardown(f *framework.Framework) {
	t.backendDisruptionTest.Teardown(f)
}

func (t *serviceLoadBalancerUpgradeTest) Setup(f *framework.Framework) {
	t.backendDisruptionTest.Setup(f)
}

// TestReachableHTTPWithMinSuccessCount tests that the given host serves HTTP on the given port for a minimum of successCount number of
// counts at a given interval. If the service reachability fails, the counter gets reset
func TestReachableHTTPWithMinSuccessCount(host string, port int, successCount int, timeout time.Duration) {
	consecutiveSuccessCnt := 0
	err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) {
		result := e2enetwork.PokeHTTP(host, port, "/echo?msg=hello",
			&e2enetwork.HTTPPokeParams{
				BodyContains:   "hello",
				RetriableCodes: []int{},
			})
		if result.Status == e2enetwork.HTTPSuccess {
			consecutiveSuccessCnt++
			return consecutiveSuccessCnt >= successCount, nil
		}
		consecutiveSuccessCnt = 0
		return false, nil // caller can retry
	})
	framework.ExpectNoError(err)
}
<|endoftext|>