Skip to content

Commit

Permalink
Merge pull request #388 from cloudflare/rulesets-nested-traversal
Browse files Browse the repository at this point in the history
Rulesets nested traversal
  • Loading branch information
manatarms authored Apr 8, 2022
2 parents baaceef + dd8dafc commit 779fd82
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 137 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.17
require (
github.com/cloudflare/cloudflare-go v0.36.0
github.com/dnaeon/go-vcr v1.2.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-version v1.4.0
github.com/hashicorp/hc-install v0.3.1
github.com/hashicorp/terraform-exec v0.16.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
Expand Down
146 changes: 40 additions & 106 deletions internal/app/cf-terraforming/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sort"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/google/uuid"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
Expand Down Expand Up @@ -61,7 +62,7 @@ func generateResources() func(cmd *cobra.Command, args []string) {
// Setup and configure Terraform to operate in the temporary directory where
// the provider is already configured.
workingDir := viper.GetString("terraform-install-path")
log.Debugf("initialising Terraform in %s", workingDir)
log.Debugf("initializing Terraform in %s", workingDir)
tf, err := tfexec.NewTerraform(workingDir, execPath)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -591,15 +592,47 @@ func generateResources() func(cmd *cobra.Command, args []string) {
}
}
jsonPayload = nonManagedRules

ruleHeaders := map[string][]map[string]interface{}{}
for i, rule := range nonManagedRules {
ruleset, _ := api.GetZoneRuleset(context.Background(), zoneID, rule.ID)
jsonPayload[i].Rules = ruleset.Rules

if ruleset.Rules != nil {
for _, rule := range ruleset.Rules {
if rule.ActionParameters != nil && rule.ActionParameters.Headers != nil {
// The structure of the API response for headers differs from the
// structure terraform requires. So we collect all the headers
// indexed by rule.ID to massage the jsonStructData later
for headerName, values := range rule.ActionParameters.Headers {
header := map[string]interface{}{
"name": headerName,
"operation": values.Operation,
"expression": values.Expression,
"value": values.Value,
}
ruleHeaders[rule.ID] = append(ruleHeaders[rule.ID], header)
}
}
}

}
}

resourceCount = len(jsonPayload)
m, _ := json.Marshal(jsonPayload)
json.Unmarshal(m, &jsonStructData)

// Make the rules have the correct header structure
for i, ruleset := range jsonStructData {
for j, rule := range ruleset.(map[string]interface{})["rules"].([]interface{}) {
ID := rule.(map[string]interface{})["id"]
headers, exists := ruleHeaders[ID.(string)]
if exists {
jsonStructData[i].(map[string]interface{})["rules"].([]interface{})[j].(map[string]interface{})["action_parameters"].(map[string]interface{})["headers"] = headers
}
}
}

}
case "cloudflare_spectrum_application":
jsonPayload, err := api.SpectrumApplications(context.Background(), zoneID)
Expand Down Expand Up @@ -729,7 +762,6 @@ func generateResources() func(cmd *cobra.Command, args []string) {
fmt.Fprintf(cmd.OutOrStdout(), "%q is not yet supported for automatic generation", resourceType)
return
}

// If we don't have any resources to generate, just bail out early.
if resourceCount == 0 {
fmt.Fprint(cmd.OutOrStdout(), "no resources found to generate. Exiting...")
Expand Down Expand Up @@ -778,12 +810,12 @@ func generateResources() func(cmd *cobra.Command, args []string) {
}

if attrName == "account_id" && accountID != "" {
output += writeAttrLine(attrName, accountID, 2, false)
output += writeAttrLine(attrName, accountID, false)
continue
}

if attrName == "zone_id" && zoneID != "" && accountID == "" {
output += writeAttrLine(attrName, zoneID, 2, false)
output += writeAttrLine(attrName, zoneID, false)
continue
}

Expand All @@ -792,14 +824,14 @@ func generateResources() func(cmd *cobra.Command, args []string) {
case ty.IsPrimitiveType():
switch ty {
case cty.String, cty.Bool, cty.Number:
output += writeAttrLine(attrName, structData[attrName], 2, false)
output += writeAttrLine(attrName, structData[attrName], false)
default:
log.Debugf("unexpected primitive type %q", ty.FriendlyName())
}
case ty.IsCollectionType():
switch {
case ty.IsListType(), ty.IsSetType(), ty.IsMapType():
output += writeAttrLine(attrName, structData[attrName], 2, false)
output += writeAttrLine(attrName, structData[attrName], false)
default:
log.Debugf("unexpected collection type %q", ty.FriendlyName())
}
Expand All @@ -812,7 +844,7 @@ func generateResources() func(cmd *cobra.Command, args []string) {
}
}

output += nestBlocks(r.Block, jsonStructData[i].(map[string]interface{}), 2)
output += nestBlocks(r.Block, jsonStructData[i].(map[string]interface{}), uuid.New().String(), map[string][]string{})
output += "}\n\n"
}

Expand All @@ -825,101 +857,3 @@ func generateResources() func(cmd *cobra.Command, args []string) {

}
}

