Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
yoanm committed Nov 5, 2022
1 parent 0bafba0 commit 131c40f
Show file tree
Hide file tree
Showing 35 changed files with 1,778 additions and 0 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

concurrency:
group: "${{ github.workflow }}-${{ github.head_ref }}"
cancel-in-progress: true

jobs:
ci:
runs-on: ubuntu
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18

- name: Verify dependencies
run: go mod verify

- name: Install dependencies
run: go mod download

- name: Build
run: go build -v ./...

- name: Run go vet
run: go vet ./...

- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest

- name: Run staticcheck
run: staticcheck ./...

- name: Install golint
run: go install golang.org/x/lint/golint@latest

- name: Run golint
run: golint ./...

- name: Run tests
run: go test -race -vet=off ./...
157 changes: 157 additions & 0 deletions block_signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package tfsig

import (
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"

"github.com/yoanm/tfsig/tokens"
)

/** Public **/

func NewSignature(name string, labels []string, elements BodyElements) *BlockSignature {
return &BlockSignature{
typeName: name,
labels: labels,
elements: elements,
}
}
func NewEmptySignature(name string, labels ...string) *BlockSignature {
return NewSignature(name, labels, BodyElements{})
}

func NewEmptyResource(name, id string, labels ...string) *BlockSignature {
return NewEmptySignature("resource", append([]string{name, id}, labels...)...)
}

type BlockSignature struct {
typeName string
labels []string
elements BodyElements
}

func (signature *BlockSignature) GetType() string {
return signature.typeName
}
func (signature *BlockSignature) GetLabels() []string {
return signature.labels
}
func (signature *BlockSignature) GetElements() BodyElements {
return signature.elements
}
func (signature *BlockSignature) SetElements(elements BodyElements) {
signature.elements = elements
}
func (signature *BlockSignature) AppendElement(element BodyElement) {
signature.elements = append(signature.elements, element)
}

func (signature *BlockSignature) AppendAttribute(name string, value cty.Value) {
signature.AppendElement(NewBodyAttribute(name, value))
}
func (signature *BlockSignature) AppendChild(child *BlockSignature) {
signature.AppendElement(NewBodyBlock(child))
}
func (signature *BlockSignature) AppendEmptyLine() {
signature.AppendElement(NewBodyEmptyLine())
}

func (signature *BlockSignature) Build() *hclwrite.Block {
block := hclwrite.NewBlock(signature.GetType(), signature.GetLabels())

signature.WriteElementsToBody(block.Body())

return block
}

func (signature *BlockSignature) BuildTokens() (tks hclwrite.Tokens) {
if block := signature.Build(); block != nil {
blockTks := block.BuildTokens(nil)
// Remove trailing new line automatically added (=remove last token)
tks = append(hclwrite.Tokens{}, blockTks[0:len(blockTks)-1]...)
}

return tks
}

func (signature *BlockSignature) WriteElementsToBody(body *hclwrite.Body) {
for _, value := range signature.GetElements() {
if value.IsBodyBlock() {
body.AppendBlock(value.Build())
} else if value.IsBodyAttribute() {
if tokens.ContainsCapsule(value.attr) {
body.SetAttributeRaw(value.GetName(), tokens.Generate(value.attr))
} else {
body.SetAttributeValue(value.GetName(), *value.attr)
}
} else if value.IsBodyEmptyLine() {
body.AppendNewline()
}
}
}

// DependsOn adds an empty line and the 'depends_on terraform directive with provided id list
func (s *BlockSignature) DependsOn(idList []string) {
if idList == nil {
return
}

s.AppendEmptyLine()
s.AppendAttribute("depends_on", *tokens.NewIdentListValue(idList))
}

type LifecycleCondition struct {
condition string
errorMessage string
}
type LifecycleConfig struct {
CreateBeforeDestroy *bool
PreventDestroy *bool
IgnoreChanges []string
ReplaceTriggeredBy []string
Precondition *LifecycleCondition
Postcondition *LifecycleCondition
}

// Lifecycle adds an empty line and the 'lifecycle terraform directive, or return the existing signature
func (s *BlockSignature) Lifecycle(config LifecycleConfig) {
sig := NewEmptySignature("lifecycle")

appendLifecycleBoolAttribute(sig, "create_before_destroy", config.CreateBeforeDestroy)
appendLifecycleBoolAttribute(sig, "prevent_destroy", config.PreventDestroy)

if config.IgnoreChanges != nil {
sig.AppendAttribute("ignore_changes", *tokens.NewIdentListValue(config.IgnoreChanges))
}

appendLifecycleConditionBlock(sig, "precondition", config.Precondition)
appendLifecycleConditionBlock(sig, "postcondition", config.Postcondition)

s.AppendEmptyLine()
s.AppendChild(sig)
}

