diff --git a/Makefile b/Makefile index 98a62d1..e7437f5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: testdata testdata: go run . \ + -v \ -f testdata/input-parent.hujson \ -d testdata/departments/ \ -o testdata/output-file-to-compare-to.hujson \ diff --git a/main.go b/main.go index 77148dc..c8c0cc8 100644 --- a/main.go +++ b/main.go @@ -138,23 +138,17 @@ func handleArray() SectionHandler { if childSection == nil { return } - parentProps := parent.FindKey(ast.TextEqual(sectionKey)) - if parentProps != nil && len(parentProps.Value.(*jwcc.Array).Values) > 0 { - pathComment(parentProps.Value.(*jwcc.Array).Values[0], parentPath) - } newArr := existingOrNewArray(*parent, sectionKey) - childArrValues := childSection.Value.(*jwcc.Array).Values pathCommentAlreadyAdded := false - for i := range childArrValues { - newArr.Values = append(newArr.Values, childArrValues[i]) + for _, v := range childSection.Value.(*jwcc.Array).Values { + newArr.Values = append(newArr.Values, v) if !pathCommentAlreadyAdded { - pathComment(childArrValues[i], childPath) + pathComment(v, childPath) pathCommentAlreadyAdded = true } - } upsertMember(parent, sectionKey, newArr) @@ -166,10 +160,6 @@ func handleObject() SectionHandler { if childSection == nil { return } - parentProps := parent.FindKey(ast.TextEqual(sectionKey)) - if parentProps != nil { - pathComment(parentProps.Value.(*jwcc.Object).Members[0], parentPath) - } newObj := existingOrNewObject(*parent, sectionKey) @@ -222,13 +212,31 @@ func upsertMember[V *jwcc.Object | *jwcc.Array](doc *jwcc.Object, key string, va } func pathComment(val jwcc.Value, path string) { + // TODO: preserve existing comments val.Comments().Before = []string{fmt.Sprintf("from `%s`", path)} } -func mergeDocs(sections map[string]SectionHandler, parentDoc *ParsedDocument, childDocs []*ParsedDocument) error { +func addParentPathComments(parentDoc *ParsedDocument) error { for _, parentSection := range parentDoc.Object.Members { - pathComment(parentSection, parentDoc.Path) + logVerbose("adding parent path comment to [%s]\n", parentSection.Key) + switch parentSection.Value.(type) { + default: + pathComment(parentSection, parentDoc.Path) + case *jwcc.Array: + pathComment(parentSection.Value.(*jwcc.Array).Values[0], parentDoc.Path) + case *jwcc.Object: + pathComment(parentSection.Value.(*jwcc.Object).Members[0], parentDoc.Path) + } } + return nil +} + +func mergeDocs(sections map[string]SectionHandler, parentDoc *ParsedDocument, childDocs []*ParsedDocument) error { + err := addParentPathComments(parentDoc) + if err != nil { + log.Fatal(err) + } + for _, child := range childDocs { if child.Path == parentDoc.Path { logVerbose("skipping [%s], same doc as parent\n", child.Path) diff --git a/main_test.go b/main_test.go index a47f00a..74602b7 100644 --- a/main_test.go +++ b/main_test.go @@ -1,6 +1,7 @@ package main import ( + "os" "strings" "testing" @@ -163,6 +164,141 @@ func TestMergeDocsParentWithSameMember(t *testing.T) { } } +func TestPathCommentsForObject(t *testing.T) { + parent, err := jwcc.Parse(strings.NewReader(`{ + "goodpath": {"bar":"foo"} + }`)) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + parentDoc := &ParsedDocument{ + Object: parent.Value.(*jwcc.Object), + Path: "parent", + } + + child, err := jwcc.Parse(strings.NewReader(`{ + "goodpath": {"foo":"bar"} + }`)) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + childDoc := &ParsedDocument{ + Object: child.Value.(*jwcc.Object), + Path: "child", + } + + sections := map[string]SectionHandler{ + "goodpath": handleObject(), + } + + err = mergeDocs(sections, parentDoc, []*ParsedDocument{childDoc}) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + + if len(parentDoc.Object.Members) != 1 { + t.Fatalf("parent members length should be [1], got [%v]", len(parentDoc.Object.Members)) + } + + memberIndexKey := parentDoc.Object.IndexKey(ast.TextEqual("goodpath")) + if memberIndexKey != 0 { + t.Fatalf("section index key length should be [0], got [%v]", memberIndexKey) + } + + member := parentDoc.Object.Members[memberIndexKey] + memberObjectMembers := member.Value.(*jwcc.Object).Members + if len(memberObjectMembers) != 2 { + t.Fatalf("member object keys length should be [2], got [%v]", len(memberObjectMembers)) + } + + barMember := member.Value.(*jwcc.Object).Find("bar") + if barMember.Value.String() != "foo" { + t.Fatalf("member value should be [foo], got [%v]", barMember.Value.String()) + } + barMemberComments := barMember.Comments().Before + if barMemberComments[0] != "from `parent`" { + t.Fatalf("member comment should be [from `parent`], got [%v]", barMemberComments[0]) + } + + fooMember := member.Value.(*jwcc.Object).Find("foo") + if fooMember.Value.String() != "bar" { + t.Fatalf("member value should be [bar], got [%v]", fooMember.Value.String()) + } + fooMemberComments := fooMember.Comments().Before + if fooMemberComments[0] != "from `child`" { + t.Fatalf("member comment should be [from `child`], got [%v]", fooMemberComments[0]) + } +} + +func TestPathCommentsForArray(t *testing.T) { + parent, err := jwcc.Parse(strings.NewReader(`{ + "things": [{"thing1":"foo"}], + }`)) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + parentDoc := &ParsedDocument{ + Object: parent.Value.(*jwcc.Object), + Path: "parent", + } + + child, err := jwcc.Parse(strings.NewReader(`{ + "things": [{"thing2":"bar"}], + }`)) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + childDoc := &ParsedDocument{ + Object: child.Value.(*jwcc.Object), + Path: "child", + } + + sections := map[string]SectionHandler{ + "things": handleArray(), + } + + err = mergeDocs(sections, parentDoc, []*ParsedDocument{childDoc}) + if err != nil { + t.Fatalf("expected no error, got [%v]", err) + } + + if len(parentDoc.Object.Members) != 1 { + t.Fatalf("parent members length should be [1], got [%v]", len(parentDoc.Object.Members)) + } + + thingsMember := parentDoc.Object.Find("things") + if thingsMember == nil { + t.Fatalf("section index key length should be not nil, got [%v]", thingsMember) + } + + thingsMemberValues := thingsMember.Value.(*jwcc.Array).Values + if len(thingsMemberValues) != 2 { + t.Fatalf("members length should be [2], got [%v]", len(thingsMemberValues)) + } + + barMember := thingsMemberValues[0].(*jwcc.Object) + if barMember.Members[0].Key.String() != "thing1" { + t.Fatalf("member key should be [thing1], got [%v]", barMember.Members[0].Key.String()) + } + if barMember.Members[0].Value.String() != "foo" { + t.Fatalf("member value should be [foo], got [%v]", barMember.Members[0].Value.String()) + } + if barMember.Comments().Before[0] != "from `parent`" { + t.Fatalf("member comment should be [from `parent`], got [%v]", barMember.Comments().Before[0]) + } + + fooMember := thingsMemberValues[1].(*jwcc.Object) + if fooMember.Members[0].Key.String() != "thing2" { + t.Fatalf("member key should be [thing2], got [%v]", fooMember.Members[0].Key.String()) + } + if fooMember.Members[0].Value.String() != "bar" { + t.Fatalf("member value should be [bar], got [%v]", fooMember.Members[0].Value.String()) + } + if fooMember.Comments().Before[0] != "from `child`" { + t.Fatalf("member comment should be [from `parent`], got [%v]", fooMember.Comments().Before[0]) + } +} + func TestExistingOrNewObject(t *testing.T) { child, err := jwcc.Parse(strings.NewReader(`{ "goodpath": {"foo":"bar"} @@ -423,3 +559,10 @@ func TestSort(t *testing.T) { } } } + +func printDocument(doc *ParsedDocument) { + err := jwcc.Format(os.Stdout, doc.Object) + if err != nil { + panic(err) + } +} diff --git a/testdata/departments/engineering/acls.hujson b/testdata/departments/engineering/acls.hujson index 58bd13f..cac695a 100644 --- a/testdata/departments/engineering/acls.hujson +++ b/testdata/departments/engineering/acls.hujson @@ -10,7 +10,7 @@ { // engineering1 "action": "accept", "src": [ - "engineering1@tsjustworks.net", + "engineering1@example.com", ], "dst": [ "tag:demo-infra:22", @@ -22,7 +22,7 @@ { // engineering2 "action": "accept", "src": [ - "engineering2@tsjustworks.net", + "engineering2@example.com", ], "dst": [ "tag:demo-infra:22", @@ -34,7 +34,7 @@ { // engineering3 "action": "accept", "src": [ - "engineering3@tsjustworks.net", + "engineering3@example.com", ], "dst": [ "tag:demo-infra:22", @@ -46,14 +46,14 @@ ], "tests": [ { - "src": "cameron@example.com", - "accept": ["tag:cameron:22"], + "src": "engineering@example.com", + "accept": ["tag:dev:22"], }, ], "ssh": [ { "action": "accept", - "src": ["group:finance"], + "src": ["group:engineering"], "dst": ["autogroup:self"], "users": ["root", "autogroup:nonroot"], }, diff --git a/testdata/departments/engineering/acls.json b/testdata/departments/engineering/acls.json index eb0ff33..eb53fbb 100644 --- a/testdata/departments/engineering/acls.json +++ b/testdata/departments/engineering/acls.json @@ -3,7 +3,7 @@ { "action": "accept", "src": [ - "json-rule@tsjustworks.net" + "engineering@example.com" ], "dst": [ "tag:json-rule:22" diff --git a/testdata/departments/engineering/autoApprovers.hujson b/testdata/departments/engineering/autoApprovers.hujson index 5f0dc8b..aa91998 100644 --- a/testdata/departments/engineering/autoApprovers.hujson +++ b/testdata/departments/engineering/autoApprovers.hujson @@ -2,9 +2,9 @@ "autoApprovers": { "routes": { "10.0.0.0/32": [ - "tag:example" + "tag:engineering" ] }, - "exitNode": ["tag:example"], + "exitNode": ["tag:engineering"], }, } diff --git a/testdata/departments/engineering/grants.hujson b/testdata/departments/engineering/grants.hujson index 895da90..3a8f4a9 100644 --- a/testdata/departments/engineering/grants.hujson +++ b/testdata/departments/engineering/grants.hujson @@ -2,7 +2,7 @@ "grants": [ { //"src": ["group:prod"], - "src": ["allen@tsjustworks.net"], + "src": ["engineering@example.com"], "dst": ["tag:k8s-operator"], "app": { "tailscale.com/cap/kubernetes": [{ diff --git a/testdata/departments/engineering/groups.hujson b/testdata/departments/engineering/groups.hujson index 9054ef4..dd374f0 100644 --- a/testdata/departments/engineering/groups.hujson +++ b/testdata/departments/engineering/groups.hujson @@ -1,7 +1,7 @@ { "groups": { - "group:group1": [ - "cameron@tsjustworks.net", + "group:engineering": [ + "user1@example.com", ], }, } diff --git a/testdata/departments/finance/acls.hujson b/testdata/departments/finance/acls.hujson index 20b0bad..abbfbc5 100644 --- a/testdata/departments/finance/acls.hujson +++ b/testdata/departments/finance/acls.hujson @@ -3,7 +3,7 @@ { // finance1 "action": "accept", "src": [ - "finance1@tsjustworks.net", + "finance1@example.com", ], "dst": [ "tag:demo-infra:22", @@ -12,7 +12,7 @@ { // finance2 "action": "accept", "src": [ - "finance2@tsjustworks.net", + "finance2@example.com", ], "dst": [ "tag:demo-infra:22", @@ -20,13 +20,13 @@ }, ], "groups": { - "group:group2": [ - "allen@tsjustworks.net", + "group:finance": [ + "finance@example.com", ], }, "tests": [ { - "src": "dave@example.com", + "src": "finance@example.com", "srcPostureAttrs": { "node:os": "windows", }, diff --git a/testdata/departments/finance/autoApprovers.hujson b/testdata/departments/finance/autoApprovers.hujson index eab33ff..98391c7 100644 --- a/testdata/departments/finance/autoApprovers.hujson +++ b/testdata/departments/finance/autoApprovers.hujson @@ -2,7 +2,7 @@ "autoApprovers": { "routes": { "10.0.10.0/32": [ - "tag:example" + "tag:finance" ] } } diff --git a/testdata/departments/finance/ssh.hujson b/testdata/departments/finance/ssh.hujson index 9b1a3fd..f5adbc1 100644 --- a/testdata/departments/finance/ssh.hujson +++ b/testdata/departments/finance/ssh.hujson @@ -3,13 +3,13 @@ { "action": "accept", "src": ["autogroup:member"], - "dst": ["tag:prod"], + "dst": ["tag:finance"], "users": ["root", "autogroup:nonroot"], }, { "action": "accept", - "src": ["tag:logging"], - "dst": ["tag:prod"], + "src": ["tag:finance"], + "dst": ["tag:finance"], "users": ["root", "autogroup:nonroot"], }, ], diff --git a/testdata/input-parent.hujson b/testdata/input-parent.hujson index ac18752..f16ad40 100644 --- a/testdata/input-parent.hujson +++ b/testdata/input-parent.hujson @@ -1,4 +1,5 @@ { + // comment in parent file "RandomizeClientPort": true, // inline comment "extraDNSRecords": [ @@ -30,7 +31,7 @@ "target": ["autogroup:admin"], }, { - "target": ["my-kid@my-home.com", "tag:server"], + "target": ["user4@example.com", "tag:server"], "attr": [ "nextdns:abc123", "nextdns:no-device-info", @@ -57,7 +58,7 @@ "tagOwners": { "tag:parent": [], - "tag:cameron": [ + "tag:user1": [ "autogroup:member", ], "tag:dave": [], diff --git a/testdata/output-file-to-compare-to.hujson b/testdata/output-file-to-compare-to.hujson index 8ddcc65..c82d211 100644 --- a/testdata/output-file-to-compare-to.hujson +++ b/testdata/output-file-to-compare-to.hujson @@ -3,12 +3,12 @@ "RandomizeClientPort": true, // inline comment "acls": [ - // from `testdata/input-parent.hujson` + // from `testdata/departments/engineering/acls.hujson` { // engineering1 "action": "accept", - "src": ["engineering1@tsjustworks.net"], + "src": ["engineering1@example.com"], "dst": ["tag:demo-infra:22"], "srcPosture": ["posture:latestMac"], }, @@ -16,7 +16,7 @@ // engineering2 "action": "accept", - "src": ["engineering2@tsjustworks.net"], + "src": ["engineering2@example.com"], "dst": ["tag:demo-infra:22"], "srcPosture": ["posture:latestMac"], }, @@ -24,14 +24,14 @@ // engineering3 "action": "accept", - "src": ["engineering3@tsjustworks.net"], + "src": ["engineering3@example.com"], "dst": ["tag:demo-infra:22"], "srcPosture": ["posture:latestMac"], }, // from `testdata/departments/engineering/acls.json` { "action": "accept", - "src": ["json-rule@tsjustworks.net"], + "src": ["engineering@example.com"], "dst": ["tag:json-rule:22"], "srcPosture": ["posture:latestMac"], }, @@ -40,14 +40,14 @@ // finance1 "action": "accept", - "src": ["finance1@tsjustworks.net"], + "src": ["finance1@example.com"], "dst": ["tag:demo-infra:22"], }, { // finance2 "action": "accept", - "src": ["finance2@tsjustworks.net"], + "src": ["finance2@example.com"], "dst": ["tag:demo-infra:22"], }, ], @@ -55,20 +55,20 @@ "autoApprovers": { "exitNode": [ // from `testdata/departments/engineering/autoApprovers.hujson` - "tag:example", + "tag:engineering", ], "routes": { - // from `testdata/input-parent.hujson` - "10.0.0.0/32": ["tag:example"], + // from `testdata/departments/engineering/autoApprovers.hujson` + "10.0.0.0/32": ["tag:engineering"], // from `testdata/departments/finance/autoApprovers.hujson` - "10.0.10.0/32": ["tag:example"], + "10.0.10.0/32": ["tag:finance"], }, }, - // from `testdata/input-parent.hujson` "extraDNSRecords": [ + // from `testdata/input-parent.hujson` { "Name": "exra.dns.records", "Value": "100.100.100.100", @@ -79,7 +79,7 @@ // from `testdata/departments/engineering/grants.hujson` { //"src": ["group:prod"], - "src": ["allen@tsjustworks.net"], + "src": ["engineering@example.com"], "dst": ["tag:k8s-operator"], "app": {"tailscale.com/cap/kubernetes": [{"impersonate":{"groups":["system:masters"]}}]}, @@ -91,10 +91,10 @@ "group:parent": ["from-parent"], // from `testdata/departments/engineering/groups.hujson` - "group:group1": ["cameron@tsjustworks.net"], + "group:engineering": ["user1@example.com"], // from `testdata/departments/finance/acls.hujson` - "group:group2": ["allen@tsjustworks.net"], + "group:finance": ["finance@example.com"], }, "ipsets": { @@ -105,8 +105,8 @@ "ipset:finance": ["192.0.2.1"], }, - // from `testdata/input-parent.hujson` "nodeAttrs": [ + // from `testdata/input-parent.hujson` { "target": ["*"], @@ -127,13 +127,15 @@ "target": ["autogroup:admin"], }, { - "target": ["my-kid@my-home.com", "tag:server"], + "target": ["user4@example.com", "tag:server"], "attr": ["nextdns:abc123", "nextdns:no-device-info"], }, ], - // from `testdata/input-parent.hujson` - "postures": {"posture:latestMac": ["node:os IN ['macos', 'linux']","node:tsReleaseTrack == 'stable'","node:tsVersion >= '1.40'"]}, + "postures": { + // from `testdata/input-parent.hujson` + "posture:latestMac": ["node:os IN ['macos', 'linux']", "node:tsReleaseTrack == 'stable'", "node:tsVersion >= '1.40'"], + }, "ssh": [ // from `testdata/input-parent.hujson` @@ -146,7 +148,7 @@ // from `testdata/departments/engineering/acls.hujson` { "action": "accept", - "src": ["group:finance"], + "src": ["group:engineering"], "dst": ["autogroup:self"], "users": ["root", "autogroup:nonroot"], }, @@ -154,34 +156,35 @@ { "action": "accept", "src": ["autogroup:member"], - "dst": ["tag:prod"], + "dst": ["tag:finance"], "users": ["root", "autogroup:nonroot"], }, { "action": "accept", - "src": ["tag:logging"], - "dst": ["tag:prod"], + "src": ["tag:finance"], + "dst": ["tag:finance"], "users": ["root", "autogroup:nonroot"], }, ], - // from `testdata/input-parent.hujson` "tagOwners": { - "tag:parent": [], - "tag:cameron": ["autogroup:member"], - "tag:dave": [], - "tag:jane": [], + // from `testdata/input-parent.hujson` + "tag:parent": [], + + "tag:user1": ["autogroup:member"], + "tag:dave": [], + "tag:jane": [], }, "tests": [ - // from `testdata/input-parent.hujson` + // from `testdata/departments/engineering/acls.hujson` { - "src": "cameron@example.com", - "accept": ["tag:cameron:22"], + "src": "engineering@example.com", + "accept": ["tag:dev:22"], }, // from `testdata/departments/finance/acls.hujson` { - "src": "dave@example.com", + "src": "finance@example.com", "srcPostureAttrs": {"node:os": "windows"}, "proto": "tcp", "accept": ["example-host-1:22", "vega:80"],