Skip to content

Commit 6fb7629

Browse files
authored
feat: Crossplane generation process automation (#141)
* feat: Crossplane generation process automation * fix reviews
1 parent 87552fa commit 6fb7629

File tree

9 files changed

+600
-2
lines changed

9 files changed

+600
-2
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Update Terraform Provider
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * *'
6+
workflow_dispatch: {}
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
update:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Go
20+
uses: actions/setup-go@v2
21+
with:
22+
go-version: '1.22'
23+
24+
- name: Install golangci-lint
25+
uses: golangci/golangci-lint-action@v7
26+
with:
27+
version: latest
28+
args: --timeout 10m
29+
30+
- name: Run update script
31+
run: |
32+
chmod +x scripts/update-provider.sh
33+
scripts/update-provider.sh
34+
35+
- name: Create Pull Request
36+
uses: peter-evans/create-pull-request@v7
37+
with:
38+
token: ${{ secrets.GITHUB_TOKEN }}
39+
branch: update-tf-provider/${{ env.new_version }}
40+
commit-message: "chore: bump Terraform provider to v${{ env.new_version }}"
41+
title: "chore: bump Terraform provider to v${{ env.new_version }}"
42+
body: |
43+
- Bumped `TERRAFORM_PROVIDER_VERSION` to **v${{ env.new_version }}**
44+
- Re-ran `make generate` and auto-generated new Crossplane resource configs
45+
46+
labels: automated, dependencies

config/tools/comparator/main.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"regexp"
9+
"strings"
10+
11+
"golang.org/x/text/cases"
12+
"golang.org/x/text/language"
13+
14+
"github.com/crossplane/upjet/pkg/registry"
15+
"github.com/pkg/errors"
16+
"github.com/scaleway/crossplane-provider-scaleway/config/tools"
17+
18+
"gopkg.in/yaml.v3"
19+
)
20+
21+
var groupAliases = map[string]string{
22+
"transactionalemail": "tem",
23+
"domainsanddns": "domain",
24+
}
25+
26+
// ProviderMetadata represents the structure of provider-metadata.yaml
27+
type ProviderMetadata struct {
28+
Resources map[string]*registry.Resource `yaml:"resources"`
29+
}
30+
31+
func main() {
32+
currentProviderMetadata := os.Getenv("CURRENT_METADATA")
33+
newProviderMetadata := os.Getenv("NEW_METADATA")
34+
35+
currentResources, err := parseProviderMetadata(currentProviderMetadata)
36+
if err != nil {
37+
fmt.Printf("Error parsing current provider metadata: %v\n", err)
38+
return
39+
}
40+
41+
newResources, err := parseProviderMetadata(newProviderMetadata)
42+
if err != nil {
43+
fmt.Printf("Error parsing new provider metadata: %v\n", err)
44+
return
45+
}
46+
47+
addedResources := findNewResources(currentResources, newResources)
48+
fmt.Println("New resources found:")
49+
resourceConfigs := make([]tools.ResourceConfig, 0, len(addedResources))
50+
51+
for _, resource := range addedResources {
52+
references := make(map[string]string)
53+
54+
for _, example := range resource.Examples {
55+
for refKey, refValue := range example.References {
56+
resourceType := strings.Split(refValue, ".")[0]
57+
references[refKey] = resourceType
58+
}
59+
}
60+
61+
raw := generatePackageName(resource.SubCategory)
62+
63+
pkg, ok := groupAliases[raw]
64+
if !ok {
65+
pkg = raw
66+
}
67+
68+
config := tools.ResourceConfig{
69+
PackageName: pkg,
70+
ShortGroup: pkg,
71+
ResourceName: resource.Title,
72+
TerraformResourceName: resource.Name,
73+
Kind: parseKindFromResourceName(resource.Name),
74+
References: references,
75+
}
76+
resourceConfigs = append(resourceConfigs, config)
77+
fmt.Println(resource.Name)
78+
}
79+
80+
jsonData, err := json.Marshal(resourceConfigs)
81+
if err != nil {
82+
fmt.Printf("Error marshaling resource configuration: %v\n", err)
83+
return
84+
}
85+
fmt.Println(string(jsonData))
86+
}
87+
88+
func parseProviderMetadata(metadata string) (map[string]*registry.Resource, error) {
89+
var providerMetadata ProviderMetadata
90+
err := yaml.Unmarshal([]byte(metadata), &providerMetadata)
91+
if err != nil {
92+
return nil, errors.Wrap(err, "Failed to unmarshal provider metadata")
93+
}
94+
return providerMetadata.Resources, nil
95+
}
96+
97+
func findNewResources(current, new map[string]*registry.Resource) []*registry.Resource {
98+
var addedResources []*registry.Resource
99+
for resourceName, resource := range new {
100+
if _, exists := current[resourceName]; !exists {
101+
addedResources = append(addedResources, resource)
102+
}
103+
}
104+
return addedResources
105+
}
106+
107+
func parseKindFromResourceName(resourceName string) string {
108+
titleCaser := cases.Title(language.English)
109+
110+
parts := strings.Split(resourceName, "_")
111+
if len(parts) == 0 {
112+
return ""
113+
}
114+
lastWord := parts[len(parts)-1]
115+
116+
return titleCaser.String(lastWord)
117+
}
118+
119+
func generatePackageName(subCategory string) string {
120+
re := regexp.MustCompile(`[^A-Za-z0-9]+`)
121+
cleaned := re.ReplaceAllString(subCategory, "")
122+
return strings.ToLower(cleaned)
123+
}

