Skip to content

Commit

Permalink
Merge pull request #66 from codefresh-io/original_yaml_string-preserv…
Browse files Browse the repository at this point in the history
…e-keys-order

preserve keys order for original_yaml_string
  • Loading branch information
denis-codefresh committed Oct 21, 2021
2 parents a8579c5 + 601a927 commit a00f023
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
terraform-provider-codefresh
dist/
.vscode/

**/.terraform
**/terraform.tfstate
Expand Down
2 changes: 1 addition & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
WEBSITE_REPO=github.com/hashicorp/terraform-website
HOSTNAME=codefresh.io
PKG_NAME=codefresh
NAMESPACE=codefresh-io
NAMESPACE=app
BINARY=terraform-provider-${PKG_NAME}
VERSION=0.1.0
OS_ARCH=darwin_amd64
Expand Down
92 changes: 19 additions & 73 deletions codefresh/resource_pipeline.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package codefresh

import (
"encoding/json"
"fmt"
"log"
"regexp"
"strings"

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

stages := "[]"
// Dynamically build JSON object for steps using String builder
stepsBuilder := strings.Builder{}
stepsBuilder.WriteString("{")
// Dynamically build JSON object for steps using String builder
hooksBuilder := strings.Builder{}
hooksBuilder.WriteString("{")

// Parse elements of the YAML string to extract Steps and Stages if defined
for _, item := range m {
steps := "{}"
hooks := "{}"

// Parse elements of the YAML string to extract Steps, Hooks and Stages if defined
for _, item := range ms {
key := item.Key.(string)
switch key {
case "steps":
switch x := item.Value.(type) {
default:
log.Fatalf("unsupported value type: %T", item.Value)

case yaml.MapSlice:
numberOfSteps := len(x)
for index, item := range x {
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
// with the standard library
y, _ := yaml.Marshal(item.Value)
j2, _ := ghodss.YAMLToJSON(y)
stepsBuilder.WriteString("\"" + item.Key.(string) + "\" : " + string(j2))
if index < numberOfSteps-1 {
stepsBuilder.WriteString(",")
}
}
case OrderedMapSlice:
s, _ := json.Marshal(x)
steps = string(s)
}
case "stages":
// For Stages we don't have ordering issue because it's a list
y, _ := yaml.Marshal(item.Value)
j2, _ := ghodss.YAMLToJSON(y)
stages = string(j2)
s, _ := json.Marshal(item.Value)
stages = string(s)

case "hooks":
switch hooks := item.Value.(type) {
switch x := item.Value.(type) {
default:
log.Fatalf("unsupported value type: %T", item.Value)

case yaml.MapSlice:
numberOfHooks := len(hooks)
for indexHook, hook := range hooks {
// E.g. on_finish
hooksBuilder.WriteString("\"" + hook.Key.(string) + "\" : {")
numberOfAttributes := len(hook.Value.(yaml.MapSlice))
for indexAttribute, hookAttribute := range hook.Value.(yaml.MapSlice) {
attribute := hookAttribute.Key.(string)
switch attribute {
case "steps":
hooksBuilder.WriteString("\"steps\" : {")
numberOfSteps := len(hookAttribute.Value.(yaml.MapSlice))
for indexStep, step := range hookAttribute.Value.(yaml.MapSlice) {
// We only need to preserve order at the first level to guarantee order of the steps, hence the child nodes can be marshalled
// with the standard library
y, _ := yaml.Marshal(step.Value)
j2, _ := ghodss.YAMLToJSON(y)
hooksBuilder.WriteString("\"" + step.Key.(string) + "\" : " + string(j2))
if indexStep < numberOfSteps-1 {
hooksBuilder.WriteString(",")
}
}
hooksBuilder.WriteString("}")
default:
// For Other elements we don't need to preserve order
y, _ := yaml.Marshal(hookAttribute.Value)
j2, _ := ghodss.YAMLToJSON(y)
hooksBuilder.WriteString("\"" + hookAttribute.Key.(string) + "\" : " + string(j2))
}

if indexAttribute < numberOfAttributes-1 {
hooksBuilder.WriteString(",")
}
}
hooksBuilder.WriteString("}")
if indexHook < numberOfHooks-1 {
hooksBuilder.WriteString(",")
}
}
case OrderedMapSlice:
h, _ := json.Marshal(x)
hooks = string(h)
}
case "mode":
pipeline.Spec.Mode = item.Value.(string)
Expand All @@ -850,10 +799,7 @@ func extractSpecAttributesFromOriginalYamlString(originalYamlString string, pipe
log.Printf("Unsupported entry %s", key)
}
}
stepsBuilder.WriteString("}")
hooksBuilder.WriteString("}")
steps := stepsBuilder.String()
hooks := hooksBuilder.String()

pipeline.Spec.Steps = &cfClient.Steps{
Steps: steps,
}
Expand Down
39 changes: 39 additions & 0 deletions codefresh/utils_encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package codefresh

import (
"bytes"
"encoding/json"
"fmt"

"gopkg.in/yaml.v2"
)

// 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
//
// // Usage example:
// ms := OrderedMapSlice{}
// yaml.Unmarshal([]byte(originalYamlString), &ms)
// orderedJson, _ := json.Marshal(ms)
//
// implements json.Marshaler interface
type OrderedMapSlice []yaml.MapItem

func (ms OrderedMapSlice) MarshalJSON() ([]byte, error) {
// keep the order of keys while converting to json with json.Marshal(ms)

buf := &bytes.Buffer{}
buf.Write([]byte{'{'})
for i, mi := range ms {
b, err := json.Marshal(&mi.Value)
if err != nil {
return nil, err
}
buf.WriteString(fmt.Sprintf("%q:", fmt.Sprintf("%v", mi.Key)))
buf.Write(b)
if i < len(ms)-1 {
buf.Write([]byte{','})
}
}
buf.Write([]byte{'}'})
return buf.Bytes(), nil
}
102 changes: 102 additions & 0 deletions examples/pipelines/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
provider "codefresh" {
api_url = var.api_url
token = var.token
}

resource "codefresh_project" "test" {
name = "myproject"

tags = [
"docker",
]
}

resource "codefresh_pipeline" "test" {
name = "${codefresh_project.test.name}/react-sample-app"

tags = [
"production",
"docker",
]

original_yaml_string = <<EOT
version: "1.0"
hooks:
on_finish:
steps:
b:
image: alpine:3.9
commands:
- echo "echo cleanup step"
a:
image: cloudposse/slack-notifier
commands:
- echo "Notify slack"
steps:
freestyle:
image: alpine
commands:
- sleep 10
a_freestyle:
image: alpine
commands:
- sleep 10
- echo Hey!
arguments:
c: 3
a: 1
b: 2
EOT

spec {
concurrency = 1
priority = 5

# spec_template {
# repo = "codefresh-contrib/react-sample-app"
# path = "./codefresh.yml"
# revision = "master"
# context = "git"
# }

contexts = [
"context1-name",
"context2-name",
]

trigger {
branch_regex = "/.*/gi"
context = "git"
description = "Trigger for commits"
disabled = false
events = [
"push.heads"
]
modified_files_glob = ""
name = "commits"
provider = "github"
repo = "codefresh-contrib/react-sample-app"
type = "git"
}

trigger {
branch_regex = "/.*/gi"
context = "git"
description = "Trigger for tags"
disabled = false
events = [
"push.tags"
]
modified_files_glob = ""
name = "tags"
provider = "github"
repo = "codefresh-contrib/react-sample-app"
type = "git"
}

variables = {
MY_PIP_VAR = "value"
ANOTHER_PIP_VAR = "another_value"
}
}
}
2 changes: 2 additions & 0 deletions examples/pipelines/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
api_url = "http://g.codefresh.io/api"
token = ""
8 changes: 8 additions & 0 deletions examples/pipelines/vars.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable api_url {
type = string
}

variable token {
type = string
default = ""
}
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package main

import (
"context"
"github.com/codefresh-io/terraform-provider-codefresh/codefresh"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"log"
"os"

"github.com/codefresh-io/terraform-provider-codefresh/codefresh"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
//"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

Expand Down

0 comments on commit a00f023

Please sign in to comment.