Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outputs for provider authors #155

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
110 changes: 69 additions & 41 deletions examples/file/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func (f *File) Annotate(a infer.Annotator) {
}

type FileArgs struct {
Path string `pulumi:"path,optional"`
Force bool `pulumi:"force,optional"`
Content string `pulumi:"content"`
Path infer.Output[string] `pulumi:"path,optional"`
Force infer.Output[bool] `pulumi:"force,optional"`
Content infer.Output[string] `pulumi:"content"`
}

func (f *FileArgs) Annotate(a infer.Annotator) {
Expand All @@ -58,9 +58,9 @@ func (f *FileArgs) Annotate(a infer.Annotator) {
}

type FileState struct {
Path string `pulumi:"path"`
Force bool `pulumi:"force"`
Content string `pulumi:"content"`
Path infer.Output[string] `pulumi:"path,optional"`
Force infer.Output[bool] `pulumi:"force,optional"`
Content infer.Output[string] `pulumi:"content"`
}

func (f *FileState) Annotate(a infer.Annotator) {
Expand All @@ -70,40 +70,54 @@ func (f *FileState) Annotate(a infer.Annotator) {
}

func (*File) Create(ctx p.Context, name string, input FileArgs, preview bool) (id string, output FileState, err error) {
if !input.Force {
_, err := os.Stat(input.Path)
if !os.IsNotExist(err) {
return "", FileState{}, fmt.Errorf("file already exists; pass force=true to override")
err = infer.Apply2Err(input.Path, input.Force, func(path string, force bool) (string, error) {
if force {
return "", nil
}
_, err := os.Stat(path)
if os.IsNotExist(err) {
return "", nil
} else if err != nil {
return "", fmt.Errorf("discovering if %s exists: %w", path, err)
}
return "", fmt.Errorf("file already exists at %s; pass `force: true` to override", path)
}).Anchor()
if err != nil {
return "", FileState{}, err
}

if preview { // Don't do the actual creating if in preview
return input.Path, FileState{}, nil
path, err := input.Path.GetMaybeUnknown()
if preview || err != nil { // Don't do the actual creating if in preview
return path, FileState{}, err
}

f, err := os.Create(input.Path)
f, err := os.Create(input.Path.MustGetKnown())
if err != nil {
return "", FileState{}, err
}
defer f.Close()
n, err := f.WriteString(input.Content)
n, err := f.WriteString(input.Content.MustGetKnown())
if err != nil {
return "", FileState{}, err
}
if n != len(input.Content) {
return "", FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(input.Content))
if n != len(input.Content.MustGetKnown()) {
return "", FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(input.Content.MustGetKnown()))
}
return input.Path, FileState{
return input.Path.MustGetKnown(), FileState{
Path: input.Path,
Force: input.Force,
Content: input.Content,
}, nil
}

func (*File) Delete(ctx p.Context, id string, props FileState) error {
err := os.Remove(props.Path)
path, err := props.Path.GetKnown()
if err != nil {
return err
}
err = os.Remove(path)
if os.IsNotExist(err) {
ctx.Logf(diag.Warning, "file %q already deleted", props.Path)
ctx.Logf(diag.Warning, "file %q already deleted", path)
err = nil
}
return err
Expand All @@ -117,38 +131,52 @@ func (*File) Check(ctx p.Context, name string, oldInputs, newInputs resource.Pro
}

func (*File) Update(ctx p.Context, id string, olds FileState, news FileArgs, preview bool) (FileState, error) {
if !preview && olds.Content != news.Content {
f, err := os.Create(olds.Path)
if err != nil {
return FileState{}, err
}
defer f.Close()
n, err := f.WriteString(news.Content)
if err != nil {
return FileState{}, err
}
if n != len(news.Content) {
return FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(news.Content))
err := infer.Apply2Err(olds.Path, news.Path, func(old, new string) (string, error) {
if old != new {
return "", fmt.Errorf("cannot change path")
}
return "", nil
}).Anchor()
if err != nil {
return FileState{}, err
}
_, err = infer.Apply3Err(olds.Content, news.Content, olds.Path,
func(path, oldContent, newContent string) (FileState, error) {
if preview || oldContent == newContent {
return FileState{}, nil
}
f, err := os.Create(path)
if err != nil {
return FileState{}, err
}
defer f.Close()
n, err := f.WriteString(newContent)
if err != nil {
return FileState{}, err
}
if n != len(newContent) {
return FileState{}, fmt.Errorf("only wrote %d/%d bytes", n, len(newContent))
}
return FileState{}, nil
}).GetMaybeUnknown()

return FileState{
Path: news.Path,
Force: news.Force,
Content: news.Content,
}, nil
}, err

}

func (*File) Diff(ctx p.Context, id string, olds FileState, news FileArgs) (p.DiffResponse, error) {
diff := map[string]p.PropertyDiff{}
if news.Content != olds.Content {
if !news.Content.Equal(olds.Content) {
diff["content"] = p.PropertyDiff{Kind: p.Update}
}
if news.Force != olds.Force {
if !news.Force.Equal(olds.Force) {
diff["force"] = p.PropertyDiff{Kind: p.Update}
}
if news.Path != olds.Path {
if !news.Path.Equal(olds.Path) {
diff["path"] = p.PropertyDiff{Kind: p.UpdateReplace}
}
return p.DiffResponse{
Expand All @@ -166,13 +194,13 @@ func (*File) Read(ctx p.Context, id string, inputs FileArgs, state FileState) (c
}
content := string(byteContent)
return path, FileArgs{
Path: path,
Force: inputs.Force && state.Force,
Content: content,
Path: infer.NewOutput(path),
Force: infer.Apply2(inputs.Force, state.Force, func(a, b bool) bool { return a || b }),
Content: infer.NewOutput(content),
}, FileState{
Path: path,
Force: inputs.Force && state.Force,
Content: content,
Path: infer.NewOutput(path),
Force: infer.Apply2(inputs.Force, state.Force, func(a, b bool) bool { return a || b }),
Content: infer.NewOutput(content),
}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions infer/gen_apply/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/pulumi/pulumi-go-provider/infer/gen_apply

go 1.21.0
114 changes: 114 additions & 0 deletions infer/gen_apply/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2023, Pulumi Corporation. All rights reserved.

package main

import (
"bytes"
"fmt"
"os"
"strings"
"text/template"
)

func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}

var applyFuncs = must(template.New("apply").Parse(
`
func Apply{{.inputs}}[{{.inputTypes}}U any]({{.inputArgsWithTypes}}f func({{.inputTypesNoComma}}) U) Output[U] {
return Apply{{.inputs}}Err({{.inputArgsNoTypes}}func({{.inputArgsRawTypes}}) (U, error) {
return f({{.inputArgsRaw}}), nil
})
}

func Apply{{.inputs}}Err[{{.inputTypes}}U any]({{.inputArgsWithTypes}}` +
`f func({{.inputTypesNoComma}}) (U, error)) Output[U] {
{{.ensureInputs}}
result := newOutput[U](nil, {{.orSecrets}}, {{.joinDeps}})
go apply{{.inputs}}Result({{.inputArgsNoTypes}}result, f)
return result
}

func apply{{.inputs}}Result[{{.inputTypes}}U any]({{.inputArgsWithTypes}}to Output[U],` +
` f func({{.inputTypesNoComma}}) (U, error)) {
if {{.orCanResolve}} {
return
}
{{.wait}}

// Propagate the change
to.join.L.Lock()
defer to.join.L.Unlock()

if err := errors.Join({{.errs}}); err == nil {
tmp, err := f({{.values}})
if err == nil {
to.value = &tmp
} else {
to.err = err
}
} else {
to.err = err
}
to.resolved = true
to.join.Broadcast()
}
`))

func main() {
b := new(bytes.Buffer)
b.WriteString(`// Copyright 2023, Pulumi Corporation. All rights reserved.

// Code generated by gen_apply/main; DO NOT EDIT.

package infer

import "errors"

`)
for i := 0; i < 9; i++ {
var inputs string
if i > 0 {
inputs = fmt.Sprintf("%d", i+1)
}
err := applyFuncs.Execute(b, map[string]string{
"inputs": inputs, // A number, like "3"
"inputTypes": formatN(i, "T%d, ", ""), // "T1, T2, T3, "
"inputTypesNoComma": formatN(i, "T%d", ", "), // "T1, T2, T3"
// "o1 Output[T1], o2 Output[T2], o3 Output[T3], "
"inputArgsWithTypes": formatN(i, "o%[1]d Output[T%[1]d], ", ""),
"inputArgsNoTypes": formatN(i, "o%d, ", ""), // "o1, o2, "
"inputArgsRawTypes": formatN(i, "v%[1]d T%[1]d", ", "), // "v1 T1, v2 T2"
"inputArgsRaw": formatN(i, "v%d", ", "), // "v1, v2"
"orSecrets": formatN(i, "o%d.secret", " || "), // "o1.secret || o2.secret"
// "!o1.deps.canResolve() || !o1.deps.canResolve()"
"orCanResolve": formatN(i, "!o%d.resolvable", " || "),
"wait": formatN(i, "o%d.wait()", "\n\t"), // "o1.wait()\n\to2.wait()"
"errs": formatN(i, "o%d.err", ", "), // "o1.err, o2.err"
"values": formatN(i, "*o%d.value", ", "), // "*o1.value, *o2.value"
"joinDeps": formatN(i, "o%d.resolvable", " && "), // "o1.resolvable && o2.resolvable"
"ensureInputs": formatN(i, "o%d.ensure()", "\n\t"), // "o1.ensure()\n\to2.ensure()"
})
if err != nil {
panic(err)
}
}

fmt.Printf("Writing to %s\n", os.Args[2])
err := os.WriteFile(os.Args[2], b.Bytes(), 0600)
if err != nil {
panic(err)
}
}

func formatN(to int, msg, join string) string {
var arr []string
for i := 1; i <= to+1; i++ {
arr = append(arr, fmt.Sprintf(msg, i))
}
return strings.Join(arr, join)
}
Loading
Loading