<commit_before>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ssoadmin"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

const (
	AWSSSOAssignmentCreateRetryTimeout = 5 * time.Minute
	AWSSSOAssignmentDeleteRetryTimeout = 5 * time.Minute
	AWSSSOAssignmentRetryDelay         = 5 * time.Second
	AWSSSOAssignmentRetryMinTimeout    = 3 * time.Second
)

func resourceAwsSsoAssignment() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSsoAssignmentCreate,
		Read:   resourceAwsSsoAssignmentRead,
		Delete: resourceAwsSsoAssignmentDelete,

		// TODO
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(AWSSSOAssignmentCreateRetryTimeout),
			Delete: schema.DefaultTimeout(AWSSSOAssignmentDeleteRetryTimeout),
		},

		Schema: map[string]*schema.Schema{
			"created_date": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"instance_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(10, 1224),
					validation.StringMatch(regexp.MustCompile(`^arn:aws:sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}$`), "must match arn:aws:sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}"),
				),
			},

			"permission_set_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(10, 1224),
					validation.StringMatch(regexp.MustCompile(`^arn:aws:sso:::permissionSet/(sso)?ins-[a-zA-Z0-9-.]{16}/ps-[a-zA-Z0-9-./]{16}$`), "must match arn:aws:sso:::permissionSet/(sso)?ins-[a-zA-Z0-9-.]{16}/ps-[a-zA-Z0-9-./]{16}"),
				),
			},

			"principal_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(1, 47),
					validation.StringMatch(regexp.MustCompile(`^([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$`), "must match ([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"),
				),
			},

			"principal_type": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringInSlice([]string{"USER", "GROUP"}, false),
			},

			"target_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAwsAccountId,
			},

			"target_type": {
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				Default:      "AWS_ACCOUNT",
				ValidateFunc: validation.StringInSlice([]string{"AWS_ACCOUNT"}, false),
			},
		},
	}
}

func resourceAwsSsoAssignmentCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	principalID := d.Get("principal_id").(string)
	principalType := d.Get("principal_type").(string)
	targetID := d.Get("target_id").(string)
	targetType := d.Get("target_type").(string)

	req := &ssoadmin.CreateAccountAssignmentInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		PrincipalId:      aws.String(principalID),
		PrincipalType:    aws.String(principalType),
		TargetId:         aws.String(targetID),
		TargetType:       aws.String(targetType),
	}

	log.Printf("[INFO] Creating AWS SSO Assignment")
	resp, err := conn.CreateAccountAssignment(req)
	if err != nil {
		return fmt.Errorf("Error creating AWS SSO Assignment: %s", err)
	}

	status := resp.AccountAssignmentCreationStatus

	if status.CreatedDate != nil {
		d.Set("created_date", status.CreatedDate.Format(time.RFC3339))
	}

	waitResp, waitErr := waitForAssignmentCreation(conn, instanceArn, aws.StringValue(status.RequestId), d.Timeout(schema.TimeoutCreate))
	if waitErr != nil {
		return waitErr
	}

	vars := []string{
		permissionSetArn,
		targetType,
		targetID,
		principalType,
		principalID,
	}
	d.SetId(strings.Join(vars, "_"))

	if waitResp.CreatedDate != nil {
		d.Set("created_date", waitResp.CreatedDate.Format(time.RFC3339))
	}

	return resourceAwsSsoAssignmentRead(d, meta)
}

func resourceAwsSsoAssignmentRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	principalID := d.Get("principal_id").(string)
	principalType := d.Get("principal_type").(string)
	targetID := d.Get("target_id").(string)
	targetType := d.Get("target_type").(string)

	req := &ssoadmin.ListAccountAssignmentsInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		AccountId:        aws.String(targetID),
	}

	log.Printf("[DEBUG] Reading AWS SSO Assignments for %s", req)
	resp, err := conn.ListAccountAssignments(req)
	if err != nil {
		return fmt.Errorf("Error getting AWS SSO Assignments: %s", err)
	}

	if resp == nil || len(resp.AccountAssignments) == 0 {
		log.Printf("[DEBUG] No account assignments found")
		d.SetId("")
		return nil
	}

	for _, accountAssignment := range resp.AccountAssignments {
		if aws.StringValue(accountAssignment.PrincipalType) == principalType {
			if aws.StringValue(accountAssignment.PrincipalId) == principalID {
				vars := []string{
					permissionSetArn,
					targetType,
					targetID,
					principalType,
					principalID,
				}
				d.SetId(strings.Join(vars, "_"))
				return nil
			}
		}
	}

	log.Printf("[DEBUG] Account assignment not found for %s", map[string]string{
		"PrincipalType": principalType,
		"PrincipalId":   principalID,
	})
	d.SetId("")
	return nil
}

func resourceAwsSsoAssignmentDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	principalID := d.Get("principal_id").(string)
	principalType := d.Get("principal_type").(string)
	targetID := d.Get("target_id").(string)
	targetType := d.Get("target_type").(string)

	req := &ssoadmin.DeleteAccountAssignmentInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		PrincipalId:      aws.String(principalID),
		PrincipalType:    aws.String(principalType),
		TargetId:         aws.String(targetID),
		TargetType:       aws.String(targetType),
	}

	log.Printf("[INFO] Deleting AWS SSO Assignment")
	resp, err := conn.DeleteAccountAssignment(req)
	if err != nil {
		aerr, ok := err.(awserr.Error)
		if ok && aerr.Code() == ssoadmin.ErrCodeResourceNotFoundException {
			log.Printf("[DEBUG] AWS SSO Assignment not found")
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error deleting AWS SSO Assignment: %s", err)
	}

	status := resp.AccountAssignmentDeletionStatus

	_, waitErr := waitForAssignmentDeletion(conn, instanceArn, aws.StringValue(status.RequestId), d.Timeout(schema.TimeoutDelete))
	if waitErr != nil {
		return waitErr
	}

	d.SetId("")
	return nil
}

func waitForAssignmentCreation(conn *ssoadmin.SSOAdmin, instanceArn string, requestID string, timeout time.Duration) (*ssoadmin.AccountAssignmentOperationStatus, error) {
	stateConf := &resource.StateChangeConf{
		Pending: []string{ssoadmin.StatusValuesInProgress},
		Target:  []string{ssoadmin.StatusValuesSucceeded},
		Refresh: func() (interface{}, string, error) {
			resp, err := conn.DescribeAccountAssignmentCreationStatus(&ssoadmin.DescribeAccountAssignmentCreationStatusInput{
				InstanceArn:                        aws.String(instanceArn),
				AccountAssignmentCreationRequestId: aws.String(requestID),
			})
			if err != nil {
				return resp, "", fmt.Errorf("Error describing account assignment creation status: %s", err)
			}
			status := resp.AccountAssignmentCreationStatus
			return status, aws.StringValue(status.Status), nil
		},
		Timeout:    timeout,
		Delay:      AWSSSOAssignmentRetryDelay,
		MinTimeout: AWSSSOAssignmentRetryMinTimeout,
	}
	status, err := stateConf.WaitForState()
	if err != nil {
		return nil, fmt.Errorf("Error waiting for account assignment to be created: %s", err)
	}
	return status.(*ssoadmin.AccountAssignmentOperationStatus), nil
}

func waitForAssignmentDeletion(conn *ssoadmin.SSOAdmin, instanceArn string, requestID string, timeout time.Duration) (*ssoadmin.AccountAssignmentOperationStatus, error) {
	stateConf := &resource.StateChangeConf{
		Pending: []string{ssoadmin.StatusValuesInProgress},
		Target:  []string{ssoadmin.StatusValuesSucceeded},
		Refresh: func() (interface{}, string, error) {
			resp, err := conn.DescribeAccountAssignmentDeletionStatus(&ssoadmin.DescribeAccountAssignmentDeletionStatusInput{
				InstanceArn:                        aws.String(instanceArn),
				AccountAssignmentDeletionRequestId: aws.String(requestID),
			})
			if err != nil {
				return resp, "", fmt.Errorf("Error describing account assignment deletion status: %s", err)
			}
			status := resp.AccountAssignmentDeletionStatus
			return status, aws.StringValue(status.Status), nil
		},
		Timeout:    timeout,
		Delay:      AWSSSOAssignmentRetryDelay,
		MinTimeout: AWSSSOAssignmentRetryMinTimeout,
	}
	status, err := stateConf.WaitForState()
	if err != nil {
		return nil, fmt.Errorf("Error waiting for account assignment to be deleted: %s", err)
	}
	return status.(*ssoadmin.AccountAssignmentOperationStatus), nil
}
<commit_msg>fix id and import<commit_after>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ssoadmin"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

const (
	AWSSSOAssignmentCreateRetryTimeout = 5 * time.Minute
	AWSSSOAssignmentDeleteRetryTimeout = 5 * time.Minute
	AWSSSOAssignmentRetryDelay         = 5 * time.Second
	AWSSSOAssignmentRetryMinTimeout    = 3 * time.Second
)

func resourceAwsSsoAssignment() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSsoAssignmentCreate,
		Read:   resourceAwsSsoAssignmentRead,
		Delete: resourceAwsSsoAssignmentDelete,

		// TODO
		Importer: &schema.ResourceImporter{
			State: resourceAwsSsoAssignmentImport,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(AWSSSOAssignmentCreateRetryTimeout),
			Delete: schema.DefaultTimeout(AWSSSOAssignmentDeleteRetryTimeout),
		},

		Schema: map[string]*schema.Schema{
			"instance_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(10, 1224),
					validation.StringMatch(regexp.MustCompile(`^arn:aws:sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}$`), "must match arn:aws:sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}"),
				),
			},

			"permission_set_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(10, 1224),
					validation.StringMatch(regexp.MustCompile(`^arn:aws:sso:::permissionSet/(sso)?ins-[a-zA-Z0-9-.]{16}/ps-[a-zA-Z0-9-./]{16}$`), "must match arn:aws:sso:::permissionSet/(sso)?ins-[a-zA-Z0-9-.]{16}/ps-[a-zA-Z0-9-./]{16}"),
				),
			},

			"target_type": {
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				Default:      ssoadmin.TargetTypeAwsAccount,
				ValidateFunc: validation.StringInSlice([]string{ssoadmin.TargetTypeAwsAccount}, false),
			},

			"target_id": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAwsAccountId,
			},

			"principal_type": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringInSlice([]string{ssoadmin.PrincipalTypeUser, ssoadmin.PrincipalTypeGroup}, false),
			},

			"principal_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(1, 47),
					validation.StringMatch(regexp.MustCompile(`^([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$`), "must match ([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"),
				),
			},

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

func resourceAwsSsoAssignmentCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	targetType := d.Get("target_type").(string)
	targetID := d.Get("target_id").(string)
	principalType := d.Get("principal_type").(string)
	principalID := d.Get("principal_id").(string)

	req := &ssoadmin.CreateAccountAssignmentInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		TargetType:       aws.String(targetType),
		TargetId:         aws.String(targetID),
		PrincipalType:    aws.String(principalType),
		PrincipalId:      aws.String(principalID),
	}

	log.Printf("[INFO] Creating AWS SSO Assignment")
	resp, err := conn.CreateAccountAssignment(req)
	if err != nil {
		return fmt.Errorf("Error creating AWS SSO Assignment: %s", err)
	}

	status := resp.AccountAssignmentCreationStatus

	if status.CreatedDate != nil {
		d.Set("created_date", status.CreatedDate.Format(time.RFC3339))
	}

	waitResp, waitErr := waitForAssignmentCreation(conn, instanceArn, aws.StringValue(status.RequestId), d.Timeout(schema.TimeoutCreate))
	if waitErr != nil {
		return waitErr
	}

	id, idErr := resourceAwsSsoAssignmentID(instanceArn, permissionSetArn, targetType, targetID, principalType, principalID)
	if idErr != nil {
		return idErr
	}
	d.SetId(id)

	if waitResp.CreatedDate != nil {
		d.Set("created_date", waitResp.CreatedDate.Format(time.RFC3339))
	}

	return resourceAwsSsoAssignmentRead(d, meta)
}

func resourceAwsSsoAssignmentRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	targetType := d.Get("target_type").(string)
	targetID := d.Get("target_id").(string)
	principalType := d.Get("principal_type").(string)
	principalID := d.Get("principal_id").(string)

	req := &ssoadmin.ListAccountAssignmentsInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		AccountId:        aws.String(targetID),
	}

	log.Printf("[DEBUG] Reading AWS SSO Assignments for %s", req)
	resp, err := conn.ListAccountAssignments(req)
	if err != nil {
		return fmt.Errorf("Error getting AWS SSO Assignments: %s", err)
	}

	if resp == nil || len(resp.AccountAssignments) == 0 {
		log.Printf("[DEBUG] No account assignments found")
		d.SetId("")
		return nil
	}

	for _, accountAssignment := range resp.AccountAssignments {
		if aws.StringValue(accountAssignment.PrincipalType) == principalType {
			if aws.StringValue(accountAssignment.PrincipalId) == principalID {
				id, idErr := resourceAwsSsoAssignmentID(instanceArn, permissionSetArn, targetType, targetID, principalType, principalID)
				if idErr != nil {
					return idErr
				}
				d.SetId(id)
				return nil
			}
		}
	}

	log.Printf("[DEBUG] Account assignment not found for %s", map[string]string{
		"PrincipalType": principalType,
		"PrincipalId":   principalID,
	})
	d.SetId("")
	return nil
}

func resourceAwsSsoAssignmentDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ssoadminconn

	instanceArn := d.Get("instance_arn").(string)
	permissionSetArn := d.Get("permission_set_arn").(string)
	targetType := d.Get("target_type").(string)
	targetID := d.Get("target_id").(string)
	principalType := d.Get("principal_type").(string)
	principalID := d.Get("principal_id").(string)

	req := &ssoadmin.DeleteAccountAssignmentInput{
		InstanceArn:      aws.String(instanceArn),
		PermissionSetArn: aws.String(permissionSetArn),
		TargetType:       aws.String(targetType),
		TargetId:         aws.String(targetID),
		PrincipalType:    aws.String(principalType),
		PrincipalId:      aws.String(principalID),
	}

	log.Printf("[INFO] Deleting AWS SSO Assignment")
	resp, err := conn.DeleteAccountAssignment(req)
	if err != nil {
		aerr, ok := err.(awserr.Error)
		if ok && aerr.Code() == ssoadmin.ErrCodeResourceNotFoundException {
			log.Printf("[DEBUG] AWS SSO Assignment not found")
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error deleting AWS SSO Assignment: %s", err)
	}

	status := resp.AccountAssignmentDeletionStatus

	_, waitErr := waitForAssignmentDeletion(conn, instanceArn, aws.StringValue(status.RequestId), d.Timeout(schema.TimeoutDelete))
	if waitErr != nil {
		return waitErr
	}

	d.SetId("")
	return nil
}

func resourceAwsSsoAssignmentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	// id = ${InstanceID}/${PermissionSetID}/${TargetType}/${TargetID}/${PrincipalType}/${PrincipalID}
	idParts := strings.Split(d.Id(), "/")
	if len(idParts) != 6 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" || idParts[4] == "" || idParts[5] == "" {
		return nil, fmt.Errorf("Unexpected format of id (%s), expected ${InstanceID}/${PermissionSetID}/${TargetType}/${TargetID}/${PrincipalType}/${PrincipalID}", d.Id())
	}

	instanceID := idParts[0]
	permissionSetID := idParts[1]
	targetType := idParts[2]
	targetID := idParts[3]
	principalType := idParts[4]
	principalID := idParts[5]

	var err error

	// arn:${Partition}:sso:::instance/${InstanceId}
	instanceArn := arn.ARN{
		Partition: meta.(*AWSClient).partition,
		Service:   "sso",
		Resource:  fmt.Sprintf("instance/%s", instanceID),
	}.String()

	// arn:${Partition}:sso:::permissionSet/${InstanceId}/${PermissionSetId}
	permissionSetArn := arn.ARN{
		Partition: meta.(*AWSClient).partition,
		Service:   "sso",
		Resource:  fmt.Sprintf("permissionSet/%s/%s", instanceID, permissionSetID),
	}.String()

	err = d.Set("instance_arn", instanceArn)
	if err != nil {
		return nil, err
	}
	err = d.Set("permission_set_arn", permissionSetArn)
	if err != nil {
		return nil, err
	}
	err = d.Set("target_type", targetType)
	if err != nil {
		return nil, err
	}
	err = d.Set("target_id", targetID)
	if err != nil {
		return nil, err
	}
	err = d.Set("principal_type", principalType)
	if err != nil {
		return nil, err
	}
	err = d.Set("principal_id", principalID)
	if err != nil {
		return nil, err
	}

	id, idErr := resourceAwsSsoAssignmentID(instanceArn, permissionSetArn, targetType, targetID, principalType, principalID)
	if idErr != nil {
		return nil, idErr
	}
	d.SetId(id)

	return []*schema.ResourceData{d}, nil
}

func resourceAwsSsoAssignmentID(
	instanceArn string,
	permissionSetArn string,
	targetType string,
	targetID string,
	principalType string,
	principalID string,
) (string, error) {
	// arn:${Partition}:sso:::instance/${InstanceId}
	iArn, err := arn.Parse(instanceArn)
	if err != nil {
		return "", err
	}
	iArnResourceParts := strings.Split(iArn.Resource, "/")
	if len(iArnResourceParts) != 2 || iArnResourceParts[0] != "instance" || iArnResourceParts[1] == "" {
		return "", fmt.Errorf("Unexpected format of ARN (%s), expected arn:${Partition}:sso:::instance/${InstanceId}", instanceArn)
	}
	instanceID := iArnResourceParts[1]

	// arn:${Partition}:sso:::permissionSet/${InstanceId}/${PermissionSetId}
	pArn, err := arn.Parse(permissionSetArn)
	if err != nil {
		return "", err
	}
	pArnResourceParts := strings.Split(pArn.Resource, "/")
	if len(iArnResourceParts) != 3 || pArnResourceParts[0] != "permissionSet" || pArnResourceParts[1] == "" || pArnResourceParts[2] == "" {
		return "", fmt.Errorf("Unexpected format of ARN (%s), expected arn:${Partition}:sso:::permissionSet/${InstanceId}/${PermissionSetId}", instanceArn)
	}
	permissionSetID := pArnResourceParts[2]

	vars := []string{
		instanceID,
		permissionSetID,
		targetType,
		targetID,
		principalType,
		principalID,
	}
	return strings.Join(vars, "/"), nil
}

func waitForAssignmentCreation(conn *ssoadmin.SSOAdmin, instanceArn string, requestID string, timeout time.Duration) (*ssoadmin.AccountAssignmentOperationStatus, error) {
	stateConf := &resource.StateChangeConf{
		Pending: []string{ssoadmin.StatusValuesInProgress},
		Target:  []string{ssoadmin.StatusValuesSucceeded},
		Refresh: func() (interface{}, string, error) {
			resp, err := conn.DescribeAccountAssignmentCreationStatus(&ssoadmin.DescribeAccountAssignmentCreationStatusInput{
				InstanceArn:                        aws.String(instanceArn),
				AccountAssignmentCreationRequestId: aws.String(requestID),
			})
			if err != nil {
				return resp, "", fmt.Errorf("Error describing account assignment creation status: %s", err)
			}
			status := resp.AccountAssignmentCreationStatus
			return status, aws.StringValue(status.Status), nil
		},
		Timeout:    timeout,
		Delay:      AWSSSOAssignmentRetryDelay,
		MinTimeout: AWSSSOAssignmentRetryMinTimeout,
	}
	status, err := stateConf.WaitForState()
	if err != nil {
		return nil, fmt.Errorf("Error waiting for account assignment to be created: %s", err)
	}
	return status.(*ssoadmin.AccountAssignmentOperationStatus), nil
}

