Skip to content

Commit 601a927

Browse files
preserve keys order for original_yaml_string
1 parent a5ba5c3 commit 601a927

File tree

8 files changed

+175
-76
lines changed

8 files changed

+175
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
terraform-provider-codefresh
22
dist/
3+
.vscode/
34

45
**/.terraform
56
**/terraform.tfstate

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
33
WEBSITE_REPO=github.com/hashicorp/terraform-website
44
HOSTNAME=codefresh.io
55
PKG_NAME=codefresh
6-
NAMESPACE=codefresh-io
6+
NAMESPACE=app
77
BINARY=terraform-provider-${PKG_NAME}
88
VERSION=0.1.0
99
OS_ARCH=darwin_amd64

codefresh/resource_pipeline.go

Lines changed: 19 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package codefresh
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"log"
67
"regexp"
78
"strings"
89

910
cfClient "github.com/codefresh-io/terraform-provider-codefresh/client"
10-
ghodss "github.com/ghodss/yaml"
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1313
"gopkg.in/yaml.v2"
@@ -701,92 +701,41 @@ func mapResourceToPipeline(d *schema.ResourceData) *cfClient.Pipeline {
701701
// We cannot leverage on the standard marshal/unmarshal because the steps attribute needs to maintain the order of elements
702702
// while by default the standard function doesn't do it because in JSON maps are unordered
703703
func extractSpecAttributesFromOriginalYamlString(originalYamlString string, pipeline *cfClient.Pipeline) {
704-
// Use mapSlice to preserve order of items from the YAML string
705-
m := yaml.MapSlice{}
706-
err := yaml.Unmarshal([]byte(originalYamlString), &m)
704+
ms := OrderedMapSlice{}
705+
err := yaml.Unmarshal([]byte(originalYamlString), &ms)
707706
if err != nil {
708707
log.Fatalf("Unable to unmarshall original_yaml_string. Error: %v", err)
709708
}
710709

711710
stages := "[]"
712-
// Dynamically build JSON object for steps using String builder
713-
stepsBuilder := strings.Builder{}
714-
stepsBuilder.WriteString("{")
715-
// Dynamically build JSON object for steps using String builder
716-
hooksBuilder := strings.Builder{}
717-
hooksBuilder.WriteString("{")
718-
719-
// Parse elements of the YAML string to extract Steps and Stages if defined
720-
for _, item := range m {
711+
steps := "{}"
712+
hooks := "{}"
713+
714+
// Parse elements of the YAML string to extract Steps, Hooks and Stages if defined
715+
for _, item := range ms {
721716
key := item.Key.(string)
722717
switch key {
723718
case "steps":
724719
switch x := item.Value.(type) {
725720
default:
726721
log.Fatalf("unsupported value type: %T", item.Value)
727722

728-
case yaml.MapSlice:
729-
numberOfSteps := len(x)
730-
for index, item := range x {
731-
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
732-
// with the standard library
733-
y, _ := yaml.Marshal(item.Value)
734-
j2, _ := ghodss.YAMLToJSON(y)
735-
stepsBuilder.WriteString("\"" + item.Key.(string) + "\" : " + string(j2))
736-
if index < numberOfSteps-1 {
737-
stepsBuilder.WriteString(",")
738-
}
739-
}
723+
case OrderedMapSlice:
724+
s, _ := json.Marshal(x)
725+
steps = string(s)
740726
}
741727
case "stages":
742-
// For Stages we don't have ordering issue because it's a list
743-
y, _ := yaml.Marshal(item.Value)
744-
j2, _ := ghodss.YAMLToJSON(y)
745-
stages = string(j2)
728+
s, _ := json.Marshal(item.Value)
729+
stages = string(s)
730+
746731
case "hooks":
747-
switch hooks := item.Value.(type) {
732+
switch x := item.Value.(type) {
748733
default:
749734
log.Fatalf("unsupported value type: %T", item.Value)
750735

751-
case yaml.MapSlice:
752-
numberOfHooks := len(hooks)
753-
for indexHook, hook := range hooks {
754-
// E.g. on_finish
755-
hooksBuilder.WriteString("\"" + hook.Key.(string) + "\" : {")
756-
numberOfAttributes := len(hook.Value.(yaml.MapSlice))
757-
for indexAttribute, hookAttribute := range hook.Value.(yaml.MapSlice) {
758-
attribute := hookAttribute.Key.(string)
759-
switch attribute {
760-
case "steps":
761-
hooksBuilder.WriteString("\"steps\" : {")
762-
numberOfSteps := len(hookAttribute.Value.(yaml.MapSlice))
763-
for indexStep, step := range hookAttribute.Value.(yaml.MapSlice) {
764-
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
765-
// with the standard library
766-
y, _ := yaml.Marshal(step.Value)
767-
j2, _ := ghodss.YAMLToJSON(y)
768-
hooksBuilder.WriteString("\"" + step.Key.(string) + "\" : " + string(j2))
769-
if indexStep < numberOfSteps-1 {
770-
hooksBuilder.WriteString(",")
771-
}
772-
}
773-
hooksBuilder.WriteString("}")
774-
default:
775-
// For Other elements we don't need to preserve order
776-
y, _ := yaml.Marshal(hookAttribute.Value)
777-
j2, _ := ghodss.YAMLToJSON(y)
778-
hooksBuilder.WriteString("\"" + hookAttribute.Key.(string) + "\" : " + string(j2))
779-
}
780-
781-
if indexAttribute < numberOfAttributes-1 {
782-
hooksBuilder.WriteString(",")
783-
}
784-
}
785-
hooksBuilder.WriteString("}")
786-
if indexHook < numberOfHooks-1 {
787-
hooksBuilder.WriteString(",")
788-
}
789-
}
736+
case OrderedMapSlice:
737+
h, _ := json.Marshal(x)
738+
hooks = string(h)
790739
}
791740
case "mode":
792741
pipeline.Spec.Mode = item.Value.(string)
@@ -799,10 +748,7 @@ func extractSpecAttributesFromOriginalYamlString(originalYamlString string, pipe
799748
log.Printf("Unsupported entry %s", key)
800749
}
801750
}
802-
stepsBuilder.WriteString("}")
803-
hooksBuilder.WriteString("}")
804-
steps := stepsBuilder.String()
805-
hooks := hooksBuilder.String()
751+
806752
pipeline.Spec.Steps = &cfClient.Steps{
807753
Steps: steps,
808754
}

codefresh/utils_encoding.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package codefresh
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
8+
"gopkg.in/yaml.v2"
9+
)
10+
11+
// can be used instead the yaml.MapSlice (as an argument to yaml.Unmarshal) in order to preserve the keys order when converting to JSON later
12+
//
13+
// // Usage example:
14+
// ms := OrderedMapSlice{}
15+
// yaml.Unmarshal([]byte(originalYamlString), &ms)
16+
// orderedJson, _ := json.Marshal(ms)
17+
//
18+
// implements json.Marshaler interface
19+
type OrderedMapSlice []yaml.MapItem
20+
21+
func (ms OrderedMapSlice) MarshalJSON() ([]byte, error) {
22+
// keep the order of keys while converting to json with json.Marshal(ms)
23+
24+
buf := &bytes.Buffer{}
25+
buf.Write([]byte{'{'})
26+
for i, mi := range ms {
27+
b, err := json.Marshal(&mi.Value)
28+
if err != nil {
29+
return nil, err
30+
}
31+
buf.WriteString(fmt.Sprintf("%q:", fmt.Sprintf("%v", mi.Key)))
32+
buf.Write(b)
33+
if i < len(ms)-1 {
34+
buf.Write([]byte{','})
35+
}
36+
}
37+
buf.Write([]byte{'}'})
38+
return buf.Bytes(), nil
39+
}

examples/pipelines/main.tf

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
provider "codefresh" {
2+
api_url = var.api_url
3+
token = var.token
4+
}
5+
6+
resource "codefresh_project" "test" {
7+
name = "myproject"
8+
9+
tags = [
10+
"docker",
11+
]
12+
}
13+
14+
resource "codefresh_pipeline" "test" {
15+
name = "${codefresh_project.test.name}/react-sample-app"
16+
17+
tags = [
18+
"production",
19+
"docker",
20+
]
21+
22+
original_yaml_string = <<EOT
23+
version: "1.0"
24+
hooks:
25+
on_finish:
26+
steps:
27+
b:
28+
image: alpine:3.9
29+
commands:
30+
- echo "echo cleanup step"
31+
a:
32+
image: cloudposse/slack-notifier
33+
commands:
34+
- echo "Notify slack"
35+
steps:
36+
freestyle:
37+
image: alpine
38+
commands:
39+
- sleep 10
40+
a_freestyle:
41+
image: alpine
42+
commands:
43+
- sleep 10
44+
- echo Hey!
45+
arguments:
46+
c: 3
47+
a: 1
48+
b: 2
49+
EOT
50+
51+
spec {
52+
concurrency = 1
53+
priority = 5
54+
55+
# spec_template {
56+
# repo = "codefresh-contrib/react-sample-app"
57+
# path = "./codefresh.yml"
58+
# revision = "master"
59+
# context = "git"
60+
# }
61+
62+
contexts = [
63+
"context1-name",
64+
"context2-name",
65+
]
66+
67+
trigger {
68+
branch_regex = "/.*/gi"
69+
context = "git"
70+
description = "Trigger for commits"
71+
disabled = false
72+
events = [
73+
"push.heads"
74+
]
75+
modified_files_glob = ""
76+
name = "commits"
77+
provider = "github"
78+
repo = "codefresh-contrib/react-sample-app"
79+
type = "git"
80+
}
81+
82+
trigger {
83+
branch_regex = "/.*/gi"
84+
context = "git"
85+
description = "Trigger for tags"
86+
disabled = false
87+
events = [
88+
"push.tags"
89+
]
90+
modified_files_glob = ""
91+
name = "tags"
92+
provider = "github"
93+
repo = "codefresh-contrib/react-sample-app"
94+
type = "git"
95+
}
96+
97+
variables = {
98+
MY_PIP_VAR = "value"
99+
ANOTHER_PIP_VAR = "another_value"
100+
}
101+
}
102+
}

examples/pipelines/terraform.tfvars

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
api_url = "http://g.codefresh.io/api"
2+
token = ""

examples/pipelines/vars.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
variable api_url {
2+
type = string
3+
}
4+
5+
variable token {
6+
type = string
7+
default = ""
8+
}

main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package main
22

33
import (
44
"context"
5-
"github.com/codefresh-io/terraform-provider-codefresh/codefresh"
6-
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
75
"log"
86
"os"
7+
8+
"github.com/codefresh-io/terraform-provider-codefresh/codefresh"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
910
//"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1011
)
1112

0 commit comments

Comments
 (0)