Changes for upcoming go-provider v1.0

This commit is contained in:
Bryce Lampe
2025-04-28 14:10:51 -07:00
parent 8987df0a8e
commit d98d613dd6
5 changed files with 144 additions and 104 deletions

2
go.mod
View File

@@ -16,7 +16,7 @@ require (
github.com/otiai10/copy v1.14.0
github.com/pulumi/providertest v0.3.1
github.com/pulumi/pulumi-dotnet/pulumi-language-dotnet v0.0.0-20241219213128-b19d8c8da35b
github.com/pulumi/pulumi-go-provider v0.26.0
github.com/pulumi/pulumi-go-provider v0.25.1-0.20250421175102-c887ad454804
github.com/pulumi/pulumi-java/pkg v1.10.0
github.com/pulumi/pulumi-yaml v1.17.0
github.com/pulumi/pulumi/pkg/v3 v3.163.0

4
go.sum
View File

@@ -950,8 +950,8 @@ github.com/pulumi/providertest v0.3.1 h1:vlftr7TZlObh81mL88IhhF0/9ZbLrZZos4NAvR4
github.com/pulumi/providertest v0.3.1/go.mod h1:fFHUP4/9DRyYnHWiRnwcynMtM/a7hHR/QcJfcuZKO3A=
github.com/pulumi/pulumi-dotnet/pulumi-language-dotnet v0.0.0-20241219213128-b19d8c8da35b h1:pyYHkDsogl5q4dorkTKVk4/87l+fFUEIIVuwBqKJMHM=
github.com/pulumi/pulumi-dotnet/pulumi-language-dotnet v0.0.0-20241219213128-b19d8c8da35b/go.mod h1:5LjEUmPDpUyXzFbQCy3s1oFgeU9K3gVA6tX+xn1Xvbo=
github.com/pulumi/pulumi-go-provider v0.26.0 h1:3ia10+irvv7qPph2NZ2YwUGI/KCf6li8Frlc1luv7D4=
github.com/pulumi/pulumi-go-provider v0.26.0/go.mod h1:zJiGxY5xnodPzLulpRS+fsS960MzeCdlsdiVCZ5TMRA=
github.com/pulumi/pulumi-go-provider v0.25.1-0.20250421175102-c887ad454804 h1:IPmEPPcRnYqwznnCPcjk8xpWZGna2situWWQdM3Ljws=
github.com/pulumi/pulumi-go-provider v0.25.1-0.20250421175102-c887ad454804/go.mod h1:WaxPrK2ohj29oHINF8aZlqbnj/iOLnQWk7WrV0EMMe0=
github.com/pulumi/pulumi-java/pkg v1.10.0 h1:gPW5dtKOdagDak7KUedkwRKKKUlhkdT7/vV21+ouWCw=
github.com/pulumi/pulumi-java/pkg v1.10.0/go.mod h1:MgV+XcV5jcs0R5Naqyxp3UBJn+lXLE3aG5JVb/O8JeE=
github.com/pulumi/pulumi-yaml v1.17.0 h1:ebzggygqBcQrtmdBUqi28B1L/fAyHCFVIt0dS5re8Oc=

View File

@@ -38,7 +38,6 @@ import (
provider "github.com/pulumi/pulumi-go-provider"
"github.com/pulumi/pulumi-go-provider/infer"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
@@ -350,17 +349,14 @@ func (i *Image) client(ctx context.Context, state ImageState, args ImageArgs) (C
// authenticated.
func (i *Image) Check(
ctx context.Context,
_ string,
_ resource.PropertyMap,
news resource.PropertyMap,
) (ImageArgs, []provider.CheckFailure, error) {
args, failures, err := infer.DefaultCheck[ImageArgs](ctx, news)
req infer.CheckRequest,
) (infer.CheckResponse[ImageArgs], error) {
args, failures, err := infer.DefaultCheck[ImageArgs](ctx, req.NewInputs)
if err != nil || len(failures) != 0 {
return args, failures, err
return infer.CheckResponse[ImageArgs]{Failures: failures, Inputs: args}, err
}
// :(
preview := news.ContainsUnknowns()
preview := req.NewInputs.ContainsUnknowns()
cfg := infer.GetConfig[Config](ctx)
supportsMultipleExports := true
@@ -376,7 +372,7 @@ func (i *Image) Check(
}
}
return args, failures, err
return infer.CheckResponse[ImageArgs]{Failures: failures, Inputs: args}, err
}
type checkFailure struct {
@@ -523,7 +519,9 @@ func (ia ImageArgs) toBuild(
// validate confirms the ImageArgs are valid and returns BuildOptions
// appropriate for passing to builders.
func (ia *ImageArgs) validate(supportsMultipleExports, preview bool) (controllerapi.BuildOptions, error) {
func (ia *ImageArgs) validate(
supportsMultipleExports, preview bool,
) (controllerapi.BuildOptions, error) {
var multierr error
if !supportsMultipleExports {
@@ -531,7 +529,6 @@ func (ia *ImageArgs) validate(supportsMultipleExports, preview bool) (controller
multierr = errors.Join(multierr,
newCheckFailure(errors.New("multiple exports require a v0.13 buildkit daemon or newer"), "exports"),
)
}
if ia.Push && ia.Load {
multierr = errors.Join(
multierr,
@@ -683,12 +680,11 @@ func (ia *ImageArgs) validate(supportsMultipleExports, preview bool) (controller
// Create builds an image using buildkit.
func (i *Image) Create(
ctx context.Context,
name string,
input ImageArgs,
preview bool,
) (string, ImageState, error) {
req infer.CreateRequest[ImageArgs],
) (infer.CreateResponse[ImageState], error) {
input := req.Inputs
state := ImageState{ImageArgs: input}
id := name
id := req.Name
// Default our ref to one of our tags.
for _, tag := range state.Tags {
@@ -701,20 +697,29 @@ func (i *Image) Create(
cli, err := i.client(ctx, state, input)
if err != nil {
return id, state, err
return infer.CreateResponse[ImageState]{ID: id, Output: state}, err
}
ok, err := cli.BuildKitEnabled()
if err != nil {
return id, state, fmt.Errorf("checking buildkit compatibility: %w", err)
return infer.CreateResponse[ImageState]{
ID: id,
Output: state,
}, fmt.Errorf("checking buildkit compatibility: %w", err)
}
if !ok {
return id, state, errors.New("buildkit is not supported on this host")
return infer.CreateResponse[ImageState]{
ID: id,
Output: state,
}, fmt.Errorf("buildkit is not supported on this host")
}
build, err := input.toBuild(ctx, cli.SupportsMultipleExports(), preview)
build, err := input.toBuild(ctx, cli.SupportsMultipleExports(), req.Preview)
if err != nil {
return id, state, fmt.Errorf("preparing: %w", err)
return infer.CreateResponse[ImageState]{
ID: id,
Output: state,
}, fmt.Errorf("preparing: %w", err)
}
hash, err := hashBuildContext(
@@ -723,21 +728,24 @@ func (i *Image) Create(
input.Context.Named.Map(),
)
if err != nil {
return id, state, fmt.Errorf("hashing build context: %w", err)
return infer.CreateResponse[ImageState]{
ID: id,
Output: state,
}, fmt.Errorf("hashing build context: %w", err)
}
state.ContextHash = hash
if preview && !input.shouldBuildOnPreview() {
return id, state, nil
if req.Preview && !input.shouldBuildOnPreview() {
return infer.CreateResponse[ImageState]{ID: id, Output: state}, nil
}
if preview && !input.buildable() {
if req.Preview && !input.buildable() {
provider.GetLogger(ctx).Warning("Skipping preview build because some inputs are unknown.")
return id, state, nil
return infer.CreateResponse[ImageState]{ID: id, Output: state}, nil
}
result, err := cli.Build(ctx, build)
if err != nil {
return id, state, err
return infer.CreateResponse[ImageState]{ID: id, Output: state}, err
}
if d, ok := result.ExporterResponse[exptypes.ExporterImageDigestKey]; ok {
@@ -747,7 +755,7 @@ func (i *Image) Create(
if state.Digest == "" {
// Can't construct a ref, nothing else to do.
return id, state, nil
return infer.CreateResponse[ImageState]{ID: id, Output: state}, nil
}
// Take the first registry tag we find and add a digest to it. That becomes
@@ -762,7 +770,7 @@ func (i *Image) Create(
break
}
return id, state, nil
return infer.CreateResponse[ImageState]{ID: id, Output: state}, nil
}
// Update builds a new image. Normally we create-replace resources, but for
@@ -770,36 +778,41 @@ func (i *Image) Create(
// updates and simply re-build the image without deleting anything.
func (i *Image) Update(
ctx context.Context,
name string,
_ ImageState,
input ImageArgs,
preview bool,
) (ImageState, error) {
_, state, err := i.Create(ctx, name, input, preview)
return state, err
req infer.UpdateRequest[ImageArgs, ImageState],
) (infer.UpdateResponse[ImageState], error) {
resp, err := i.Create(ctx,
infer.CreateRequest[ImageArgs]{Name: req.ID, Inputs: req.News, Preview: req.Preview},
)
return infer.UpdateResponse[ImageState]{Output: resp.Output}, err
}
// Read attempts to read manifests from an image's exports. An image without
// exports will have no manifests.
func (i *Image) Read(
ctx context.Context,
name string,
input ImageArgs,
state ImageState,
req infer.ReadRequest[ImageArgs, ImageState],
) (
string, // id
ImageArgs, // normalized inputs
ImageState, // normalized state
infer.ReadResponse[ImageArgs, ImageState],
error,
) {
state, input := req.State, req.Inputs
cli, err := i.client(ctx, state, input)
if err != nil {
return name, input, state, err
return infer.ReadResponse[ImageArgs, ImageState]{
ID: req.ID,
Inputs: input,
State: state,
}, err
}
if !state.isExported() {
// Nothing was pushed -- all done.
return name, input, state, nil
return infer.ReadResponse[ImageArgs, ImageState]{
ID: req.ID,
Inputs: input,
State: state,
}, nil
}
tagsToKeep := []string{}
@@ -835,29 +848,29 @@ func (i *Image) Read(
// If we couldn't find the tags we expected then return an empty ID to
// delete the resource.
if len(input.Tags) > 0 && len(tagsToKeep) == 0 {
return "", input, state, nil
return infer.ReadResponse[ImageArgs, ImageState]{ID: "", Inputs: input, State: state}, nil
}
state.Tags = tagsToKeep
return name, input, state, nil
return infer.ReadResponse[ImageArgs, ImageState]{ID: req.ID, Inputs: input, State: state}, nil
}
// Delete deletes an Image. If the Image was already deleted out-of-band it is
// treated as a success.
func (i *Image) Delete(
ctx context.Context,
_ string,
state ImageState,
) error {
req infer.DeleteRequest[ImageState],
) (infer.DeleteResponse, error) {
state := req.State
cli, err := i.client(ctx, state, state.ImageArgs)
if err != nil {
return err
return infer.DeleteResponse{}, err
}
if state.Digest == "" {
// Nothing was exported. Just try to delete the local image.
return cli.Delete(ctx, state.Ref)
return infer.DeleteResponse{}, cli.Delete(ctx, state.Ref)
}
digests := []string{}
@@ -885,17 +898,17 @@ func (i *Image) Delete(
multierr = errors.Join(multierr, err)
}
return multierr
return infer.DeleteResponse{}, multierr
}
// Diff re-implements most of the default diff behavior, with the exception of
// ignoring "password" changes on registry inputs.
func (*Image) Diff(
_ context.Context,
_ string,
olds ImageState,
news ImageArgs,
req infer.DiffRequest[ImageArgs, ImageState],
) (provider.DiffResponse, error) {
olds, news := req.Olds, req.News
diff := map[string]provider.PropertyDiff{}
update := provider.PropertyDiff{Kind: provider.Update}

View File

@@ -28,7 +28,6 @@ import (
provider "github.com/pulumi/pulumi-go-provider"
"github.com/pulumi/pulumi-go-provider/infer"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
)
var (
@@ -132,66 +131,83 @@ func (i *IndexState) Annotate(a infer.Annotator) {
// Create is a passthrough to Update.
func (i *Index) Create(
ctx context.Context,
name string,
input IndexArgs,
preview bool,
) (string, IndexState, error) {
state, err := i.Update(ctx, name, IndexState{}, input, preview)
return name, state, err
req infer.CreateRequest[IndexArgs],
) (infer.CreateResponse[IndexState], error) {
resp, err := i.Update(
ctx,
infer.UpdateRequest[IndexArgs, IndexState]{
ID: req.Name,
News: req.Inputs,
Preview: req.Preview,
},
)
return infer.CreateResponse[IndexState]{ID: req.Name, Output: resp.Output}, err
}
// Update performs `buildx imagetools create` to create a new OCI index /
// manifest list.
func (i *Index) Update(
ctx context.Context,
name string,
state IndexState,
input IndexArgs,
preview bool,
) (IndexState, error) {
req infer.UpdateRequest[IndexArgs, IndexState],
) (infer.UpdateResponse[IndexState], error) {
state, input := req.Olds, req.News
state.IndexArgs = input
state.Ref = input.Tag
cli, err := i.client(ctx, state, input)
if err != nil {
return state, err
return infer.UpdateResponse[IndexState]{Output: state}, err
}
if preview {
return state, nil
if req.Preview {
return infer.UpdateResponse[IndexState]{Output: state}, nil
}
provider.GetLogger(ctx).Debugf("creating index with tag %s and sources %s", input.Tag, input.Sources)
provider.GetLogger(ctx).
Debugf("creating index with tag %s and sources %s", input.Tag, input.Sources)
err = cli.ManifestCreate(ctx, input.isPushed(), input.Tag, input.Sources...)
if err != nil {
return state, fmt.Errorf("creating: %w", err)
return infer.UpdateResponse[IndexState]{Output: state}, fmt.Errorf("creating: %w", err)
}
_, _, state, err = i.Read(ctx, name, input, state)
// Read remote manifest information, if it exists.
_, err = i.Read(
ctx,
infer.ReadRequest[IndexArgs, IndexState]{ID: req.ID, Inputs: input, State: state},
)
if err != nil {
return state, fmt.Errorf("reading: %w", err)
return infer.UpdateResponse[IndexState]{Output: state}, fmt.Errorf("reading: %w", err)
}
return state, nil
return infer.UpdateResponse[IndexState]{Output: state}, nil
}
func (i *Index) Read(
ctx context.Context,
name string,
input IndexArgs,
state IndexState,
) (string, IndexArgs, IndexState, error) {
req infer.ReadRequest[IndexArgs, IndexState],
) (infer.ReadResponse[IndexArgs, IndexState], error) {
state, input := req.State, req.Inputs
state.IndexArgs = input
state.Ref = input.Tag
if !input.isPushed() {
provider.GetLogger(ctx).Debug("skipping read because index was not pushed")
return name, input, state, nil // Nothing to read.
return infer.ReadResponse[IndexArgs, IndexState]{
ID: req.ID,
Inputs: input,
State: state,
}, nil // Nothing to read.
}
cli, err := i.client(ctx, state, input)
if err != nil {
return name, input, state, err
return infer.ReadResponse[IndexArgs, IndexState]{
ID: req.ID,
Inputs: input,
State: state,
}, err
}
provider.GetLogger(ctx).Debug("reading index with tag " + input.Tag)
@@ -199,21 +215,29 @@ func (i *Index) Read(
digest, err := cli.ManifestInspect(ctx, input.Tag)
if errors.Is(err, errs.ErrNotFound) {
// A remote tag was expected but isn't there -- delete the resource.
return "", input, state, nil
return infer.ReadResponse[IndexArgs, IndexState]{ID: "", Inputs: input, State: state}, nil
}
if errors.Is(err, errs.ErrHTTPUnauthorized) {
provider.GetLogger(ctx).Warning("invalid credentials, skipping")
return name, input, state, nil
return infer.ReadResponse[IndexArgs, IndexState]{
ID: req.ID,
Inputs: input,
State: state,
}, nil
}
if err != nil {
return name, input, state, err
return infer.ReadResponse[IndexArgs, IndexState]{
ID: req.ID,
Inputs: input,
State: state,
}, err
}
if ref, ok := addDigest(input.Tag, digest); ok {
state.Ref = ref
}
return name, input, state, nil
return infer.ReadResponse[IndexArgs, IndexState]{ID: req.ID, Inputs: input, State: state}, nil
}
// Check confirms the Index's tag and source refs are all valid. This doesn't
@@ -222,13 +246,11 @@ func (i *Index) Read(
// cases for now.
func (i *Index) Check(
ctx context.Context,
_ string,
_ resource.PropertyMap,
news resource.PropertyMap,
) (IndexArgs, []provider.CheckFailure, error) {
args, failures, err := infer.DefaultCheck[IndexArgs](ctx, news)
req infer.CheckRequest,
) (infer.CheckResponse[IndexArgs], error) {
args, failures, err := infer.DefaultCheck[IndexArgs](ctx, req.NewInputs)
if err != nil {
return args, failures, err
return infer.CheckResponse[IndexArgs]{Failures: failures, Inputs: args}, err
}
if _, err := normalizeReference(args.Tag); args.Tag != "" && err != nil {
@@ -253,27 +275,31 @@ func (i *Index) Check(
}
}
return args, failures, nil
return infer.CheckResponse[IndexArgs]{Failures: failures, Inputs: args}, nil
}
// Delete attempts to delete the remote manifest.
func (i *Index) Delete(ctx context.Context, _ string, state IndexState) error {
func (i *Index) Delete(
ctx context.Context,
req infer.DeleteRequest[IndexState],
) (infer.DeleteResponse, error) {
state := req.State
if !state.isPushed() {
return nil // Nothing to delete.
return infer.DeleteResponse{}, nil // Nothing to delete.
}
cli, err := i.client(ctx, state, state.IndexArgs)
if err != nil {
return err
return infer.DeleteResponse{}, err
}
err = cli.ManifestDelete(ctx, state.Ref)
// TODO: Upstream buildx swallows the error types we'd like to test for
// here.
if err != nil && strings.Contains(err.Error(), "No such manifest:") {
return nil
return infer.DeleteResponse{}, nil
}
return err
return infer.DeleteResponse{}, err
}
// Diff returns a diff of proposed changes against current state. Ideally we
@@ -282,10 +308,10 @@ func (i *Index) Delete(ctx context.Context, _ string, state IndexState) error {
// change all the time due to short-lived AWS credentials).
func (i *Index) Diff(
_ context.Context,
_ string,
olds IndexState,
news IndexArgs,
req infer.DiffRequest[IndexArgs, IndexState],
) (provider.DiffResponse, error) {
olds, news := req.Olds, req.News
diff := map[string]provider.PropertyDiff{}
update := provider.PropertyDiff{Kind: provider.Update}
replace := provider.PropertyDiff{Kind: provider.UpdateReplace}

View File

@@ -68,6 +68,7 @@ func TestSchema(t *testing.T) {
type annotator struct{}
func (annotator) Deprecate(_ any, _ string) {}
func (annotator) Describe(_ any, _ string) {}
func (annotator) SetDefault(_, _ any, _ ...string) {}
func (annotator) SetToken(tokens.ModuleName, tokens.TypeName) {}