func waitForAssignmentDeletion(conn *ssoadmin.SSOAdmin, instanceArn string, requestID string, timeout time.Duration) (*ssoadmin.AccountAssignmentOperationStatus, error) {
	stateConf := &resource.StateChangeConf{
		Pending: []string{ssoadmin.StatusValuesInProgress},
		Target:  []string{ssoadmin.StatusValuesSucceeded},
		Refresh: func() (interface{}, string, error) {
			resp, err := conn.DescribeAccountAssignmentDeletionStatus(&ssoadmin.DescribeAccountAssignmentDeletionStatusInput{
				InstanceArn:                        aws.String(instanceArn),
				AccountAssignmentDeletionRequestId: aws.String(requestID),
			})
			if err != nil {
				return resp, "", fmt.Errorf("Error describing account assignment deletion status: %s", err)
			}
			status := resp.AccountAssignmentDeletionStatus
			return status, aws.StringValue(status.Status), nil
		},
		Timeout:    timeout,
		Delay:      AWSSSOAssignmentRetryDelay,
		MinTimeout: AWSSSOAssignmentRetryMinTimeout,
	}
	status, err := stateConf.WaitForState()
	if err != nil {
		return nil, fmt.Errorf("Error waiting for account assignment to be deleted: %s", err)
	}
	return status.(*ssoadmin.AccountAssignmentOperationStatus), nil
}
<|endoftext|>


<commit_before>// +build linux

package linux

import (
	"os"
	"testing"
	"time"
)

func TestDiskGenerator(t *testing.T) {
	if os.Getenv("TRAVIS") != "" {
		// ref. https://github.com/travis-ci/travis-ci/issues/2627
		t.Skipf("Skip: can't access `/proc/diskstats` in Travis environment.")
	}

	g := &DiskGenerator{1 * time.Second}
	values, err := g.Generate()
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	metrics := []string{
		"reads", "writes",
	}

	if _, ok := values["disk.sda.reads.delta"]; !ok {
		t.Skipf("Skip: this node does not have sda device")
	}

	for _, metric := range metrics {
		if value, ok := values["disk.sda."+metric+".delta"]; !ok {
			t.Errorf("Value for disk.sda.%s.delta should be collected", metric)
		} else {
			t.Logf("Disk '%s' delta collected: %+v", metric, value)
		}
	}

	for _, key := range metrics {
		if value, ok := values["disk.loop0."+key+".delta"]; ok {
			t.Errorf("Value for disk.loop0.%s should not be collected but got %v. The value won't change.", key, value)
		}
	}
}

// var diskMetricsNames = []string{
// 	"reads", "readsMerged", "sectorsRead", "readTime",
// 	"writes", "writesMerged", "sectorsWritten", "writeTime",
// 	"ioInProgress", "ioTime", "ioTimeWeighted",
// }

func TestParseDiskStats(t *testing.T) {

	out := []byte(`202       1 xvda1 750193 3037 28116978 368712 16600606 7233846 424712632 23987908 0 2355636 24345740

202       2 xvda2 1641 9310 87552 1252 6365 3717 80664 24192 0 15040 25428`)

	result, err := parseDiskStats(out)
	if err != nil {
		t.Errorf("error should be nil but: %s", err)
	}

	expect := metrics.Values(map[string]float64{
		"disk.xvda1.reads": 750193,
		"disk.xvda1.readsMerged": 3037,
		"disk.xvda1.sectorsRead": 28116978,
		"disk.xvda1.readTime": 368712,
		"disk.xvda1.writes": 16600606,
		"disk.xvda1.writesMerged": 7233846,
		"disk.xvda1.sectorsWritten": 424712632,
		"disk.xvda1.writeTime": 23987908,
		"disk.xvda1.ioInProgress": 0,
		"disk.xvda1.ioTime": 2355636,
		"disk.xvda1.ioTimeWeighted": 24345740,
		"disk.xvda2.reads": 1641,
		"disk.xvda2.readsMerged": 9310,
		"disk.xvda2.sectorsRead": 87552,
		"disk.xvda2.readTime": 1252,
		"disk.xvda2.writes": 6365,
		"disk.xvda2.writesMerged": 3717,
		"disk.xvda2.sectorsWritten": 80664,
		"disk.xvda2.writeTime": 24192,
		"disk.xvda2.ioInProgress": 0,
		"disk.xvda2.ioTime": 15040,
		"disk.xvda2.ioTimeWeighted": 25428,
	})
	if !reflect.DeepEqual(result, expect) {
		t.Errorf("result is not expected one: %+v", result)
	}
}
<commit_msg>fix import<commit_after>// +build linux

package linux

import (
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/mackerelio/mackerel-agent/metrics"
)

func TestDiskGenerator(t *testing.T) {
	if os.Getenv("TRAVIS") != "" {
		// ref. https://github.com/travis-ci/travis-ci/issues/2627
		t.Skipf("Skip: can't access `/proc/diskstats` in Travis environment.")
	}

	g := &DiskGenerator{1 * time.Second}
	values, err := g.Generate()
	if err != nil {
		t.Errorf("should not raise error: %v", err)
	}

	metrics := []string{
		"reads", "writes",
	}

	if _, ok := values["disk.sda.reads.delta"]; !ok {
		t.Skipf("Skip: this node does not have sda device")
	}

	for _, metric := range metrics {
		if value, ok := values["disk.sda."+metric+".delta"]; !ok {
			t.Errorf("Value for disk.sda.%s.delta should be collected", metric)
		} else {
			t.Logf("Disk '%s' delta collected: %+v", metric, value)
		}
	}

	for _, key := range metrics {
		if value, ok := values["disk.loop0."+key+".delta"]; ok {
			t.Errorf("Value for disk.loop0.%s should not be collected but got %v. The value won't change.", key, value)
		}
	}
}

func TestParseDiskStats(t *testing.T) {
	// insert empty line intentionally
	out := []byte(`202       1 xvda1 750193 3037 28116978 368712 16600606 7233846 424712632 23987908 0 2355636 24345740

202       2 xvda2 1641 9310 87552 1252 6365 3717 80664 24192 0 15040 25428`)

	result, err := parseDiskStats(out)
	if err != nil {
		t.Errorf("error should be nil but: %s", err)
	}

	expect := metrics.Values(map[string]float64{
		"disk.xvda1.reads": 750193,
		"disk.xvda1.readsMerged": 3037,
		"disk.xvda1.sectorsRead": 28116978,
		"disk.xvda1.readTime": 368712,
		"disk.xvda1.writes": 16600606,
		"disk.xvda1.writesMerged": 7233846,
		"disk.xvda1.sectorsWritten": 424712632,
		"disk.xvda1.writeTime": 23987908,
		"disk.xvda1.ioInProgress": 0,
		"disk.xvda1.ioTime": 2355636,
		"disk.xvda1.ioTimeWeighted": 24345740,
		"disk.xvda2.reads": 1641,
		"disk.xvda2.readsMerged": 9310,
		"disk.xvda2.sectorsRead": 87552,
		"disk.xvda2.readTime": 1252,
		"disk.xvda2.writes": 6365,
		"disk.xvda2.writesMerged": 3717,
		"disk.xvda2.sectorsWritten": 80664,
		"disk.xvda2.writeTime": 24192,
		"disk.xvda2.ioInProgress": 0,
		"disk.xvda2.ioTime": 15040,
		"disk.xvda2.ioTimeWeighted": 25428,
	})
	if !reflect.DeepEqual(result, expect) {
		t.Errorf("result is not expected one: %+v", result)
	}
}
<|endoftext|>


<commit_before>package astmatcher

import (
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
)

var file *File

type File struct {
	file *ast.File
	err  error
}

type MatcherFunc func(*ast.FuncDecl) bool

func (matcher MatcherFunc) Match(fnDecl *ast.FuncDecl) bool {
	return matcher(fnDecl)
}

func HasName(pattern string) MatcherFunc {
	return func(fnDecl *ast.FuncDecl) bool {
		matched, err := regexp.MatchString(pattern, fnDecl.Name.Name)
		return (err == nil) && matched
	}
}

func FuncDecl(matchers ...MatcherFunc) []*ast.FuncDecl {
	fnDecls := []*ast.FuncDecl{}

	for _, decl := range file.file.Decls {
		switch fnDecl := decl.(type) {
		case *ast.FuncDecl:
			if match(fnDecl, matchers) {
				fnDecls = append(fnDecls, fnDecl)
			}
		}
	}

	return fnDecls
}

func match(fnDecl *ast.FuncDecl, matchers []MatcherFunc) bool {
	return len(matchers) == 0 || matchers[0].Match(fnDecl)
}

func ParseSrc(src string) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "src.go", src, 0)

	file = &File{
		file: f,
		err:  err,
	}
}
<commit_msg>Remove struct File<commit_after>package astmatcher

import (
	"go/ast"
	"go/parser"
	"go/token"
	"regexp"
)

var file *ast.File

type MatcherFunc func(*ast.FuncDecl) bool

func (matcher MatcherFunc) Match(fnDecl *ast.FuncDecl) bool {
	return matcher(fnDecl)
}

func HasName(pattern string) MatcherFunc {
	return func(fnDecl *ast.FuncDecl) bool {
		matched, err := regexp.MatchString(pattern, fnDecl.Name.Name)
		return (err == nil) && matched
	}
}

func FuncDecl(matchers ...MatcherFunc) []*ast.FuncDecl {
	fnDecls := []*ast.FuncDecl{}

	for _, decl := range file.Decls {
		switch fnDecl := decl.(type) {
		case *ast.FuncDecl:
			if match(fnDecl, matchers) {
				fnDecls = append(fnDecls, fnDecl)
			}
		}
	}

	return fnDecls
}

func match(fnDecl *ast.FuncDecl, matchers []MatcherFunc) bool {
	return len(matchers) == 0 || matchers[0].Match(fnDecl)
}

func ParseSrc(src string) {
	fset := token.NewFileSet()
	f, _ := parser.ParseFile(fset, "src.go", src, 0)

	file = f
}
<|endoftext|>


<commit_before>package cgotest

