2017-02-22 18:31:24 +01:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-05-12 15:14:07 +02:00
|
|
|
"log"
|
2017-02-22 18:31:24 +01:00
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
2017-05-12 15:14:07 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
2017-02-22 18:31:24 +01:00
|
|
|
"github.com/aws/aws-sdk-go/service/codepipeline"
|
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourceAwsCodePipeline() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceAwsCodePipelineCreate,
|
|
|
|
Read: resourceAwsCodePipelineRead,
|
|
|
|
Update: resourceAwsCodePipelineUpdate,
|
|
|
|
Delete: resourceAwsCodePipelineDelete,
|
|
|
|
Importer: &schema.ResourceImporter{
|
|
|
|
State: schema.ImportStatePassthrough,
|
|
|
|
},
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"name": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"role_arn": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"artifact_store": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Required: true,
|
|
|
|
MaxItems: 1,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"location": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"type": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ValidateFunc: validateAwsCodePipelineArtifactStoreType,
|
|
|
|
},
|
|
|
|
|
|
|
|
"encryption_key": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
MaxItems: 1,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"id": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"type": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ValidateFunc: validateAwsCodePipelineEncryptionKeyType,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"stage": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
MinItems: 2,
|
|
|
|
Required: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"name": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"configuration": {
|
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"category": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ValidateFunc: validateAwsCodePipelineStageActionCategory,
|
|
|
|
},
|
|
|
|
"owner": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ValidateFunc: validateAwsCodePipelineStageActionOwner,
|
|
|
|
},
|
|
|
|
"provider": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"version": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"input_artifacts": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"output_artifacts": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"name": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"role_arn": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"run_order": {
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func validateAwsCodePipelineEncryptionKeyType(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
if v.(string) != "KMS" {
|
|
|
|
errors = append(errors, fmt.Errorf("CodePipeline: encryption_key type can only be KMS"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateAwsCodePipelineArtifactStoreType(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
if v.(string) != "S3" {
|
|
|
|
errors = append(errors, fmt.Errorf("CodePipeline: artifact_store type can only be S3"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateAwsCodePipelineStageActionCategory(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
value := v.(string)
|
|
|
|
types := map[string]bool{
|
|
|
|
"Source": true,
|
|
|
|
"Build": true,
|
|
|
|
"Deploy": true,
|
|
|
|
"Test": true,
|
|
|
|
"Invoke": true,
|
|
|
|
"Approval": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
if !types[value] {
|
|
|
|
errors = append(errors, fmt.Errorf("CodePipeline: category can only be one of Source | Build | Deploy | Test | Invoke | Approval"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateAwsCodePipelineStageActionOwner(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
value := v.(string)
|
|
|
|
types := map[string]bool{
|
|
|
|
"AWS": true,
|
|
|
|
"ThirdParty": true,
|
|
|
|
"Custom": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
if !types[value] {
|
|
|
|
errors = append(errors, fmt.Errorf("CodePipeline: owner can only be one of AWS | ThirdParty | Custom"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateAwsCodePipelineStageActionConfiguration(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
for k := range v.(map[string]interface{}) {
|
|
|
|
if k == "OAuthToken" {
|
|
|
|
errors = append(errors, fmt.Errorf("CodePipeline: OAuthToken should be set as environment variable 'GITHUB_TOKEN'"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsCodePipelineCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
conn := meta.(*AWSClient).codepipelineconn
|
|
|
|
params := &codepipeline.CreatePipelineInput{
|
|
|
|
Pipeline: expandAwsCodePipeline(d),
|
|
|
|
}
|
|
|
|
|
|
|
|
var resp *codepipeline.CreatePipelineOutput
|
|
|
|
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
resp, err = conn.CreatePipeline(params)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return resource.RetryableError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resource.NonRetryableError(err)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("[ERROR] Error creating CodePipeline: %s", err)
|
|
|
|
}
|
|
|
|
if resp.Pipeline == nil {
|
|
|
|
return fmt.Errorf("[ERROR] Error creating CodePipeline: invalid response from AWS")
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId(*resp.Pipeline.Name)
|
|
|
|
return resourceAwsCodePipelineRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipeline(d *schema.ResourceData) *codepipeline.PipelineDeclaration {
|
|
|
|
pipelineArtifactStore := expandAwsCodePipelineArtifactStore(d)
|
|
|
|
pipelineStages := expandAwsCodePipelineStages(d)
|
|
|
|
|
|
|
|
pipeline := codepipeline.PipelineDeclaration{
|
|
|
|
Name: aws.String(d.Get("name").(string)),
|
|
|
|
RoleArn: aws.String(d.Get("role_arn").(string)),
|
|
|
|
ArtifactStore: pipelineArtifactStore,
|
|
|
|
Stages: pipelineStages,
|
|
|
|
}
|
|
|
|
return &pipeline
|
|
|
|
}
|
|
|
|
func expandAwsCodePipelineArtifactStore(d *schema.ResourceData) *codepipeline.ArtifactStore {
|
|
|
|
configs := d.Get("artifact_store").([]interface{})
|
|
|
|
data := configs[0].(map[string]interface{})
|
|
|
|
pipelineArtifactStore := codepipeline.ArtifactStore{
|
|
|
|
Location: aws.String(data["location"].(string)),
|
|
|
|
Type: aws.String(data["type"].(string)),
|
|
|
|
}
|
|
|
|
tek := data["encryption_key"].([]interface{})
|
|
|
|
if len(tek) > 0 {
|
|
|
|
vk := tek[0].(map[string]interface{})
|
|
|
|
ek := codepipeline.EncryptionKey{
|
|
|
|
Type: aws.String(vk["type"].(string)),
|
|
|
|
Id: aws.String(vk["id"].(string)),
|
|
|
|
}
|
|
|
|
pipelineArtifactStore.EncryptionKey = &ek
|
|
|
|
}
|
|
|
|
return &pipelineArtifactStore
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineArtifactStore(artifactStore *codepipeline.ArtifactStore) []interface{} {
|
|
|
|
values := map[string]interface{}{}
|
|
|
|
values["type"] = *artifactStore.Type
|
|
|
|
values["location"] = *artifactStore.Location
|
|
|
|
if artifactStore.EncryptionKey != nil {
|
|
|
|
as := map[string]interface{}{
|
|
|
|
"id": *artifactStore.EncryptionKey.Id,
|
|
|
|
"type": *artifactStore.EncryptionKey.Type,
|
|
|
|
}
|
|
|
|
values["encryption_key"] = []interface{}{as}
|
|
|
|
}
|
|
|
|
return []interface{}{values}
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipelineStages(d *schema.ResourceData) []*codepipeline.StageDeclaration {
|
|
|
|
configs := d.Get("stage").([]interface{})
|
|
|
|
pipelineStages := []*codepipeline.StageDeclaration{}
|
|
|
|
|
|
|
|
for _, stage := range configs {
|
|
|
|
data := stage.(map[string]interface{})
|
|
|
|
a := data["action"].([]interface{})
|
|
|
|
actions := expandAwsCodePipelineActions(a)
|
|
|
|
pipelineStages = append(pipelineStages, &codepipeline.StageDeclaration{
|
|
|
|
Name: aws.String(data["name"].(string)),
|
|
|
|
Actions: actions,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return pipelineStages
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineStages(stages []*codepipeline.StageDeclaration) []interface{} {
|
|
|
|
stagesList := []interface{}{}
|
|
|
|
for _, stage := range stages {
|
|
|
|
values := map[string]interface{}{}
|
|
|
|
values["name"] = *stage.Name
|
|
|
|
values["action"] = flattenAwsCodePipelineStageActions(stage.Actions)
|
|
|
|
stagesList = append(stagesList, values)
|
|
|
|
}
|
|
|
|
return stagesList
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipelineActions(s []interface{}) []*codepipeline.ActionDeclaration {
|
|
|
|
actions := []*codepipeline.ActionDeclaration{}
|
|
|
|
for _, config := range s {
|
|
|
|
data := config.(map[string]interface{})
|
|
|
|
|
|
|
|
conf := expandAwsCodePipelineStageActionConfiguration(data["configuration"].(map[string]interface{}))
|
|
|
|
if data["provider"].(string) == "GitHub" {
|
|
|
|
githubToken := os.Getenv("GITHUB_TOKEN")
|
|
|
|
if githubToken != "" {
|
|
|
|
conf["OAuthToken"] = aws.String(githubToken)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
action := codepipeline.ActionDeclaration{
|
|
|
|
ActionTypeId: &codepipeline.ActionTypeId{
|
|
|
|
Category: aws.String(data["category"].(string)),
|
|
|
|
Owner: aws.String(data["owner"].(string)),
|
|
|
|
|
|
|
|
Provider: aws.String(data["provider"].(string)),
|
|
|
|
Version: aws.String(data["version"].(string)),
|
|
|
|
},
|
|
|
|
Name: aws.String(data["name"].(string)),
|
|
|
|
Configuration: conf,
|
|
|
|
}
|
|
|
|
|
|
|
|
oa := data["output_artifacts"].([]interface{})
|
|
|
|
if len(oa) > 0 {
|
|
|
|
outputArtifacts := expandAwsCodePipelineActionsOutputArtifacts(oa)
|
|
|
|
action.OutputArtifacts = outputArtifacts
|
|
|
|
|
|
|
|
}
|
|
|
|
ia := data["input_artifacts"].([]interface{})
|
|
|
|
if len(ia) > 0 {
|
|
|
|
inputArtifacts := expandAwsCodePipelineActionsInputArtifacts(ia)
|
|
|
|
action.InputArtifacts = inputArtifacts
|
|
|
|
|
|
|
|
}
|
|
|
|
ro := data["run_order"].(int)
|
|
|
|
if ro > 0 {
|
|
|
|
action.RunOrder = aws.Int64(int64(ro))
|
|
|
|
}
|
|
|
|
actions = append(actions, &action)
|
|
|
|
}
|
|
|
|
return actions
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineStageActions(actions []*codepipeline.ActionDeclaration) []interface{} {
|
|
|
|
actionsList := []interface{}{}
|
|
|
|
for _, action := range actions {
|
|
|
|
values := map[string]interface{}{
|
|
|
|
"category": *action.ActionTypeId.Category,
|
|
|
|
"owner": *action.ActionTypeId.Owner,
|
|
|
|
"provider": *action.ActionTypeId.Provider,
|
|
|
|
"version": *action.ActionTypeId.Version,
|
|
|
|
"name": *action.Name,
|
|
|
|
}
|
|
|
|
if action.Configuration != nil {
|
|
|
|
config := flattenAwsCodePipelineStageActionConfiguration(action.Configuration)
|
|
|
|
_, ok := config["OAuthToken"]
|
|
|
|
actionProvider := *action.ActionTypeId.Provider
|
|
|
|
if ok && actionProvider == "GitHub" {
|
|
|
|
delete(config, "OAuthToken")
|
|
|
|
}
|
|
|
|
values["configuration"] = config
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(action.OutputArtifacts) > 0 {
|
|
|
|
values["output_artifacts"] = flattenAwsCodePipelineActionsOutputArtifacts(action.OutputArtifacts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(action.InputArtifacts) > 0 {
|
|
|
|
values["input_artifacts"] = flattenAwsCodePipelineActionsInputArtifacts(action.InputArtifacts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if action.RunOrder != nil {
|
|
|
|
values["run_order"] = int(*action.RunOrder)
|
|
|
|
}
|
|
|
|
|
|
|
|
actionsList = append(actionsList, values)
|
|
|
|
}
|
|
|
|
return actionsList
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipelineStageActionConfiguration(config map[string]interface{}) map[string]*string {
|
|
|
|
m := map[string]*string{}
|
|
|
|
for k, v := range config {
|
|
|
|
s := v.(string)
|
|
|
|
m[k] = &s
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineStageActionConfiguration(config map[string]*string) map[string]string {
|
|
|
|
m := map[string]string{}
|
|
|
|
for k, v := range config {
|
|
|
|
m[k] = *v
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipelineActionsOutputArtifacts(s []interface{}) []*codepipeline.OutputArtifact {
|
|
|
|
outputArtifacts := []*codepipeline.OutputArtifact{}
|
|
|
|
for _, artifact := range s {
|
|
|
|
outputArtifacts = append(outputArtifacts, &codepipeline.OutputArtifact{
|
|
|
|
Name: aws.String(artifact.(string)),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return outputArtifacts
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineActionsOutputArtifacts(artifacts []*codepipeline.OutputArtifact) []string {
|
|
|
|
values := []string{}
|
|
|
|
for _, artifact := range artifacts {
|
|
|
|
values = append(values, *artifact.Name)
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandAwsCodePipelineActionsInputArtifacts(s []interface{}) []*codepipeline.InputArtifact {
|
|
|
|
outputArtifacts := []*codepipeline.InputArtifact{}
|
|
|
|
for _, artifact := range s {
|
|
|
|
outputArtifacts = append(outputArtifacts, &codepipeline.InputArtifact{
|
|
|
|
Name: aws.String(artifact.(string)),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return outputArtifacts
|
|
|
|
}
|
|
|
|
|
|
|
|
func flattenAwsCodePipelineActionsInputArtifacts(artifacts []*codepipeline.InputArtifact) []string {
|
|
|
|
values := []string{}
|
|
|
|
for _, artifact := range artifacts {
|
|
|
|
values = append(values, *artifact.Name)
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsCodePipelineRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
conn := meta.(*AWSClient).codepipelineconn
|
|
|
|
resp, err := conn.GetPipeline(&codepipeline.GetPipelineInput{
|
|
|
|
Name: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2017-05-12 15:14:07 +02:00
|
|
|
pipelineerr, ok := err.(awserr.Error)
|
|
|
|
if ok && pipelineerr.Code() == "PipelineNotFoundException" {
|
|
|
|
log.Printf("[INFO] Codepipeline %q not found", d.Id())
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
2017-02-22 18:31:24 +01:00
|
|
|
return fmt.Errorf("[ERROR] Error retreiving Pipeline: %q", err)
|
|
|
|
}
|
|
|
|
pipeline := resp.Pipeline
|
|
|
|
|
|
|
|
if err := d.Set("artifact_store", flattenAwsCodePipelineArtifactStore(pipeline.ArtifactStore)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := d.Set("stage", flattenAwsCodePipelineStages(pipeline.Stages)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("name", pipeline.Name)
|
|
|
|
d.Set("role_arn", pipeline.RoleArn)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsCodePipelineUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
conn := meta.(*AWSClient).codepipelineconn
|
|
|
|
|
|
|
|
pipeline := expandAwsCodePipeline(d)
|
|
|
|
params := &codepipeline.UpdatePipelineInput{
|
|
|
|
Pipeline: pipeline,
|
|
|
|
}
|
|
|
|
_, err := conn.UpdatePipeline(params)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"[ERROR] Error updating CodePipeline (%s): %s",
|
|
|
|
d.Id(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resourceAwsCodePipelineRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsCodePipelineDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
conn := meta.(*AWSClient).codepipelineconn
|
|
|
|
|
|
|
|
_, err := conn.DeletePipeline(&codepipeline.DeletePipelineInput{
|
|
|
|
Name: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId("")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|