/** Private **/

func appendLifecycleConditionBlock(lifecycleSig *BlockSignature, name string, c *LifecycleCondition) {
if c == nil {
return
}
cond := NewEmptySignature(name)

cond.AppendAttribute("condition", *tokens.NewIdentValue(c.condition))
cond.AppendAttribute("error_message", *tokens.NewIdentValue(c.errorMessage))

lifecycleSig.AppendChild(cond)
}

func appendLifecycleBoolAttribute(lifecycleSig *BlockSignature, name string, value *bool) {
if value == nil {
return
}
val := "false"
if *value {
val = "true"
}
lifecycleSig.AppendAttribute(name, *tokens.NewIdentValue(val))
}
110 changes: 110 additions & 0 deletions block_signature_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package tfsig

import (
"fmt"

"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
)

func Example() {
// Create a resource block
sig := NewEmptyResource("res_name", "res_id")
sig.AppendAttribute("attribute1", cty.StringVal("value1"))
sig.AppendEmptyLine()
sig.AppendAttribute("attribute2", cty.BoolVal(true))
sig.AppendAttribute("attribute3", cty.NumberFloatVal(-12.34))
sig.AppendEmptyLine()

hclFile := hclwrite.NewEmptyFile()
hclFile.Body().AppendBlock(sig.Build())

fmt.Println(string(hclFile.Bytes()))
// Output:
// resource "res_name" "res_id" {
// attribute1 = "value1"
//
// attribute2 = true
// attribute3 = -12.34
//
// }
}

func Example_second() {
// Enhance an existing signature
sig := NewEmptyResource("res_name", "res_id")
sig.AppendAttribute("attribute1", cty.StringVal("value1"))
sig.AppendEmptyLine()
sig.AppendAttribute("attribute2", cty.BoolVal(true))
sig.AppendAttribute("attribute3", cty.NumberFloatVal(-12.34))
sig.AppendEmptyLine()

// ... Later in the code
// Enhance signature by removing empty lines and add an attribute below attribute2
newElems := BodyElements{}
for _, v := range sig.GetElements() {
// Remove all empty lines
if !v.IsBodyEmptyLine() {
newElems = append(newElems, v)
}
// Append a new attribute right after attribute2
if v.GetName() == "attribute2" {
newElems = append(newElems, NewBodyAttribute("attribute22", cty.BoolVal(false)))
}
}
sig.SetElements(newElems)

// ... Finally
hclFile := hclwrite.NewEmptyFile()
hclFile.Body().AppendBlock(sig.Build())

fmt.Println(string(hclFile.Bytes()))
// Output:
// resource "res_name" "res_id" {
// attribute1 = "value1"
// attribute2 = true
// attribute22 = false
// attribute3 = -12.34
// }
}

func Example_third() {
sig := NewEmptyResource("res_name", "res_id")
sig.AppendAttribute("attribute1", cty.StringVal("value1"))
sig.AppendAttribute("attribute2", cty.BoolVal(true))
sig.AppendAttribute("attribute3", cty.NumberFloatVal(-12.34))
sig.AppendEmptyLine()
sig.AppendAttribute("attribute4", cty.NumberIntVal(42))
sig.AppendAttribute("attribute5", cty.StringVal("value5"))

// ... Later in the code
// Re-order elements and remove empty lines
newElems := make(BodyElements, len(sig.GetElements()))
extraElemCont := 0
for _, v := range sig.GetElements() {
if v.GetName() == "attribute1" {
newElems[2] = v
} else if v.GetName() == "attribute4" {
newElems[1] = v
} else if v.GetName() == "attribute3" {
newElems[0] = v
} else if !v.IsBodyEmptyLine() {
newElems[3+extraElemCont] = v
extraElemCont++
}
}
sig.SetElements(newElems)

hclFile := hclwrite.NewEmptyFile()
hclFile.Body().AppendBlock(sig.Build())

fmt.Println(string(hclFile.Bytes()))
// Output:
// resource "res_name" "res_id" {
// attribute3 = -12.34
// attribute4 = 42
// attribute1 = "value1"
// attribute2 = true
// attribute5 = "value5"
// }
}
Loading

0 comments on commit 131c40f

Please sign in to comment.