/*
void lockOSThreadCallback(void);
inline static void lockOSThreadC(void)
{
        lockOSThreadCallback();
}
int usleep(unsigned usec);
*/
import "C"

import (
	"runtime"
	"testing"
)

func test3775(t *testing.T) {
	// Used to panic because of the UnlockOSThread below.
	C.lockOSThreadC()
}

//export lockOSThreadCallback
func lockOSThreadCallback() {
	runtime.LockOSThread()
	runtime.UnlockOSThread()
	go C.usleep(10000)
	runtime.Gosched()
}
<commit_msg>misc/cgo/test: test recursive internal OS thread lock<commit_after>package cgotest

/*
void lockOSThreadCallback(void);
inline static void lockOSThreadC(void)
{
        lockOSThreadCallback();
}
int usleep(unsigned usec);
*/
import "C"

import (
	"runtime"
	"testing"
)

func init() {
	// Same as test3775 but run during init so that
	// there are two levels of internal runtime lock
	// (1 for init, 1 for cgo).
	// This would have been broken by CL 11663043.
	C.lockOSThreadC()
}

func test3775(t *testing.T) {
	// Used to panic because of the UnlockOSThread below.
	C.lockOSThreadC()
}

//export lockOSThreadCallback
func lockOSThreadCallback() {
	runtime.LockOSThread()
	runtime.UnlockOSThread()
	go C.usleep(10000)
	runtime.Gosched()
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgotest

import "testing"

/*
extern int issue5548_in_c(void);
*/
import "C"

//export issue5548FromC
func issue5548FromC(s string, i int) int {
	if len(s) == 4 && s == "test" && i == 42 {
		return 1
	}
	return 0
}

func test5548(t *testing.T) {
	if C.issue5548_in_c() == 0 {
		t.Fail()
	}
}
<commit_msg>misc/cgo/test: make issue5548 test pickier<commit_after>// Copyright 2013 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgotest

import "testing"

/*
extern int issue5548_in_c(void);
*/
import "C"

//export issue5548FromC
func issue5548FromC(s string, i int) int {
	if len(s) == 4 && s == "test" && i == 42 {
		return 12345
	}
	println("got", len(s), i)
	return 9876
}

func test5548(t *testing.T) {
	if x := C.issue5548_in_c(); x != 12345 {
		t.Errorf("issue5548_in_c = %d, want %d", x, 12345)
	}
}
<|endoftext|>


<commit_before>package auth2

import (
	"database/sql"
	"time"

	"github.com/lib/pq"
)

type store interface {
	createSchema() error

	createUser(id, appId, email, hashedPass, lang, confirmationKey string, createdAt, confirmedAt time.Time) error
	setUserConfirmedAt(appId, email string, confirmedAt time.Time) error
	setResetKey(appId, email, resetKey string, setResetKeyAt time.Time) error
	setUserHashedPass(appId, email, hashedPass string) error
	getUserConfirmation(appId, email string) (confirmationKey string, confirmedAt time.Time, err error)
	getUserPassword(appId, email string) (userId, hashedPass string, confirmedAt time.Time, err error)
	getUserResetKey(appId, email string) (resetKey string, setResetKeyAt time.Time, err error)

	createSession(id, userId string, createdAt time.Time) error
	removeSession(id string) error
	getSession(sessionId string) (userId string, createdAt time.Time, err error)
}

type storePg struct {
	db *sql.DB
}

func NewStorePg(db *sql.DB) storePg {
	return storePg{db}
}

func (self storePg) createSchema() error {
	schema := `
		CREATE SCHEMA auth;

		CREATE TYPE auth.lang AS ENUM ('pt_PT', 'en_US');

		CREATE TABLE auth.user (
		   id               CHAR(36) NOT NULL,
		   appId            TEXT NOT NULL,
		   email            TEXT NOT NULL,
		   hashedPass       TEXT NOT NULL,
		   createdAt        TIMESTAMPTZ NOT NULL,
		   lang             auth.lang NOT NULL,
		   -- confirm
		   confirmationKey  CHAR(36),
		   confirmedAt      TIMESTAMPTZ,
		   -- reset
		   resetKey         CHAR(36),
		   setResetKeyAt    TIMESTAMPTZ,

		   CONSTRAINT pk_auth_user PRIMARY KEY (id)
		);

		CREATE UNIQUE INDEX idx_auth_user_appId_email ON auth.user (appId, email);

		CREATE TABLE auth.session (
			id         CHAR(36) NOT NULL,
			userId     CHAR(36) NOT NULL,
			createdAt  TIMESTAMPTZ NOT NULL,

			CONSTRAINT pk_auth_session PRIMARY KEY (id),
			CONSTRAINT fk_auth_session_userId FOREIGN KEY (userId) REFERENCES auth.user(id)
		);
	`

	_, err := self.db.Exec(schema)
	return err
}

func (self storePg) createUser(id, appId, email, hashedPass, lang, confirmationKey string, createdAt, confirmedAt time.Time) error {
	insert := `
		INSERT INTO auth.user
		(id, appId, email, hashedPass, lang, confirmationKey, createdAt, confirmedAt)
		VALUES
		($1, $2, $3, $4, $5, $6, $7, $8);
	`

	stmt, err := self.db.Prepare(insert)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id, appId, email, hashedPass, lang, confirmationKey, createdAt, confirmedAt)
	return err
}

func (self storePg) setUserConfirmedAt(appId, email string, confirmedAt time.Time) error {
	update := `
		UPDATE auth.user
		SET confirmedAt = $1
		WHERE appId = $2 AND email = $3;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(confirmedAt, appId, email)
	return err
}

func (self storePg) setResetKey(appId, email, resetKey string, setResetKeyAt time.Time) error {
	update := `
		UPDATE auth.user
		SET resetKey = $1, setResetKeyAt = $2
		WHERE appId = $3 AND email = $4;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(resetKey, setResetKeyAt, appId, email)
	return err
}

func (self storePg) setUserHashedPass(appId, email, hashedPass string) error {
	resetKey := sql.NullString{}
	setResetKeyAt := time.Time{}
	update := `
		UPDATE auth.user
		SET hashedPass = $1, resetKey = $2, setResetKeyAt = $3
		WHERE appId = $4 AND email = $5;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(hashedPass, resetKey, setResetKeyAt, appId, email)
	return err
}

func (self storePg) getUserConfirmation(appId, email string) (confirmationKey string, confirmedAt time.Time, err error) {
	query := `
		SELECT confirmationKey, confirmedAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&confirmationKey, &confirmedAt)
	return
}

func (self storePg) getUserPassword(appId, email string) (userId, hashedPass string, confirmedAt time.Time, err error) {
	query := `
		SELECT id, hashedPass, confirmedAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&userId, &hashedPass, &confirmedAt)
	return
}

func (self storePg) getUserResetKey(appId, email string) (resetKey string, setResetKeyAt time.Time, err error) {
	var resetKey2 sql.NullString
	var setResetKeyAt2 pq.NullTime
	query := `
		SELECT resetKey, setResetKeyAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&resetKey2, &setResetKeyAt2)
	resetKey = resetKey2.String
	setResetKeyAt = setResetKeyAt2.Time
	return
}

func (self storePg) createSession(id, userId string, createdAt time.Time) error {
	insert := `
		INSERT INTO auth.session
		(id, userId, createdAt)
		VALUES
		($1, $2, $3);
	`

	stmt, err := self.db.Prepare(insert)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id, userId, createdAt)
	return err
}

func (self storePg) removeSession(id string) error {
	delete := `
		DELETE FROM auth.session
		WHERE id = $1;
	`

	stmt, err := self.db.Prepare(delete)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id)
	return err
}

func (self storePg) getSession(sessionId string) (userId string, createdAt time.Time, err error) {
	query := `
		SELECT userId, createdAt
		FROM auth.session
		WHERE id = $1;
	`
	err = self.db.QueryRow(query, sessionId).Scan(&userId, &createdAt)
	return
}
<commit_msg>update<commit_after>package auth2

import (
	"database/sql"
	"time"

	"github.com/lib/pq"
)

type store interface {
	createSchema() error

	createUser(id, appId, email, hashedPass, lang, confirmationKey string, createdAt, confirmedAt time.Time) error
	setUserConfirmedAt(appId, email string, confirmedAt time.Time) error
	setResetKey(appId, email, resetKey string, setResetKeyAt time.Time) error
	setUserHashedPass(appId, email, hashedPass string) error
	getUserConfirmation(appId, email string) (confirmationKey string, confirmedAt time.Time, err error)
	getUserPassword(appId, email string) (userId, hashedPass string, confirmedAt time.Time, err error)
	getUserResetKey(appId, email string) (resetKey string, setResetKeyAt time.Time, err error)

	createSession(id, userId string, createdAt time.Time) error
	removeSession(id string) error
	getSession(sessionId string) (userId string, createdAt time.Time, err error)
}

type storePg struct {
	db *sql.DB
}

func NewStorePg(db *sql.DB) storePg {
	return storePg{db}
}

func (self storePg) createSchema() error {
	schema := `
		CREATE SCHEMA auth;

		CREATE TYPE auth.lang AS ENUM ('pt_PT', 'en_US');

		CREATE TABLE auth.user (
		   id               CHAR(36) NOT NULL,
		   appId            TEXT NOT NULL,
		   email            TEXT NOT NULL,
		   hashedPass       TEXT NOT NULL,
		   createdAt        TIMESTAMPTZ NOT NULL,
		   lang             auth.lang NOT NULL,
		   -- confirm
		   confirmationKey  CHAR(36),
		   confirmedAt      TIMESTAMPTZ,
		   -- reset
		   resetKey         CHAR(36),
		   setResetKeyAt    TIMESTAMPTZ,

		   CONSTRAINT pk_auth_user PRIMARY KEY (id)
		);

		CREATE UNIQUE INDEX idx_auth_user_appId_email ON auth.user (appId, email);

		CREATE TABLE auth.session (
			id         CHAR(36) NOT NULL,
			userId     CHAR(36) NOT NULL,
			createdAt  TIMESTAMPTZ NOT NULL,

			CONSTRAINT pk_auth_session PRIMARY KEY (id),
			CONSTRAINT fk_auth_session_userId FOREIGN KEY (userId) REFERENCES auth.user(id)
		);
	`

	_, err := self.db.Exec(schema)
	return err
}

