Skip to content

Commit

Permalink
feat: add changelog generator (#1869)
Browse files Browse the repository at this point in the history
Co-authored-by: Timo Riski <[email protected]>
  • Loading branch information
byashimov and rriski authored Oct 28, 2024
1 parent daaeb73 commit 94363d7
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 21 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,14 @@ sweep-check:

generate: gen-go docs


OLD_SCHEMA ?= .oldSchema.json
CHANGELOG := PROVIDER_AIVEN_ENABLE_BETA=1 go run ./changelog/...
gen-go:
go generate ./...
$(CHANGELOG) -save -schema=$(OLD_SCHEMA)
go generate ./...;
$(MAKE) fmt-imports
$(CHANGELOG) -diff -schema=$(OLD_SCHEMA) -changelog=CHANGELOG.md
rm $(OLD_SCHEMA)


docs: $(TFPLUGINDOCS)
Expand Down
151 changes: 151 additions & 0 deletions changelog/differ.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package main

import (
"encoding/json"
"fmt"
"slices"
"strings"

"github.com/google/go-cmp/cmp"
"github.com/samber/lo"
)

func diffItems(resourceType ResourceType, was, have *Item) (*Diff, error) {
// Added or removed
if was == nil || have == nil {
action := ChangeTypeAdd
if have == nil {
action = ChangeTypeRemove
have = was
}

return &Diff{
Type: action,
ResourceType: resourceType,
Description: removeEnum(have.Description),
Item: have,
}, nil
}

// Equal items
if cmp.Equal(was, have) {
return nil, nil
}

// Compare all the fields
wasMap, err := toMap(was)
if err != nil {
return nil, err
}

haveMap, err := toMap(have)
if err != nil {
return nil, err
}

entries := make([]string, 0)
for k, wasValue := range wasMap {
haveValue := haveMap[k]
if cmp.Equal(wasValue, haveValue) {
continue
}

var entry string
switch k {
case "deprecated":
entry = "remove deprecation"
if have.Deprecated != "" {
entry = fmt.Sprintf("deprecate: %s", have.Deprecated)
}
case "beta":
entry = "marked as beta"
if !haveValue.(bool) {
entry = "no longer beta"
}
default:
// The rest of the fields will have diff-like entry
entry = fmt.Sprintf("%s ~~`%s`~~ -> `%s`", k, strValue(wasValue), strValue(haveValue))

// Fixes formatting issues
entry = strings.ReplaceAll(entry, "``", "`")
}

entries = append(entries, entry)
}

if len(entries) == 0 {
return nil, nil
}

return &Diff{
Type: ChangeTypeChange,
ResourceType: resourceType,
Description: strings.Join(entries, ", "),
Item: have,
}, nil
}

func diffItemMaps(was, have ItemMap) ([]string, error) {
result := make([]string, 0)
kinds := []ResourceType{ResourceKind, DataSourceKind}
for _, kind := range kinds {
wasItems := was[kind]
haveItems := have[kind]
keys := append(lo.Keys(wasItems), lo.Keys(haveItems)...)
slices.Sort(keys)

var skipPrefix string
seen := make(map[string]bool)
for _, k := range keys {
if seen[k] {
continue
}

// Skips duplicate keys
seen[k] = true

// When a resource added or removed, it skips all its fields until the next resource
if skipPrefix != "" && strings.HasPrefix(k, skipPrefix) {
continue
}

skipPrefix = ""
wasVal, wasOk := wasItems[k]
haveVal, haveOk := haveItems[k]
if wasOk != haveOk {
// Resource added or removed, must skip all its fields
skipPrefix = k
}

change, err := diffItems(kind, wasVal, haveVal)
if err != nil {
return nil, fmt.Errorf("failed to compare %s %s: %w", kind, k, err)
}

if change != nil {
result = append(result, change.String())
}
}
}
return result, nil
}

func toMap(item *Item) (map[string]any, error) {
b, err := json.Marshal(item)
if err != nil {
return nil, err
}

m := make(map[string]any)
err = json.Unmarshal(b, &m)
if err != nil {
return nil, err
}

m["enum"] = findEnums(item.Description)
m["beta"] = hasBeta(item.Description)
m["type"] = strValueType(item.Type)
m["elemType"] = strValueType(item.ElemType)
delete(m, "description") // Not needed to compare descriptions
return m, err
}
99 changes: 99 additions & 0 deletions changelog/differ_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/stretchr/testify/assert"
)

func TestCompare(t *testing.T) {
tests := []struct {
name string
expect string
kind ResourceType
old, new *Item
}{
{
name: "change enums",
expect: "Change resource `foo` field `bar`: enum ~~`bar`, `baz`~~ -> `foo`, `baz`",
kind: ResourceKind,
old: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "Foo. The possible values are `bar`, `baz`.",
},
new: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "Foo. The possible values are `foo`, `baz`.",
},
},
{
name: "add resource field",
expect: "Add resource `foo` field `bar`: Foo",
kind: ResourceKind,
new: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "Foo",
},
},
{
name: "remove resource field",
expect: "Remove resource `foo` field `bar`: Foo",
kind: ResourceKind,
old: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "Foo",
},
},
{
name: "remove beta from the field",
expect: "Change resource `foo` field `bar`: no longer beta",
kind: ResourceKind,
old: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "PROVIDER_AIVEN_ENABLE_BETA",
},
new: &Item{
Type: schema.TypeString,
Path: "foo.bar",
Description: "Foo",
},
},
{
name: "add beta resource",
expect: "Add resource `foo` _(beta)_: does stuff, PROVIDER_AIVEN_ENABLE_BETA",
kind: ResourceKind,
new: &Item{
Type: schema.TypeString,
Path: "foo",
Description: "does stuff, PROVIDER_AIVEN_ENABLE_BETA",
},
},
{
name: "change type",
expect: "Change resource `foo` field `bar`: type ~~`list`~~ -> `set`",
kind: ResourceKind,
old: &Item{
Type: schema.TypeList,
Path: "foo.bar",
},
new: &Item{
Type: schema.TypeSet,
Path: "foo.bar",
},
},
}

for _, opt := range tests {
t.Run(opt.name, func(t *testing.T) {
got, err := diffItems(opt.kind, opt.old, opt.new)
assert.NoError(t, err)
assert.Equal(t, opt.expect, got.String())
})
}
}
Loading

0 comments on commit 94363d7

Please sign in to comment.