From eb6f04a87d9d6766c93c9f23a146ebb78922ba59 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 4 Jun 2024 14:13:33 +0200 Subject: [PATCH 01/20] Introduce AssetOrArchive to support the SDK's Asset type which can be both --- infer/internal/ende/ende.go | 14 +++++- infer/internal/ende/ende_test.go | 82 ++++++++++++++++++++++++++++++++ infer/schema.go | 6 +++ types/asset.go | 22 +++++++++ 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 types/asset.go diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index ad880fad..0de924af 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -18,6 +18,8 @@ import ( "reflect" "github.com/pulumi/pulumi-go-provider/internal/introspect" + "github.com/pulumi/pulumi-go-provider/types" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper" @@ -60,7 +62,6 @@ func decode( IgnoreUnrecognized: ignoreUnrecognized, IgnoreMissing: allowMissing, }).Decode(m.Mappable(), target.Addr().Interface()) - } func DecodeAny(m resource.PropertyMap, dst any) (Encoder, mapper.MappingError) { @@ -188,8 +189,17 @@ func (e *ende) walk( if typ == nil || typ.Kind() != reflect.Struct { return e.walkMap(v, path, elemType, alignTypes) } - // This is a scalar value, so we can return it as is. + // This is a scalar value, so we can return it as is. The exception is types.AssetOrArchive, which we translate + // to a types.Asset or types.Archive, depending on what it contains, to match the SDK's AssetOrArchive type. default: + if typ == reflect.TypeOf(types.AssetOrArchive{}) { + if v.IsAsset() { + v = resource.NewPropertyValue(types.AssetOrArchive{Asset: v.AssetValue()}) + } else if v.IsArchive() { + v = resource.NewPropertyValue(types.AssetOrArchive{Archive: v.ArchiveValue()}) + } + } + return v } } diff --git a/infer/internal/ende/ende_test.go b/infer/internal/ende/ende_test.go index a23c4787..26f6d9f8 100644 --- a/infer/internal/ende/ende_test.go +++ b/infer/internal/ende/ende_test.go @@ -19,12 +19,15 @@ import ( "testing" r "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/archive" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "pgregory.net/rapid" rType "github.com/pulumi/pulumi-go-provider/internal/rapid/reflect" rResource "github.com/pulumi/pulumi-go-provider/internal/rapid/resource" + "github.com/pulumi/pulumi-go-provider/types" ) // testRoundTrip asserts that the result of pMap can be decoded onto T, and then @@ -144,3 +147,82 @@ func TestRoundtripIn(t *testing.T) { } }) } + +func TestDecodeAssets(t *testing.T) { + t.Parallel() + + type foo struct { + AA types.AssetOrArchive `pulumi:"aa"` + } + + simplify := func(v any) r.PropertyMap { + m := r.NewPropertyMap(v) + e := ende{} + return e.simplify(m, reflect.TypeOf(v)) + } + + assertDecodedFoo := func(kind string, m r.PropertyMap) { + key := r.PropertyKey(kind) + + require.True(t, m["aa"].IsObject()) + obj := m["aa"].ObjectValue() + require.True(t, obj.HasValue(key)) + require.Len(t, obj, 1) + + require.True(t, obj[key].IsObject()) + arch := obj[key].ObjectValue() + require.True(t, arch.HasValue("path")) + } + + t.Run("asset", func(t *testing.T) { + t.Parallel() + + asset := asset.Asset{ + Path: "asset://foo", + } + f := foo{ + AA: types.AssetOrArchive{Asset: &asset}, + } + + mNew := simplify(f) + + assertDecodedFoo("asset", mNew) + }) + + t.Run("archive", func(t *testing.T) { + t.Parallel() + + archive := archive.Archive{ + Path: "/data", + } + f := foo{ + AA: types.AssetOrArchive{Archive: &archive}, + } + + mNew := simplify(f) + + assertDecodedFoo("archive", mNew) + }) + + type bar struct { + Foo foo `pulumi:"foo"` + } + + t.Run("nested", func(t *testing.T) { + t.Parallel() + + asset := asset.Asset{ + Path: "asset://foo", + } + f := foo{ + AA: types.AssetOrArchive{Asset: &asset}, + } + b := bar{Foo: f} + + mNew := simplify(b) + + require.True(t, mNew["foo"].IsObject()) + inner := mNew["foo"].ObjectValue() + assertDecodedFoo("asset", inner) + }) +} diff --git a/infer/schema.go b/infer/schema.go index 32b4fcd0..998de376 100644 --- a/infer/schema.go +++ b/infer/schema.go @@ -26,6 +26,7 @@ import ( "github.com/pulumi/pulumi-go-provider/internal/introspect" sch "github.com/pulumi/pulumi-go-provider/middleware/schema" + "github.com/pulumi/pulumi-go-provider/types" ) func getAnnotated(t reflect.Type) introspect.Annotator { @@ -127,6 +128,11 @@ func serializeTypeAsPropertyType( Ref: "pulumi.json#/Archive", }, nil } + if t == reflect.TypeOf(types.AssetOrArchive{}) { + return schema.TypeSpec{ + Ref: "pulumi.json#/Asset", + }, nil + } if enum, ok := isEnum(t); ok { return schema.TypeSpec{ Ref: "#/types/" + enum.token, diff --git a/types/asset.go b/types/asset.go new file mode 100644 index 00000000..1b577bc7 --- /dev/null +++ b/types/asset.go @@ -0,0 +1,22 @@ +// Copyright 2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + +type AssetOrArchive struct { + Asset *resource.Asset `pulumi:"asset,optional"` + Archive *resource.Archive `pulumi:"archive,optional"` +} From fd2d152a254ac7709845e739aa0081cb33195cc4 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Wed, 5 Jun 2024 14:25:12 +0200 Subject: [PATCH 02/20] Move AssetOrArchive to infer/types --- infer/internal/ende/ende.go | 2 +- infer/internal/ende/ende_test.go | 2 +- infer/schema.go | 2 +- {types => infer/types}/asset.go | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename {types => infer/types}/asset.go (100%) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 0de924af..b90cbd9e 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -17,8 +17,8 @@ package ende import ( "reflect" + "github.com/pulumi/pulumi-go-provider/infer/types" "github.com/pulumi/pulumi-go-provider/internal/introspect" - "github.com/pulumi/pulumi-go-provider/types" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" diff --git a/infer/internal/ende/ende_test.go b/infer/internal/ende/ende_test.go index 26f6d9f8..43d55feb 100644 --- a/infer/internal/ende/ende_test.go +++ b/infer/internal/ende/ende_test.go @@ -25,9 +25,9 @@ import ( "github.com/stretchr/testify/require" "pgregory.net/rapid" + "github.com/pulumi/pulumi-go-provider/infer/types" rType "github.com/pulumi/pulumi-go-provider/internal/rapid/reflect" rResource "github.com/pulumi/pulumi-go-provider/internal/rapid/resource" - "github.com/pulumi/pulumi-go-provider/types" ) // testRoundTrip asserts that the result of pMap can be decoded onto T, and then diff --git a/infer/schema.go b/infer/schema.go index 998de376..90e12e4b 100644 --- a/infer/schema.go +++ b/infer/schema.go @@ -24,9 +24,9 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi-go-provider/infer/types" "github.com/pulumi/pulumi-go-provider/internal/introspect" sch "github.com/pulumi/pulumi-go-provider/middleware/schema" - "github.com/pulumi/pulumi-go-provider/types" ) func getAnnotated(t reflect.Type) introspect.Annotator { diff --git a/types/asset.go b/infer/types/asset.go similarity index 100% rename from types/asset.go rename to infer/types/asset.go From d64467e5a79fd446bed7e79e462eb5f9b84f39e4 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Fri, 7 Jun 2024 09:34:59 +0200 Subject: [PATCH 03/20] WIP for decoding and encoding AssetOrArchive --- examples/assets/consumer/Pulumi.yaml | 16 ++ examples/assets/consumer/file.txt | 1 + examples/assets/consumer/file.txt.zip | Bin 0 -> 170 bytes examples/assets/go.mod | 97 ++++++++ examples/assets/go.sum | 326 ++++++++++++++++++++++++++ examples/assets/main.go | 75 ++++++ infer/internal/ende/ende.go | 64 ++++- infer/resource.go | 13 + 8 files changed, 583 insertions(+), 9 deletions(-) create mode 100644 examples/assets/consumer/Pulumi.yaml create mode 100644 examples/assets/consumer/file.txt create mode 100644 examples/assets/consumer/file.txt.zip create mode 100644 examples/assets/go.mod create mode 100644 examples/assets/go.sum create mode 100644 examples/assets/main.go diff --git a/examples/assets/consumer/Pulumi.yaml b/examples/assets/consumer/Pulumi.yaml new file mode 100644 index 00000000..8ecdfe01 --- /dev/null +++ b/examples/assets/consumer/Pulumi.yaml @@ -0,0 +1,16 @@ +name: consume-assets +runtime: yaml + +plugins: + providers: + - name: assets + path: .. + +resources: + assetsResource: + type: assets:HasAssets + properties: + a1: + fn::fileAsset: ./file.txt + a2: + fn::fileArchive: ./file.txt.zip diff --git a/examples/assets/consumer/file.txt b/examples/assets/consumer/file.txt new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/examples/assets/consumer/file.txt @@ -0,0 +1 @@ +foo diff --git a/examples/assets/consumer/file.txt.zip b/examples/assets/consumer/file.txt.zip new file mode 100644 index 0000000000000000000000000000000000000000..100bfc0cc68fbaf79be0f4ed9d15a17637b48c5a GIT binary patch literal 170 zcmWIWW@h1H00HIsMD!2kdN literal 0 HcmV?d00001 diff --git a/examples/assets/go.mod b/examples/assets/go.mod new file mode 100644 index 00000000..07673856 --- /dev/null +++ b/examples/assets/go.mod @@ -0,0 +1,97 @@ +module github.com/pulumi/pulumi-go-provider/examples/assets + +replace github.com/pulumi/pulumi-go-provider => ../.. + +go 1.22.4 + +require github.com/pulumi/pulumi-go-provider v0.0.0-00010101000000-000000000000 + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.24.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.17.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/natefinch/atomic v1.0.1 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pgavlin/fx v0.1.6 // indirect + github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect + github.com/pulumi/esc v0.6.2 // indirect + github.com/pulumi/pulumi/pkg/v3 v3.117.0 // indirect + github.com/pulumi/pulumi/sdk/v3 v3.117.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.3.5 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.2 // indirect + go.uber.org/atomic v1.10.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/frand v1.4.2 // indirect +) diff --git a/examples/assets/go.sum b/examples/assets/go.sum new file mode 100644 index 00000000..dcdfeab6 --- /dev/null +++ b/examples/assets/go.sum @@ -0,0 +1,326 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= +github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= +github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= +github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= +github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= +github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= +github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 h1:LoCV5cscNVWyK5ChN/uCoIFJz8jZD63VQiGJIRgr6uo= +github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386/go.mod h1:MRxHTJrf9FhdfNQ8Hdeh9gmHevC9RJE/fu8M3JIGjoE= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= +github.com/pulumi/esc v0.6.2 h1:+z+l8cuwIauLSwXQS0uoI3rqB+YG4SzsZYtHfNoXBvw= +github.com/pulumi/esc v0.6.2/go.mod h1:jNnYNjzsOgVTjCp0LL24NsCk8ZJxq4IoLQdCT0X7l8k= +github.com/pulumi/pulumi/pkg/v3 v3.117.0 h1:QgTg+gPDbC7cckc/Vpm4M84qlBzwcQqD22pQPhVb97E= +github.com/pulumi/pulumi/pkg/v3 v3.117.0/go.mod h1:dz640vQ0WJQ1iSIXuX/PzKzQmiGpgY3LvP9CM2iwAZk= +github.com/pulumi/pulumi/sdk/v3 v3.117.0 h1:ImIsukZ2ZIYQG94uWdSZl9dJjJTosQSTsOQTauTNX7U= +github.com/pulumi/pulumi/sdk/v3 v3.117.0/go.mod h1:kNea72+FQk82OjZ3yEP4dl6nbAl2ngE8PDBc0iFAaHg= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.3.5 h1:UZEiaZ55nlXGDL92scoVuw00RmiRCazIEmvPSbSvt8Y= +github.com/segmentio/encoding v0.3.5/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= +github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= +lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/examples/assets/main.go b/examples/assets/main.go new file mode 100644 index 00000000..e342b131 --- /dev/null +++ b/examples/assets/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "fmt" + "os" + "strings" + + p "github.com/pulumi/pulumi-go-provider" + "github.com/pulumi/pulumi-go-provider/infer" + "github.com/pulumi/pulumi-go-provider/infer/types" +) + +type HasAssets struct{} + +type HasAssetsArgs struct { + A1 types.AssetOrArchive `pulumi:"a1"` + A2 types.AssetOrArchive `pulumi:"a2"` +} + +func main() { + err := p.RunProvider("assets", "0.1.0", provider()) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s", err.Error()) + os.Exit(1) + } +} + +func provider() p.Provider { + return infer.Provider(infer.Options{ + Resources: []infer.InferredResource{infer.Resource[*HasAssets, HasAssetsArgs]()}, + }) +} + +func assert(v bool, msg string) { + if !v { + fmt.Println("INVALID STATE: " + msg) + } else { + fmt.Println("valid state: " + msg) + } +} + +// TODO,tkappler just prints failures for now, needs to actually fail the test later +func assertState(s HasAssetsArgs) { + assert(s.A1.Asset == nil || s.A1.Archive == nil, + fmt.Sprintf("cannot specify both asset and archive for a1: %+v", s.A1)) + assert(s.A2.Asset == nil || s.A2.Archive == nil, + fmt.Sprintf("cannot specify both asset and archive for a2: %+v", s.A2)) + + assert(s.A1.Asset != nil || s.A1.Archive != nil, "must specify either asset or archive for a1") + assert(s.A2.Asset != nil || s.A2.Archive != nil, "must specify either asset or archive for a2") + + assert(s.A1.Asset.IsPath(), fmt.Sprintf("a1 asset must be a path: %+v", s.A1.Asset)) + assert(strings.HasSuffix(s.A1.Asset.Path, "file.txt"), + fmt.Sprintf("a1 path must have file.txt: %v", s.A1.Asset.Path)) + + assert(s.A2.Archive.IsPath(), fmt.Sprintf("a2 archive must be a path: %+v", s.A2.Archive)) + assert(strings.HasSuffix(s.A2.Archive.Path, "file.txt.zip"), + fmt.Sprintf("a2 path must have file.txt.zip: %v", s.A2.Archive.Path)) +} + +func (*HasAssets) Create(ctx context.Context, name string, input HasAssetsArgs, preview bool) (id string, output HasAssetsArgs, err error) { + if preview { + return "", HasAssetsArgs{}, nil + } + + output = input + assertState(output) + return name, output, nil +} + +func (*HasAssets) Delete(ctx context.Context, id string, state HasAssetsArgs) error { + assertState(state) + return nil +} diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index b90cbd9e..a7b50b83 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -15,6 +15,8 @@ package ende import ( + "encoding/json" + "fmt" "reflect" "github.com/pulumi/pulumi-go-provider/infer/types" @@ -189,15 +191,19 @@ func (e *ende) walk( if typ == nil || typ.Kind() != reflect.Struct { return e.walkMap(v, path, elemType, alignTypes) } - // This is a scalar value, so we can return it as is. The exception is types.AssetOrArchive, which we translate - // to a types.Asset or types.Archive, depending on what it contains, to match the SDK's AssetOrArchive type. + // This is a scalar value, so we can return it as is. The exception is assets and archives from Pulumi's + // AssetOrArchive union type, which we translate to types.AssetOrArchive. default: if typ == reflect.TypeOf(types.AssetOrArchive{}) { + // set v to a special value/property map as a signal to Encode + var aa types.AssetOrArchive if v.IsAsset() { - v = resource.NewPropertyValue(types.AssetOrArchive{Asset: v.AssetValue()}) + aa = types.AssetOrArchive{Asset: v.AssetValue()} } else if v.IsArchive() { - v = resource.NewPropertyValue(types.AssetOrArchive{Archive: v.ArchiveValue()}) + aa = types.AssetOrArchive{Archive: v.ArchiveValue()} } + + v = resource.NewPropertyValue(aa) } return v @@ -225,8 +231,8 @@ func (e *ende) walk( } pName := resource.PropertyKey(tag.Name) path := append(path, tag.Name) - if v, ok := result[pName]; ok { - result[pName] = e.walk(v, path, field.Type, alignTypes) + if vInner, ok := result[pName]; ok { + result[pName] = e.walk(vInner, path, field.Type, alignTypes) } else { if tag.Optional || !alignTypes { continue @@ -303,6 +309,13 @@ func (e *ende) walkMap( return resource.NewObjectProperty(result) } +func prettyPrint(v interface{}) { + b, err := json.MarshalIndent(v, "", " ") + if err == nil { + fmt.Println(string(b)) + } +} + func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { props, err := mapper.New(&mapper.Opts{ IgnoreMissing: true, @@ -310,9 +323,38 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { if err != nil { return nil, err } - m := resource.NewObjectProperty( - resource.NewPropertyMapFromMap(props), - ) + + // If we see "asset" or "archive" properties who have the magic signatures inside, we assume we have an + // AssetOrArchive and need to pull the asset or archive out of the object. + // TODO,tkappler This is not safe: a user could have defined a property that looks like an + // AssetOrArchive but isn't. + m := resource.NewPropertyValueRepl(props, + nil, // keys are not changed + func(a any) (resource.PropertyValue, bool) { + if aMap, ok := a.(map[string]any); ok { + if rawAsset, ok := aMap["asset"]; ok { + if asset, ok := rawAsset.(map[string]any); ok { + if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { + if sigStr, ok := sig.(string); ok && sigStr == "c44067f5952c0a294b673a41bacd8c17" { + // It's an asset inside an AssetOrArchive. Pull it out. + return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true + } + } + } + } else if rawArchive, ok := aMap["archive"]; ok { + if asset, ok := rawArchive.(map[string]any); ok { + if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { + if sigStr, ok := sig.(string); ok && sigStr == "0def7320c3a5731c473e5ecbe6d01bc7" { + // It's an archive inside an AssetOrArchive. Pull it out. + return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true + } + } + } + } + } + return resource.NewNullProperty(), false + }) + contract.Assertf(!m.ContainsUnknowns(), "NewPropertyMapFromMap cannot produce unknown values") contract.Assertf(!m.ContainsSecrets(), @@ -339,6 +381,10 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { s.path.Set(m, s.apply(v)) } + + fmt.Println("Post Encode:") + prettyPrint(m) + return m.ObjectValue(), nil } diff --git a/infer/resource.go b/infer/resource.go index f5890a5c..60ffeaa7 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -16,6 +16,7 @@ package infer import ( "context" + "encoding/json" "errors" "fmt" "reflect" @@ -1082,6 +1083,8 @@ func (rc *derivedResourceController[R, I, O]) Create( } setDeps(nil, req.Properties, m) + fmt.Println("CreateResponse") + prettyPrint(m) return p.CreateResponse{ ID: id, Properties: m, @@ -1248,11 +1251,21 @@ func (rc *derivedResourceController[R, I, O]) Delete(ctx context.Context, req p. if err != nil { return err } + + fmt.Println("Delete") + prettyPrint(olds) return del.Delete(ctx, req.ID, olds) } return nil } +func prettyPrint(v interface{}) { + b, err := json.MarshalIndent(v, "", " ") + if err == nil { + fmt.Println(string(b)) + } +} + // Apply dependencies to a property map, flowing secretness and computedness from input to // output. type setDeps func(oldInputs, input, output resource.PropertyMap) From 7aa8fdb4d25b01ad402984491a5009bca0476186 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Mon, 10 Jun 2024 14:28:42 +0200 Subject: [PATCH 04/20] Use opaque and unique signatures as property names --- infer/internal/ende/ende.go | 4 ++-- infer/internal/ende/ende_test.go | 6 +++--- infer/resource.go | 1 - infer/types/asset.go | 7 +++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index a7b50b83..412d5077 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -332,7 +332,7 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { nil, // keys are not changed func(a any) (resource.PropertyValue, bool) { if aMap, ok := a.(map[string]any); ok { - if rawAsset, ok := aMap["asset"]; ok { + if rawAsset, ok := aMap[types.AssetSignature]; ok { if asset, ok := rawAsset.(map[string]any); ok { if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { if sigStr, ok := sig.(string); ok && sigStr == "c44067f5952c0a294b673a41bacd8c17" { @@ -341,7 +341,7 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { } } } - } else if rawArchive, ok := aMap["archive"]; ok { + } else if rawArchive, ok := aMap[types.ArchiveSignature]; ok { if asset, ok := rawArchive.(map[string]any); ok { if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { if sigStr, ok := sig.(string); ok && sigStr == "0def7320c3a5731c473e5ecbe6d01bc7" { diff --git a/infer/internal/ende/ende_test.go b/infer/internal/ende/ende_test.go index 43d55feb..fa915630 100644 --- a/infer/internal/ende/ende_test.go +++ b/infer/internal/ende/ende_test.go @@ -186,7 +186,7 @@ func TestDecodeAssets(t *testing.T) { mNew := simplify(f) - assertDecodedFoo("asset", mNew) + assertDecodedFoo(types.AssetSignature, mNew) }) t.Run("archive", func(t *testing.T) { @@ -201,7 +201,7 @@ func TestDecodeAssets(t *testing.T) { mNew := simplify(f) - assertDecodedFoo("archive", mNew) + assertDecodedFoo(types.ArchiveSignature, mNew) }) type bar struct { @@ -223,6 +223,6 @@ func TestDecodeAssets(t *testing.T) { require.True(t, mNew["foo"].IsObject()) inner := mNew["foo"].ObjectValue() - assertDecodedFoo("asset", inner) + assertDecodedFoo(types.AssetSignature, inner) }) } diff --git a/infer/resource.go b/infer/resource.go index 60ffeaa7..b46e6ec6 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -1252,7 +1252,6 @@ func (rc *derivedResourceController[R, I, O]) Delete(ctx context.Context, req p. return err } - fmt.Println("Delete") prettyPrint(olds) return del.Delete(ctx, req.ID, olds) } diff --git a/infer/types/asset.go b/infer/types/asset.go index 1b577bc7..37fc2578 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -16,7 +16,10 @@ package types import "github.com/pulumi/pulumi/sdk/v3/go/common/resource" +const AssetSignature = "a9e28acb8ab501f883219e7c9f624fb6" +const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" + type AssetOrArchive struct { - Asset *resource.Asset `pulumi:"asset,optional"` - Archive *resource.Archive `pulumi:"archive,optional"` + Asset *resource.Asset `pulumi:"a9e28acb8ab501f883219e7c9f624fb6,optional"` + Archive *resource.Archive `pulumi:"195f3948f6769324d4661e1e245f3a4d,optional"` } From 51b3c7017553cd1710870da280783edaf781eaf8 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Mon, 10 Jun 2024 14:28:57 +0200 Subject: [PATCH 05/20] better assertions --- examples/assets/main.go | 53 +++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/examples/assets/main.go b/examples/assets/main.go index e342b131..88cd4181 100644 --- a/examples/assets/main.go +++ b/examples/assets/main.go @@ -32,31 +32,44 @@ func provider() p.Provider { }) } -func assert(v bool, msg string) { - if !v { - fmt.Println("INVALID STATE: " + msg) - } else { - fmt.Println("valid state: " + msg) - } -} - // TODO,tkappler just prints failures for now, needs to actually fail the test later func assertState(s HasAssetsArgs) { - assert(s.A1.Asset == nil || s.A1.Archive == nil, - fmt.Sprintf("cannot specify both asset and archive for a1: %+v", s.A1)) - assert(s.A2.Asset == nil || s.A2.Archive == nil, - fmt.Sprintf("cannot specify both asset and archive for a2: %+v", s.A2)) + failures := []string{} + add := func(msg string, obj any) { + failures = append(failures, fmt.Sprintf(msg, obj)) + } - assert(s.A1.Asset != nil || s.A1.Archive != nil, "must specify either asset or archive for a1") - assert(s.A2.Asset != nil || s.A2.Archive != nil, "must specify either asset or archive for a2") + if !(s.A1.Asset == nil || s.A1.Archive == nil) { + add("cannot specify both asset and archive for a1: %+v", s.A1) + } + if !(s.A2.Asset == nil || s.A2.Archive == nil) { + add("cannot specify both asset and archive for a2: %+v", s.A2) + } - assert(s.A1.Asset.IsPath(), fmt.Sprintf("a1 asset must be a path: %+v", s.A1.Asset)) - assert(strings.HasSuffix(s.A1.Asset.Path, "file.txt"), - fmt.Sprintf("a1 path must have file.txt: %v", s.A1.Asset.Path)) + if !(s.A1.Asset != nil || s.A1.Archive != nil) { + add("must specify either asset or archive for a1: %+v", s.A1) + } + if !(s.A2.Asset != nil || s.A2.Archive != nil) { + add("must specify either asset or archive for a2: %+v", s.A2) + } - assert(s.A2.Archive.IsPath(), fmt.Sprintf("a2 archive must be a path: %+v", s.A2.Archive)) - assert(strings.HasSuffix(s.A2.Archive.Path, "file.txt.zip"), - fmt.Sprintf("a2 path must have file.txt.zip: %v", s.A2.Archive.Path)) + if !s.A1.Asset.IsPath() { + add("a1 asset must be a path: %+v", s.A1.Asset) + } + if !strings.HasSuffix(s.A1.Asset.Path, "file.txt") { + add("a1 path must have file.txt: %v", s.A1.Asset.Path) + } + + if !s.A2.Archive.IsPath() { + add("a2 archive must be a path: %+v", s.A2.Archive) + } + if !strings.HasSuffix(s.A2.Archive.Path, "file.txt.zip") { + add("a2 path must have file.txt.zip: %v", s.A2.Archive.Path) + } + + if len(failures) > 0 { + fmt.Printf("INVALID state:\n %s", strings.Join(failures, "\n ")) + } } func (*HasAssets) Create(ctx context.Context, name string, input HasAssetsArgs, preview bool) (id string, output HasAssetsArgs, err error) { From 26a52789b2b6b08df42a6cfa1e383a4b7a61be13 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Mon, 10 Jun 2024 18:57:59 +0200 Subject: [PATCH 06/20] cleanup --- examples/assets/main.go | 39 ++++++++++++++++++++++++------------- infer/internal/ende/ende.go | 25 +++++++----------------- infer/resource.go | 11 ----------- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/examples/assets/main.go b/examples/assets/main.go index 88cd4181..f0df345c 100644 --- a/examples/assets/main.go +++ b/examples/assets/main.go @@ -32,8 +32,10 @@ func provider() p.Provider { }) } -// TODO,tkappler just prints failures for now, needs to actually fail the test later -func assertState(s HasAssetsArgs) { +// assertState asserts invariants about the state of the resource defined in consumer/Pulumi.yaml. +// Note on includesAssets: by default, the engine doesn't send assets to the provider for Delete +// requests. See https://github.com/pulumi/pulumi/pull/548. +func assertState(s HasAssetsArgs, includesAssets bool) { failures := []string{} add := func(msg string, obj any) { failures = append(failures, fmt.Sprintf(msg, obj)) @@ -53,22 +55,31 @@ func assertState(s HasAssetsArgs) { add("must specify either asset or archive for a2: %+v", s.A2) } - if !s.A1.Asset.IsPath() { - add("a1 asset must be a path: %+v", s.A1.Asset) + if s.A1.Asset.Hash == "" { + add("a1 asset hash must be set: %+v", s.A1.Asset) } - if !strings.HasSuffix(s.A1.Asset.Path, "file.txt") { - add("a1 path must have file.txt: %v", s.A1.Asset.Path) + if s.A2.Archive.Hash == "" { + add("a2 archive hash must be set: %+v", s.A2.Archive) } - if !s.A2.Archive.IsPath() { - add("a2 archive must be a path: %+v", s.A2.Archive) - } - if !strings.HasSuffix(s.A2.Archive.Path, "file.txt.zip") { - add("a2 path must have file.txt.zip: %v", s.A2.Archive.Path) + if includesAssets { + if !s.A1.Asset.IsPath() { + add("a1 asset must be a path: %+v", s.A1.Asset) + } + if !strings.HasSuffix(s.A1.Asset.Path, "file.txt") { + add("a1 path must have file.txt: %v", s.A1.Asset.Path) + } + + if !s.A2.Archive.IsPath() { + add("a2 archive must be a path: %+v", s.A2.Archive) + } + if !strings.HasSuffix(s.A2.Archive.Path, "file.txt.zip") { + add("a2 path must have file.txt.zip: %v", s.A2.Archive.Path) + } } if len(failures) > 0 { - fmt.Printf("INVALID state:\n %s", strings.Join(failures, "\n ")) + panic(fmt.Sprintf("INVALID STATE:\n %s", strings.Join(failures, "\n "))) } } @@ -78,11 +89,11 @@ func (*HasAssets) Create(ctx context.Context, name string, input HasAssetsArgs, } output = input - assertState(output) + assertState(output, true) return name, output, nil } func (*HasAssets) Delete(ctx context.Context, id string, state HasAssetsArgs) error { - assertState(state) + assertState(state, false) return nil } diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 412d5077..8bb1cf68 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -15,8 +15,6 @@ package ende import ( - "encoding/json" - "fmt" "reflect" "github.com/pulumi/pulumi-go-provider/infer/types" @@ -191,8 +189,9 @@ func (e *ende) walk( if typ == nil || typ.Kind() != reflect.Struct { return e.walkMap(v, path, elemType, alignTypes) } - // This is a scalar value, so we can return it as is. The exception is assets and archives from Pulumi's - // AssetOrArchive union type, which we translate to types.AssetOrArchive. + // This is a scalar value, so we can return it as is. The exception is assets and archives + // from Pulumi's AssetOrArchive union type, which we translate to types.AssetOrArchive. + // See #237 for more background. default: if typ == reflect.TypeOf(types.AssetOrArchive{}) { // set v to a special value/property map as a signal to Encode @@ -309,13 +308,6 @@ func (e *ende) walkMap( return resource.NewObjectProperty(result) } -func prettyPrint(v interface{}) { - b, err := json.MarshalIndent(v, "", " ") - if err == nil { - fmt.Println(string(b)) - } -} - func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { props, err := mapper.New(&mapper.Opts{ IgnoreMissing: true, @@ -324,10 +316,10 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { return nil, err } - // If we see "asset" or "archive" properties who have the magic signatures inside, we assume we have an - // AssetOrArchive and need to pull the asset or archive out of the object. - // TODO,tkappler This is not safe: a user could have defined a property that looks like an - // AssetOrArchive but isn't. + // If we see the magic signatures meaning "asset" or "archive", it's an AssetOrArchive and need + // to pull the actual, inner asset or archive out of the object and discard the outer + // AssetOrArchive. See #237 for more background. + // The literal magic signatures are from pulumi/pulumi and are not exported by the SDK. m := resource.NewPropertyValueRepl(props, nil, // keys are not changed func(a any) (resource.PropertyValue, bool) { @@ -382,9 +374,6 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { s.path.Set(m, s.apply(v)) } - fmt.Println("Post Encode:") - prettyPrint(m) - return m.ObjectValue(), nil } diff --git a/infer/resource.go b/infer/resource.go index b46e6ec6..1337fc6a 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -16,7 +16,6 @@ package infer import ( "context" - "encoding/json" "errors" "fmt" "reflect" @@ -1083,8 +1082,6 @@ func (rc *derivedResourceController[R, I, O]) Create( } setDeps(nil, req.Properties, m) - fmt.Println("CreateResponse") - prettyPrint(m) return p.CreateResponse{ ID: id, Properties: m, @@ -1252,19 +1249,11 @@ func (rc *derivedResourceController[R, I, O]) Delete(ctx context.Context, req p. return err } - prettyPrint(olds) return del.Delete(ctx, req.ID, olds) } return nil } -func prettyPrint(v interface{}) { - b, err := json.MarshalIndent(v, "", " ") - if err == nil { - fmt.Println(string(b)) - } -} - // Apply dependencies to a property map, flowing secretness and computedness from input to // output. type setDeps func(oldInputs, input, output resource.PropertyMap) From 740f68923f5ea2956c179a1019fdf90b1c4b1877 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 10:11:02 +0200 Subject: [PATCH 07/20] Extract nested function and add more assertions --- infer/internal/ende/ende.go | 59 +++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 8bb1cf68..2da71b4e 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -21,6 +21,7 @@ import ( "github.com/pulumi/pulumi-go-provider/internal/introspect" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/sig" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper" ) @@ -322,30 +323,7 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { // The literal magic signatures are from pulumi/pulumi and are not exported by the SDK. m := resource.NewPropertyValueRepl(props, nil, // keys are not changed - func(a any) (resource.PropertyValue, bool) { - if aMap, ok := a.(map[string]any); ok { - if rawAsset, ok := aMap[types.AssetSignature]; ok { - if asset, ok := rawAsset.(map[string]any); ok { - if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { - if sigStr, ok := sig.(string); ok && sigStr == "c44067f5952c0a294b673a41bacd8c17" { - // It's an asset inside an AssetOrArchive. Pull it out. - return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true - } - } - } - } else if rawArchive, ok := aMap[types.ArchiveSignature]; ok { - if asset, ok := rawArchive.(map[string]any); ok { - if sig, ok := asset["4dabf18193072939515e22adb298388d"]; ok { - if sigStr, ok := sig.(string); ok && sigStr == "0def7320c3a5731c473e5ecbe6d01bc7" { - // It's an archive inside an AssetOrArchive. Pull it out. - return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true - } - } - } - } - } - return resource.NewNullProperty(), false - }) + flattenAssets) contract.Assertf(!m.ContainsUnknowns(), "NewPropertyMapFromMap cannot produce unknown values") @@ -383,7 +361,38 @@ const ( isEmptyArr = iota ) -// Mark a encoder as generating values only. +func flattenAssets(a any) (resource.PropertyValue, bool) { + if aMap, ok := a.(map[string]any); ok { + rawAsset, hasAsset := aMap[types.AssetSignature] + rawArchive, hasArchive := aMap[types.ArchiveSignature] + + if hasAsset && hasArchive { + panic(`Encountered both an asset and an archive in the same AssetOrArchive. This +should never happen. Please file an issue at https://github.com/pulumi/pulumi-go-provider/issues.`) + } + + raw := rawAsset + if hasArchive { + raw = rawArchive + } + + if asset, ok := raw.(map[string]any); ok { + if kind, ok := asset[sig.Key]; ok { + if kind, ok := kind.(string); ok { + if kind == sig.AssetSig || kind == sig.ArchiveSig { + // It's an asset/archive inside an AssetOrArchive. Pull it out. + return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true + } + panic(`Encountered an unknown kind in an AssetOrArchive. This should never +happen. Please file an issue at https://github.com/pulumi/pulumi-go-provider/issues.`) + } + } + } + } + return resource.NewNullProperty(), false +} + +// Mark an encoder as generating values only. // // This is appropriate when you are encoding a value where all fields must be known, such // as a non-preview create or update. From 6a2e5d79366e304f264baa51355f8c64f8f28c92 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 10:15:47 +0200 Subject: [PATCH 08/20] Undo whitespace change in infer/resource.go Co-authored-by: Ian Wahbe --- infer/resource.go | 1 - 1 file changed, 1 deletion(-) diff --git a/infer/resource.go b/infer/resource.go index 1337fc6a..f5890a5c 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -1248,7 +1248,6 @@ func (rc *derivedResourceController[R, I, O]) Delete(ctx context.Context, req p. if err != nil { return err } - return del.Delete(ctx, req.ID, olds) } return nil From 81648d028aa49ef13eb2b8a3bac1b4fc0d331595 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 10:31:16 +0200 Subject: [PATCH 09/20] Document AssetOrArchive --- infer/types/asset.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infer/types/asset.go b/infer/types/asset.go index 37fc2578..d7ba6beb 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -19,6 +19,10 @@ import "github.com/pulumi/pulumi/sdk/v3/go/common/resource" const AssetSignature = "a9e28acb8ab501f883219e7c9f624fb6" const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" +// AssetOrArchive is a union type that can represent either an Asset or an Archive. +// Setting both fields to non-nil values is an error. +// This type exists to accomodate the semantics of the core Pulumi SDK's Asset type, +// which is also a union of Asset and Archive. type AssetOrArchive struct { Asset *resource.Asset `pulumi:"a9e28acb8ab501f883219e7c9f624fb6,optional"` Archive *resource.Archive `pulumi:"195f3948f6769324d4661e1e245f3a4d,optional"` From e624ea0c323ff503da7e0d3620891d8d144e34c7 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 14:34:27 +0200 Subject: [PATCH 10/20] simplify --- infer/internal/ende/ende.go | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 2da71b4e..01ac7bc4 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -190,22 +190,18 @@ func (e *ende) walk( if typ == nil || typ.Kind() != reflect.Struct { return e.walkMap(v, path, elemType, alignTypes) } - // This is a scalar value, so we can return it as is. The exception is assets and archives - // from Pulumi's AssetOrArchive union type, which we translate to types.AssetOrArchive. - // See #237 for more background. - default: - if typ == reflect.TypeOf(types.AssetOrArchive{}) { - // set v to a special value/property map as a signal to Encode - var aa types.AssetOrArchive - if v.IsAsset() { - aa = types.AssetOrArchive{Asset: v.AssetValue()} - } else if v.IsArchive() { - aa = types.AssetOrArchive{Archive: v.ArchiveValue()} - } - - v = resource.NewPropertyValue(aa) + case typ == reflect.TypeOf(types.AssetOrArchive{}): + // Translate Pulumi's AssetOrArchive union type to types.AssetOrArchive. + // See #237 for more background. + var aa types.AssetOrArchive + if v.IsAsset() { + aa = types.AssetOrArchive{Asset: v.AssetValue()} + } else if v.IsArchive() { + aa = types.AssetOrArchive{Archive: v.ArchiveValue()} } - + return resource.NewPropertyValue(aa) + // This is a scalar value, so we can return it as is. + default: return v } } From 0222d7a3eb146261931d21d0874de26af2ea7028 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 14:34:42 +0200 Subject: [PATCH 11/20] Add state hydration test for assets --- infer/resource_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/infer/resource_test.go b/infer/resource_test.go index 7035edc5..b89cd9da 100644 --- a/infer/resource_test.go +++ b/infer/resource_test.go @@ -22,12 +22,15 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/diag" r "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/sig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "pgregory.net/rapid" p "github.com/pulumi/pulumi-go-provider" "github.com/pulumi/pulumi-go-provider/infer/internal/ende" + "github.com/pulumi/pulumi-go-provider/infer/types" rRapid "github.com/pulumi/pulumi-go-provider/internal/rapid/resource" ) @@ -452,6 +455,30 @@ func TestHydrateFromState(t *testing.T) { }, nil }), )) + + type hasAsset struct { + AA types.AssetOrArchive `pulumi:"aa"` + } + testAsset, err := asset.FromText("pulumi") + require.NoError(t, err) + + // testHydrateFromState decodes and encodes, so the asset should come back out as a plain asset + // after having been decoded to an AssetOrArchive. + t.Run("assets", testHydrateFromState[hasAsset]( + r.PropertyMap{ + "aa": r.NewPropertyValue(testAsset), + }, + r.PropertyMap{ + "aa": r.NewObjectProperty(r.PropertyMap{ + sig.Key: r.NewStringProperty(sig.AssetSig), + "text": r.NewStringProperty("pulumi"), + "hash": r.NewStringProperty(testAsset.Hash), + "path": r.NewStringProperty(""), + "uri": r.NewStringProperty(""), + }), + }, + nil, + )) } type checkResource struct { From 5ab144040fc373aef4d1e1e591c4d1e3ef67bb9f Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 15:10:02 +0200 Subject: [PATCH 12/20] Unit tests for encoding assets --- infer/internal/ende/ende_test.go | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/infer/internal/ende/ende_test.go b/infer/internal/ende/ende_test.go index fa915630..5b0abca1 100644 --- a/infer/internal/ende/ende_test.go +++ b/infer/internal/ende/ende_test.go @@ -21,6 +21,7 @@ import ( r "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/archive" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource/sig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "pgregory.net/rapid" @@ -226,3 +227,73 @@ func TestDecodeAssets(t *testing.T) { assertDecodedFoo(types.AssetSignature, inner) }) } + +func TestEncodeAsset(t *testing.T) { + t.Parallel() + + t.Run("standard asset", func(t *testing.T) { + t.Parallel() + + a, err := asset.FromText("pulumi") + require.NoError(t, err) + aa := types.AssetOrArchive{Asset: a} + + encoder := Encoder{new(ende)} + + properties, err := encoder.Encode(aa) + require.NoError(t, err) + + assert.Equal(t, + r.PropertyMap{ + sig.Key: r.NewStringProperty(sig.AssetSig), + "hash": r.NewStringProperty(a.Hash), + "text": r.NewStringProperty("pulumi"), + "path": r.NewStringProperty(""), + "uri": r.NewStringProperty(""), + }, + properties) + }) + + t.Run("standard archive", func(t *testing.T) { + t.Parallel() + + a, err := archive.FromPath(t.TempDir()) + require.NoError(t, err) + aa := types.AssetOrArchive{Archive: a} + + encoder := Encoder{new(ende)} + + properties, err := encoder.Encode(aa) + require.NoError(t, err) + + assert.Equal(t, + r.PropertyMap{ + sig.Key: r.NewStringProperty(sig.ArchiveSig), + "hash": r.NewStringProperty(a.Hash), + "path": r.NewStringProperty(a.Path), + "uri": r.NewStringProperty(""), + }, + properties) + }) + + t.Run("invalid AssetOrArchive with archive and asset", func(t *testing.T) { + t.Parallel() + + a, err := asset.FromText("pulumi") + require.NoError(t, err) + + b, err := archive.FromPath(t.TempDir()) + require.NoError(t, err) + + aa := types.AssetOrArchive{ + Asset: a, + Archive: b, + } + + encoder := Encoder{new(ende)} + + assert.Panics(t, func() { + _, _ = encoder.Encode(aa) + }) + }) +} From f50c8804a534b5dc838fcf540be3872dbeecefef Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 20:38:35 +0200 Subject: [PATCH 13/20] Warn about using the core Asset and Archive types --- infer/component.go | 8 ++++---- infer/configuration.go | 10 +++++----- infer/function.go | 16 +++++++++------- infer/resource.go | 10 +++++----- infer/schema.go | 33 ++++++++++++++++++++++++-------- infer/types.go | 5 +++-- infer/types/asset.go | 2 +- infer/types_test.go | 13 +++++++------ middleware/schema/schema.go | 22 ++++++++++----------- middleware/schema/schema_test.go | 2 +- tests/schema_test.go | 3 ++- 11 files changed, 73 insertions(+), 51 deletions(-) diff --git a/infer/component.go b/infer/component.go index 9d5e3160..abd01a0c 100644 --- a/infer/component.go +++ b/infer/component.go @@ -59,16 +59,16 @@ func Component[R ComponentResource[I, O], I any, O pulumi.ComponentResource]() I type derivedComponentController[R ComponentResource[I, O], I any, O pulumi.ComponentResource] struct{} -func (rc *derivedComponentController[R, I, O]) GetSchema(reg schema.RegisterDerivativeType) ( +func (rc *derivedComponentController[R, I, O]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) ( pschema.ResourceSpec, error) { - r, err := getResourceSchema[R, I, O](true) + r, err := getResourceSchema[R, I, O](ctx, true) if err := err.ErrorOrNil(); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[I](reg); err != nil { + if err := registerTypes[I](ctx, reg); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[O](reg); err != nil { + if err := registerTypes[O](ctx, reg); err != nil { return pschema.ResourceSpec{}, err } return r, nil diff --git a/infer/configuration.go b/infer/configuration.go index 53f129aa..3bf9834a 100644 --- a/infer/configuration.go +++ b/infer/configuration.go @@ -71,11 +71,11 @@ func (*config[T]) underlyingType() reflect.Type { } func (*config[T]) GetToken() (tokens.Type, error) { return "pulumi:providers:pkg", nil } -func (*config[T]) GetSchema(reg schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { - if err := registerTypes[T](reg); err != nil { +func (*config[T]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { + if err := registerTypes[T](ctx, reg); err != nil { return pschema.ResourceSpec{}, err } - r, errs := getResourceSchema[T, T, T](false) + r, errs := getResourceSchema[T, T, T](ctx, false) return r, errs.ErrorOrNil() } @@ -85,7 +85,7 @@ func (c *config[T]) checkConfig(ctx context.Context, req p.CheckRequest) (p.Chec t = reflect.New(v.Type().Elem()).Interface().(T) } - r, err := c.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + r, err := c.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if err != nil { return p.CheckResponse{}, fmt.Errorf("could not get config secrets: %w", err) } @@ -181,7 +181,7 @@ func (c *config[T]) handleConfigFailures(ctx context.Context, err mapper.Mapping } pkgName := p.GetRunInfo(ctx).PackageName - schema, mErr := c.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + schema, mErr := c.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if mErr != nil { return mErr } diff --git a/infer/function.go b/infer/function.go index 1be03e4b..7ddf4a98 100644 --- a/infer/function.go +++ b/infer/function.go @@ -78,23 +78,25 @@ func fnToken(tk tokens.Type) tokens.Type { return tokens.NewTypeToken(tk.Module(), tokens.TypeName(name)) } -func (*derivedInvokeController[F, I, O]) GetSchema(reg schema.RegisterDerivativeType) (pschema.FunctionSpec, error) { +func (*derivedInvokeController[F, I, O]) GetSchema( + ctx context.Context, reg schema.RegisterDerivativeType, +) (pschema.FunctionSpec, error) { var f F descriptions := getAnnotated(reflect.TypeOf(f)) - input, err := objectSchema(reflect.TypeOf(new(I))) + input, err := objectSchema(ctx, reflect.TypeOf(new(I))) if err != nil { return pschema.FunctionSpec{}, err } - output, err := objectSchema(reflect.TypeOf(new(O))) + output, err := objectSchema(ctx, reflect.TypeOf(new(O))) if err != nil { return pschema.FunctionSpec{}, err } - if err := registerTypes[I](reg); err != nil { + if err := registerTypes[I](ctx, reg); err != nil { return pschema.FunctionSpec{}, err } - if err := registerTypes[O](reg); err != nil { + if err := registerTypes[O](ctx, reg); err != nil { return pschema.FunctionSpec{}, err } @@ -105,9 +107,9 @@ func (*derivedInvokeController[F, I, O]) GetSchema(reg schema.RegisterDerivative }, nil } -func objectSchema(t reflect.Type) (*pschema.ObjectTypeSpec, error) { +func objectSchema(ctx context.Context, t reflect.Type) (*pschema.ObjectTypeSpec, error) { descriptions := getAnnotated(t) - props, required, err := propertyListFromType(t, false) + props, required, err := propertyListFromType(ctx, t, false) if err != nil { return nil, fmt.Errorf("could not serialize input type %s: %w", t, err) } diff --git a/infer/resource.go b/infer/resource.go index f5890a5c..c458013f 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -806,15 +806,15 @@ type derivedResourceController[R CustomResource[I, O], I, O any] struct{} func (*derivedResourceController[R, I, O]) isInferredResource() {} -func (*derivedResourceController[R, I, O]) GetSchema(reg schema.RegisterDerivativeType) ( +func (*derivedResourceController[R, I, O]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) ( pschema.ResourceSpec, error) { - if err := registerTypes[I](reg); err != nil { + if err := registerTypes[I](ctx, reg); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[O](reg); err != nil { + if err := registerTypes[O](ctx, reg); err != nil { return pschema.ResourceSpec{}, err } - r, errs := getResourceSchema[R, I, O](false) + r, errs := getResourceSchema[R, I, O](ctx, false) return r, errs.ErrorOrNil() } @@ -940,7 +940,7 @@ func (rc *derivedResourceController[R, I, O]) Diff(ctx context.Context, req p.Di _, hasUpdate := ((interface{})(*r)).(CustomUpdate[I, O]) var forceReplace func(string) bool if hasUpdate { - schema, err := rc.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + schema, err := rc.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if err != nil { return p.DiffResponse{}, err } diff --git a/infer/schema.go b/infer/schema.go index 90e12e4b..77271873 100644 --- a/infer/schema.go +++ b/infer/schema.go @@ -20,9 +20,11 @@ import ( "strings" "github.com/hashicorp/go-multierror" + provider "github.com/pulumi/pulumi-go-provider" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "golang.org/x/net/context" "github.com/pulumi/pulumi-go-provider/infer/types" "github.com/pulumi/pulumi-go-provider/internal/introspect" @@ -83,18 +85,18 @@ func getAnnotated(t reflect.Type) introspect.Annotator { return ret } -func getResourceSchema[R, I, O any](isComponent bool) (schema.ResourceSpec, multierror.Error) { +func getResourceSchema[R, I, O any](ctx context.Context, isComponent bool) (schema.ResourceSpec, multierror.Error) { var r R var errs multierror.Error descriptions := getAnnotated(reflect.TypeOf(r)) - properties, required, err := propertyListFromType(reflect.TypeOf(new(O)), isComponent) + properties, required, err := propertyListFromType(ctx, reflect.TypeOf(new(O)), isComponent) if err != nil { var o O errs.Errors = append(errs.Errors, fmt.Errorf("could not serialize output type %T: %w", o, err)) } - inputProperties, requiredInputs, err := propertyListFromType(reflect.TypeOf(new(I)), isComponent) + inputProperties, requiredInputs, err := propertyListFromType(ctx, reflect.TypeOf(new(I)), isComponent) if err != nil { var i I errs.Errors = append(errs.Errors, fmt.Errorf("could not serialize input type %T: %w", i, err)) @@ -112,18 +114,33 @@ func getResourceSchema[R, I, O any](isComponent bool) (schema.ResourceSpec, mult }, errs } +// serializeTypeAsPropertyType is called often and we want to avoid repeated identical warnings. +var warnedAboutAsset = false + +func warnAboutAsset(ctx context.Context) { + if warnedAboutAsset { + return + } + provider.GetLogger(ctx).WarningStatus( + "Please use pulumi-go-provider's types.AssetOrArchive type instead of resource.Asset or " + + "resource.Archive. For context, see https://github.com/pulumi/pulumi-go-provider/issues/237.") + warnedAboutAsset = true +} + func serializeTypeAsPropertyType( - t reflect.Type, indicatePlain bool, extType *introspect.ExplicitType, + ctx context.Context, t reflect.Type, indicatePlain bool, extType *introspect.ExplicitType, ) (schema.TypeSpec, error) { for t.Kind() == reflect.Pointer { t = t.Elem() } if t == reflect.TypeOf(resource.Asset{}) { + warnAboutAsset(ctx) return schema.TypeSpec{ Ref: "pulumi.json#/Asset", }, nil } if t == reflect.TypeOf(resource.Archive{}) { + warnAboutAsset(ctx) return schema.TypeSpec{ Ref: "pulumi.json#/Archive", }, nil @@ -165,7 +182,7 @@ func serializeTypeAsPropertyType( if t.Key().Kind() != reflect.String { return schema.TypeSpec{}, fmt.Errorf("map keys must be strings, found %s", t.Key().String()) } - el, err := serializeTypeAsPropertyType(t.Elem(), indicatePlain, extType) + el, err := serializeTypeAsPropertyType(ctx, t.Elem(), indicatePlain, extType) if err != nil { return schema.TypeSpec{}, err } @@ -174,7 +191,7 @@ func serializeTypeAsPropertyType( AdditionalProperties: &el, }, nil case reflect.Array, reflect.Slice: - el, err := serializeTypeAsPropertyType(t.Elem(), indicatePlain, extType) + el, err := serializeTypeAsPropertyType(ctx, t.Elem(), indicatePlain, extType) if err != nil { return schema.TypeSpec{}, err } @@ -247,7 +264,7 @@ func underlyingType(t reflect.Type) (reflect.Type, bool, error) { return t, isOutputType || isInputType, nil } -func propertyListFromType(typ reflect.Type, indicatePlain bool) ( +func propertyListFromType(ctx context.Context, typ reflect.Type, indicatePlain bool) ( props map[string]schema.PropertySpec, required []string, err error) { for typ.Kind() == reflect.Pointer { typ = typ.Elem() @@ -267,7 +284,7 @@ func propertyListFromType(typ reflect.Type, indicatePlain bool) ( if tags.Internal { continue } - serialized, err := serializeTypeAsPropertyType(fieldType, indicatePlain, tags.ExplicitRef) + serialized, err := serializeTypeAsPropertyType(ctx, fieldType, indicatePlain, tags.ExplicitRef) if err != nil { return nil, nil, fmt.Errorf("invalid type '%s' on '%s.%s': %w", fieldType, typ, field.Name, err) } diff --git a/infer/types.go b/infer/types.go index 7a7b9888..1b85381b 100644 --- a/infer/types.go +++ b/infer/types.go @@ -15,6 +15,7 @@ package infer import ( + "context" "errors" "fmt" "reflect" @@ -245,7 +246,7 @@ func crawlTypes[T any](crawler Crawler) error { } // registerTypes recursively examines fields of T, calling reg on the schematized type when appropriate. -func registerTypes[T any](reg schema.RegisterDerivativeType) error { +func registerTypes[T any](ctx context.Context, reg schema.RegisterDerivativeType) error { crawler := func( t reflect.Type, isReference bool, info *introspect.FieldTag, parent, field string, @@ -285,7 +286,7 @@ func registerTypes[T any](reg schema.RegisterDerivativeType) error { return false, err } if t.Kind() == reflect.Struct { - spec, err := objectSchema(t) + spec, err := objectSchema(ctx, t) if err != nil { return false, err } diff --git a/infer/types/asset.go b/infer/types/asset.go index d7ba6beb..759642a6 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -21,7 +21,7 @@ const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" // AssetOrArchive is a union type that can represent either an Asset or an Archive. // Setting both fields to non-nil values is an error. -// This type exists to accomodate the semantics of the core Pulumi SDK's Asset type, +// This type exists to accomomdate the semantics of the core Pulumi SDK's Asset type, // which is also a union of Asset and Archive. type AssetOrArchive struct { Asset *resource.Asset `pulumi:"a9e28acb8ab501f883219e7c9f624fb6,optional"` diff --git a/infer/types_test.go b/infer/types_test.go index 00ab58df..1b2947b3 100644 --- a/infer/types_test.go +++ b/infer/types_test.go @@ -15,6 +15,7 @@ package infer import ( + "context" "reflect" "testing" @@ -162,7 +163,7 @@ func TestCrawlTypes(t *testing.T) { m[typ.String()] = spec return true } - err := registerTypes[Foo](reg) + err := registerTypes[Foo](context.Background(), reg) assert.NoError(t, err) assert.Equal(t, @@ -228,10 +229,10 @@ func TestReservedFields(t *testing.T) { reg := func(tokens.Type, pschema.ComplexTypeSpec) bool { return true } - err := registerTypes[outer](reg) + err := registerTypes[outer](context.Background(), reg) assert.NoError(t, err, "id isn't reserved on nested fields") - err = registerTypes[inner](reg) + err = registerTypes[inner](context.Background(), reg) assert.ErrorContains(t, err, `"id" is a reserved field name`) } @@ -247,7 +248,7 @@ func noOpRegister() schema.RegisterDerivativeType { func registerOk[T any]() func(t *testing.T) { return func(t *testing.T) { t.Parallel() - err := registerTypes[T](noOpRegister()) + err := registerTypes[T](context.Background(), noOpRegister()) assert.NoError(t, err) } } @@ -268,7 +269,7 @@ func TestInvalidOptionalProperty(t *testing.T) { t.Run("invalid optional enum", func(t *testing.T) { t.Parallel() - err := registerTypes[invalidContainsOptionalEnum](noOpRegister()) + err := registerTypes[invalidContainsOptionalEnum](context.Background(), noOpRegister()) var actual optionalNeedsPointerError if assert.ErrorAs(t, err, &actual) { @@ -290,7 +291,7 @@ func TestInvalidOptionalProperty(t *testing.T) { t.Run("invalid optional struct", func(t *testing.T) { t.Parallel() - err := registerTypes[invalidContainsOptionalStruct](noOpRegister()) + err := registerTypes[invalidContainsOptionalStruct](context.Background(), noOpRegister()) var actual optionalNeedsPointerError if assert.ErrorAs(t, err, &actual) { diff --git a/middleware/schema/schema.go b/middleware/schema/schema.go index 33258013..cd1f07b4 100644 --- a/middleware/schema/schema.go +++ b/middleware/schema/schema.go @@ -47,7 +47,7 @@ type Resource interface { // Return the Resource's schema definition. The passed in function should be called on // types transitively referenced by the resource. See the documentation of // RegisterDerivativeType for more details. - GetSchema(RegisterDerivativeType) (schema.ResourceSpec, error) + GetSchema(context.Context, RegisterDerivativeType) (schema.ResourceSpec, error) } // A Function that can generate its own schema definition. @@ -57,7 +57,7 @@ type Function interface { // Return the Function's schema definition. The passed in function should be called on // types transitively referenced by the function. See the documentation of // RegisterDerivativeType for more details. - GetSchema(RegisterDerivativeType) (schema.FunctionSpec, error) + GetSchema(context.Context, RegisterDerivativeType) (schema.FunctionSpec, error) } type cache struct { @@ -309,13 +309,13 @@ func (s *state) generateSchema(ctx context.Context) (schema.PackageSpec, error) pkg.Types[tkString] = renamePackage(t, info.PackageName, s.ModuleMap) return true } - errs := addElements(s.Resources, pkg.Resources, info.PackageName, registerDerivative, s.ModuleMap) - e := addElements(s.Invokes, pkg.Functions, info.PackageName, registerDerivative, s.ModuleMap) + errs := addElements(ctx, s.Resources, pkg.Resources, info.PackageName, registerDerivative, s.ModuleMap) + e := addElements(ctx, s.Invokes, pkg.Functions, info.PackageName, registerDerivative, s.ModuleMap) errs.Errors = append(errs.Errors, e.Errors...) if s.Provider != nil { _, prov, err := addElement[Resource, schema.ResourceSpec]( - info.PackageName, registerDerivative, s.ModuleMap, s.Provider) + ctx, info.PackageName, registerDerivative, s.ModuleMap, s.Provider) if err != nil { errs.Errors = append(errs.Errors, err) } @@ -333,15 +333,15 @@ func (s *state) generateSchema(ctx context.Context) (schema.PackageSpec, error) type canGetSchema[T any] interface { GetToken() (tokens.Type, error) - GetSchema(RegisterDerivativeType) (T, error) + GetSchema(context.Context, RegisterDerivativeType) (T, error) } -func addElements[T canGetSchema[S], S any](els []T, m map[string]S, - pkgName string, reg RegisterDerivativeType, +func addElements[T canGetSchema[S], S any](ctx context.Context, + els []T, m map[string]S, pkgName string, reg RegisterDerivativeType, modMap map[tokens.ModuleName]tokens.ModuleName) multierror.Error { errs := multierror.Error{} for _, f := range els { - tk, element, err := addElement[T, S](pkgName, reg, modMap, f) + tk, element, err := addElement[T, S](ctx, pkgName, reg, modMap, f) if err != nil { errs.Errors = append(errs.Errors, err) continue @@ -351,7 +351,7 @@ func addElements[T canGetSchema[S], S any](els []T, m map[string]S, return errs } -func addElement[T canGetSchema[S], S any](pkgName string, reg RegisterDerivativeType, +func addElement[T canGetSchema[S], S any](ctx context.Context, pkgName string, reg RegisterDerivativeType, modMap map[tokens.ModuleName]tokens.ModuleName, f T) (tokens.Type, S, error) { var s S tk, err := f.GetToken() @@ -359,7 +359,7 @@ func addElement[T canGetSchema[S], S any](pkgName string, reg RegisterDerivative return "", s, err } tk = assignTo(tk, pkgName, modMap) - fun, err := f.GetSchema(reg) + fun, err := f.GetSchema(ctx, reg) if err != nil { return "", s, fmt.Errorf("failed to get schema for '%s': %w", tk, err) } diff --git a/middleware/schema/schema_test.go b/middleware/schema/schema_test.go index 99c569fc..c5af0de5 100644 --- a/middleware/schema/schema_test.go +++ b/middleware/schema/schema_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRenamePacakge(t *testing.T) { +func TestRenamePackage(t *testing.T) { t.Parallel() p := schema.ResourceSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ diff --git a/tests/schema_test.go b/tests/schema_test.go index a6ac3831..c23d9268 100644 --- a/tests/schema_test.go +++ b/tests/schema_test.go @@ -16,6 +16,7 @@ package tests import ( "bytes" + "context" "encoding/json" "testing" @@ -39,7 +40,7 @@ func (r *givenResource) GetToken() (tokens.Type, error) { return r.token, nil } -func (r *givenResource) GetSchema(f schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { +func (r *givenResource) GetSchema(context.Context, schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { var s pschema.ResourceSpec s.Description = r.text return s, nil From a5ed00b9dd7bab09af29d64aa12b55447a078cd5 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Tue, 11 Jun 2024 20:57:48 +0200 Subject: [PATCH 14/20] Comment and simplify flattenAssets --- infer/internal/ende/ende.go | 53 +++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 01ac7bc4..735ff254 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -357,34 +357,47 @@ const ( isEmptyArr = iota ) +// flattenAssets pulls out assets and archives from AssetOrArchive objects. +// From: +// +// types.AssetSignature: +// sig.Key: sig.AssetSig +// ... +// +// To: +// +// sig.Key: sig.AssetSig +// ... func flattenAssets(a any) (resource.PropertyValue, bool) { - if aMap, ok := a.(map[string]any); ok { - rawAsset, hasAsset := aMap[types.AssetSignature] - rawArchive, hasArchive := aMap[types.ArchiveSignature] + aMap, ok := a.(map[string]any) + if !ok { + return resource.NewNullProperty(), false + } - if hasAsset && hasArchive { - panic(`Encountered both an asset and an archive in the same AssetOrArchive. This + rawAsset, hasAsset := aMap[types.AssetSignature] + rawArchive, hasArchive := aMap[types.ArchiveSignature] + + if hasAsset && hasArchive { + panic(`Encountered both an asset and an archive in the same AssetOrArchive. This should never happen. Please file an issue at https://github.com/pulumi/pulumi-go-provider/issues.`) - } + } - raw := rawAsset - if hasArchive { - raw = rawArchive - } + // After `raw` is set, it doesn't matter if we have an asset or an archive. + raw := rawAsset + if hasArchive { + raw = rawArchive + } - if asset, ok := raw.(map[string]any); ok { - if kind, ok := asset[sig.Key]; ok { - if kind, ok := kind.(string); ok { - if kind == sig.AssetSig || kind == sig.ArchiveSig { - // It's an asset/archive inside an AssetOrArchive. Pull it out. - return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true - } - panic(`Encountered an unknown kind in an AssetOrArchive. This should never -happen. Please file an issue at https://github.com/pulumi/pulumi-go-provider/issues.`) - } + if asset, ok := raw.(map[string]any); ok { + if kind, ok := asset[sig.Key]; ok { + if kind == sig.AssetSig || kind == sig.ArchiveSig { + return resource.NewObjectProperty(resource.NewPropertyMapFromMap(asset)), true } + panic(`Encountered an unknown kind in an AssetOrArchive. This should never +happen. Please file an issue at https://github.com/pulumi/pulumi-go-provider/issues.`) } } + return resource.NewNullProperty(), false } From 914caf387671be6a9544f51e425323ade6be6937 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Wed, 12 Jun 2024 20:44:48 +0200 Subject: [PATCH 15/20] Revert "Warn about using the core Asset and Archive types" This reverts commit f50c8804a534b5dc838fcf540be3872dbeecefef. The problem, as implemented, is that it'll warn if the Asset only occurs as output, like in local.Command. --- infer/component.go | 8 ++++---- infer/configuration.go | 10 +++++----- infer/function.go | 16 +++++++--------- infer/resource.go | 10 +++++----- infer/schema.go | 33 ++++++++------------------------ infer/types.go | 5 ++--- infer/types/asset.go | 2 +- infer/types_test.go | 13 ++++++------- middleware/schema/schema.go | 22 ++++++++++----------- middleware/schema/schema_test.go | 2 +- tests/schema_test.go | 3 +-- 11 files changed, 51 insertions(+), 73 deletions(-) diff --git a/infer/component.go b/infer/component.go index abd01a0c..9d5e3160 100644 --- a/infer/component.go +++ b/infer/component.go @@ -59,16 +59,16 @@ func Component[R ComponentResource[I, O], I any, O pulumi.ComponentResource]() I type derivedComponentController[R ComponentResource[I, O], I any, O pulumi.ComponentResource] struct{} -func (rc *derivedComponentController[R, I, O]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) ( +func (rc *derivedComponentController[R, I, O]) GetSchema(reg schema.RegisterDerivativeType) ( pschema.ResourceSpec, error) { - r, err := getResourceSchema[R, I, O](ctx, true) + r, err := getResourceSchema[R, I, O](true) if err := err.ErrorOrNil(); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[I](ctx, reg); err != nil { + if err := registerTypes[I](reg); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[O](ctx, reg); err != nil { + if err := registerTypes[O](reg); err != nil { return pschema.ResourceSpec{}, err } return r, nil diff --git a/infer/configuration.go b/infer/configuration.go index 3bf9834a..53f129aa 100644 --- a/infer/configuration.go +++ b/infer/configuration.go @@ -71,11 +71,11 @@ func (*config[T]) underlyingType() reflect.Type { } func (*config[T]) GetToken() (tokens.Type, error) { return "pulumi:providers:pkg", nil } -func (*config[T]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { - if err := registerTypes[T](ctx, reg); err != nil { +func (*config[T]) GetSchema(reg schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { + if err := registerTypes[T](reg); err != nil { return pschema.ResourceSpec{}, err } - r, errs := getResourceSchema[T, T, T](ctx, false) + r, errs := getResourceSchema[T, T, T](false) return r, errs.ErrorOrNil() } @@ -85,7 +85,7 @@ func (c *config[T]) checkConfig(ctx context.Context, req p.CheckRequest) (p.Chec t = reflect.New(v.Type().Elem()).Interface().(T) } - r, err := c.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + r, err := c.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if err != nil { return p.CheckResponse{}, fmt.Errorf("could not get config secrets: %w", err) } @@ -181,7 +181,7 @@ func (c *config[T]) handleConfigFailures(ctx context.Context, err mapper.Mapping } pkgName := p.GetRunInfo(ctx).PackageName - schema, mErr := c.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + schema, mErr := c.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if mErr != nil { return mErr } diff --git a/infer/function.go b/infer/function.go index 7ddf4a98..1be03e4b 100644 --- a/infer/function.go +++ b/infer/function.go @@ -78,25 +78,23 @@ func fnToken(tk tokens.Type) tokens.Type { return tokens.NewTypeToken(tk.Module(), tokens.TypeName(name)) } -func (*derivedInvokeController[F, I, O]) GetSchema( - ctx context.Context, reg schema.RegisterDerivativeType, -) (pschema.FunctionSpec, error) { +func (*derivedInvokeController[F, I, O]) GetSchema(reg schema.RegisterDerivativeType) (pschema.FunctionSpec, error) { var f F descriptions := getAnnotated(reflect.TypeOf(f)) - input, err := objectSchema(ctx, reflect.TypeOf(new(I))) + input, err := objectSchema(reflect.TypeOf(new(I))) if err != nil { return pschema.FunctionSpec{}, err } - output, err := objectSchema(ctx, reflect.TypeOf(new(O))) + output, err := objectSchema(reflect.TypeOf(new(O))) if err != nil { return pschema.FunctionSpec{}, err } - if err := registerTypes[I](ctx, reg); err != nil { + if err := registerTypes[I](reg); err != nil { return pschema.FunctionSpec{}, err } - if err := registerTypes[O](ctx, reg); err != nil { + if err := registerTypes[O](reg); err != nil { return pschema.FunctionSpec{}, err } @@ -107,9 +105,9 @@ func (*derivedInvokeController[F, I, O]) GetSchema( }, nil } -func objectSchema(ctx context.Context, t reflect.Type) (*pschema.ObjectTypeSpec, error) { +func objectSchema(t reflect.Type) (*pschema.ObjectTypeSpec, error) { descriptions := getAnnotated(t) - props, required, err := propertyListFromType(ctx, t, false) + props, required, err := propertyListFromType(t, false) if err != nil { return nil, fmt.Errorf("could not serialize input type %s: %w", t, err) } diff --git a/infer/resource.go b/infer/resource.go index c458013f..f5890a5c 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -806,15 +806,15 @@ type derivedResourceController[R CustomResource[I, O], I, O any] struct{} func (*derivedResourceController[R, I, O]) isInferredResource() {} -func (*derivedResourceController[R, I, O]) GetSchema(ctx context.Context, reg schema.RegisterDerivativeType) ( +func (*derivedResourceController[R, I, O]) GetSchema(reg schema.RegisterDerivativeType) ( pschema.ResourceSpec, error) { - if err := registerTypes[I](ctx, reg); err != nil { + if err := registerTypes[I](reg); err != nil { return pschema.ResourceSpec{}, err } - if err := registerTypes[O](ctx, reg); err != nil { + if err := registerTypes[O](reg); err != nil { return pschema.ResourceSpec{}, err } - r, errs := getResourceSchema[R, I, O](ctx, false) + r, errs := getResourceSchema[R, I, O](false) return r, errs.ErrorOrNil() } @@ -940,7 +940,7 @@ func (rc *derivedResourceController[R, I, O]) Diff(ctx context.Context, req p.Di _, hasUpdate := ((interface{})(*r)).(CustomUpdate[I, O]) var forceReplace func(string) bool if hasUpdate { - schema, err := rc.GetSchema(ctx, func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) + schema, err := rc.GetSchema(func(tokens.Type, pschema.ComplexTypeSpec) bool { return false }) if err != nil { return p.DiffResponse{}, err } diff --git a/infer/schema.go b/infer/schema.go index 77271873..90e12e4b 100644 --- a/infer/schema.go +++ b/infer/schema.go @@ -20,11 +20,9 @@ import ( "strings" "github.com/hashicorp/go-multierror" - provider "github.com/pulumi/pulumi-go-provider" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" - "golang.org/x/net/context" "github.com/pulumi/pulumi-go-provider/infer/types" "github.com/pulumi/pulumi-go-provider/internal/introspect" @@ -85,18 +83,18 @@ func getAnnotated(t reflect.Type) introspect.Annotator { return ret } -func getResourceSchema[R, I, O any](ctx context.Context, isComponent bool) (schema.ResourceSpec, multierror.Error) { +func getResourceSchema[R, I, O any](isComponent bool) (schema.ResourceSpec, multierror.Error) { var r R var errs multierror.Error descriptions := getAnnotated(reflect.TypeOf(r)) - properties, required, err := propertyListFromType(ctx, reflect.TypeOf(new(O)), isComponent) + properties, required, err := propertyListFromType(reflect.TypeOf(new(O)), isComponent) if err != nil { var o O errs.Errors = append(errs.Errors, fmt.Errorf("could not serialize output type %T: %w", o, err)) } - inputProperties, requiredInputs, err := propertyListFromType(ctx, reflect.TypeOf(new(I)), isComponent) + inputProperties, requiredInputs, err := propertyListFromType(reflect.TypeOf(new(I)), isComponent) if err != nil { var i I errs.Errors = append(errs.Errors, fmt.Errorf("could not serialize input type %T: %w", i, err)) @@ -114,33 +112,18 @@ func getResourceSchema[R, I, O any](ctx context.Context, isComponent bool) (sche }, errs } -// serializeTypeAsPropertyType is called often and we want to avoid repeated identical warnings. -var warnedAboutAsset = false - -func warnAboutAsset(ctx context.Context) { - if warnedAboutAsset { - return - } - provider.GetLogger(ctx).WarningStatus( - "Please use pulumi-go-provider's types.AssetOrArchive type instead of resource.Asset or " + - "resource.Archive. For context, see https://github.com/pulumi/pulumi-go-provider/issues/237.") - warnedAboutAsset = true -} - func serializeTypeAsPropertyType( - ctx context.Context, t reflect.Type, indicatePlain bool, extType *introspect.ExplicitType, + t reflect.Type, indicatePlain bool, extType *introspect.ExplicitType, ) (schema.TypeSpec, error) { for t.Kind() == reflect.Pointer { t = t.Elem() } if t == reflect.TypeOf(resource.Asset{}) { - warnAboutAsset(ctx) return schema.TypeSpec{ Ref: "pulumi.json#/Asset", }, nil } if t == reflect.TypeOf(resource.Archive{}) { - warnAboutAsset(ctx) return schema.TypeSpec{ Ref: "pulumi.json#/Archive", }, nil @@ -182,7 +165,7 @@ func serializeTypeAsPropertyType( if t.Key().Kind() != reflect.String { return schema.TypeSpec{}, fmt.Errorf("map keys must be strings, found %s", t.Key().String()) } - el, err := serializeTypeAsPropertyType(ctx, t.Elem(), indicatePlain, extType) + el, err := serializeTypeAsPropertyType(t.Elem(), indicatePlain, extType) if err != nil { return schema.TypeSpec{}, err } @@ -191,7 +174,7 @@ func serializeTypeAsPropertyType( AdditionalProperties: &el, }, nil case reflect.Array, reflect.Slice: - el, err := serializeTypeAsPropertyType(ctx, t.Elem(), indicatePlain, extType) + el, err := serializeTypeAsPropertyType(t.Elem(), indicatePlain, extType) if err != nil { return schema.TypeSpec{}, err } @@ -264,7 +247,7 @@ func underlyingType(t reflect.Type) (reflect.Type, bool, error) { return t, isOutputType || isInputType, nil } -func propertyListFromType(ctx context.Context, typ reflect.Type, indicatePlain bool) ( +func propertyListFromType(typ reflect.Type, indicatePlain bool) ( props map[string]schema.PropertySpec, required []string, err error) { for typ.Kind() == reflect.Pointer { typ = typ.Elem() @@ -284,7 +267,7 @@ func propertyListFromType(ctx context.Context, typ reflect.Type, indicatePlain b if tags.Internal { continue } - serialized, err := serializeTypeAsPropertyType(ctx, fieldType, indicatePlain, tags.ExplicitRef) + serialized, err := serializeTypeAsPropertyType(fieldType, indicatePlain, tags.ExplicitRef) if err != nil { return nil, nil, fmt.Errorf("invalid type '%s' on '%s.%s': %w", fieldType, typ, field.Name, err) } diff --git a/infer/types.go b/infer/types.go index 1b85381b..7a7b9888 100644 --- a/infer/types.go +++ b/infer/types.go @@ -15,7 +15,6 @@ package infer import ( - "context" "errors" "fmt" "reflect" @@ -246,7 +245,7 @@ func crawlTypes[T any](crawler Crawler) error { } // registerTypes recursively examines fields of T, calling reg on the schematized type when appropriate. -func registerTypes[T any](ctx context.Context, reg schema.RegisterDerivativeType) error { +func registerTypes[T any](reg schema.RegisterDerivativeType) error { crawler := func( t reflect.Type, isReference bool, info *introspect.FieldTag, parent, field string, @@ -286,7 +285,7 @@ func registerTypes[T any](ctx context.Context, reg schema.RegisterDerivativeType return false, err } if t.Kind() == reflect.Struct { - spec, err := objectSchema(ctx, t) + spec, err := objectSchema(t) if err != nil { return false, err } diff --git a/infer/types/asset.go b/infer/types/asset.go index 759642a6..d7ba6beb 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -21,7 +21,7 @@ const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" // AssetOrArchive is a union type that can represent either an Asset or an Archive. // Setting both fields to non-nil values is an error. -// This type exists to accomomdate the semantics of the core Pulumi SDK's Asset type, +// This type exists to accomodate the semantics of the core Pulumi SDK's Asset type, // which is also a union of Asset and Archive. type AssetOrArchive struct { Asset *resource.Asset `pulumi:"a9e28acb8ab501f883219e7c9f624fb6,optional"` diff --git a/infer/types_test.go b/infer/types_test.go index 1b2947b3..00ab58df 100644 --- a/infer/types_test.go +++ b/infer/types_test.go @@ -15,7 +15,6 @@ package infer import ( - "context" "reflect" "testing" @@ -163,7 +162,7 @@ func TestCrawlTypes(t *testing.T) { m[typ.String()] = spec return true } - err := registerTypes[Foo](context.Background(), reg) + err := registerTypes[Foo](reg) assert.NoError(t, err) assert.Equal(t, @@ -229,10 +228,10 @@ func TestReservedFields(t *testing.T) { reg := func(tokens.Type, pschema.ComplexTypeSpec) bool { return true } - err := registerTypes[outer](context.Background(), reg) + err := registerTypes[outer](reg) assert.NoError(t, err, "id isn't reserved on nested fields") - err = registerTypes[inner](context.Background(), reg) + err = registerTypes[inner](reg) assert.ErrorContains(t, err, `"id" is a reserved field name`) } @@ -248,7 +247,7 @@ func noOpRegister() schema.RegisterDerivativeType { func registerOk[T any]() func(t *testing.T) { return func(t *testing.T) { t.Parallel() - err := registerTypes[T](context.Background(), noOpRegister()) + err := registerTypes[T](noOpRegister()) assert.NoError(t, err) } } @@ -269,7 +268,7 @@ func TestInvalidOptionalProperty(t *testing.T) { t.Run("invalid optional enum", func(t *testing.T) { t.Parallel() - err := registerTypes[invalidContainsOptionalEnum](context.Background(), noOpRegister()) + err := registerTypes[invalidContainsOptionalEnum](noOpRegister()) var actual optionalNeedsPointerError if assert.ErrorAs(t, err, &actual) { @@ -291,7 +290,7 @@ func TestInvalidOptionalProperty(t *testing.T) { t.Run("invalid optional struct", func(t *testing.T) { t.Parallel() - err := registerTypes[invalidContainsOptionalStruct](context.Background(), noOpRegister()) + err := registerTypes[invalidContainsOptionalStruct](noOpRegister()) var actual optionalNeedsPointerError if assert.ErrorAs(t, err, &actual) { diff --git a/middleware/schema/schema.go b/middleware/schema/schema.go index cd1f07b4..33258013 100644 --- a/middleware/schema/schema.go +++ b/middleware/schema/schema.go @@ -47,7 +47,7 @@ type Resource interface { // Return the Resource's schema definition. The passed in function should be called on // types transitively referenced by the resource. See the documentation of // RegisterDerivativeType for more details. - GetSchema(context.Context, RegisterDerivativeType) (schema.ResourceSpec, error) + GetSchema(RegisterDerivativeType) (schema.ResourceSpec, error) } // A Function that can generate its own schema definition. @@ -57,7 +57,7 @@ type Function interface { // Return the Function's schema definition. The passed in function should be called on // types transitively referenced by the function. See the documentation of // RegisterDerivativeType for more details. - GetSchema(context.Context, RegisterDerivativeType) (schema.FunctionSpec, error) + GetSchema(RegisterDerivativeType) (schema.FunctionSpec, error) } type cache struct { @@ -309,13 +309,13 @@ func (s *state) generateSchema(ctx context.Context) (schema.PackageSpec, error) pkg.Types[tkString] = renamePackage(t, info.PackageName, s.ModuleMap) return true } - errs := addElements(ctx, s.Resources, pkg.Resources, info.PackageName, registerDerivative, s.ModuleMap) - e := addElements(ctx, s.Invokes, pkg.Functions, info.PackageName, registerDerivative, s.ModuleMap) + errs := addElements(s.Resources, pkg.Resources, info.PackageName, registerDerivative, s.ModuleMap) + e := addElements(s.Invokes, pkg.Functions, info.PackageName, registerDerivative, s.ModuleMap) errs.Errors = append(errs.Errors, e.Errors...) if s.Provider != nil { _, prov, err := addElement[Resource, schema.ResourceSpec]( - ctx, info.PackageName, registerDerivative, s.ModuleMap, s.Provider) + info.PackageName, registerDerivative, s.ModuleMap, s.Provider) if err != nil { errs.Errors = append(errs.Errors, err) } @@ -333,15 +333,15 @@ func (s *state) generateSchema(ctx context.Context) (schema.PackageSpec, error) type canGetSchema[T any] interface { GetToken() (tokens.Type, error) - GetSchema(context.Context, RegisterDerivativeType) (T, error) + GetSchema(RegisterDerivativeType) (T, error) } -func addElements[T canGetSchema[S], S any](ctx context.Context, - els []T, m map[string]S, pkgName string, reg RegisterDerivativeType, +func addElements[T canGetSchema[S], S any](els []T, m map[string]S, + pkgName string, reg RegisterDerivativeType, modMap map[tokens.ModuleName]tokens.ModuleName) multierror.Error { errs := multierror.Error{} for _, f := range els { - tk, element, err := addElement[T, S](ctx, pkgName, reg, modMap, f) + tk, element, err := addElement[T, S](pkgName, reg, modMap, f) if err != nil { errs.Errors = append(errs.Errors, err) continue @@ -351,7 +351,7 @@ func addElements[T canGetSchema[S], S any](ctx context.Context, return errs } -func addElement[T canGetSchema[S], S any](ctx context.Context, pkgName string, reg RegisterDerivativeType, +func addElement[T canGetSchema[S], S any](pkgName string, reg RegisterDerivativeType, modMap map[tokens.ModuleName]tokens.ModuleName, f T) (tokens.Type, S, error) { var s S tk, err := f.GetToken() @@ -359,7 +359,7 @@ func addElement[T canGetSchema[S], S any](ctx context.Context, pkgName string, r return "", s, err } tk = assignTo(tk, pkgName, modMap) - fun, err := f.GetSchema(ctx, reg) + fun, err := f.GetSchema(reg) if err != nil { return "", s, fmt.Errorf("failed to get schema for '%s': %w", tk, err) } diff --git a/middleware/schema/schema_test.go b/middleware/schema/schema_test.go index c5af0de5..99c569fc 100644 --- a/middleware/schema/schema_test.go +++ b/middleware/schema/schema_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRenamePackage(t *testing.T) { +func TestRenamePacakge(t *testing.T) { t.Parallel() p := schema.ResourceSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ diff --git a/tests/schema_test.go b/tests/schema_test.go index c23d9268..a6ac3831 100644 --- a/tests/schema_test.go +++ b/tests/schema_test.go @@ -16,7 +16,6 @@ package tests import ( "bytes" - "context" "encoding/json" "testing" @@ -40,7 +39,7 @@ func (r *givenResource) GetToken() (tokens.Type, error) { return r.token, nil } -func (r *givenResource) GetSchema(context.Context, schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { +func (r *givenResource) GetSchema(f schema.RegisterDerivativeType) (pschema.ResourceSpec, error) { var s pschema.ResourceSpec s.Description = r.text return s, nil From cf2865520064933bb03ac716bee56a79544e0e6b Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Thu, 13 Jun 2024 09:45:59 +0200 Subject: [PATCH 16/20] lint typo --- infer/types/asset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infer/types/asset.go b/infer/types/asset.go index d7ba6beb..02830cb5 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -21,7 +21,7 @@ const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" // AssetOrArchive is a union type that can represent either an Asset or an Archive. // Setting both fields to non-nil values is an error. -// This type exists to accomodate the semantics of the core Pulumi SDK's Asset type, +// This type exists to accommodate the semantics of the core Pulumi SDK's Asset type, // which is also a union of Asset and Archive. type AssetOrArchive struct { Asset *resource.Asset `pulumi:"a9e28acb8ab501f883219e7c9f624fb6,optional"` From 134c0386a740201d060ddc978198927432ceb10f Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Thu, 13 Jun 2024 09:46:40 +0200 Subject: [PATCH 17/20] copyright 2024 --- infer/types/asset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infer/types/asset.go b/infer/types/asset.go index 02830cb5..b6fd6ddb 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -1,4 +1,4 @@ -// Copyright 2022, Pulumi Corporation. +// Copyright 2024, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 6433bc5b106cb85a596ccebc23e0144470f46fc8 Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Thu, 13 Jun 2024 09:48:40 +0200 Subject: [PATCH 18/20] Comment about #243 warn about resource.Asset --- infer/schema.go | 1 + 1 file changed, 1 insertion(+) diff --git a/infer/schema.go b/infer/schema.go index 90e12e4b..14616a86 100644 --- a/infer/schema.go +++ b/infer/schema.go @@ -119,6 +119,7 @@ func serializeTypeAsPropertyType( t = t.Elem() } if t == reflect.TypeOf(resource.Asset{}) { + // Provider authors should not be using resource.Asset directly, but rather types.AssetOrArchive. #243 return schema.TypeSpec{ Ref: "pulumi.json#/Asset", }, nil From c44c52f4d27f29002bdd989f7744748bf369b13f Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Thu, 13 Jun 2024 09:52:52 +0200 Subject: [PATCH 19/20] comment cleanup --- infer/internal/ende/ende.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 735ff254..0f716ff0 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -313,10 +313,6 @@ func (e *ende) Encode(src any) (resource.PropertyMap, mapper.MappingError) { return nil, err } - // If we see the magic signatures meaning "asset" or "archive", it's an AssetOrArchive and need - // to pull the actual, inner asset or archive out of the object and discard the outer - // AssetOrArchive. See #237 for more background. - // The literal magic signatures are from pulumi/pulumi and are not exported by the SDK. m := resource.NewPropertyValueRepl(props, nil, // keys are not changed flattenAssets) @@ -358,6 +354,7 @@ const ( ) // flattenAssets pulls out assets and archives from AssetOrArchive objects. +// See #237 for more background. // From: // // types.AssetSignature: From 742f24b13cc4028e659a82b889a311eaae82f49f Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Thu, 13 Jun 2024 12:17:44 +0200 Subject: [PATCH 20/20] Move signature constants to ende where they are used --- infer/internal/ende/ende.go | 10 ++++++++-- infer/internal/ende/ende_test.go | 6 +++--- infer/types/asset.go | 3 --- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/infer/internal/ende/ende.go b/infer/internal/ende/ende.go index 0f716ff0..d006f92e 100644 --- a/infer/internal/ende/ende.go +++ b/infer/internal/ende/ende.go @@ -26,6 +26,12 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper" ) +// A unique key for use for assets in the AssetOrArchive union type. +const AssetSignature = "a9e28acb8ab501f883219e7c9f624fb6" + +// A unique key for use for archives in the AssetOrArchive union type. +const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" + type Encoder struct{ *ende } // Decode a property map to a `pulumi:"x"` annotated struct. @@ -371,8 +377,8 @@ func flattenAssets(a any) (resource.PropertyValue, bool) { return resource.NewNullProperty(), false } - rawAsset, hasAsset := aMap[types.AssetSignature] - rawArchive, hasArchive := aMap[types.ArchiveSignature] + rawAsset, hasAsset := aMap[AssetSignature] + rawArchive, hasArchive := aMap[ArchiveSignature] if hasAsset && hasArchive { panic(`Encountered both an asset and an archive in the same AssetOrArchive. This diff --git a/infer/internal/ende/ende_test.go b/infer/internal/ende/ende_test.go index 5b0abca1..cc8243c0 100644 --- a/infer/internal/ende/ende_test.go +++ b/infer/internal/ende/ende_test.go @@ -187,7 +187,7 @@ func TestDecodeAssets(t *testing.T) { mNew := simplify(f) - assertDecodedFoo(types.AssetSignature, mNew) + assertDecodedFoo(AssetSignature, mNew) }) t.Run("archive", func(t *testing.T) { @@ -202,7 +202,7 @@ func TestDecodeAssets(t *testing.T) { mNew := simplify(f) - assertDecodedFoo(types.ArchiveSignature, mNew) + assertDecodedFoo(ArchiveSignature, mNew) }) type bar struct { @@ -224,7 +224,7 @@ func TestDecodeAssets(t *testing.T) { require.True(t, mNew["foo"].IsObject()) inner := mNew["foo"].ObjectValue() - assertDecodedFoo(types.AssetSignature, inner) + assertDecodedFoo(AssetSignature, inner) }) } diff --git a/infer/types/asset.go b/infer/types/asset.go index b6fd6ddb..1bf45aa2 100644 --- a/infer/types/asset.go +++ b/infer/types/asset.go @@ -16,9 +16,6 @@ package types import "github.com/pulumi/pulumi/sdk/v3/go/common/resource" -const AssetSignature = "a9e28acb8ab501f883219e7c9f624fb6" -const ArchiveSignature = "195f3948f6769324d4661e1e245f3a4d" - // AssetOrArchive is a union type that can represent either an Asset or an Archive. // Setting both fields to non-nil values is an error. // This type exists to accommodate the semantics of the core Pulumi SDK's Asset type,