func (self storePg) createUser(id, appId, email, hashedPass, lang, confirmationKey string, createdAt, confirmedAt time.Time) error {
	insert := `
		INSERT INTO auth.user
		(id, appId, email, hashedPass, lang, confirmationKey, createdAt, confirmedAt)
		VALUES
		($1, $2, $3, $4, $5, $6, $7, $8);
	`

	stmt, err := self.db.Prepare(insert)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id, appId, email, hashedPass, lang, confirmationKey, createdAt, confirmedAt)
	return err
}

func (self storePg) setUserConfirmedAt(appId, email string, confirmedAt time.Time) error {
	update := `
		UPDATE auth.user
		SET confirmedAt = $1
		WHERE appId = $2 AND email = $3;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(confirmedAt, appId, email)
	return err
}

func (self storePg) setResetKey(appId, email, resetKey string, setResetKeyAt time.Time) error {
	update := `
		UPDATE auth.user
		SET resetKey = $1, setResetKeyAt = $2
		WHERE appId = $3 AND email = $4;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(resetKey, setResetKeyAt, appId, email)
	return err
}

func (self storePg) setUserHashedPass(appId, email, hashedPass string) error {
	update := `
		UPDATE auth.user
		SET hashedPass = $1, resetKey = NULL, setResetKeyAt = NULL
		WHERE appId = $2 AND email = $3;
	`

	stmt, err := self.db.Prepare(update)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(hashedPass, appId, email)
	return err
}

func (self storePg) getUserConfirmation(appId, email string) (confirmationKey string, confirmedAt time.Time, err error) {
	query := `
		SELECT confirmationKey, confirmedAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&confirmationKey, &confirmedAt)
	return
}

func (self storePg) getUserPassword(appId, email string) (userId, hashedPass string, confirmedAt time.Time, err error) {
	query := `
		SELECT id, hashedPass, confirmedAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&userId, &hashedPass, &confirmedAt)
	return
}

func (self storePg) getUserResetKey(appId, email string) (resetKey string, setResetKeyAt time.Time, err error) {
	var scanResetKey sql.NullString
	var scanSetResetKeyAt pq.NullTime
	query := `
		SELECT resetKey, setResetKeyAt
		FROM auth.user
		WHERE appId = $1 AND email = $2;
	`
	err = self.db.QueryRow(query, appId, email).Scan(&scanResetKey, &scanSetResetKeyAt)
	if scanResetKey.Valid {
		resetKey = scanResetKey.String
	}
	if scanSetResetKeyAt.Valid {
		setResetKeyAt = scanSetResetKeyAt.Time
	}
	return
}

func (self storePg) createSession(id, userId string, createdAt time.Time) error {
	insert := `
		INSERT INTO auth.session
		(id, userId, createdAt)
		VALUES
		($1, $2, $3);
	`

	stmt, err := self.db.Prepare(insert)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id, userId, createdAt)
	return err
}

func (self storePg) removeSession(id string) error {
	delete := `
		DELETE FROM auth.session
		WHERE id = $1;
	`

	stmt, err := self.db.Prepare(delete)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(id)
	return err
}

func (self storePg) getSession(sessionId string) (userId string, createdAt time.Time, err error) {
	query := `
		SELECT userId, createdAt
		FROM auth.session
		WHERE id = $1;
	`
	err = self.db.QueryRow(query, sessionId).Scan(&userId, &createdAt)
	return
}
<|endoftext|>


<commit_before>/*
   Copyright (C) 2014  Salsita s.r.o.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see {http://www.gnu.org/licenses/}.
*/

package jira

import (
	// Stdlib
	"fmt"
	"net/http"

	// Vendor
	"github.com/google/go-querystring/query"
)

// Resources -------------------------------------------------------------------

// IssueList represents a list of issues, what a surprise.
type IssueList struct {
	Expand     string   `json:"expand,omitempty"`
	StartAt    int      `json:"startAt,omitempty"`
	MaxResults int      `json:"maxResults,omitempty"`
	Total      int      `json:"total,omitempty"`
	Issues     []*Issue `json:"issues,omitempty"`
}

// Issue represents the issue resource as produces by the REST API.
type Issue struct {
	Id     string `json:"id,omitempty"`
	Self   string `json:"self,omitempty"`
	Key    string `json:"key,omitempty"`
	Fields struct {
		Summary   string `json:"summary,omitempty"`
		IssueType struct {
			Id          string `json:"id,omitempty"`
			Self        string `json:"self,omitempty"`
			Name        string `json:"name,omitempty"`
			Description string `json:"description,omitempty"`
			Subtask     bool   `json:"subtask,omitempty"`
			IconURL     string `json:"iconUrl,omitempty"`
		} `json:"issuetype,omitempty"`
		Parent         *Issue           `json:"parent,omitempty"`
		Subtasks       []*Issue         `json:"subtasks,omitempty"`
		Assignee       *User            `json:"assignee,omitempty"`
		FixVersions    []*Version       `json:"fixVersions,omitempty"`
		Labels         []string         `json:"labels,omitempty"`
		Status         *IssueStatus     `json:"status,omitempty"`
		Resolution     *IssueResolution `json:"resolution,omitempty"`
		Created        string           `json:"created,omitempty"`
		Updated        string           `json:"updated,omitempty"`
		Creator        *User            `json:"creator,omitempty"`
		Reporter       *User            `json:"reporter,omitempty"`
		ResolutionDate string           `json:"resolutiondate,omitempty"`
		Project        *Project         `json:"project,omitempty"`
		Priority       struct {
			Self    string `json:"self,omitempty"`
			IconURL string `json:"iconUrl,omitempty"`
			Name    string `json:"name,omitempty"`
			Id      string `json:"id,omitempty"`
		} `json:"priority,omitempty"`
		Components []*Component `json:"components,omitempty"`
		ChangeLog  *ChangeLog   `json:"changelog,omitempty"`
	} `json:"fields,omitempty"`
}

// IssueStatus represents an issue status, e.g. "Scheduled".
type IssueStatus struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	IconURL     string `json:"iconUrl,omitempty"`
}

// IssueResolution represents an issue resolution, e.g. "Cannot Reproduce".
type IssueResolution struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
}

type Transition struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name	,omitempty"`
	To   struct {
		Self           string `json:"self,omitempty"`
		Description    string `json:"description,omitempty"`
		IconUrl        string `json:"iconUrl,omitempty"`
		Name           string `json:"name,omitempty"`
		Id             string `json:"id,omitempty"`
		StatusCategory struct {
			Self      string `json:"self,omitempty"`
			Id        int    `json:"id,omitempty"`
			Key       string `json:"key,omitempty"`
			ColorName string `json:"colorName,omitempty"`
			Name      string `json:"name,omitempty"`
		}
	}
}

type TransitionsInfo struct {
	Expand      string       `json:"expand,omitempty"`
	Transitions []Transition `json:"transitions,omitempty"`
}

// The service -----------------------------------------------------------------

type IssueService struct {
	client *Client
}

func newIssueService(client *Client) *IssueService {
	return &IssueService{client}
}

type SearchOptions struct {
	JQL           string `url:"jql,omitempty"`
	StartAt       int    `url:"startAt,omitempty"`
	MaxResults    int    `url:"maxResults,omitempty"`
	ValidateQuery bool   `url:"validateQuery,omitempty"`
}

func (service *IssueService) Search(opts *SearchOptions) ([]*Issue, *http.Response, error) {
	u := "search"
	if opts != nil {
		vs, err := query.Values(opts)
		if err != nil {
			panic(err)
		}
		u += "?" + vs.Encode()
	}

	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issueList IssueList
	resp, err := service.client.Do(req, &issueList)
	if err != nil {
		return nil, nil, err
	}

	// TODO: Deal with pagination.
	return issueList.Issues, resp, nil
}

// Get returns the chosen issue.
func (service *IssueService) Get(issueIdOrKey string) (*Issue, *http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issue Issue
	resp, err := service.client.Do(req, &issue)
	if err != nil {
		return nil, nil, err
	}
	return &issue, resp, nil
}

// Update updates the chosen issue.
func (service *IssueService) Update(issueIdOrKey string, body interface{}) (*http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("PUT", u, body)
	if err != nil {
		return nil, err
	}
	return service.client.Do(req, nil)
}

// PerformTransition performs the requested transition for the chosen issue.
func (service *IssueService) PerformTransition(
	issueIdOrKey string,
	transition interface{},
) (*http.Response, error) {

	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("POST", u, transition)
	if err != nil {
		return nil, err
	}

	return service.client.Do(req, nil)
}

// GetTransitions returns list of the transitions possible for this issue by the current user.
func (service *IssueService) GetTransitions(issueIdOrKey string) (*TransitionsInfo, *http.Response, error) {
	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var transitionsInfo TransitionsInfo
	resp, err := service.client.Do(req, &transitionsInfo)
	if err != nil {
		return nil, nil, err
	}

	return &transitionsInfo, resp, nil
}
<commit_msg>Fixed nesting level of changelog in Issue<commit_after>/*
   Copyright (C) 2014  Salsita s.r.o.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see {http://www.gnu.org/licenses/}.
*/

package jira

import (
	// Stdlib
	"fmt"
	"net/http"

	// Vendor
	"github.com/google/go-querystring/query"
)

// Resources -------------------------------------------------------------------

// IssueList represents a list of issues, what a surprise.
type IssueList struct {
	Expand     string   `json:"expand,omitempty"`
	StartAt    int      `json:"startAt,omitempty"`
	MaxResults int      `json:"maxResults,omitempty"`
	Total      int      `json:"total,omitempty"`
	Issues     []*Issue `json:"issues,omitempty"`
}

