Skip to content

Commit

Permalink
feat: Add resource mapping generator add upgrade Terraform Bridge (#43)
Browse files Browse the repository at this point in the history
* feat: add provider/generate.go to create missing resources mappings and added target generate to Makefile

* fix(.github/workflows/release.yml): changed  secrets.PYPI_API_TOKEN to secrets.PYPI_PASSWORD

* chore: bumped version of github.com/pulumi/pulumi-terraform-bridge/v3 to v3.63.2 and github.com/pulumi/pulumi-terraform-bridge/pf to v0.18.3

Closes: #42

* cicd: bumped go version to 1.21

* build: added targets go.work and tidy to Makefile and bumped GO version to 1.21

* feat: bumped version of upstream provider to v0.11.1 and version of Terraform Bridge to 3.78.0

* chore: reacreated language SDKs

---------

Co-authored-by: Thomas Meckel <[email protected]>
  • Loading branch information
tmeckel and tmeckel authored Mar 22, 2024
1 parent 089d730 commit 37bb2af
Show file tree
Hide file tree
Showing 34 changed files with 1,142 additions and 1,016 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
dotnetversion:
- 6.0.x
goversion:
- 1.20.x
- 1.21.x
nodeversion:
- 16.x
pythonversion:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ env:
NUGET_PUBLISH_KEY: ${{ secrets.NUGET_PUBLISH_KEY }}
NUGET_FEED_URL: https://api.nuget.org/v3/index.json
PUBLISH_NUGET: true
# IF YOU NEED TO PUBLISH A PYPI PACKAGE THEN ENSURE AN PYPI_API_TOKEN
# IF YOU NEED TO PUBLISH A PYPI PACKAGE THEN ENSURE AN PYPI_PASSWORD
# SECRET IS SET AND PUBLISH_PYPI: TRUE. IF YOU WANT TO PUBLISH TO AN ALTERNATIVE
# PYPI REGISTRY THEN ENSURE THE PYPI_REPOSITORY_URL IS SET. IF YOU ARE USING AN API_TOKEN THEN
# YOU DO NOT NEED TO CHANGE THE PYPI_USERNAME (__token__) , IF YOU ARE USING PASSWORD AUTHENTICATION THEN YOU WILL
# NEED TO CHANGE TO USE THE CORRECT PASSWORD
PYPI_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
PYPI_USERNAME: "__token__"
PYPI_REPOSITORY_URL: ""
PUBLISH_PYPI: true
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
fail-fast: true
matrix:
goversion:
- 1.20.x
- 1.21.x

publish_sdk:
name: Publish SDKs
Expand Down Expand Up @@ -162,7 +162,7 @@ jobs:
dotnetversion:
- 6.0.x
goversion:
- 1.20.x
- 1.21.x
nodeversion:
- 16.x
pythonversion:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ sdk/java/.gradle
sdk/java/gradle
sdk/java/gradlew
sdk/java/gradlew.bat

go.work
go.work.sum
20 changes: 19 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ NODE_MODULE_NAME := @pulumiverse/time
TF_NAME := time
PROVIDER_PATH := provider
VERSION_PATH := ${PROVIDER_PATH}/pkg/version.Version
PROJECT_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

JAVA_GEN := pulumi-java-gen
JAVA_GEN_VERSION := v0.9.8
Expand All @@ -22,7 +23,7 @@ GO_MINOR_VERSION := $(shell go version | cut -c 14- | cut -d' ' -f1 | cut -d'.'
# the (local) version must match the version specified in .github/workflows/release.yml
# otherwise publkishing the Go SDK of the provider will fail
REQUIRED_GO_MAJOR_VERSION := 1
REQUIRED_GO_MINOR_VERSION := 20
REQUIRED_GO_MINOR_VERSION := 21
GO_VERSION_VALIDATION_ERR_MSG := Golang version $(REQUIRED_GO_MAJOR_VERSION).$(REQUIRED_GO_MINOR_VERSION) is required

.PHONY: development provider build_sdks build_nodejs build_dotnet build_go build_python cleanup validate_go_version
Expand All @@ -43,6 +44,9 @@ development:: install_plugins provider lint_provider build_sdks install_sdks cle
build:: install_plugins provider build_sdks install_sdks
only_build:: build

generate::
go generate provider/resources.go

tfgen:: install_plugins
(cd provider && go build -o $(WORKING_DIR)/bin/${TFGEN} -ldflags "-X ${PROJECT}/${VERSION_PATH}=${VERSION}" ${PROJECT}/${PROVIDER_PATH}/cmd/${TFGEN})
$(WORKING_DIR)/bin/${TFGEN} schema --out provider/cmd/${PROVIDER}
Expand Down Expand Up @@ -99,6 +103,9 @@ $(WORKING_DIR)/bin/$(JAVA_GEN)::
lint_provider:: provider # lint the provider code
cd provider && golangci-lint run -c ../.golangci.yml

tidy:: # call go mod tidy in relevant directories
find ./provider -name go.mod -execdir go mod tidy \;

cleanup:: # cleans up the temporary directory
rm -r $(WORKING_DIR)/bin
rm -f provider/cmd/${PROVIDER}/schema.go
Expand Down Expand Up @@ -137,3 +144,14 @@ install_sdks:: install_dotnet_sdk install_python_sdk install_nodejs_sdk

test::
cd examples && go test -v -tags=all -parallel ${TESTPARALLELISM} -timeout 2h

.PHONY: go.work
go.work::
@cd $(PROJECT_DIR)
ifeq (,$(wildcard $(PROJECT_DIR)/go.work))
@echo "Initializing go.work..."
@go work init
else
@echo "Updating go.work..."
endif
@go work use provider provider/shim sdk examples
9 changes: 0 additions & 9 deletions go.work

This file was deleted.

351 changes: 0 additions & 351 deletions go.work.sum

This file was deleted.

8 changes: 4 additions & 4 deletions provider/cmd/pulumi-resource-time/schema.json

Large diffs are not rendered by default.

222 changes: 222 additions & 0 deletions provider/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//go:build exclude

package main

import (
"bufio"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
"os"
"os/exec"
"reflect"
"regexp"
"strings"
)

var (
rxMissingResource = regexp.MustCompile(`TF resource "(\w+)" not`)
rxMissingDataSource = regexp.MustCompile(`TF data source "(\w+)" not`)
)

// addNewline is a hack to let us force a newline at a certain position. (https://github.com/mvdan/gofumpt/blob/master/format/format.go#L217)
func addNewline(f *token.File, at token.Pos) {
offset := f.Offset(at)

field := reflect.ValueOf(f).Elem().FieldByName("lines")
n := field.Len()
lines := make([]int, 0, n+1)
for i := 0; i < n; i++ {
cur := int(field.Index(i).Int())
if offset == cur {
// This newline already exists; do nothing. Duplicate
// newlines can't exist.
return
}
if offset >= 0 && offset < cur {
lines = append(lines, offset)
offset = -1
}
lines = append(lines, cur)
}
if offset >= 0 {
lines = append(lines, offset)
}
if !f.SetLines(lines) {
panic(fmt.Sprintf("could not set lines to %v", lines))
}
}

func addKeyToMap(name, fnc string, pos token.Pos, kve *ast.KeyValueExpr) {
resExpr := ast.KeyValueExpr{
Key: &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", name),
ValuePos: pos,
},
Value: &ast.CompositeLit{
Elts: []ast.Expr{
&ast.KeyValueExpr{
Key: ast.NewIdent("Tok"),
Value: &ast.CallExpr{
Fun: ast.NewIdent(fnc),
Args: []ast.Expr{
ast.NewIdent("mainMod"),
&ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", name),
},
},
},
},
},
},
}
kve.Value.(*ast.CompositeLit).Elts = append(kve.Value.(*ast.CompositeLit).Elts, &resExpr)
}