// writeAttrLine outputs a line of HCL configuration with a configurable depth
// for known types.
func writeAttrLine(key string, value interface{}, depth int, usedInBlock bool) string {
switch values := value.(type) {
case map[string]interface{}:
sortedKeys := make([]string, 0, len(values))
for k := range values {
sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)

s := ""
for _, v := range sortedKeys {
s += writeAttrLine(v, values[v], depth+2, false)
}

if usedInBlock {
if s != "" {
return fmt.Sprintf("%s%s {\n%s%s}\n", strings.Repeat(" ", depth), key, s, strings.Repeat(" ", depth))
}
} else {
if s != "" {
return fmt.Sprintf("%s%s = {\n%s%s}\n", strings.Repeat(" ", depth), key, s, strings.Repeat(" ", depth))
}
}
case []interface{}:
var stringItems []string
var intItems []int
var interfaceItems []map[string]interface{}

for _, item := range value.([]interface{}) {
switch item.(type) {
case string:
stringItems = append(stringItems, item.(string))
case map[string]interface{}:
interfaceItems = append(interfaceItems, item.(map[string]interface{}))
case float64:
intItems = append(intItems, int(item.(float64)))
}
}
if len(stringItems) > 0 {
return writeAttrLine(key, stringItems, depth, false)
}

if len(intItems) > 0 {
return writeAttrLine(key, intItems, depth, false)
}

if len(interfaceItems) > 0 {
return writeAttrLine(key, interfaceItems, depth, false)
}

case []map[string]interface{}:
var stringyInterfaces []string
var op string
var mapLen = len(value.([]map[string]interface{}))
for i, item := range value.([]map[string]interface{}) {
// Use an empty key to prevent rendering the key
op = writeAttrLine("", item, depth, true)
// if condition handles adding new line for just the last element
if i != mapLen-1 {
op = strings.TrimRight(op, "\n")
}
stringyInterfaces = append(stringyInterfaces, op)
}
return fmt.Sprintf("%s%s = [ \n%s ]\n", strings.Repeat(" ", depth), key, strings.Join(stringyInterfaces, ",\n"))

case []int:
stringyInts := []string{}
for _, int := range value.([]int) {
stringyInts = append(stringyInts, fmt.Sprintf("%d", int))
}
return fmt.Sprintf("%s%s = [ %s ]\n", strings.Repeat(" ", depth), key, strings.Join(stringyInts, ", "))
case []string:
var items []string
for _, item := range value.([]string) {
items = append(items, fmt.Sprintf("%q", item))
}
if len(items) > 0 {
return fmt.Sprintf("%s%s = [ %s ]\n", strings.Repeat(" ", depth), key, strings.Join(items, ", "))
}
case string:
if value != "" {
return fmt.Sprintf("%s%s = %q\n", strings.Repeat(" ", depth), key, value)
}
case int:
return fmt.Sprintf("%s%s = %d\n", strings.Repeat(" ", depth), key, value)
case float64:
return fmt.Sprintf("%s%s = %0.f\n", strings.Repeat(" ", depth), key, value)
case bool:
return fmt.Sprintf("%s%s = %t\n", strings.Repeat(" ", depth), key, value)
default:
log.Debugf("got unknown attribute configuration: key %s, value %v, value type %T", key, value, value)
return ""
}
return ""
}
22 changes: 9 additions & 13 deletions internal/app/cf-terraforming/cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,21 @@ func TestGenerate_writeAttrLine(t *testing.T) {
tests := map[string]struct {
key string
value interface{}
depth int
want string
}{
"value is string": {key: "a", value: "b", depth: 0, want: fmt.Sprintf("a = %q\n", "b")},
"value is int": {key: "a", value: 1, depth: 0, want: "a = 1\n"},
"value is float": {key: "a", value: 1.0, depth: 0, want: "a = 1\n"},
"value is bool": {key: "a", value: true, depth: 0, want: "a = true\n"},
"value is list of strings": {key: "a", value: listOfString, depth: 0, want: "a = [ \"b\", \"c\", \"d\" ]\n"},
"value is block of strings": {key: "a", value: configBlockOfStrings, depth: 0, want: "a = {\n c = \"d\"\n e = \"f\"\n}\n"},
"value is nil": {key: "a", value: nil, depth: 0, want: ""},

"depth is 0": {key: "a", value: "b", depth: 0, want: fmt.Sprintf("a = %q\n", "b")},
"depth is 6": {key: "a", value: "b", depth: 6, want: fmt.Sprintf(" a = %q\n", "b")},
"value is string": {key: "a", value: "b", want: fmt.Sprintf("a = %q\n", "b")},
"value is int": {key: "a", value: 1, want: "a = 1\n"},
"value is float": {key: "a", value: 1.0, want: "a = 1\n"},
"value is bool": {key: "a", value: true, want: "a = true\n"},
"value is list of strings": {key: "a", value: listOfString, want: "a = [ \"b\", \"c\", \"d\" ]\n"},
"value is block of strings": {key: "a", value: configBlockOfStrings, want: "a = {\nc = \"d\"\ne = \"f\"\n}\n"},
"value is nil": {key: "a", value: nil, want: ""},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := writeAttrLine(tc.key, tc.value, tc.depth, false)
assert.Equal(t, got, tc.want)
got := writeAttrLine(tc.key, tc.value, false)
assert.Equal(t, tc.want, got)
})
}
}
Expand Down
Loading

0 comments on commit 779fd82

Please sign in to comment.