// Issue represents the issue resource as produces by the REST API.
type Issue struct {
	Id     string `json:"id,omitempty"`
	Self   string `json:"self,omitempty"`
	Key    string `json:"key,omitempty"`
	Fields struct {
		Summary   string `json:"summary,omitempty"`
		IssueType struct {
			Id          string `json:"id,omitempty"`
			Self        string `json:"self,omitempty"`
			Name        string `json:"name,omitempty"`
			Description string `json:"description,omitempty"`
			Subtask     bool   `json:"subtask,omitempty"`
			IconURL     string `json:"iconUrl,omitempty"`
		} `json:"issuetype,omitempty"`
		Parent         *Issue           `json:"parent,omitempty"`
		Subtasks       []*Issue         `json:"subtasks,omitempty"`
		Assignee       *User            `json:"assignee,omitempty"`
		FixVersions    []*Version       `json:"fixVersions,omitempty"`
		Labels         []string         `json:"labels,omitempty"`
		Status         *IssueStatus     `json:"status,omitempty"`
		Resolution     *IssueResolution `json:"resolution,omitempty"`
		Created        string           `json:"created,omitempty"`
		Updated        string           `json:"updated,omitempty"`
		Creator        *User            `json:"creator,omitempty"`
		Reporter       *User            `json:"reporter,omitempty"`
		ResolutionDate string           `json:"resolutiondate,omitempty"`
		Project        *Project         `json:"project,omitempty"`
		Priority       struct {
			Self    string `json:"self,omitempty"`
			IconURL string `json:"iconUrl,omitempty"`
			Name    string `json:"name,omitempty"`
			Id      string `json:"id,omitempty"`
		} `json:"priority,omitempty"`
		Components []*Component `json:"components,omitempty"`
	} `json:"fields,omitempty"`
	ChangeLog *ChangeLog `json:"changelog,omitempty"`
}

// IssueStatus represents an issue status, e.g. "Scheduled".
type IssueStatus struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	IconURL     string `json:"iconUrl,omitempty"`
}

// IssueResolution represents an issue resolution, e.g. "Cannot Reproduce".
type IssueResolution struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
}

type Transition struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name	,omitempty"`
	To   struct {
		Self           string `json:"self,omitempty"`
		Description    string `json:"description,omitempty"`
		IconUrl        string `json:"iconUrl,omitempty"`
		Name           string `json:"name,omitempty"`
		Id             string `json:"id,omitempty"`
		StatusCategory struct {
			Self      string `json:"self,omitempty"`
			Id        int    `json:"id,omitempty"`
			Key       string `json:"key,omitempty"`
			ColorName string `json:"colorName,omitempty"`
			Name      string `json:"name,omitempty"`
		}
	}
}

type TransitionsInfo struct {
	Expand      string       `json:"expand,omitempty"`
	Transitions []Transition `json:"transitions,omitempty"`
}

// The service -----------------------------------------------------------------

type IssueService struct {
	client *Client
}

func newIssueService(client *Client) *IssueService {
	return &IssueService{client}
}

type SearchOptions struct {
	JQL           string `url:"jql,omitempty"`
	StartAt       int    `url:"startAt,omitempty"`
	MaxResults    int    `url:"maxResults,omitempty"`
	ValidateQuery bool   `url:"validateQuery,omitempty"`
}

func (service *IssueService) Search(opts *SearchOptions) ([]*Issue, *http.Response, error) {
	u := "search"
	if opts != nil {
		vs, err := query.Values(opts)
		if err != nil {
			panic(err)
		}
		u += "?" + vs.Encode()
	}

	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issueList IssueList
	resp, err := service.client.Do(req, &issueList)
	if err != nil {
		return nil, nil, err
	}

	// TODO: Deal with pagination.
	return issueList.Issues, resp, nil
}

// Get returns the chosen issue.
func (service *IssueService) Get(issueIdOrKey string) (*Issue, *http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issue Issue
	resp, err := service.client.Do(req, &issue)
	if err != nil {
		return nil, nil, err
	}
	return &issue, resp, nil
}

// Update updates the chosen issue.
func (service *IssueService) Update(issueIdOrKey string, body interface{}) (*http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("PUT", u, body)
	if err != nil {
		return nil, err
	}
	return service.client.Do(req, nil)
}

// PerformTransition performs the requested transition for the chosen issue.
func (service *IssueService) PerformTransition(
	issueIdOrKey string,
	transition interface{},
) (*http.Response, error) {

	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("POST", u, transition)
	if err != nil {
		return nil, err
	}

	return service.client.Do(req, nil)
}

// GetTransitions returns list of the transitions possible for this issue by the current user.
func (service *IssueService) GetTransitions(issueIdOrKey string) (*TransitionsInfo, *http.Response, error) {
	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var transitionsInfo TransitionsInfo
	resp, err := service.client.Do(req, &transitionsInfo)
	if err != nil {
		return nil, nil, err
	}

	return &transitionsInfo, resp, nil
}
<|endoftext|>


<commit_before>/*
   Copyright (C) 2014  Salsita s.r.o.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see {http://www.gnu.org/licenses/}.
*/

package jira

import (
	// Stdlib
	"fmt"
	"net/http"

	// Vendor
	"github.com/google/go-querystring/query"
)

// Resources -------------------------------------------------------------------

// IssueList represents a list of issues, what a surprise.
type IssueList struct {
	Expand     string   `json:"expand,omitempty"`
	StartAt    int      `json:"startAt,omitempty"`
	MaxResults int      `json:"maxResults,omitempty"`
	Total      int      `json:"total,omitempty"`
	Issues     []*Issue `json:"issues,omitempty"`
}

// Issue represents the issue resource as produces by the REST API.
type Issue struct {
	Id     string `json:"id,omitempty"`
	Self   string `json:"self,omitempty"`
	Key    string `json:"key,omitempty"`
	Fields struct {
		Summary   string `json:"summary,omitempty"`
		IssueType struct {
			Id          string `json:"id,omitempty"`
			Self        string `json:"self,omitempty"`
			Name        string `json:"name,omitempty"`
			Description string `json:"description,omitempty"`
			Subtask     bool   `json:"subtask,omitempty"`
			IconURL     string `json:"iconUrl,omitempty"`
		} `json:"issuetype,omitempty"`
		Parent      *Issue           `json:"parent,omitempty"`
		Subtasks    []*Issue         `json:"subtasks,omitempty"`
		Assignee    *User            `json:"assignee,omitempty"`
		FixVersions []*Version       `json:"fixVersions,omitempty"`
		Labels      []string         `json:"labels,omitempty"`
		Status      *IssueStatus     `json:"status,omitempty"`
		Resolution  *IssueResolution `json:"resolution,omitempty"`
	} `json:"fields,omitempty"`
}

// IssueStatus represents an issue status, e.g. "Scheduled".
type IssueStatus struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	IconURL     string `json:"iconUrl,omitempty"`
}

// IssueResolution represents an issue resolution, e.g. "Cannot Reproduce".
type IssueResolution struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
}

type Transition struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name	,omitempty"`
	To   struct {
		Self           string `json:"self,omitempty"`
		Description    string `json:"description,omitempty"`
		IconUrl        string `json:"iconUrl,omitempty"`
		Name           string `json:"name,omitempty"`
		Id             string `json:"id,omitempty"`
		StatusCategory struct {
			Self      string `json:"self,omitempty"`
			Id        int    `json:"id,omitempty"`
			Key       string `json:"key,omitempty"`
			ColorName string `json:"colorName,omitempty"`
			Name      string `json:"name,omitempty"`
		}
	}
}

type TransitionsInfo struct {
	Expand      string       `json:"expand,omitempty"`
	Transitions []Transition `json:"transitions,omitempty"`
}

// The service -----------------------------------------------------------------

type IssueService struct {
	client *Client
}

func newIssueService(client *Client) *IssueService {
	return &IssueService{client}
}

type SearchOptions struct {
	JQL           string `url:"jql,omitempty"`
	StartAt       int    `url:"startAt,omitempty"`
	MaxResults    int    `url:"maxResults,omitempty"`
	ValidateQuery bool   `url:"validateQuery,omitempty"`
}

func (service *IssueService) Search(opts *SearchOptions) ([]*Issue, *http.Response, error) {
	u := "search"
	if opts != nil {
		vs, err := query.Values(opts)
		if err != nil {
			panic(err)
		}
		u += "?" + vs.Encode()
	}

	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issueList IssueList
	resp, err := service.client.Do(req, &issueList)
	if err != nil {
		return nil, nil, err
	}

	// TODO: Deal with pagination.
	return issueList.Issues, resp, nil
}

// Get returns the chosen issue.
func (service *IssueService) Get(issueIdOrKey string) (*Issue, *http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issue Issue
	resp, err := service.client.Do(req, &issue)
	if err != nil {
		return nil, nil, err
	}
	return &issue, resp, nil
}

// Update updates the chosen issue.
func (service *IssueService) Update(issueIdOrKey string, body interface{}) (*http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("PUT", u, body)
	if err != nil {
		return nil, err
	}
	return service.client.Do(req, nil)
}

// PerformTransition performs the requested transition for the chosen issue.
func (service *IssueService) PerformTransition(
	issueIdOrKey string,
	transition interface{},
) (*http.Response, error) {

	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("POST", u, transition)
	if err != nil {
		return nil, err
	}

	return service.client.Do(req, nil)
}

// GetTransitions returns list of the transitions possible for this issue by the current user.
func (service *IssueService) GetTransitions(issueIdOrKey string) (*TransitionsInfo, *http.Response, error) {
	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var transitionsInfo TransitionsInfo
	resp, err := service.client.Do(req, &transitionsInfo)
	if err != nil {
		return nil, nil, err
	}

	return &transitionsInfo, resp, nil
}
<commit_msg>Added created field to Issue struct<commit_after>/*
   Copyright (C) 2014  Salsita s.r.o.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see {http://www.gnu.org/licenses/}.
*/

package jira

import (
	// Stdlib
	"fmt"
	"net/http"

	// Vendor
	"github.com/google/go-querystring/query"
)

// Resources -------------------------------------------------------------------