func main() {
fmt.Println("🧱 Building tfgen ...")
cmd := exec.Command("make", "-C", "..", "tfgen")
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "PULUMI_SKIP_MISSING_MAPPING_ERROR=1")

r, _ := cmd.StderrPipe()
done := make(chan struct{})
scanner := bufio.NewScanner(r)

stderr := []string{}
missingResources := []string{}
missingDataSources := []string{}

go func() {
// Read line by line and process it
for scanner.Scan() {
line := scanner.Text()
stderr = append(stderr, line)

for i, m := range rxMissingResource.FindStringSubmatch(line) {
if i > 0 { // ignore initial match because it contains the complete line if the regex matches
fmt.Printf("✨ Missing resource %s\n", m)
missingResources = append(missingResources, m)
}
}

for i, m := range rxMissingDataSource.FindStringSubmatch(line) {
if i > 0 { // ignore initial match because it contains the complete line if the regex matches
fmt.Printf("✨ Missing data source %s\n", m)
missingDataSources = append(missingDataSources, m)
}
}

}

// We're all done, unblock the channel
done <- struct{}{}
}()

// Start the command and check for errors
err := cmd.Start()
if err != nil {
log.Fatalf("failed to start cmd: error(%T): %s", err, err)
}

// Wait for all output to be processed
<-done

