Skip to content

Commit

Permalink
resolve dependency via arm template and add it into hcl
Browse files Browse the repository at this point in the history
  • Loading branch information
magodo committed Aug 18, 2021
1 parent 4614496 commit febf5ed
Show file tree
Hide file tree
Showing 5 changed files with 619 additions and 98 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ require (
github.com/hashicorp/hcl/v2 v2.10.1
github.com/hashicorp/terraform-exec v0.14.1-0.20210812105923-7fa6ba66697a
github.com/hashicorp/terraform-schema v0.0.0-20210804102346-b9355678f0bc
github.com/stretchr/testify v1.7.0 // indirect
github.com/zclconf/go-cty v1.9.0 // indirect
)
185 changes: 120 additions & 65 deletions internal/armtemplate/armtemplate.go
Original file line number Diff line number Diff line change
@@ -1,84 +1,139 @@
package armtemplate

type Template struct {
Schema string `json:"$schema"`
ContentVersion string `json:"contentVersion"`
ApiProfile string `json:"apiProfile,omitempty"`
Parameters map[string]Parameter `json:"parameters,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
Functions []Function `json:"functions,omitempty"`
Resources []Resource `json:"resources"`
Outputs []Output `json:"outputs,omitempty"`
}

type ParameterType string

const (
ParameterTypeString ParameterType = "string"
ParameterTypeSecureString = "securestring"
ParameterTypeInt = "int"
ParameterTypeBool = "bool"
ParameterTypeObject = "object"
ParameterTypeSecureObject = "secureObject"
ParameterTypeArray = "array"
import (
"encoding/json"
"fmt"
"regexp"
"strings"
)

type Parameter struct {
Type ParameterType `json:"type"`
DefaultValue interface{} `json:"defaultValue,omitempty"`
AllowedValues []interface{} `json:"allowedValues,omitempty"`
MinValue int `json:"minValue,omitempty"`
MaxValue int `json:"maxValue,omitempty"`
MinLength int `json:"minLength,omitempty"`
MaxLength int `json:"maxLength,omitempty"`
Metadata *ParameterMetadata `json:"metadata,omitempty"`
type Template struct {
Resources []Resource `json:"resources"`
}

type ParameterMetadata struct {
Description string `json:"description,omitempty"`
type Resource struct {
ResourceId
DependsOn Dependencies `json:"dependsOn,omitempty"`
}

type Function struct {
Namespace string `json:"namespace"`
Members map[string]FunctionMember `json:"members"`
type ResourceId struct {
Type string `json:"type"`
Name string `json:"name"`
}

type FunctionMember struct {
Parameters []FunctionParameter `json:"parameters,omitempty"`
Output FunctionOutput `json:"output"`
}
var ResourceGroupId = ResourceId{}

func NewResourceId(id string) (*ResourceId, error) {
id = strings.TrimPrefix(id, "/")
id = strings.TrimSuffix(id, "/")
segs := strings.Split(id, "/")
if len(segs)%2 != 0 {
return nil, fmt.Errorf("invalid resource id format of %q: amount of segments is not even", id)
}
// ==4: resource group
// >=8: general resources resides in a resource group
if len(segs) != 4 && len(segs) < 8 {
return nil, fmt.Errorf("invalid resource id format of %q: amount of segments is too small", id)
}
if segs[0] != "subscriptions" {
return nil, fmt.Errorf("invalid resource id format of %q: the 1st segment is not subscriptions", id)
}
segs = segs[2:]
if !strings.EqualFold(segs[0], "resourcegroups") {
return nil, fmt.Errorf("invalid resource id format of %q: the 2nd segment is not resourcegroups (case insensitive)", id)
}
segs = segs[2:]

if len(segs) == 0 {
return &ResourceGroupId, nil
}

if segs[0] != "providers" {
return nil, fmt.Errorf("invalid resource id format of %q: the 3rd segment is not providers", id)
}
providerName := segs[1]
segs = segs[2:]

t := []string{providerName}
n := []string{}

type FunctionParameter struct {
Name string `json:"name"`
Type ParameterType `json:"type"`
for i := 0; i < len(segs); i += 2 {
t = append(t, segs[0])
n = append(n, segs[1])
}

return &ResourceId{
Type: strings.Join(t, "/"),
Name: strings.Join(n, "/"),
}, nil
}

type FunctionOutput struct {
Type ParameterType `json:"type"`
Value interface{} `json:"value"`
// ID returns the azure resource id
func (res ResourceId) ID(sub, rg string) string {
typeSegs := strings.Split(res.Type, "/")
nameSegs := strings.Split(res.Name, "/")

out := []string{"subscriptions", sub, "resourceGroups", rg}
if len(typeSegs) != 0 {
if len(typeSegs)-1 != len(nameSegs) {
panic(fmt.Sprintf("The resource of type %q and name %q is not a valid identifier", res.Type, res.Name))
}
out = append(out, typeSegs[0])
for i := 0; i < len(nameSegs); i++ {
out = append(out, typeSegs[i+1])
out = append(out, nameSegs[i])
}
}
return strings.Join(out, "/")
}

type Resource struct {
Condition *bool `json:"condition,omitempty"`
Type string `json:"type"`
ApiVersion string `json:"apiVersion"`
Name string `json:"name"`
Comments string `json:"comments,omitempty"`
Location string `json:"location,omitempty"`
DependsOn []Dependency `json:"dependsOn,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Sku *ResourceSku `json:"sku,omitempty"`
Kind string `json:"kind,omitempty"`
Scope string `json:"scope,omitempty"`
//TODO Copy
type Dependencies []ResourceId

func (deps *Dependencies) UnmarshalJSON(b []byte) error {
var dependenciesRaw []string
if err := json.Unmarshal(b, &dependenciesRaw); err != nil {
return err
}

for _, dep := range dependenciesRaw {
matches := regexp.MustCompile(`^\[resourceId\(([^,]+), (.+)\)]$`).FindAllStringSubmatch(dep, 1)
if len(matches) == 0 {
panic(fmt.Sprintf("the dependency %q is not valid (no match)", dep))
}
m := matches[0]
if len(m) != 3 {
panic(fmt.Sprintf("the dependency %q is not valid (the matched one has invalid form)", dep))
}

tlit, nlit := m[1], m[2]

t := strings.Trim(tlit, "' ")

var names []string
for _, seg := range strings.Split(nlit, ",") {
names = append(names, strings.Trim(seg, "' "))
}
n := strings.Join(names, "/")

*deps = append(*deps, ResourceId{
Type: t,
Name: n,
})
}
return nil
}

type Dependency string
type DependencyInfo map[ResourceId][]ResourceId

func (tpl Template) DependencyInfo(rgName string) DependencyInfo {
s := map[ResourceId][]ResourceId{}
for _, res := range tpl.Resources {
if len(res.DependsOn) == 0 {
s[res.ResourceId] = []ResourceId{ResourceGroupId}
continue
}

type ResourceSku struct {
Name string `json:"name"`
Tier string `json:"tier,omitempty"`
Size string `json:"size,omitempty"`
Family string `json:family,omitempty"`
Capacity string `json:"capacity,omitempty"`
s[res.ResourceId] = res.DependsOn
}
return s
}
Loading

0 comments on commit febf5ed

Please sign in to comment.