// IssueList represents a list of issues, what a surprise.
type IssueList struct {
	Expand     string   `json:"expand,omitempty"`
	StartAt    int      `json:"startAt,omitempty"`
	MaxResults int      `json:"maxResults,omitempty"`
	Total      int      `json:"total,omitempty"`
	Issues     []*Issue `json:"issues,omitempty"`
}

// Issue represents the issue resource as produces by the REST API.
type Issue struct {
	Id     string `json:"id,omitempty"`
	Self   string `json:"self,omitempty"`
	Key    string `json:"key,omitempty"`
	Fields struct {
		Summary   string `json:"summary,omitempty"`
		IssueType struct {
			Id          string `json:"id,omitempty"`
			Self        string `json:"self,omitempty"`
			Name        string `json:"name,omitempty"`
			Description string `json:"description,omitempty"`
			Subtask     bool   `json:"subtask,omitempty"`
			IconURL     string `json:"iconUrl,omitempty"`
		} `json:"issuetype,omitempty"`
		Parent      *Issue           `json:"parent,omitempty"`
		Subtasks    []*Issue         `json:"subtasks,omitempty"`
		Assignee    *User            `json:"assignee,omitempty"`
		FixVersions []*Version       `json:"fixVersions,omitempty"`
		Labels      []string         `json:"labels,omitempty"`
		Status      *IssueStatus     `json:"status,omitempty"`
		Resolution  *IssueResolution `json:"resolution,omitempty"`
		Created     string           `json:"created,omitempty"`
	} `json:"fields,omitempty"`
}

// IssueStatus represents an issue status, e.g. "Scheduled".
type IssueStatus struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	IconURL     string `json:"iconUrl,omitempty"`
}

// IssueResolution represents an issue resolution, e.g. "Cannot Reproduce".
type IssueResolution struct {
	Id          string `json:"id,omitempty"`
	Self        string `json:"self,omitempty"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
}

type Transition struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name	,omitempty"`
	To   struct {
		Self           string `json:"self,omitempty"`
		Description    string `json:"description,omitempty"`
		IconUrl        string `json:"iconUrl,omitempty"`
		Name           string `json:"name,omitempty"`
		Id             string `json:"id,omitempty"`
		StatusCategory struct {
			Self      string `json:"self,omitempty"`
			Id        int    `json:"id,omitempty"`
			Key       string `json:"key,omitempty"`
			ColorName string `json:"colorName,omitempty"`
			Name      string `json:"name,omitempty"`
		}
	}
}

type TransitionsInfo struct {
	Expand      string       `json:"expand,omitempty"`
	Transitions []Transition `json:"transitions,omitempty"`
}

// The service -----------------------------------------------------------------

type IssueService struct {
	client *Client
}

func newIssueService(client *Client) *IssueService {
	return &IssueService{client}
}

type SearchOptions struct {
	JQL           string `url:"jql,omitempty"`
	StartAt       int    `url:"startAt,omitempty"`
	MaxResults    int    `url:"maxResults,omitempty"`
	ValidateQuery bool   `url:"validateQuery,omitempty"`
}

func (service *IssueService) Search(opts *SearchOptions) ([]*Issue, *http.Response, error) {
	u := "search"
	if opts != nil {
		vs, err := query.Values(opts)
		if err != nil {
			panic(err)
		}
		u += "?" + vs.Encode()
	}

	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issueList IssueList
	resp, err := service.client.Do(req, &issueList)
	if err != nil {
		return nil, nil, err
	}

	// TODO: Deal with pagination.
	return issueList.Issues, resp, nil
}

// Get returns the chosen issue.
func (service *IssueService) Get(issueIdOrKey string) (*Issue, *http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var issue Issue
	resp, err := service.client.Do(req, &issue)
	if err != nil {
		return nil, nil, err
	}
	return &issue, resp, nil
}

// Update updates the chosen issue.
func (service *IssueService) Update(issueIdOrKey string, body interface{}) (*http.Response, error) {
	u := fmt.Sprintf("issue/%v", issueIdOrKey)
	req, err := service.client.NewRequest("PUT", u, body)
	if err != nil {
		return nil, err
	}
	return service.client.Do(req, nil)
}

// PerformTransition performs the requested transition for the chosen issue.
func (service *IssueService) PerformTransition(
	issueIdOrKey string,
	transition interface{},
) (*http.Response, error) {

	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("POST", u, transition)
	if err != nil {
		return nil, err
	}

	return service.client.Do(req, nil)
}

// GetTransitions returns list of the transitions possible for this issue by the current user.
func (service *IssueService) GetTransitions(issueIdOrKey string) (*TransitionsInfo, *http.Response, error) {
	u := fmt.Sprintf("issue/%v/transitions", issueIdOrKey)
	req, err := service.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var transitionsInfo TransitionsInfo
	resp, err := service.client.Do(req, &transitionsInfo)
	if err != nil {
		return nil, nil, err
	}

	return &transitionsInfo, resp, nil
}
<|endoftext|>


<commit_before>package cache;

import (
   "os"
   "path/filepath"
   "sort"
   "sync"

   "com/eriq-augustine/mediaserver/config"
   "com/eriq-augustine/mediaserver/log"
   "com/eriq-augustine/mediaserver/model"
   "com/eriq-augustine/mediaserver/util"
)

const (
   BYTES_PER_GIGABYTE = 1024 * 1024 * 1024
)

// {cacheDir: cahceEntry}
var cache *map[string]CacheEntry;
var cacheLock sync.Mutex;
var saveLock sync.Mutex;

// This is the worst case possible cache size.
// Instead of checking the cache size every time, maintain this and check for maintenance when
// it grows too large.
// Like |cache|, this should only be accessed under the protection of |cacheLock|.
var maxCacheSize uint64;

func init() {
   cache = nil;
   maxCacheSize = 0;
}

// Use these getters and setters to interact with he cache.

func getCachedPoster(cacheDir string) (string, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.Poster == nil) {
      return "", false;
   }

   return *cacheEntry.Poster, true;
}

func getCachedSubtitles(cacheDir string) ([]string, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.Subtitles == nil) {
      return []string{}, false;
   }

   return *cacheEntry.Subtitles, true;
}

func getCachedVideoEncode(cacheDir string) (model.CompleteEncode, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.VideoEncode == nil) {
      return model.CompleteEncode{}, false;
   }

   return *cacheEntry.VideoEncode, true;
}

func setCachedPoster(cacheDir string, posterPath string) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetPoster(&posterPath);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

func setCachedSubtitles(cacheDir string, subtitles []string) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetSubtitles(&subtitles);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

func setCachedVideoEncode(cacheDir string, videoEncode model.CompleteEncode) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetVideoEncode(&videoEncode);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

// Calling this will persist the cache entry to disk.
// The caller should call this after it is fully done with the cache entry.
func saveCache(cacheDir string) {
   saveLock.Lock();
   defer saveLock.Unlock();

   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      return;
   }

   // If the cache is size dirty, then it may have changed sizes recently.
   // Add the cache's size to the max cache size.
   sizeDirty := cacheEntry.IsSizeDirty();

   cacheEntry.Save();

   if (sizeDirty) {
      maxCacheSize += cacheEntry.Size;
   }

   maintainCacheSize();
}

// This is internal only.
// Callers should use specific functions like getCachedPoster() instead.
func internalGetCacheEntry(cacheDir string) (CacheEntry, bool) {
   if (cache == nil) {
      scanCache();
   }

   cacheLock.Lock();
   defer cacheLock.Unlock();

   entry, ok := (*cache)[cacheDir];
   return entry, ok;
}

// Same access rules as internalGetCacheEntry().
func internalSetCacheEntry(cacheDir string, cacheEntry CacheEntry) {
   if (cache == nil) {
      scanCache();
   }

   cacheLock.Lock();
   defer cacheLock.Unlock();

   (*cache)[cacheDir] = cacheEntry;
}

// Scan the cache directory for cache entries and load them into memory.
func scanCache() {
   cacheLock.Lock();
   defer cacheLock.Unlock();

   cacheScan := make(map[string]CacheEntry);
   completeEncodes := make([]model.CompleteEncode, 0);

   cachePath := config.GetString("cacheBaseDir");
   err := filepath.Walk(cachePath, func(childPath string, fileInfo os.FileInfo, err error) error {
      if (err != nil) {
         return err;
      }

      if (cachePath == childPath) {
         return nil;
      }

      if (!fileInfo.IsDir() && fileInfo.Name() == CACHE_ENTRY_FILE_NAME) {
         cacheEntry := LoadCacheEntryFromFile(childPath);
         if (cacheEntry != nil) {
            cacheScan[cacheEntry.Dir] = *cacheEntry;
            maxCacheSize += cacheEntry.Size;

            if (cacheEntry.VideoEncode != nil) {
               completeEncodes = append(completeEncodes, *cacheEntry.VideoEncode);
            }
         }
      }

      return nil;
   });

   if (err != nil) {
      log.ErrorE("Error scanning cache", err);
   }

   cache = &cacheScan;

   loadRecentVideoEncodes(completeEncodes);

   maintainCacheSize();
}

// Note that this does not obtain the cache lock itself.
// The caller should obtain the lock and release it when fully done.
func maintainCacheSize() {
   var totalSize uint64 = 0;

   var lowerThresholdBytes uint64 = uint64(config.GetInt("cacheLowerThresholdGB") * BYTES_PER_GIGABYTE);
   var upperThresholdBytes uint64 = uint64(config.GetInt("cacheUpperThresholdGB") * BYTES_PER_GIGABYTE);

   if (maxCacheSize < upperThresholdBytes) {
      return;
   }

   var sortedCache []CacheEntry = make([]CacheEntry, 0, len(*cache));

   for _, cacheEntry := range(*cache) {
      totalSize += cacheEntry.Size;
      sortedCache = append(sortedCache, cacheEntry);
   }

   // We overestimated too much.
   if (totalSize < upperThresholdBytes) {
      maxCacheSize = totalSize;
      return;
   }

   sort.Sort(CacheByScore(sortedCache));
   for (totalSize > lowerThresholdBytes) {
      if (len(sortedCache) == 0) {
         break;
      }

      cacheEntry := sortedCache[0];
      sortedCache = sortedCache[1:];

      delete(*cache, cacheEntry.Dir);
      removeEncode(cacheEntry.Dir);
      util.RmDir(cacheEntry.Dir);
      totalSize -= cacheEntry.Size;
   }

   maxCacheSize = totalSize;
}
<commit_msg>Now asynchronously scanning cache on startup.<commit_after>package cache;