// Wait for the command to finish
err = cmd.Wait()
if execErr := (&exec.ExitError{}); errors.As(err, &execErr) {
if execErr.ExitCode() != 2 {
log.Fatalf("🔥 Failed build tfgen failed with error(%T): %s", err, err)
} else if len(missingResources) == 0 && len(missingDataSources) == 0 {
log.Fatalf("🔥 Failed build tfgen with error(%T): %s\n", err, err, strings.Join(stderr, "\n"))
}
} else if err != nil {
log.Fatalf("🔥 Failed build tfgen with error(%T): %s", err, err)
}

if len(missingResources) == 0 && len(missingDataSources) == 0 {
fmt.Println("🌈 No missing resources or data sources found")
return
}

srcFile := "resources.go"
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, srcFile, nil, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
log.Fatal(err)
}

var providerDecl *ast.CompositeLit
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.AssignStmt:
if c, ok := x.Rhs[0].(*ast.CompositeLit); ok {
s, ok := c.Type.(*ast.SelectorExpr)
if ok && s.Sel.Name == "ProviderInfo" {
providerDecl = c
return false
}
}
}
return true
})

if providerDecl != nil {
fmt.Println("🎯 Adding missing resources and data sources ...")
for _, e := range providerDecl.Elts {
kve, ok := e.(*ast.KeyValueExpr)
if ok {
iterateItems := func(items []string) {
n := kve.Key.(*ast.Ident).Name
funcName := "make" + n[:len(n)-1]

var offset token.Pos
l := len(kve.Value.(*ast.CompositeLit).Elts)
if l == 0 {
offset = kve.Value.(*ast.CompositeLit).Rbrace - 7
} else {
offset = kve.Value.(*ast.CompositeLit).Elts[l-1].End()
}
f := fset.File(kve.Value.(*ast.CompositeLit).Rbrace - 7)
for i, r := range items {
pos := token.Pos(int(offset) + i)
addKeyToMap(r, funcName, pos, kve)
addNewline(f, pos)
}
}
switch kve.Key.(*ast.Ident).Name {
case "DataSources":
iterateItems(missingDataSources)
case "Resources":
iterateItems(missingResources)
}
}
}
io, err := os.Create(srcFile)
if err != nil {
log.Fatalf("🔥 Failed to open source file %s", err)
}
defer io.Close()

w := bufio.NewWriter(io)
err = printer.Fprint(w, fset, f)
if err != nil {
log.Fatal(err)
}
w.Flush()

fmt.Println("🚀 Formatting code ...")
cmd := exec.Command("go", "fmt", srcFile)
if err := cmd.Run(); err != nil {
log.Fatal("🔥 Failed formatting code with error(%T): %s", err, err)
}
} else {
log.Fatal("🔥 ProviderInfo declaration not found")
}
}
Loading

0 comments on commit 37bb2af

Please sign in to comment.