config/tools/comparator/main_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestParseKindFromResourceName tests the parseKindFromResourceName function.
8+
func TestParseKindFromResourceName(t *testing.T) {
9+
tests := []struct {
10+
resourceName string
11+
expected string
12+
}{
13+
{"scaleway_object_bucket", "Bucket"},
14+
{"scaleway_flexible_ip", "Ip"},
15+
{"simple_resource", "Resource"},
16+
{"", ""},
17+
{"singleword", "Singleword"},
18+
{"trailing_underscore_", ""},
19+
{"_leading_underscore", "Underscore"},
20+
{"middle__double__underscore", "Underscore"},
21+
{"_underscore", "Underscore"},
22+
}
23+
24+
for _, tt := range tests {
25+
t.Run(tt.resourceName, func(t *testing.T) {
26+
actual := parseKindFromResourceName(tt.resourceName)
27+
if actual != tt.expected {
28+
t.Errorf("parseKindFromResourceName(%s) = %s; expected %s", tt.resourceName, actual, tt.expected)
29+
}
30+
})
31+
}
32+
}
33+
34+
// TestGeneratePackageName tests the generatePackageName function.
35+
func TestGeneratePackageName(t *testing.T) {
36+
tests := []struct {
37+
subCategory string
38+
expected string
39+
}{
40+
{"Apple Silicon", "applesilicon"},
41+
{"Virtual Machines", "virtualmachines"},
42+
{" ", ""},
43+
{"", ""},
44+
{"Special Chars *&^%$#", "specialchars*&^%$#"},
45+
{"MixedCASE Category", "mixedcasecategory"},
46+
{" Leading and Trailing ", "leadingandtrailing"},
47+
{"Dotted.Category", "dotted.category"},
48+
{"Hyphenated-Category", "hyphenated-category"},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.subCategory, func(t *testing.T) {
53+
actual := generatePackageName(tt.subCategory)
54+
if actual != tt.expected {
55+
t.Errorf("generatePackageName(%s) = %s; expected %s", tt.subCategory, actual, tt.expected)
56+
}
57+
})
58+
}
59+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{{ define "initialConfigTemplate" }}
2+
package {{ .PackageName }}
3+
4+
import "github.com/crossplane/upjet/pkg/config"
5+
6+
const shortGroup = "{{ .ShortGroup }}"
7+
{{ end }}
8+
9+
{{ define "resourceConfigTemplate" }}
10+
// Configure adds configurations for {{ .ResourceName }} resource.
11+
func Configure(p *config.Provider) {
12+
p.AddResourceConfigurator("{{ .TerraformResourceName }}", func(r *config.Resource) {
13+
r.ExternalName = config.IdentifierFromProvider
14+
r.ShortGroup = shortGroup
15+
r.Kind = "{{ .Kind }}"
16+
{{ range $key, $value := .References }}
17+
r.References["{{$key}}"] = config.Reference{
18+
TerraformName: "{{$value}}",
19+
}
20+
{{ end }}
21+
})
22+
}
23+
{{ end }}
24+
25+
{{ define "updateExistingConfigTemplate" }}
26+
p.AddResourceConfigurator("{{ .TerraformResourceName }}", func(r *config.Resource) {
27+
r.ExternalName = config.IdentifierFromProvider
28+
r.ShortGroup = "{{ .ShortGroup }}"
29+
r.Kind = "{{ .Kind }}"
30+
{{ range $key, $value := .References }}
31+
r.References["{{$key}}"] = config.Reference{
32+
TerraformName: "{{$value}}",
33+
}
34+
{{ end }}
35+
})
36+
{{ end }}

0 commit comments

Comments
 (0)