import (
   "os"
   "path/filepath"
   "sort"
   "sync"

   "com/eriq-augustine/mediaserver/config"
   "com/eriq-augustine/mediaserver/log"
   "com/eriq-augustine/mediaserver/model"
   "com/eriq-augustine/mediaserver/util"
)

const (
   BYTES_PER_GIGABYTE = 1024 * 1024 * 1024
)

// {cacheDir: cahceEntry}
var cache *map[string]CacheEntry;
var cacheLock sync.Mutex;
var saveLock sync.Mutex;

// This is the worst case possible cache size.
// Instead of checking the cache size every time, maintain this and check for maintenance when
// it grows too large.
// Like |cache|, this should only be accessed under the protection of |cacheLock|.
var maxCacheSize uint64;

func init() {
   cache = nil;
   maxCacheSize = 0;

   go scanCache();
}

// Use these getters and setters to interact with he cache.

func getCachedPoster(cacheDir string) (string, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.Poster == nil) {
      return "", false;
   }

   return *cacheEntry.Poster, true;
}

func getCachedSubtitles(cacheDir string) ([]string, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.Subtitles == nil) {
      return []string{}, false;
   }

   return *cacheEntry.Subtitles, true;
}

func getCachedVideoEncode(cacheDir string) (model.CompleteEncode, bool) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok || cacheEntry.VideoEncode == nil) {
      return model.CompleteEncode{}, false;
   }

   return *cacheEntry.VideoEncode, true;
}

func setCachedPoster(cacheDir string, posterPath string) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetPoster(&posterPath);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

func setCachedSubtitles(cacheDir string, subtitles []string) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetSubtitles(&subtitles);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

func setCachedVideoEncode(cacheDir string, videoEncode model.CompleteEncode) {
   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      cacheEntry = NewCacheEntry(cacheDir);
   }

   cacheEntry.SetVideoEncode(&videoEncode);
   internalSetCacheEntry(cacheDir, cacheEntry);
}

// Calling this will persist the cache entry to disk.
// The caller should call this after it is fully done with the cache entry.
func saveCache(cacheDir string) {
   saveLock.Lock();
   defer saveLock.Unlock();

   cacheEntry, ok := internalGetCacheEntry(cacheDir);

   if (!ok) {
      return;
   }

   // If the cache is size dirty, then it may have changed sizes recently.
   // Add the cache's size to the max cache size.
   sizeDirty := cacheEntry.IsSizeDirty();

   cacheEntry.Save();

   if (sizeDirty) {
      maxCacheSize += cacheEntry.Size;
   }

   maintainCacheSize();
}

// This is internal only.
// Callers should use specific functions like getCachedPoster() instead.
func internalGetCacheEntry(cacheDir string) (CacheEntry, bool) {
   if (cache == nil) {
      scanCache();
   }

   cacheLock.Lock();
   defer cacheLock.Unlock();

   entry, ok := (*cache)[cacheDir];
   return entry, ok;
}

// Same access rules as internalGetCacheEntry().
func internalSetCacheEntry(cacheDir string, cacheEntry CacheEntry) {
   if (cache == nil) {
      scanCache();
   }

   cacheLock.Lock();
   defer cacheLock.Unlock();

   (*cache)[cacheDir] = cacheEntry;
}

// Scan the cache directory for cache entries and load them into memory.
func scanCache() {
   cacheLock.Lock();
   defer cacheLock.Unlock();

   cacheScan := make(map[string]CacheEntry);
   completeEncodes := make([]model.CompleteEncode, 0);

   cachePath := config.GetString("cacheBaseDir");
   err := filepath.Walk(cachePath, func(childPath string, fileInfo os.FileInfo, err error) error {
      if (err != nil) {
         return err;
      }

      if (cachePath == childPath) {
         return nil;
      }

      if (!fileInfo.IsDir() && fileInfo.Name() == CACHE_ENTRY_FILE_NAME) {
         cacheEntry := LoadCacheEntryFromFile(childPath);
         if (cacheEntry != nil) {
            cacheScan[cacheEntry.Dir] = *cacheEntry;
            maxCacheSize += cacheEntry.Size;

            if (cacheEntry.VideoEncode != nil) {
               completeEncodes = append(completeEncodes, *cacheEntry.VideoEncode);
            }
         }
      }

      return nil;
   });

   if (err != nil) {
      log.ErrorE("Error scanning cache", err);
   }

   cache = &cacheScan;

   loadRecentVideoEncodes(completeEncodes);

   maintainCacheSize();
}

// Note that this does not obtain the cache lock itself.
// The caller should obtain the lock and release it when fully done.
func maintainCacheSize() {
   var totalSize uint64 = 0;

   var lowerThresholdBytes uint64 = uint64(config.GetInt("cacheLowerThresholdGB") * BYTES_PER_GIGABYTE);
   var upperThresholdBytes uint64 = uint64(config.GetInt("cacheUpperThresholdGB") * BYTES_PER_GIGABYTE);

   if (maxCacheSize < upperThresholdBytes) {
      return;
   }

   var sortedCache []CacheEntry = make([]CacheEntry, 0, len(*cache));

   for _, cacheEntry := range(*cache) {
      totalSize += cacheEntry.Size;
      sortedCache = append(sortedCache, cacheEntry);
   }

   // We overestimated too much.
   if (totalSize < upperThresholdBytes) {
      maxCacheSize = totalSize;
      return;
   }

   sort.Sort(CacheByScore(sortedCache));
   for (totalSize > lowerThresholdBytes) {
      if (len(sortedCache) == 0) {
         break;
      }

      cacheEntry := sortedCache[0];
      sortedCache = sortedCache[1:];

      delete(*cache, cacheEntry.Dir);
      removeEncode(cacheEntry.Dir);
      util.RmDir(cacheEntry.Dir);
      totalSize -= cacheEntry.Size;
   }

   maxCacheSize = totalSize;
}
<|endoftext|>


<commit_before>// Package launcher configures Lantern to run on system start
package launcher

import (
	"github.com/kardianos/osext"
	"github.com/luisiturrios/gowin"

	"github.com/getlantern/golog"
)

const (
	runDir = `Software\Microsoft\Windows\CurrentVersion\Run`
)

var (
	log = golog.LoggerFor("launcher")
)

func CreateLaunchFile(autoLaunch bool) {
	var err error

	if autoLaunch {
		lanternPath, err := osext.Executable()
		if err != nil {
			log.Errorf("Could not get Lantern directory path: %q", err)
			return
		}
		err = gowin.WriteStringReg("HKCU", runDir, "Lantern", lanternPath+" -startup")
		if err != nil {
			log.Errorf("Error inserting Lantern auto-start registry key: %q", err)
		}
	} else {
		err = gowin.DeleteKey("HKCU", runDir, "Lantern")
		if err != nil {
			log.Errorf("Error removing Lantern auto-start registry key: %q", err)
		}
	}
}
<commit_msg>Added startup flag and fixed code to remove lantern from startup items<commit_after>// Package launcher configures Lantern to run on system start
package launcher

import (
	"github.com/kardianos/osext"
	"github.com/luisiturrios/gowin"

	"github.com/getlantern/golog"
)

const (
	runDir = `Software\Microsoft\Windows\CurrentVersion\Run`
)

var (
	log = golog.LoggerFor("launcher")
)

func CreateLaunchFile(autoLaunch bool) {
	var err error

	if autoLaunch {
		lanternPath, err := osext.Executable()
		if err != nil {
			log.Errorf("Could not get Lantern directory path: %q", err)
			return
		}
		err = gowin.WriteStringReg("HKCU", runDir, "Lantern", "\""+lanternPath+"\""+" -startup")
		if err != nil {
			log.Errorf("Error inserting Lantern auto-start registry key: %q", err)
		}
	} else {
		// We just set the value to the empty string because the windows registry
		// library we're using doesn't support RegDeleteValue
		err = gowin.WriteStringReg("HKCU", runDir, "Lantern", "")
		if err != nil {
			log.Errorf("Error removing Lantern auto-start registry key: %q", err)
		}
	}
}
<|endoftext|>


<commit_before>package vm

import "fmt"

type BaseObject interface {
	ReturnClass() Class
	Object
}

type RObject struct {
	Class             *RClass
	InstanceVariables *Environment
	Scope             *Scope
	InitializeMethod  *Method
}

func (ro *RObject) Type() ObjectType {
	return BASE_OBJECT_OBJ
}

func (ro *RObject) Inspect() string {
	return "<Instance of: " + ro.Class.Name + ">"
}

func (ro *RObject) ReturnClass() Class {
	if ro.Class == nil {
		panic(fmt.Sprintf("Object %s doesn't have class.", ro.Inspect()))
	}
	return ro.Class
}

<commit_msg>Add comments to vm/base_object.go<commit_after>package vm

import "fmt"

// BaseObject is an interface that implements basic functions any object requires.
type BaseObject interface {
	ReturnClass() Class
	Object
}

// RObject represents any non built-in class's instance.
type RObject struct {
	Class             *RClass
	InstanceVariables *Environment
	Scope             *Scope
	InitializeMethod  *Method
}

func (ro *RObject) Type() ObjectType {
	return BASE_OBJECT_OBJ
}

func (ro *RObject) Inspect() string {
	return "<Instance of: " + ro.Class.Name + ">"
}

// ReturnClass will return object's class
func (ro *RObject) ReturnClass() Class {
	if ro.Class == nil {
		panic(fmt.Sprintf("Object %s doesn't have class.", ro.Inspect()))
	}
	return ro.Class
}

<|endoftext|>