Skip to content

Commit a00f023

Browse files
Merge pull request #66 from codefresh-io/original_yaml_string-preserve-keys-order
preserve keys order for original_yaml_string
2 parents a8579c5 + 601a927 commit a00f023

File tree

8 files changed

+175
-76
lines changed

8 files changed

+175
-76
lines changed

.gitignore

+1
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

+1-1
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

+19-73
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"
@@ -752,92 +752,41 @@ func mapResourceToPipeline(d *schema.ResourceData) *cfClient.Pipeline {
752752
// We cannot leverage on the standard marshal/unmarshal because the steps attribute needs to maintain the order of elements
753753
// while by default the standard function doesn't do it because in JSON maps are unordered
754754
func extractSpecAttributesFromOriginalYamlString(originalYamlString string, pipeline *cfClient.Pipeline) {
755-
// Use mapSlice to preserve order of items from the YAML string
756-
m := yaml.MapSlice{}
757-
err := yaml.Unmarshal([]byte(originalYamlString), &m)
755+
ms := OrderedMapSlice{}
756+
err := yaml.Unmarshal([]byte(originalYamlString), &ms)
758757
if err != nil {
759758
log.Fatalf("Unable to unmarshall original_yaml_string. Error: %v", err)
760759
}
761760

762761
stages := "[]"
763-
// Dynamically build JSON object for steps using String builder
764-
stepsBuilder := strings.Builder{}
765-
stepsBuilder.WriteString("{")
766-
// Dynamically build JSON object for steps using String builder
767-
hooksBuilder := strings.Builder{}
768-
hooksBuilder.WriteString("{")
769-
770-
// Parse elements of the YAML string to extract Steps and Stages if defined
771-
for _, item := range m {
762+
steps := "{}"
763+
hooks := "{}"
764+
765+
// Parse elements of the YAML string to extract Steps, Hooks and Stages if defined
766+
for _, item := range ms {
772767
key := item.Key.(string)
773768
switch key {
774769
case "steps":
775770
switch x := item.Value.(type) {
776771
default:
777772
log.Fatalf("unsupported value type: %T", item.Value)
778773

779-
case yaml.MapSlice:
780-
numberOfSteps := len(x)
781-
for index, item := range x {
782-
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
783-
// with the standard library
784-
y, _ := yaml.Marshal(item.Value)
785-
j2, _ := ghodss.YAMLToJSON(y)
786-
stepsBuilder.WriteString("\"" + item.Key.(string) + "\" : " + string(j2))
787-
if index < numberOfSteps-1 {
788-
stepsBuilder.WriteString(",")
789-
}
790-
}
774+
case OrderedMapSlice:
775+
s, _ := json.Marshal(x)
776+
steps = string(s)
791777
}
792778
case "stages":
793-
// For Stages we don't have ordering issue because it's a list
794-
y, _ := yaml.Marshal(item.Value)
795-
j2, _ := ghodss.YAMLToJSON(y)
796-
stages = string(j2)
779+
s, _ := json.Marshal(item.Value)
780+
stages = string(s)
781+
797782
case "hooks":
798-
switch hooks := item.Value.(type) {
783+
switch x := item.Value.(type) {
799784
default:
800785
log.Fatalf("unsupported value type: %T", item.Value)
801786

802-
case yaml.MapSlice:
803-
numberOfHooks := len(hooks)
804-
for indexHook, hook := range hooks {
805-
// E.g. on_finish
806-
hooksBuilder.WriteString("\"" + hook.Key.(string) + "\" : {")
807-
numberOfAttributes := len(hook.Value.(yaml.MapSlice))
808-
for indexAttribute, hookAttribute := range hook.Value.(yaml.MapSlice) {
809-
attribute := hookAttribute.Key.(string)
810-
switch attribute {
811-
case "steps":
812-
hooksBuilder.WriteString("\"steps\" : {")
813-
numberOfSteps := len(hookAttribute.Value.(yaml.MapSlice))
814-
for indexStep, step := range hookAttribute.Value.(yaml.MapSlice) {
815-
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
816-
// with the standard library
817-
y, _ := yaml.Marshal(step.Value)
818-
j2, _ := ghodss.YAMLToJSON(y)
819-
hooksBuilder.WriteString("\"" + step.Key.(string) + "\" : " + string(j2))
820-
if indexStep < numberOfSteps-1 {
821-
hooksBuilder.WriteString(",")
822-
}
823-
}
824-
hooksBuilder.WriteString("}")
825-
default:
826-
// For Other elements we don't need to preserve order
827-
y, _ := yaml.Marshal(hookAttribute.Value)
828-
j2, _ := ghodss.YAMLToJSON(y)
829-
hooksBuilder.WriteString("\"" + hookAttribute.Key.(string) + "\" : " + string(j2))
830-
}
831-
832-
if indexAttribute < numberOfAttributes-1 {
833-
hooksBuilder.WriteString(",")
834-
}
835-
}
836-
hooksBuilder.WriteString("}")
837-
if indexHook < numberOfHooks-1 {
838-
hooksBuilder.WriteString(",")
839-
}
840-
}
787+
case OrderedMapSlice:
788+
h, _ := json.Marshal(x)
789+
hooks = string(h)
841790
}
842791
case "mode":
843792
pipeline.Spec.Mode = item.Value.(string)
@@ -850,10 +799,7 @@ func extractSpecAttributesFromOriginalYamlString(originalYamlString string, pipe
850799
log.Printf("Unsupported entry %s", key)
851800
}
852801
}
853-
stepsBuilder.WriteString("}")
854-
hooksBuilder.WriteString("}")
855-
steps := stepsBuilder.String()
856-
hooks := hooksBuilder.String()
802+
857803
pipeline.Spec.Steps = &cfClient.Steps{
858804
Steps: steps,
859805
}

codefresh/utils_encoding.go

+39
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

+102
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

+2
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

+8
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

+3-2
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)