diff --git a/Gopkg.lock b/Gopkg.lock index ef69ce14db..fc0079d84d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -4,8 +4,14 @@ [[projects]] name = "cloud.google.com/go" packages = ["compute/metadata"] - revision = "767c40d6a2e058483c25fa193e963a22da17236d" - version = "v0.18.0" + revision = "0ebda48a7f143b1cce9eb37a8c1106ac762a3430" + version = "v0.34.0" + +[[projects]] + name = "contrib.go.opencensus.io/exporter/ocagent" + packages = ["."] + revision = "00af367e65149ff1f2f4b93bbfbb84fd9297170d" + version = "v0.2.0" [[projects]] branch = "master" @@ -22,9 +28,12 @@ "autorest", "autorest/adal", "autorest/azure", - "autorest/date" + "autorest/date", + "logger", + "tracing" ] - revision = "ab5671379918d9af294b6f0e3d8aaa98c829416d" + revision = "be17756531f50014397912b7aa557ec335e39b98" + version = "v11.3.0" [[projects]] branch = "master" @@ -50,18 +59,18 @@ packages = ["."] revision = "de5bf2ad457846296e2031421a34e2568e304e35" +[[projects]] + name = "github.com/Sirupsen/logrus" + packages = ["."] + revision = "e1e72e9de974bd926e5c56f83753fba2df402ce5" + version = "v1.3.0" + [[projects]] name = "github.com/ahmetb/go-linq" packages = ["."] revision = "7e71c124c1f903df09ca08678f145f68b45d125e" version = "v3.0.0" -[[projects]] - name = "github.com/apache/thrift" - packages = ["lib/go/thrift"] - revision = "b2a4d4ae21c789b689dd162deb819665567f481c" - version = "0.10.0" - [[projects]] name = "github.com/aws/aws-sdk-go" packages = [ @@ -74,15 +83,24 @@ "aws/credentials", "aws/credentials/ec2rolecreds", "aws/credentials/endpointcreds", + "aws/credentials/processcreds", "aws/credentials/stscreds", + "aws/csm", "aws/defaults", "aws/ec2metadata", "aws/endpoints", "aws/request", "aws/session", "aws/signer/v4", + "internal/ini", + "internal/s3err", + "internal/sdkio", + "internal/sdkrand", + "internal/sdkuri", "internal/shareddefaults", "private/protocol", + "private/protocol/eventstream", + "private/protocol/eventstream/eventstreamapi", "private/protocol/json/jsonutil", "private/protocol/jsonrpc", "private/protocol/query", @@ -94,20 +112,8 @@ "service/s3", "service/sts" ] - revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28" - version = "v1.12.67" - -[[projects]] - branch = "master" - name = "github.com/beorn7/perks" - packages = ["quantile"] - revision = "3a771d992973f24aa725d07868b467d1ddfceafb" - -[[projects]] - name = "github.com/bgentry/speakeasy" - packages = ["."] - revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd" - version = "v0.1.0" + revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2" + version = "v1.16.15" [[projects]] name = "github.com/blang/semver" @@ -118,7 +124,19 @@ [[projects]] name = "github.com/cbroglie/mustache" packages = ["."] - revision = "v1.0.0" + revision = "eb931a9d20042e51d8a3a9ad5a9ba9bc8282c564" + version = "v1.0.1" + +[[projects]] + name = "github.com/census-instrumentation/opencensus-proto" + packages = [ + "gen-go/agent/common/v1", + "gen-go/agent/trace/v1", + "gen-go/resource/v1", + "gen-go/trace/v1" + ] + revision = "7f2434bc10da710debe5c4315ed6d4df454b4024" + version = "v0.1.0" [[projects]] branch = "master" @@ -129,59 +147,29 @@ [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] name = "github.com/dgrijalva/jwt-go" packages = ["."] - revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29" - version = "v3.1.0" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" [[projects]] name = "github.com/djherbis/times" packages = ["."] - revision = "95292e44976d1217cf3611dc7c8d9466877d3ed5" - version = "v1.0.1" - -[[projects]] - name = "github.com/docker/distribution" - packages = [ - "digestset", - "reference" - ] - revision = "83389a148052d74ac602f5f1d62f86ff2f3c4aa5" + revision = "847c5208d8924cea0acea3376ff62aede93afe39" + version = "v1.2.0" [[projects]] name = "github.com/docker/docker" packages = [ - "api/types", - "api/types/blkiodev", - "api/types/container", - "api/types/filters", - "api/types/mount", - "api/types/network", - "api/types/registry", - "api/types/strslice", - "api/types/swarm", - "api/types/swarm/runtime", - "api/types/versions", "pkg/term", "pkg/term/windows" ] - revision = "04864cb3cb526ddeee70dc6e17a6c7802ef91a0b" - -[[projects]] - name = "github.com/docker/go-connections" - packages = ["nat"] - revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" - version = "v0.3.0" - -[[projects]] - name = "github.com/docker/go-units" - packages = ["."] - revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" - version = "v0.3.3" + revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" + version = "v1.13.1" [[projects]] branch = "master" @@ -190,7 +178,7 @@ ".", "spdy" ] - revision = "bc6354cbbc295e925e4c611ffe90c1f287ee54db" + revision = "6480d4af844c189cf5dd913db24ddd339d3a4f85" [[projects]] name = "github.com/dustin/go-humanize" @@ -207,13 +195,8 @@ ".", "log" ] - revision = "2dd44038f0b95ae693b266c5f87593b5d2fdd78d" - version = "v2.5.0" - -[[projects]] - name = "github.com/emicklei/go-restful-swagger12" - packages = ["."] - revision = "7524189396c68dc4b04d53852f9edc00f816b123" + revision = "3eb9738c1697594ea6e71a7156a9bb32ed216cf0" + version = "v2.8.0" [[projects]] name = "github.com/emirpasic/gods" @@ -225,8 +208,8 @@ "trees/binaryheap", "utils" ] - revision = "f6c17b524822278a87e3b3bd809fec33b51f5b46" - version = "v1.9.0" + revision = "1615341f118ae12f353cc8a983f35b584342c9b3" + version = "v1.12.0" [[projects]] name = "github.com/evanphx/json-patch" @@ -240,12 +223,6 @@ packages = ["."] revision = "d6023ce2651d8eafb5c75bb0c7167536102ec9f5" -[[projects]] - branch = "master" - name = "github.com/fatih/camelcase" - packages = ["."] - revision = "44e46d280b43ec1531bb25252440e34f1b800b65" - [[projects]] name = "github.com/ghodss/yaml" packages = ["."] @@ -253,33 +230,28 @@ version = "v1.0.0" [[projects]] - name = "github.com/go-ini/ini" - packages = ["."] - revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a" - version = "v1.32.0" - -[[projects]] - branch = "master" name = "github.com/go-openapi/jsonpointer" packages = ["."] - revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2" + revision = "ef5f0afec364d3b9396b7b77b43dbe26bf1f8004" + version = "v0.18.0" [[projects]] - branch = "master" name = "github.com/go-openapi/jsonreference" packages = ["."] - revision = "3fb327e6747da3043567ee86abd02bb6376b6be2" + revision = "8483a886a90412cd6858df4ea3483dce9c8e35a3" + version = "v0.18.0" [[projects]] name = "github.com/go-openapi/spec" packages = ["."] - revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5" + revision = "5b6cdde3200976e3ecceb2868706ee39b6aff3e4" + version = "v0.18.0" [[projects]] - branch = "master" name = "github.com/go-openapi/swag" packages = ["."] - revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5" + revision = "1d29f06aebd59ccdf11ae04aa0334ded96e2d909" + version = "v0.18.0" [[projects]] name = "github.com/gogo/protobuf" @@ -287,8 +259,8 @@ "proto", "sortkeys" ] - revision = "342cbe0a04158f6dcb03ca0079991a51a4248c02" - version = "v0.5" + revision = "4cbf7e384e768b4e01799441fdf2a706a5635ae7" + version = "v1.2.0" [[projects]] branch = "master" @@ -296,12 +268,6 @@ packages = ["."] revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" -[[projects]] - branch = "master" - name = "github.com/golang/groupcache" - packages = ["lru"] - revision = "24b0969c4cb722950103eed87108c8d291a8df00" - [[projects]] name = "github.com/golang/protobuf" packages = [ @@ -312,16 +278,17 @@ "ptypes/duration", "ptypes/empty", "ptypes/struct", - "ptypes/timestamp" + "ptypes/timestamp", + "ptypes/wrappers" ] - revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" - version = "v1.1.0" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" [[projects]] branch = "master" name = "github.com/google/btree" packages = ["."] - revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4" + revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" [[projects]] branch = "master" @@ -336,7 +303,8 @@ "compiler", "extensions" ] - revision = "0c5108395e2debce0d731cf0287ddf7242066aba" + revision = "7c663266750e7d82587642f65e60bc4083f1f84e" + version = "v0.2.0" [[projects]] branch = "master" @@ -350,7 +318,7 @@ "openstack/utils", "pagination" ] - revision = "c51806d07f1d12dc4786b792fb9610679e96f549" + revision = "f27ceddc323ff01fdd909ac8377fb06b12db7f4f" [[projects]] branch = "master" @@ -359,45 +327,31 @@ ".", "diskcache" ] - revision = "9cad4c3443a7200dd6400aef47183728de563a38" + revision = "c63ab54fda8f77302f8d414e19933f2b6026a089" [[projects]] branch = "master" name = "github.com/grpc-ecosystem/grpc-opentracing" packages = ["go/otgrpc"] - revision = "0e7658f8ee99ee5aa683e2a032b8880091b7a055" + revision = "8e809c8a86450a29b90dcc9efbf062d0fe6d9746" [[projects]] - branch = "master" name = "github.com/hashicorp/errwrap" packages = ["."] - revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55" + revision = "8a6fb523712970c966eefc6b39ed2c5e74880354" + version = "v1.0.0" [[projects]] name = "github.com/hashicorp/go-multierror" packages = ["."] - revision = "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5" - -[[projects]] - branch = "master" - name = "github.com/hashicorp/golang-lru" - packages = [ - ".", - "simplelru" - ] - revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" - -[[projects]] - branch = "master" - name = "github.com/howeyc/gopass" - packages = ["."] - revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" + revision = "886a7fbe3eb1c874d46f623bfa70af45f425b3d1" + version = "v1.0.0" [[projects]] name = "github.com/imdario/mergo" packages = ["."] - revision = "163f41321a19dd09362d4c63cc2489db2015f1f4" - version = "0.3.2" + revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" + version = "v0.3.6" [[projects]] name = "github.com/inconshreveable/mousetrap" @@ -420,13 +374,13 @@ [[projects]] name = "github.com/jmespath/go-jmespath" packages = ["."] - revision = "0b12d6b5" + revision = "c2b33e84" [[projects]] name = "github.com/json-iterator/go" packages = ["."] - revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4" - version = "1.1.3" + revision = "1624edc4454b8682399def8740d46db5e4362ba4" + version = "v1.1.5" [[projects]] branch = "master" @@ -437,8 +391,14 @@ [[projects]] name = "github.com/kevinburke/ssh_config" packages = ["."] - revision = "9fc7bb800b555d63157c65a904c86a2cc7b4e795" - version = "0.4" + revision = "81db2a75821ed34e682567d48be488a1c3121088" + version = "0.5" + +[[projects]] + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" [[projects]] branch = "master" @@ -448,7 +408,7 @@ "jlexer", "jwriter" ] - revision = "8b799c424f57fa123fc63a99d6383bc6e4c02578" + revision = "60711f1a8329503b04e1c88535f419d0bb440bff" [[projects]] name = "github.com/mattn/go-colorable" @@ -462,12 +422,6 @@ revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" version = "v0.0.4" -[[projects]] - name = "github.com/matttproud/golang_protobuf_extensions" - packages = ["pbutil"] - revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" - version = "v1.0.0" - [[projects]] branch = "master" name = "github.com/mgutz/ansi" @@ -475,10 +429,10 @@ revision = "9520e82c474b0a04dd04f8a40959027271bab992" [[projects]] - branch = "master" name = "github.com/mitchellh/go-homedir" packages = ["."] - revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66" + revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" + version = "v1.0.0" [[projects]] branch = "master" @@ -487,10 +441,10 @@ revision = "4fdf99ab29366514c69ccccddab5dc58b8d84062" [[projects]] - branch = "master" name = "github.com/mitchellh/go-wordwrap" packages = ["."] - revision = "ad45545899c7b13c020ea92b2072220eefad42b8" + revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c" + version = "v1.0.0" [[projects]] name = "github.com/modern-go/concurrent" @@ -501,23 +455,8 @@ [[projects]] name = "github.com/modern-go/reflect2" packages = ["."] - revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f" - version = "1.0.0" - -[[projects]] - name = "github.com/opencontainers/go-digest" - packages = ["."] - revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" - version = "v1.0.0-rc1" - -[[projects]] - name = "github.com/opencontainers/image-spec" - packages = [ - "specs-go", - "specs-go/v1" - ] - revision = "d60099175f88c47cd379c4738d158884749ed235" - version = "v1.0.1" + revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" + version = "1.0.1" [[projects]] name = "github.com/opentracing/opentracing-go" @@ -529,12 +468,6 @@ revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" -[[projects]] - name = "github.com/pborman/uuid" - packages = ["."] - revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53" - version = "v1.1" - [[projects]] name = "github.com/pelletier/go-buffruneio" packages = ["."] @@ -556,8 +489,8 @@ [[projects]] name = "github.com/pkg/errors" packages = ["."] - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" + revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" + version = "v0.8.1" [[projects]] name = "github.com/pmezard/go-difflib" @@ -565,39 +498,6 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" -[[projects]] - name = "github.com/prometheus/client_golang" - packages = ["prometheus"] - revision = "c5b7fccd204277076155f10851dad72b76a49317" - version = "v0.8.0" - -[[projects]] - branch = "master" - name = "github.com/prometheus/client_model" - packages = ["go"] - revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" - -[[projects]] - branch = "master" - name = "github.com/prometheus/common" - packages = [ - "expfmt", - "internal/bitbucket.org/ww/goautoneg", - "model" - ] - revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" - -[[projects]] - branch = "master" - name = "github.com/prometheus/procfs" - packages = [ - ".", - "internal/util", - "nfs", - "xfs" - ] - revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e" - [[projects]] branch = "master" name = "github.com/pulumi/pulumi" @@ -641,7 +541,7 @@ "pkg/workspace", "sdk/proto/go" ] - revision = "466f92dfb13e7d7c14be85acc8e7ce954f4edd21" + revision = "8b52e480edac268f35b4a1787efdacac25263668" [[projects]] branch = "master" @@ -652,8 +552,8 @@ [[projects]] name = "github.com/russross/blackfriday" packages = ["."] - revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5" - version = "v1.5.1" + revision = "d3b5b032dc8e8927d31a5071b56e14c89f045135" + version = "v2.0.1" [[projects]] name = "github.com/satori/go.uuid" @@ -668,22 +568,22 @@ version = "v1.0.0" [[projects]] - name = "github.com/sirupsen/logrus" + name = "github.com/shurcooL/sanitized_anchor_name" packages = ["."] - revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba" - version = "v1.0.4" + revision = "7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615" + version = "v1.0.0" [[projects]] - branch = "master" name = "github.com/spf13/cobra" packages = ["."] - revision = "f91529fc609202eededff4de2dc0ba2f662240a3" + revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" + version = "v0.0.3" [[projects]] name = "github.com/spf13/pflag" packages = ["."] - revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" - version = "v1.0.0" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" [[projects]] name = "github.com/src-d/gcfg" @@ -693,13 +593,14 @@ "token", "types" ] - revision = "f187355171c936ac84a82793659ebb4936bc1c23" - version = "v1.3.0" + revision = "1ac3a1ac202429a54835fe8408a92880156b489d" + version = "v1.4.0" [[projects]] name = "github.com/stretchr/testify" packages = ["assert"] - revision = "f6abca593680b2315d2075e0f5e2a9751e3f431a" + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + version = "v1.3.0" [[projects]] branch = "master" @@ -713,7 +614,9 @@ ".", "internal/baggage", "internal/spanlog", + "internal/throttler", "log", + "thrift", "thrift-gen/agent", "thrift-gen/jaeger", "thrift-gen/sampling", @@ -721,14 +624,14 @@ "transport/zipkin", "utils" ] - revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7" - version = "v2.11.2" + revision = "1a782e2da844727691fef1757c72eb190c2909f0" + version = "v2.15.0" [[projects]] name = "github.com/uber/jaeger-lib" packages = ["metrics"] - revision = "7f95f4f7e80028096410abddaae2556e4c61b59f" - version = "v1.3.1" + revision = "ed3a127ec5fef7ae9ea95b01b542c47fbd999ce5" + version = "v1.5.0" [[projects]] name = "github.com/xanzy/ssh-agent" @@ -739,7 +642,8 @@ [[projects]] name = "github.com/yudai/gojsondiff" packages = ["."] - revision = "9209d1532c51cabe0439993586a71c207b09a0ac" + revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" + version = "1.0.0" [[projects]] branch = "master" @@ -747,6 +651,28 @@ packages = ["."] revision = "ecda9a501e8220fae3b4b600c3db4b0ba22cfc68" +[[projects]] + name = "go.opencensus.io" + packages = [ + ".", + "exemplar", + "internal", + "internal/tagencoding", + "plugin/ochttp", + "plugin/ochttp/propagation/b3", + "plugin/ochttp/propagation/tracecontext", + "stats", + "stats/internal", + "stats/view", + "tag", + "trace", + "trace/internal", + "trace/propagation", + "trace/tracestate" + ] + revision = "b7bf3cdb64150a8c8c53b769fdeb2ba581bd4d4b" + version = "v0.18.0" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -756,6 +682,7 @@ "ed25519", "ed25519/internal/edwards25519", "internal/chacha20", + "internal/subtle", "openpgp", "openpgp/armor", "openpgp/elgamal", @@ -769,21 +696,22 @@ "ssh/knownhosts", "ssh/terminal" ] - revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b" + revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" [[projects]] + branch = "master" name = "golang.org/x/net" packages = [ "context", "context/ctxhttp", + "http/httpguts", "http2", "http2/hpack", "idna", "internal/timeseries", - "lex/httplex", "trace" ] - revision = "a04bdaca5b32abe1c069418fb7088ae607de5bd0" + revision = "395948e2f546cb82afa9e1f6d1a6e87849b9af1d" [[projects]] branch = "master" @@ -795,7 +723,13 @@ "jws", "jwt" ] - revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067" + revision = "d668ce993890a79bda886613ee587a69dd5da7a6" + +[[projects]] + branch = "master" + name = "golang.org/x/sync" + packages = ["semaphore"] + revision = "37e7f081c4d4c64e13b10787722085407fe5d15f" [[projects]] branch = "master" @@ -804,10 +738,9 @@ "unix", "windows" ] - revision = "af50095a40f9041b3b38960738837185c26e9419" + revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" [[projects]] - branch = "master" name = "golang.org/x/text" packages = [ "collate", @@ -832,13 +765,20 @@ "unicode/rangetable", "width" ] - revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" [[projects]] branch = "master" name = "golang.org/x/time" packages = ["rate"] - revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" + revision = "85acf8d2951cb2a3bde7632f9ff273ef0379bcbd" + +[[projects]] + name = "google.golang.org/api" + packages = ["support/bundler"] + revision = "19e022d8cf43ce81f046bae8cc18c5397cc7732f" + version = "v0.1.0" [[projects]] name = "google.golang.org/appengine" @@ -854,26 +794,39 @@ "internal/urlfetch", "urlfetch" ] - revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" - version = "v1.0.0" + revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1" + version = "v1.4.0" [[projects]] branch = "master" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] - revision = "14790a1795ea4c3387aaf24aed670ddb51f18292" + revision = "ae2f86662275e140f395167f1dab7081a5bd5fa8" [[projects]] name = "google.golang.org/grpc" packages = [ ".", "balancer", + "balancer/base", + "balancer/roundrobin", + "binarylog/grpc_binarylog_v1", "codes", "connectivity", "credentials", - "grpclb/grpc_lb_v1/messages", + "credentials/internal", + "encoding", + "encoding/proto", "grpclog", "internal", + "internal/backoff", + "internal/binarylog", + "internal/channelz", + "internal/envconfig", + "internal/grpcrand", + "internal/grpcsync", + "internal/syscall", + "internal/transport", "keepalive", "metadata", "naming", @@ -881,13 +834,14 @@ "reflection", "reflection/grpc_reflection_v1alpha", "resolver", + "resolver/dns", + "resolver/passthrough", "stats", "status", - "tap", - "transport" + "tap" ] - revision = "5ffe3083946d5603a0578721101dc8165b1d5b5f" - version = "v1.7.2" + revision = "df014850f6dee74ba2fc94874043a9f3f75fbfd8" + version = "v1.17.0" [[projects]] name = "gopkg.in/AlecAivazis/survey.v1" @@ -896,25 +850,14 @@ "core", "terminal" ] - revision = "f30c5d1830c892f533140f29a1de89141dc217f5" - version = "v1.6.2" + revision = "e205523512c83e9236698dbe7eb9649ec56e80ca" + version = "v1.7.1" [[projects]] name = "gopkg.in/inf.v0" packages = ["."] - revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4" - version = "v0.9.0" - -[[projects]] - name = "gopkg.in/square/go-jose.v2" - packages = [ - ".", - "cipher", - "json", - "jwt" - ] - revision = "76dd09796242edb5b897103a75df2645c028c960" - version = "v2.1.6" + revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" + version = "v0.9.1" [[projects]] name = "gopkg.in/src-d/go-billy.v4" @@ -925,8 +868,8 @@ "osfs", "util" ] - revision = "df053870ae7070b0350624ba5a22161ba3796cc0" - version = "v4.1.1" + revision = "982626487c60a5252e7d0b695ca23fb0fa2fd670" + version = "v4.3.0" [[projects]] name = "gopkg.in/src-d/go-git.v4" @@ -972,8 +915,8 @@ "utils/merkletrie/internal/frame", "utils/merkletrie/noder" ] - revision = "b23570073eaee3489e5e3d666f22ba5cbeb53243" - version = "v4.4.1" + revision = "3dbfb89e0f5bce0008724e547b999fe3af9f60db" + version = "v4.8.1" [[projects]] name = "gopkg.in/warnings.v0" @@ -982,10 +925,10 @@ version = "v0.1.2" [[projects]] - branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" [[projects]] name = "k8s.io/api" @@ -996,16 +939,19 @@ "apps/v1", "apps/v1beta1", "apps/v1beta2", + "auditregistration/v1alpha1", "authentication/v1", "authentication/v1beta1", "authorization/v1", "authorization/v1beta1", "autoscaling/v1", "autoscaling/v2beta1", + "autoscaling/v2beta2", "batch/v1", "batch/v1beta1", "batch/v2alpha1", "certificates/v1beta1", + "coordination/v1beta1", "core/v1", "events/v1beta1", "extensions/v1beta1", @@ -1016,20 +962,16 @@ "rbac/v1alpha1", "rbac/v1beta1", "scheduling/v1alpha1", + "scheduling/v1beta1", "settings/v1alpha1", "storage/v1", "storage/v1alpha1", "storage/v1beta1" ] - revision = "kubernetes-1.10.2" - -[[projects]] - branch = "master" - name = "k8s.io/apiextensions-apiserver" - packages = ["pkg/features"] - revision = "bd76ce7dd8e65e8c2197803a1e64869ca5905309" + revision = "kubernetes-1.13.0" [[projects]] + branch = "release-1.13" name = "k8s.io/apimachinery" packages = [ "pkg/api/equality", @@ -1037,12 +979,9 @@ "pkg/api/meta", "pkg/api/resource", "pkg/api/validation", - "pkg/apimachinery", - "pkg/apimachinery/announced", - "pkg/apimachinery/registered", - "pkg/apis/meta/internalversion", "pkg/apis/meta/v1", "pkg/apis/meta/v1/unstructured", + "pkg/apis/meta/v1/unstructured/unstructuredscheme", "pkg/apis/meta/v1/validation", "pkg/apis/meta/v1beta1", "pkg/conversion", @@ -1059,10 +998,7 @@ "pkg/runtime/serializer/versioning", "pkg/selection", "pkg/types", - "pkg/util/cache", "pkg/util/clock", - "pkg/util/diff", - "pkg/util/duration", "pkg/util/errors", "pkg/util/framer", "pkg/util/httpstream", @@ -1071,16 +1007,14 @@ "pkg/util/json", "pkg/util/jsonmergepatch", "pkg/util/mergepatch", + "pkg/util/naming", "pkg/util/net", - "pkg/util/rand", "pkg/util/remotecommand", "pkg/util/runtime", "pkg/util/sets", "pkg/util/strategicpatch", - "pkg/util/uuid", "pkg/util/validation", "pkg/util/validation/field", - "pkg/util/wait", "pkg/util/yaml", "pkg/version", "pkg/watch", @@ -1088,69 +1022,31 @@ "third_party/forked/golang/netutil", "third_party/forked/golang/reflect" ] - revision = "302974c03f7e50f16561ba237db776ab93594ef6" - version = "kubernetes-1.10.2" + revision = "2b1284ed4c93a43499e781493253e2ac5959c4fd" [[projects]] - name = "k8s.io/apiserver" + branch = "master" + name = "k8s.io/cli-runtime" packages = [ - "pkg/apis/audit", - "pkg/authentication/authenticator", - "pkg/authentication/serviceaccount", - "pkg/authentication/user", - "pkg/endpoints/request", - "pkg/features", - "pkg/util/feature", - "pkg/util/flag" + "pkg/genericclioptions", + "pkg/genericclioptions/printers", + "pkg/genericclioptions/resource", + "pkg/kustomize/k8sdeps", + "pkg/kustomize/k8sdeps/configmapandsecret", + "pkg/kustomize/k8sdeps/kunstruct", + "pkg/kustomize/k8sdeps/transformer", + "pkg/kustomize/k8sdeps/transformer/hash", + "pkg/kustomize/k8sdeps/transformer/patch", + "pkg/kustomize/k8sdeps/validator" ] - revision = "0841753fc26e934b715ca7a83dced5bcb721245a" - version = "kubernetes-1.10.2" + revision = "31214e12222d5ddfec04d05163ba91b55336ef07" [[projects]] name = "k8s.io/client-go" packages = [ "discovery", + "discovery/cached", "dynamic", - "informers", - "informers/admissionregistration", - "informers/admissionregistration/v1alpha1", - "informers/admissionregistration/v1beta1", - "informers/apps", - "informers/apps/v1", - "informers/apps/v1beta1", - "informers/apps/v1beta2", - "informers/autoscaling", - "informers/autoscaling/v1", - "informers/autoscaling/v2beta1", - "informers/batch", - "informers/batch/v1", - "informers/batch/v1beta1", - "informers/batch/v2alpha1", - "informers/certificates", - "informers/certificates/v1beta1", - "informers/core", - "informers/core/v1", - "informers/events", - "informers/events/v1beta1", - "informers/extensions", - "informers/extensions/v1beta1", - "informers/internalinterfaces", - "informers/networking", - "informers/networking/v1", - "informers/policy", - "informers/policy/v1beta1", - "informers/rbac", - "informers/rbac/v1", - "informers/rbac/v1alpha1", - "informers/rbac/v1beta1", - "informers/scheduling", - "informers/scheduling/v1alpha1", - "informers/settings", - "informers/settings/v1alpha1", - "informers/storage", - "informers/storage/v1", - "informers/storage/v1alpha1", - "informers/storage/v1beta1", "kubernetes", "kubernetes/scheme", "kubernetes/typed/admissionregistration/v1alpha1", @@ -1158,16 +1054,19 @@ "kubernetes/typed/apps/v1", "kubernetes/typed/apps/v1beta1", "kubernetes/typed/apps/v1beta2", + "kubernetes/typed/auditregistration/v1alpha1", "kubernetes/typed/authentication/v1", "kubernetes/typed/authentication/v1beta1", "kubernetes/typed/authorization/v1", "kubernetes/typed/authorization/v1beta1", "kubernetes/typed/autoscaling/v1", "kubernetes/typed/autoscaling/v2beta1", + "kubernetes/typed/autoscaling/v2beta2", "kubernetes/typed/batch/v1", "kubernetes/typed/batch/v1beta1", "kubernetes/typed/batch/v2alpha1", "kubernetes/typed/certificates/v1beta1", + "kubernetes/typed/coordination/v1beta1", "kubernetes/typed/core/v1", "kubernetes/typed/events/v1beta1", "kubernetes/typed/extensions/v1beta1", @@ -1177,36 +1076,14 @@ "kubernetes/typed/rbac/v1alpha1", "kubernetes/typed/rbac/v1beta1", "kubernetes/typed/scheduling/v1alpha1", + "kubernetes/typed/scheduling/v1beta1", "kubernetes/typed/settings/v1alpha1", "kubernetes/typed/storage/v1", "kubernetes/typed/storage/v1alpha1", "kubernetes/typed/storage/v1beta1", - "listers/admissionregistration/v1alpha1", - "listers/admissionregistration/v1beta1", - "listers/apps/v1", - "listers/apps/v1beta1", - "listers/apps/v1beta2", - "listers/autoscaling/v1", - "listers/autoscaling/v2beta1", - "listers/batch/v1", - "listers/batch/v1beta1", - "listers/batch/v2alpha1", - "listers/certificates/v1beta1", - "listers/core/v1", - "listers/events/v1beta1", - "listers/extensions/v1beta1", - "listers/networking/v1", - "listers/policy/v1beta1", - "listers/rbac/v1", - "listers/rbac/v1alpha1", - "listers/rbac/v1beta1", - "listers/scheduling/v1alpha1", - "listers/settings/v1alpha1", - "listers/storage/v1", - "listers/storage/v1alpha1", - "listers/storage/v1beta1", "pkg/apis/clientauthentication", "pkg/apis/clientauthentication/v1alpha1", + "pkg/apis/clientauthentication/v1beta1", "pkg/version", "plugin/pkg/client/auth", "plugin/pkg/client/auth/azure", @@ -1216,6 +1093,7 @@ "plugin/pkg/client/auth/openstack", "rest", "rest/watch", + "restmapper", "scale", "scale/scheme", "scale/scheme/appsint", @@ -1226,224 +1104,100 @@ "scale/scheme/extensionsv1beta1", "third_party/forked/golang/template", "tools/auth", - "tools/cache", "tools/clientcmd", "tools/clientcmd/api", "tools/clientcmd/api/latest", "tools/clientcmd/api/v1", "tools/metrics", - "tools/pager", - "tools/record", "tools/reference", "tools/remotecommand", "transport", "transport/spdy", - "util/buffer", "util/cert", + "util/connrotation", "util/exec", "util/flowcontrol", "util/homedir", "util/integer", - "util/jsonpath", - "util/retry", - "util/workqueue" + "util/jsonpath" ] - revision = "23781f4d6632d88e869066eaebb743857aa1ef9b" - version = "v7.0.0" + revision = "e64494209f554a6723674bd494d69445fb76a1d4" + version = "v10.0.0" + +[[projects]] + name = "k8s.io/klog" + packages = ["."] + revision = "a5bc97fbc634d635061f3146511332c7e313a55a" + version = "v0.1.0" [[projects]] branch = "master" name = "k8s.io/kube-openapi" packages = [ + "pkg/common", "pkg/util/proto", "pkg/util/proto/validation" ] - revision = "41e43949ca69d04b66104069ed3ffd619b289b3d" + revision = "0317810137be915b9cf888946c6e115c1bfac693" [[projects]] name = "k8s.io/kubernetes" packages = [ - "pkg/api/events", - "pkg/api/legacyscheme", - "pkg/api/pod", - "pkg/api/ref", - "pkg/api/resource", - "pkg/api/service", - "pkg/api/v1/pod", - "pkg/apis/admissionregistration", - "pkg/apis/admissionregistration/install", - "pkg/apis/admissionregistration/v1alpha1", - "pkg/apis/admissionregistration/v1beta1", - "pkg/apis/apps", - "pkg/apis/apps/install", - "pkg/apis/apps/v1", - "pkg/apis/apps/v1beta1", - "pkg/apis/apps/v1beta2", - "pkg/apis/authentication", - "pkg/apis/authentication/install", - "pkg/apis/authentication/v1", - "pkg/apis/authentication/v1beta1", - "pkg/apis/authorization", - "pkg/apis/authorization/install", - "pkg/apis/authorization/v1", - "pkg/apis/authorization/v1beta1", - "pkg/apis/autoscaling", - "pkg/apis/autoscaling/install", - "pkg/apis/autoscaling/v1", - "pkg/apis/autoscaling/v2beta1", - "pkg/apis/batch", - "pkg/apis/batch/install", - "pkg/apis/batch/v1", - "pkg/apis/batch/v1beta1", - "pkg/apis/batch/v2alpha1", - "pkg/apis/certificates", - "pkg/apis/certificates/install", - "pkg/apis/certificates/v1beta1", - "pkg/apis/componentconfig", - "pkg/apis/componentconfig/install", - "pkg/apis/componentconfig/v1alpha1", - "pkg/apis/core", - "pkg/apis/core/helper", - "pkg/apis/core/helper/qos", - "pkg/apis/core/install", - "pkg/apis/core/pods", - "pkg/apis/core/v1", - "pkg/apis/core/v1/helper", - "pkg/apis/core/v1/helper/qos", - "pkg/apis/core/validation", - "pkg/apis/events", - "pkg/apis/events/install", - "pkg/apis/events/v1beta1", - "pkg/apis/extensions", - "pkg/apis/extensions/install", - "pkg/apis/extensions/v1beta1", - "pkg/apis/networking", - "pkg/apis/networking/install", - "pkg/apis/networking/v1", - "pkg/apis/policy", - "pkg/apis/policy/install", - "pkg/apis/policy/v1beta1", - "pkg/apis/rbac", - "pkg/apis/rbac/install", - "pkg/apis/rbac/v1", - "pkg/apis/rbac/v1alpha1", - "pkg/apis/rbac/v1beta1", - "pkg/apis/scheduling", - "pkg/apis/scheduling/install", - "pkg/apis/scheduling/v1alpha1", - "pkg/apis/settings", - "pkg/apis/settings/install", - "pkg/apis/settings/v1alpha1", - "pkg/apis/storage", - "pkg/apis/storage/install", - "pkg/apis/storage/util", - "pkg/apis/storage/v1", - "pkg/apis/storage/v1alpha1", - "pkg/apis/storage/v1beta1", - "pkg/capabilities", - "pkg/client/clientset_generated/internalclientset", - "pkg/client/clientset_generated/internalclientset/scheme", - "pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/apps/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/batch/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/core/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/events/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/networking/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/policy/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/settings/internalversion", - "pkg/client/clientset_generated/internalclientset/typed/storage/internalversion", - "pkg/cloudprovider", - "pkg/controller", - "pkg/controller/daemon", - "pkg/controller/daemon/util", - "pkg/controller/deployment/util", - "pkg/controller/history", - "pkg/controller/statefulset", - "pkg/controller/volume/events", - "pkg/controller/volume/persistentvolume", - "pkg/controller/volume/persistentvolume/metrics", - "pkg/credentialprovider", - "pkg/features", - "pkg/fieldpath", - "pkg/kubectl", - "pkg/kubectl/apps", - "pkg/kubectl/categories", - "pkg/kubectl/cmd/templates", "pkg/kubectl/cmd/util", "pkg/kubectl/cmd/util/openapi", "pkg/kubectl/cmd/util/openapi/validation", - "pkg/kubectl/plugins", - "pkg/kubectl/resource", "pkg/kubectl/scheme", - "pkg/kubectl/util", - "pkg/kubectl/util/hash", - "pkg/kubectl/util/slice", + "pkg/kubectl/util/templates", "pkg/kubectl/util/term", - "pkg/kubectl/util/transport", "pkg/kubectl/validation", - "pkg/kubelet/apis", - "pkg/kubelet/types", - "pkg/master/ports", - "pkg/printers", - "pkg/printers/internalversion", - "pkg/registry/rbac/validation", - "pkg/scheduler/algorithm", - "pkg/scheduler/algorithm/predicates", - "pkg/scheduler/algorithm/priorities/util", - "pkg/scheduler/api", - "pkg/scheduler/schedulercache", - "pkg/scheduler/util", - "pkg/scheduler/volumebinder", - "pkg/security/apparmor", - "pkg/serviceaccount", - "pkg/util/file", - "pkg/util/goroutinemap", - "pkg/util/goroutinemap/exponentialbackoff", - "pkg/util/hash", "pkg/util/interrupt", - "pkg/util/io", - "pkg/util/labels", - "pkg/util/metrics", - "pkg/util/mount", - "pkg/util/net/sets", - "pkg/util/node", - "pkg/util/nsenter", - "pkg/util/parsers", - "pkg/util/pointer", - "pkg/util/slice", - "pkg/util/taints", - "pkg/version", - "pkg/volume", - "pkg/volume/util", - "pkg/volume/util/fs", - "pkg/volume/util/recyclerclient", - "pkg/volume/util/types" + "pkg/version" ] - revision = "81753b10df112992bf51bbc2c2f85208aad78335" - version = "v1.10.2" + revision = "eec55b9ba98609a46fee712359c7b5b365bdd920" + version = "v1.13.1" [[projects]] branch = "master" name = "k8s.io/utils" packages = ["exec"] - revision = "258e2a2fa64568210fbd6267cf1d8fd87c3cb86e" + revision = "8a16e7dd8fb6d97d1331b0c79a16722f934b00b1" [[projects]] - branch = "master" - name = "vbom.ml/util" - packages = ["sortorder"] - revision = "256737ac55c46798123f754ab7d2c784e2c71783" + name = "sigs.k8s.io/kustomize" + packages = [ + "pkg/commands/build", + "pkg/constants", + "pkg/expansion", + "pkg/factory", + "pkg/fs", + "pkg/gvk", + "pkg/ifc", + "pkg/ifc/transformer", + "pkg/internal/error", + "pkg/loader", + "pkg/patch", + "pkg/patch/transformer", + "pkg/resid", + "pkg/resmap", + "pkg/resource", + "pkg/target", + "pkg/transformers", + "pkg/transformers/config", + "pkg/transformers/config/defaultconfig", + "pkg/types" + ] + revision = "8f701a00417a812558a7b785e8354957afa469ae" + version = "v1.0.11" + +[[projects]] + name = "sigs.k8s.io/yaml" + packages = ["."] + revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" + version = "v1.1.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "94c5b5a36a241d6f2517bb5242a65af10f98b5e6798727baa698b8516e8bf2a0" + inputs-digest = "903739d8f06f573609d6e1aec0293588b22bbc4f0003834dac7c2d242868e003" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a0caa810c0..0bb2aee596 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -8,53 +8,59 @@ required = ["k8s.io/kubernetes/pkg/kubectl/cmd/util"] name = "github.com/pulumi/pulumi" branch = "master" -[[override]] - name = "github.com/ugorji/go" - revision = "8c0409fcbb70099c748d71f714529204975f6c3f" - -[[override]] - name = "github.com/cbroglie/mustache" - revision = "v1.0.0" - -# --------------------------------------------------------------------------- -# NOTE: the k8s.io dependencies, (including go-autorest) are highly sensitive -# to change! They are pinned at versions where (1) they compile (nb., -# surprisingly challenging) and (2) their quirks are well-understood. cf., -# [here][1], [here][2], and [here][3]. # -# [1]: https://medium.com/@andy.goldstein/upgrading-kubernetes-client-go-from-v4-to-v5-bbd5025fe381 -# [2]: https://medium.com/@vladimirvivien/using-gos-dep-to-organize-your-kubernetes-client-go-dependencies-509ddc766ed3 -# [3]: https://blog.heptio.com/straighten-out-your-kubernetes-client-go-dependencies-heptioprotip-8baeed46fe7d -# --------------------------------------------------------------------------- +# Kubernetes packages +# [[constraint]] - name = "k8s.io/api" - revision = "kubernetes-1.10.2" + name = "k8s.io/kubernetes" + version = "1.13.1" + +[[constraint]] + name = "k8s.io/client-go" + version = "10.0.0" [[constraint]] name = "k8s.io/apimachinery" - version = "kubernetes-1.10.2" + version = "kubernetes-1.13.0" + +[[constraint]] + name = "k8s.io/api" + revision = "kubernetes-1.13.0" [[override]] - name = "k8s.io/apiserver" - version = "kubernetes-1.10.2" + name = "sigs.k8s.io/kustomize" + version = "1.0.11" -[[constraint]] - name = "k8s.io/client-go" - version = "7.0.0" +# +# Cloud provider packages +# + +[[override]] + name = "github.com/aws/aws-sdk-go" + version = "1.13.12" + +# +# Third party packages +# [[constraint]] - name = "k8s.io/kubernetes" - version = "v1.10.2" + name = "github.com/cbroglie/mustache" + version = "v1.0.0" [[override]] - name = "github.com/russross/blackfriday" - version = "v1.5" + name = "github.com/json-iterator/go" + version = "1.1.5" -# --------------------------------------------------------------------------- -# End sensitive k8s.io dependencies. -# --------------------------------------------------------------------------- +[[override]] + name = "github.com/satori/go.uuid" + version = "1.2.0" + +[[override]] + name = "github.com/spf13/cobra" + version = "0.0.3" + +[[override]] + name = "github.com/spf13/pflag" + version = "1.0.2" -[[constraint]] - name = "github.com/yudai/gojsondiff" - revision = "9209d1532c51cabe0439993586a71c207b09a0ac" diff --git a/examples/provider/index.ts b/examples/provider/index.ts index 7a91f3ac60..db25d35bf1 100644 --- a/examples/provider/index.ts +++ b/examples/provider/index.ts @@ -1,6 +1,5 @@ // Copyright 2016-2018, Pulumi Corporation. All rights reserved. -import * as pulumi from "@pulumi/pulumi"; import * as k8s from "@pulumi/kubernetes"; import * as fs from "fs"; import * as os from "os"; @@ -20,7 +19,7 @@ const nginxcontainer = new k8s.core.v1.Pod("nginx", { containers: [{ image: "nginx:1.7.9", name: "nginx", - ports: [{ containerPort: 80 }], + ports: [{containerPort: 80}], }], }, -}, { provider: myk8s }); +}, {provider: myk8s}); diff --git a/pkg/await/apps_deployment.go b/pkg/await/apps_deployment.go index f2763188dc..a1ec4a488b 100644 --- a/pkg/await/apps_deployment.go +++ b/pkg/await/apps_deployment.go @@ -8,13 +8,12 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" ) @@ -137,15 +136,15 @@ func (dia *deploymentInitAwaiter) Await() error { // corresponding ReplicaSet), and therefore there is no rollout to mark as "Progressing". // - replicaSetClient, podClient, pvcClient, err := dia.makeClients() + deploymentClient, replicaSetClient, podClient, pvcClient, err := dia.makeClients() if err != nil { return err } // Create Deployment watcher. - deploymentWatcher, err := dia.config.clientForResource.Watch(metav1.ListOptions{}) + deploymentWatcher, err := deploymentClient.Watch(metav1.ListOptions{}) if err != nil { - return errors.Wrapf(err, "Could not set up watch for Deployment object %q", + return errors.Wrapf(err, "could not set up watch for Deployment object %q", dia.config.currentInputs.GetName()) } defer deploymentWatcher.Stop() @@ -185,13 +184,13 @@ func (dia *deploymentInitAwaiter) Await() error { func (dia *deploymentInitAwaiter) Read() error { // Get clients needed to retrieve live versions of relevant Deployments, ReplicaSets, and Pods. - replicaSetClient, podClient, pvcClient, err := dia.makeClients() + deploymentClient, replicaSetClient, podClient, pvcClient, err := dia.makeClients() if err != nil { return err } // Get live versions of Deployment, ReplicaSets, and Pods. - deployment, err := dia.config.clientForResource.Get(dia.config.currentInputs.GetName(), + deployment, err := deploymentClient.Get(dia.config.currentInputs.GetName(), metav1.GetOptions{}) if err != nil { // IMPORTANT: Do not wrap this error! If this is a 404, the provider need to know so that it @@ -226,8 +225,7 @@ func (dia *deploymentInitAwaiter) Read() error { pvcList = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}} } - return dia.read(deployment, rsList.(*unstructured.UnstructuredList), - podList.(*unstructured.UnstructuredList), pvcList.(*unstructured.UnstructuredList)) + return dia.read(deployment, rsList, podList, pvcList) } func (dia *deploymentInitAwaiter) read( @@ -650,13 +648,13 @@ func (dia *deploymentInitAwaiter) aggregatePodErrors() ([]string, []string) { } } - scheduleErrors := []string{} + scheduleErrors := make([]string, 0) for message, count := range scheduleErrorCounts { message = fmt.Sprintf("%d Pods failed to schedule because: %s", count, message) scheduleErrors = append(scheduleErrors, message) } - containerErrors := []string{} + containerErrors := make([]string, 0) for message, count := range containerErrorCounts { message = fmt.Sprintf("%d Pods failed to run because: %s", count, message) containerErrors = append(containerErrors, message) @@ -670,7 +668,7 @@ func (dia *deploymentInitAwaiter) getFailedPersistentValueClaims() []string { return nil } - failed := []string{} + failed := make([]string, 0) for _, pvc := range dia.pvcs { phase, _ := openapi.Pluck(pvc.Object, "status", "phase") if phase != statusBound { @@ -681,7 +679,7 @@ func (dia *deploymentInitAwaiter) getFailedPersistentValueClaims() []string { } func (dia *deploymentInitAwaiter) errorMessages() []string { - messages := []string{} + messages := make([]string, 0) for _, message := range dia.deploymentErrors { messages = append(messages, message) } @@ -725,43 +723,36 @@ func (dia *deploymentInitAwaiter) errorMessages() []string { } func (dia *deploymentInitAwaiter) makeClients() ( - replicaSetClient, podClient, pvcClient dynamic.ResourceInterface, err error, + deploymentClient, replicaSetClient, podClient, pvcClient dynamic.ResourceInterface, err error, ) { - replicaSetClient, err = client.FromGVK(dia.config.pool, dia.config.disco, - schema.GroupVersionKind{ - Group: "extensions", - Version: "v1beta1", - Kind: "ReplicaSet", - }, dia.config.currentInputs.GetNamespace()) + deploymentClient, err = clients.ResourceClient( + clients.Deployment, dia.config.currentInputs.GetNamespace(), dia.config.clientSet) if err != nil { - return nil, nil, nil, errors.Wrapf(err, - "Could not make client to watch ReplicaSets associated with Deployment %q", + err = errors.Wrapf(err, "Could not make client to watch Deployment %q", dia.config.currentInputs.GetName()) + return } - - podClient, err = client.FromGVK(dia.config.pool, dia.config.disco, - schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Pod", - }, dia.config.currentInputs.GetNamespace()) + replicaSetClient, err = clients.ResourceClient( + clients.ReplicaSet, dia.config.currentInputs.GetNamespace(), dia.config.clientSet) if err != nil { - return nil, nil, nil, errors.Wrapf(err, - "Could not make client to watch Pods associated with Deployment %q", + err = errors.Wrapf(err, "Could not make client to watch ReplicaSets associated with Deployment %q", dia.config.currentInputs.GetName()) + return } - - pvcClient, err = client.FromGVK(dia.config.pool, dia.config.disco, - schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "PersistentVolumeClaim", - }, dia.config.currentInputs.GetNamespace()) + podClient, err = clients.ResourceClient( + clients.Pod, dia.config.currentInputs.GetNamespace(), dia.config.clientSet) if err != nil { - return nil, nil, nil, errors.Wrapf(err, - "Could not make client to watch PersistentVolumeClaims associated with Deployment %q", + err = errors.Wrapf(err, "Could not make client to watch Pods associated with Deployment %q", dia.config.currentInputs.GetName()) + return + } + pvcClient, err = clients.ResourceClient( + clients.PersistentVolumeClaim, dia.config.currentInputs.GetNamespace(), dia.config.clientSet) + if err != nil { + err = errors.Wrapf(err, "Could not make client to watch PVCs associated with Deployment %q", + dia.config.currentInputs.GetName()) + return } - return replicaSetClient, podClient, pvcClient, nil + return } diff --git a/pkg/await/apps_statefulset.go b/pkg/await/apps_statefulset.go index 5715ced323..8c90c29c9b 100644 --- a/pkg/await/apps_statefulset.go +++ b/pkg/await/apps_statefulset.go @@ -7,13 +7,12 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" ) @@ -130,18 +129,18 @@ func makeStatefulSetInitAwaiter(c updateAwaitConfig) *statefulsetInitAwaiter { // 2. The value of `.status.updateRevision` matches `.status.currentRevision`. func (sia *statefulsetInitAwaiter) Await() error { - podClient, err := sia.makeClients() + statefulSetClient, podClient, err := sia.makeClients() if err != nil { return err } // Create Deployment watcher. - statefulsetWatcher, err := sia.config.clientForResource.Watch(metav1.ListOptions{}) + statefulSetWatcher, err := statefulSetClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, "Could not set up watch for StatefulSet object %q", sia.config.currentInputs.GetName()) } - defer statefulsetWatcher.Stop() + defer statefulSetWatcher.Stop() // Create Pod watcher. podWatcher, err := podClient.Watch(metav1.ListOptions{}) @@ -155,18 +154,18 @@ func (sia *statefulsetInitAwaiter) Await() error { period := time.NewTicker(10 * time.Second) defer period.Stop() - return sia.await(statefulsetWatcher, podWatcher, time.After(5*time.Minute), period.C) + return sia.await(statefulSetWatcher, podWatcher, time.After(5*time.Minute), period.C) } func (sia *statefulsetInitAwaiter) Read() error { // Get clients needed to retrieve live versions of relevant Deployments, ReplicaSets, and Pods. - podClient, err := sia.makeClients() + statefulSetClient, podClient, err := sia.makeClients() if err != nil { return err } // Get live versions of StatefulSet and Pods. - statefulset, err := sia.config.clientForResource.Get(sia.config.currentInputs.GetName(), + statefulset, err := statefulSetClient.Get(sia.config.currentInputs.GetName(), metav1.GetOptions{}) if err != nil { // IMPORTANT: Do not wrap this error! If this is a 404, the provider need to know so that it @@ -187,7 +186,7 @@ func (sia *statefulsetInitAwaiter) Read() error { podList = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}} } - return sia.read(statefulset, podList.(*unstructured.UnstructuredList)) + return sia.read(statefulset, podList) } // read is a helper companion to `Read` designed to make it easy to test this module. @@ -449,19 +448,22 @@ func (sia *statefulsetInitAwaiter) errorMessages() []string { } func (sia *statefulsetInitAwaiter) makeClients() ( - podClient dynamic.ResourceInterface, err error, + statefulSetClient, podClient dynamic.ResourceInterface, err error, ) { - podClient, err = client.FromGVK(sia.config.pool, sia.config.disco, - schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Pod", - }, sia.config.currentInputs.GetNamespace()) + statefulSetClient, err = clients.ResourceClient( + clients.StatefulSet, sia.config.currentInputs.GetNamespace(), sia.config.clientSet) if err != nil { - return nil, errors.Wrapf(err, + return nil, nil, errors.Wrapf(err, + "Could not make client to watch StatefulSet %q", + sia.config.currentInputs.GetName()) + } + podClient, err = clients.ResourceClient( + clients.Pod, sia.config.currentInputs.GetNamespace(), sia.config.clientSet) + if err != nil { + return nil, nil, errors.Wrapf(err, "Could not make client to watch Pods associated with StatefulSet %q", sia.config.currentInputs.GetName()) } - return podClient, nil + return statefulSetClient, podClient, nil } diff --git a/pkg/await/await.go b/pkg/await/await.go index bb7113b58f..87d0f10d0d 100644 --- a/pkg/await/await.go +++ b/pkg/await/await.go @@ -19,18 +19,16 @@ import ( "fmt" "github.com/golang/glog" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" "github.com/pulumi/pulumi/pkg/resource" - "github.com/pulumi/pulumi/pkg/resource/provider" + pulumiprovider "github.com/pulumi/pulumi/pkg/resource/provider" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" ) @@ -46,13 +44,44 @@ import ( // -------------------------------------------------------------------------- +type ProviderConfig struct { + Context context.Context + Host *pulumiprovider.HostClient + URN resource.URN + + ClientSet *clients.DynamicClientSet +} + +type CreateConfig struct { + ProviderConfig + Inputs *unstructured.Unstructured + Namespace string +} + +type ReadConfig struct { + ProviderConfig + Inputs *unstructured.Unstructured + Namespace string + Name string +} + +type UpdateConfig struct { + ProviderConfig + Previous *unstructured.Unstructured + Inputs *unstructured.Unstructured + Namespace string +} + +type DeleteConfig struct { + ProviderConfig + Inputs *unstructured.Unstructured + Name string +} + // Creation (as the usage, `await.Creation`, implies) will block until one of the following is true: // (1) the Kubernetes resource is reported to be initialized; (2) the initialization timeout has // occurred; or (3) an error has occurred while the resource was being initialized. -func Creation( - ctx context.Context, host *provider.HostClient, pool dynamic.ClientPool, - disco discovery.ServerResourcesInterface, urn resource.URN, inputs *unstructured.Unstructured, -) (*unstructured.Unstructured, error) { +func Creation(c CreateConfig) (*unstructured.Unstructured, error) { // Issue create request. We retry the create REST request on failure, so that we can tolerate // some amount of misordering (e.g., creating a `Pod` before the `Namespace` it goes in; // creating a custom resource before the CRD is registered; etc.), which is common among Helm @@ -63,8 +92,9 @@ func Creation( // // nolint // https://github.com/kubernetes/kubernetes/blob/54889d581a35acf940d52a8a384cccaa0b597ddc/pkg/kubectl/cmd/apply/apply.go#L94 + var outputs *unstructured.Unstructured - var clientForResource dynamic.ResourceInterface + var client dynamic.ResourceInterface err := sleepingRetry( func(i uint) error { // Recreate the client for resource, in case the client's cache of the server API was @@ -72,17 +102,22 @@ func Creation( // this allows CRs that we tried (and failed) to create before to re-try with the new // server API, at which point they should hopefully succeed. var err error - if clientForResource == nil { - clientForResource, err = client.FromResource(pool, disco, inputs) + if client == nil { + client, err = c.ClientSet.ResourceClient(c.Inputs.GroupVersionKind(), c.Namespace) if err != nil { return err } } - outputs, err = clientForResource.Create(inputs) + + outputs, err = client.Create(c.Inputs, metav1.CreateOptions{}) if err != nil { - _ = host.LogStatus(ctx, diag.Info, urn, fmt.Sprintf("Retry #%d; creation failed: %v", i, err)) + _ = c.Host.LogStatus(c.Context, diag.Info, c.URN, fmt.Sprintf( + "Retry #%d; creation failed: %v", i, err)) + return err } + return err + }). WithMaxRetries(5). WithBackoffFactor(2). @@ -90,23 +125,22 @@ func Creation( if err != nil { return nil, err } - _ = clearStatus(ctx, host, urn) + _ = clearStatus(c.Context, c.Host, c.URN) // Wait until create resolves as success or error. Note that the conditional is set up to log // only if we don't have an entry for the resource type; in the event that we do, but the await // logic is blank, simply do nothing instead of logging. - id := fmt.Sprintf("%s/%s", inputs.GetAPIVersion(), inputs.GetKind()) + id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { if awaiter.awaitCreation != nil { conf := createAwaitConfig{ - host: host, - ctx: ctx, - pool: pool, - disco: disco, - clientForResource: clientForResource, - urn: urn, - currentInputs: inputs, - currentOutputs: outputs, + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + // TODO(lblackstone): maybe pass create output into input here? + currentInputs: c.Inputs, + currentOutputs: outputs, } waitErr := awaiter.awaitCreation(conf) if waitErr != nil { @@ -115,45 +149,39 @@ func Creation( } } else { glog.V(1).Infof( - "No initialization logic found for object of type '%s'; defaulting to assuming initialization successful", id) + "No initialization logic found for object of type %q; assuming initialization successful", id) } - return clientForResource.Get(inputs.GetName(), metav1.GetOptions{}) + return client.Get(outputs.GetName(), metav1.GetOptions{}) } // Read checks a resource, returning the object if it was created and initialized successfully. -func Read( - ctx context.Context, host *provider.HostClient, pool dynamic.ClientPool, - disco discovery.ServerResourcesInterface, urn resource.URN, gvk schema.GroupVersionKind, - namespace, name string, inputs *unstructured.Unstructured, -) (*unstructured.Unstructured, error) { - // Retrieve live version of last submitted version of object. - clientForResource, err := client.FromGVK(pool, disco, gvk, namespace) +func Read(c ReadConfig) (*unstructured.Unstructured, error) { + client, err := c.ClientSet.ResourceClient(c.Inputs.GroupVersionKind(), c.Namespace) if err != nil { return nil, err } - outputs, err := clientForResource.Get(name, metav1.GetOptions{}) + // Retrieve live version of the object from k8s. + outputs, err := client.Get(c.Name, metav1.GetOptions{}) if err != nil { return nil, err - } else if inputs == nil || len(inputs.Object) == 0 { + } else if c.Inputs == nil || len(c.Inputs.Object) == 0 { // No inputs means that we do not manage the resource, i.e., it's a call to // `CustomResource#get`. Simply return the object. return outputs, nil } - id := fmt.Sprintf("%s/%s", gvk.GroupVersion(), gvk.Kind) + id := fmt.Sprintf("%s/%s", outputs.GetAPIVersion(), outputs.GetKind()) if awaiter, exists := awaiters[id]; exists { if awaiter.awaitRead != nil { conf := createAwaitConfig{ - host: host, - ctx: ctx, - pool: pool, - disco: disco, - clientForResource: clientForResource, - urn: urn, - currentInputs: inputs, - currentOutputs: outputs, + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + currentInputs: c.Inputs, + currentOutputs: outputs, } waitErr := awaiter.awaitRead(conf) if waitErr != nil { @@ -163,11 +191,11 @@ func Read( } glog.V(1).Infof( - "No read logic found for object of type '%s'; falling back to retrieving object", id) + "No read logic found for object of type %q; falling back to retrieving object", id) // Get the "live" version of the last submitted object. This is necessary because the server // may have populated some fields automatically, updated status fields, and so on. - return clientForResource.Get(name, metav1.GetOptions{}) + return client.Get(c.Name, metav1.GetOptions{}) } // Update takes `lastSubmitted` (the last version of a Kubernetes API object submitted to the API @@ -184,11 +212,7 @@ func Read( // https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment // [2]: // https://kubernetes.io/docs/concepts/overview/object-management-kubectl/declarative-config/#how-apply-calculates-differences-and-merges-changes -func Update( - ctx context.Context, host *provider.HostClient, pool dynamic.ClientPool, - disco discovery.CachedDiscoveryInterface, urn resource.URN, - lastSubmitted, currentSubmitted *unstructured.Unstructured, -) (*unstructured.Unstructured, error) { +func Update(c UpdateConfig) (*unstructured.Unstructured, error) { // // TREAD CAREFULLY. The semantics of a Kubernetes update are subtle and you should proceed to // change them only if you understand them deeply. @@ -229,29 +253,29 @@ func Update( // - [ ] Support server-side apply, when it comes out. // - // Retrieve live version of last submitted version of object. - clientForResource, err := client.FromResource(pool, disco, lastSubmitted) + client, err := c.ClientSet.ResourceClient(c.Previous.GroupVersionKind(), c.Namespace) if err != nil { return nil, err } // Get the "live" version of the last submitted object. This is necessary because the server may // have populated some fields automatically, updated status fields, and so on. - liveOldObj, err := clientForResource.Get(lastSubmitted.GetName(), metav1.GetOptions{}) + liveOldObj, err := client.Get(c.Previous.GetName(), metav1.GetOptions{}) if err != nil { return nil, err } // Create merge patch (prefer strategic merge patch, fall back to JSON merge patch). patch, patchType, err := openapi.PatchForResourceUpdate( - disco, lastSubmitted, currentSubmitted, liveOldObj) + c.ClientSet.DiscoveryClientCached, c.Previous, c.Inputs, liveOldObj) if err != nil { return nil, err } - // Issue patch request. NOTE: We can use the same client because if the `kind` changes, this - // will cause a replace (i.e., destroy and create). - currentOutputs, err := clientForResource.Patch(currentSubmitted.GetName(), patchType, patch) + // Issue patch request. + // NOTE: We can use the same client because if the `kind` changes, this will cause + // a replace (i.e., destroy and create). + currentOutputs, err := client.Patch(c.Inputs.GetName(), patchType, patch, metav1.UpdateOptions{}) if err != nil { return nil, err } @@ -259,21 +283,19 @@ func Update( // Wait until patch resolves as success or error. Note that the conditional is set up to log only // if we don't have an entry for the resource type; in the event that we do, but the await logic // is blank, simply do nothing instead of logging. - id := fmt.Sprintf("%s/%s", currentSubmitted.GetAPIVersion(), currentSubmitted.GetKind()) + id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists { if awaiter.awaitUpdate != nil { conf := updateAwaitConfig{ createAwaitConfig: createAwaitConfig{ - host: host, - ctx: ctx, - pool: pool, - disco: disco, - clientForResource: clientForResource, - urn: urn, - currentInputs: currentSubmitted, - currentOutputs: currentOutputs, + host: c.Host, + ctx: c.Context, + urn: c.URN, + clientSet: c.ClientSet, + currentInputs: c.Inputs, + currentOutputs: currentOutputs, }, - lastInputs: lastSubmitted, + lastInputs: c.Previous, lastOutputs: liveOldObj, } waitErr := awaiter.awaitUpdate(conf) @@ -282,25 +304,21 @@ func Update( } } } else { - glog.V(1).Infof("No initialization logic found for object of type '%s'; defaulting to assuming initialization successful", id) + glog.V(1).Infof("No initialization logic found for object of type %q; assuming initialization successful", id) } - gvk := currentSubmitted.GroupVersionKind() + gvk := c.Inputs.GroupVersionKind() glog.V(3).Infof("Resource %s/%s/%s '%s.%s' patched and updated", gvk.Group, gvk.Version, - gvk.Kind, currentSubmitted.GetNamespace(), currentSubmitted.GetName()) + gvk.Kind, c.Inputs.GetNamespace(), c.Inputs.GetName()) // Return new, updated version of object. - return clientForResource.Get(currentSubmitted.GetName(), metav1.GetOptions{}) + return client.Get(c.Inputs.GetName(), metav1.GetOptions{}) } // Deletion (as the usage, `await.Deletion`, implies) will block until one of the following is true: // (1) the Kubernetes resource is reported to be deleted; (2) the initialization timeout has // occurred; or (3) an error has occurred while the resource was being deleted. -func Deletion( - ctx context.Context, host *provider.HostClient, pool dynamic.ClientPool, - disco discovery.DiscoveryInterface, urn resource.URN, gvk schema.GroupVersionKind, namespace, - name string, -) error { +func Deletion(c DeleteConfig) error { // nilIfGVKDeleted takes an error and returns nil if `errors.IsNotFound`; otherwise, it returns // the error argument unchanged. // @@ -319,10 +337,16 @@ func Deletion( return err } - // Make delete options based on the version of the client. - version, err := client.FetchVersion(disco) - if err != nil { - version = client.DefaultVersion() + // Attempt to retrieve k8s server version. Use default version in case this fails. + var version ServerVersion + if sv, err := c.ClientSet.DiscoveryClientCached.ServerVersion(); err == nil { + if v, err := parseVersion(sv); err == nil { + version = v + } else { + version = DefaultVersion() + } + } else { + version = DefaultVersion() } // Manually set delete propagation for Kubernetes versions < 1.6 to avoid bugs. @@ -349,25 +373,25 @@ func Deletion( } // Obtain client for the resource being deleted. - clientForResource, err := client.FromGVK(pool, disco, gvk, namespace) + client, err := c.ClientSet.ResourceClientForObject(c.Inputs) if err != nil { return nilIfGVKDeleted(err) } timeoutSeconds := int64(300) listOpts := metav1.ListOptions{ - FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(), + FieldSelector: fields.OneTermEqualSelector("metadata.name", c.Name).String(), TimeoutSeconds: &timeoutSeconds, } // Set up a watcher for the selected resource. - watcher, err := clientForResource.Watch(listOpts) + watcher, err := client.Watch(listOpts) if err != nil { return nilIfGVKDeleted(err) } // Issue deletion request. - err = clientForResource.Delete(name, &deleteOpts) + err = client.Delete(c.Name, &deleteOpts) if err != nil { return nilIfGVKDeleted(err) } @@ -376,32 +400,34 @@ func Deletion( // if we don't have an entry for the resource type; in the event that we do, but the await logic // is blank, simply do nothing instead of logging. var waitErr error - id := fmt.Sprintf("%s/%s", gvk.GroupVersion().String(), gvk.Kind) + id := fmt.Sprintf("%s/%s", c.Inputs.GetAPIVersion(), c.Inputs.GetKind()) if awaiter, exists := awaiters[id]; exists && awaiter.awaitDeletion != nil { - waitErr = awaiter.awaitDeletion(ctx, clientForResource, name) + waitErr = awaiter.awaitDeletion(c.Context, client, c.Name) } else { for { select { case event, ok := <-watcher.ResultChan(): if !ok { - if deleted, obj := checkIfResourceDeleted(name, clientForResource); deleted { - _ = clearStatus(ctx, host, urn) + if deleted, obj := checkIfResourceDeleted(c.Name, client); deleted { + _ = clearStatus(c.Context, c.Host, c.URN) return nil } else { return &timeoutError{ - object: obj, - subErrors: []string{fmt.Sprintf("Timed out waiting for deletion of %s '%s'", id, name)}, + object: obj, + subErrors: []string{ + fmt.Sprintf("Timed out waiting for deletion of %s %q", id, c.Name), + }, } } } switch event.Type { case watch.Deleted: - _ = clearStatus(ctx, host, urn) + _ = clearStatus(c.Context, c.Host, c.URN) return nil case watch.Error: - if deleted, obj := checkIfResourceDeleted(name, clientForResource); deleted { - _ = clearStatus(ctx, host, urn) + if deleted, obj := checkIfResourceDeleted(c.Name, client); deleted { + _ = clearStatus(c.Context, c.Host, c.URN) return nil } else { return &initializationError{ @@ -410,11 +436,11 @@ func Deletion( } } } - case <-ctx.Done(): // Handle user cancellation during watch for deletion. + case <-c.Context.Done(): // Handle user cancellation during watch for deletion. watcher.Stop() - glog.V(3).Infof("Received error deleting object '%s': %#v", id, err) - if deleted, obj := checkIfResourceDeleted(name, clientForResource); deleted { - _ = clearStatus(ctx, host, urn) + glog.V(3).Infof("Received error deleting object %q: %#v", id, err) + if deleted, obj := checkIfResourceDeleted(c.Name, client); deleted { + _ = clearStatus(c.Context, c.Host, c.URN) return nil } else { return &cancellationError{ @@ -440,6 +466,6 @@ func checkIfResourceDeleted(name string, client dynamic.ResourceInterface) (bool } // clearStatus will clear the `Info` column of the CLI of all statuses and messages. -func clearStatus(context context.Context, host *provider.HostClient, urn resource.URN) error { +func clearStatus(context context.Context, host *pulumiprovider.HostClient, urn resource.URN) error { return host.LogStatus(context, diag.Info, urn, "") } diff --git a/pkg/await/await_test.go b/pkg/await/await_test.go index 6058863492..bbabacc9f7 100644 --- a/pkg/await/await_test.go +++ b/pkg/await/await_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" @@ -48,31 +47,40 @@ type mockResourceInterface struct{} var _ dynamic.ResourceInterface = (*mockResourceInterface)(nil) -func (mri *mockResourceInterface) List(opts metav1.ListOptions) (runtime.Object, error) { - panic("List not implemented") +func (mri *mockResourceInterface) Create( + obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string, +) (*unstructured.Unstructured, error) { + panic("Create not implemented") } -func (mri *mockResourceInterface) Get( - name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { - return &unstructured.Unstructured{Object: map[string]interface{}{}}, nil +func (mri *mockResourceInterface) Update( + obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string, +) (*unstructured.Unstructured, error) { + panic("Update not implemented") +} +func (mri *mockResourceInterface) UpdateStatus( + obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error) { + panic("UpdateStatus not implemented") } -func (mri *mockResourceInterface) Delete(name string, opts *metav1.DeleteOptions) error { +func (mri *mockResourceInterface) Delete(name string, options *metav1.DeleteOptions, subresources ...string) error { panic("Delete not implemented") } func (mri *mockResourceInterface) DeleteCollection( deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error { panic("DeleteCollection not implemented") } -func (mri *mockResourceInterface) Create( - obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - panic("Create not implemented") +func (mri *mockResourceInterface) Get( + name string, options metav1.GetOptions, subresources ...string, +) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{Object: map[string]interface{}{}}, nil } -func (mri *mockResourceInterface) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - panic("Update not implemented") +func (mri *mockResourceInterface) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + panic("List not implemented") } func (mri *mockResourceInterface) Watch(opts metav1.ListOptions) (watch.Interface, error) { panic("Watch not implemented") } func (mri *mockResourceInterface) Patch( - name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { + name string, pt types.PatchType, data []byte, options metav1.UpdateOptions, subresources ...string, +) (*unstructured.Unstructured, error) { panic("Patch not implemented") } diff --git a/pkg/await/awaiters.go b/pkg/await/awaiters.go index d414cd8e8f..43cfabddab 100644 --- a/pkg/await/awaiters.go +++ b/pkg/await/awaiters.go @@ -21,15 +21,13 @@ import ( "time" "github.com/golang/glog" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi-kubernetes/pkg/watcher" "github.com/pulumi/pulumi/pkg/diag" "github.com/pulumi/pulumi/pkg/resource" "github.com/pulumi/pulumi/pkg/resource/provider" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" ) @@ -43,23 +41,12 @@ const ( // live number of Pods reaches the minimum liveness threshold. `pool` and `disco` are provided // typically from a client pool so that polling is reasonably efficient. type createAwaitConfig struct { - host *provider.HostClient - ctx context.Context - pool dynamic.ClientPool - disco discovery.ServerResourcesInterface - clientForResource dynamic.ResourceInterface - urn resource.URN - currentInputs *unstructured.Unstructured - currentOutputs *unstructured.Unstructured -} - -// nolint -func (cac *createAwaitConfig) eventClient() (dynamic.ResourceInterface, error) { - return client.FromGVK(cac.pool, cac.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Event", - }, cac.currentInputs.GetNamespace()) + host *provider.HostClient + ctx context.Context + urn resource.URN + clientSet *clients.DynamicClientSet + currentInputs *unstructured.Unstructured + currentOutputs *unstructured.Unstructured } func (cac *createAwaitConfig) logStatus(sev diag.Severity, message string) { @@ -276,7 +263,7 @@ func untilAppsDeploymentDeleted( specReplicas, _ := deploymentSpecReplicas(d) return watcher.RetryableError( - fmt.Errorf("Deployment '%s' still exists (%d / %d replicas exist)", name, + fmt.Errorf("deployment %q still exists (%d / %d replicas exist)", name, currReplicas, specReplicas)) } @@ -359,7 +346,7 @@ func untilCoreV1NamespaceDeleted( return nil } - return watcher.RetryableError(fmt.Errorf("Namespace %q still exists (%v)", name, statusPhase)) + return watcher.RetryableError(fmt.Errorf("namespace %q still exists (%v)", name, statusPhase)) } return watcher.ForObject(ctx, clientForResource, name). @@ -384,7 +371,11 @@ func untilCoreV1PersistentVolumeInitialized(c createAwaitConfig) error { return statusPhase == statusAvailable || statusPhase == statusBound } - return watcher.ForObject(c.ctx, c.clientForResource, c.currentInputs.GetName()). + client, err := c.clientSet.ResourceClient(c.currentInputs.GroupVersionKind(), c.currentInputs.GetNamespace()) + if err != nil { + return err + } + return watcher.ForObject(c.ctx, client, c.currentInputs.GetName()). WatchUntil(pvAvailableOrBound, 5*time.Minute) } @@ -401,7 +392,11 @@ func untilCoreV1PersistentVolumeClaimBound(c createAwaitConfig) error { return statusPhase == statusBound } - return watcher.ForObject(c.ctx, c.clientForResource, c.currentInputs.GetName()). + client, err := c.clientSet.ResourceClient(c.currentInputs.GroupVersionKind(), c.currentInputs.GetNamespace()) + if err != nil { + return err + } + return watcher.ForObject(c.ctx, client, c.currentInputs.GetName()). WatchUntil(pvcBound, 5*time.Minute) } @@ -411,6 +406,7 @@ func untilCoreV1PersistentVolumeClaimBound(c createAwaitConfig) error { // -------------------------------------------------------------------------- +// TODO(lblackstone): unify the function signatures across awaiters func untilCoreV1PodDeleted( ctx context.Context, clientForResource dynamic.ResourceInterface, name string, ) error { @@ -423,7 +419,7 @@ func untilCoreV1PodDeleted( statusPhase, _ := openapi.Pluck(pod.Object, "status", "phase") glog.V(3).Infof("Current state of pod %q: %#v", name, statusPhase) - e := fmt.Errorf("Pod %q still exists (%v)", name, statusPhase) + e := fmt.Errorf("pod %q still exists (%v)", name, statusPhase) return watcher.RetryableError(e) } @@ -452,14 +448,14 @@ func untilCoreV1ReplicationControllerInitialized(c createAwaitConfig) error { glog.V(3).Infof("Waiting for replication controller %q to schedule '%v' replicas", name, replicas) + client, err := c.clientSet.ResourceClient(c.currentInputs.GroupVersionKind(), c.currentInputs.GetNamespace()) + if err != nil { + return err + } // 10 mins should be sufficient for scheduling ~10k replicas - err := watcher.ForObject(c.ctx, c.clientForResource, name). + err = watcher.ForObject(c.ctx, client, name). WatchUntil( - waitForDesiredReplicasFunc( - c.clientForResource, - name, - replicationControllerSpecReplicas, - availableReplicas), + waitForDesiredReplicasFunc(replicationControllerSpecReplicas, availableReplicas), 10*time.Minute) if err != nil { return err @@ -542,7 +538,11 @@ func untilCoreV1ResourceQuotaInitialized(c createAwaitConfig) error { return false } - return watcher.ForObject(c.ctx, c.clientForResource, c.currentInputs.GetName()). + client, err := c.clientSet.ResourceClient(c.currentInputs.GroupVersionKind(), c.currentInputs.GetNamespace()) + if err != nil { + return err + } + return watcher.ForObject(c.ctx, client, c.currentInputs.GetName()). WatchUntil(rqInitialized, 1*time.Minute) } @@ -567,7 +567,7 @@ func untilCoreV1ServiceAccountInitialized(c createAwaitConfig) error { // secrets array (i.e., in addition to the secrets specified by the user). // - specSecrets, _ := openapi.Pluck(c.currentInputs.Object, "secrets") + specSecrets, _ := openapi.Pluck(c.currentOutputs.Object, "secrets") var numSpecSecrets int if specSecretsArr, isArr := specSecrets.([]interface{}); isArr { numSpecSecrets = len(specSecretsArr) @@ -587,7 +587,11 @@ func untilCoreV1ServiceAccountInitialized(c createAwaitConfig) error { return false } - return watcher.ForObject(c.ctx, c.clientForResource, c.currentInputs.GetName()). + client, err := c.clientSet.ResourceClient(c.currentOutputs.GroupVersionKind(), c.currentOutputs.GetNamespace()) + if err != nil { + return err + } + return watcher.ForObject(c.ctx, client, c.currentOutputs.GetName()). WatchUntil(defaultSecretAllocated, 5*time.Minute) } @@ -601,8 +605,6 @@ func untilCoreV1ServiceAccountInitialized(c createAwaitConfig) error { // it until the desired replicas are the same as the current replicas. The user provides two // functions to obtain the replicas spec and status fields, as well as a client to access them. func waitForDesiredReplicasFunc( - clientForResource dynamic.ResourceInterface, - name string, getReplicasSpec func(*unstructured.Unstructured) (interface{}, bool), getReplicasStatus func(*unstructured.Unstructured) (interface{}, bool), ) watcher.Predicate { diff --git a/pkg/await/core_pod.go b/pkg/await/core_pod.go index 83a48010a7..e5a1e61678 100644 --- a/pkg/await/core_pod.go +++ b/pkg/await/core_pod.go @@ -8,6 +8,7 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -120,7 +121,7 @@ func (pc *podChecker) check(pod *unstructured.Unstructured) { } status, isMap := rawStatus.(map[string]interface{}) if !isMap { - glog.V(3).Infof("Pod watch received unexpected status type '%s'", + glog.V(3).Infof("Pod watch received unexpected status type %q", reflect.TypeOf(rawStatus)) return @@ -136,7 +137,7 @@ func (pc *podChecker) check(pod *unstructured.Unstructured) { case "Succeeded": pc.podSuccess = true default: - glog.V(3).Infof("Pod '%s' has unknown status phase '%s'", + glog.V(3).Infof("Pod %q has unknown status phase %q", pod.GetName(), phase) } } @@ -154,7 +155,7 @@ func (pc *podChecker) checkPod(pod *unstructured.Unstructured, status map[string for _, rawCondition := range conditions { condition, isMap := rawCondition.(map[string]interface{}) if !isMap { - glog.V(3).Infof("Pod '%s' has condition of unknown type: '%s'", pod.GetName(), + glog.V(3).Infof("Pod %q has condition of unknown type: %q", pod.GetName(), reflect.TypeOf(rawCondition)) continue } @@ -344,9 +345,16 @@ func (pia *podInitAwaiter) Await() error { // We succeed when `.status.phase` is set to "Running". // - podWatcher, err := pia.config.clientForResource.Watch(metav1.ListOptions{}) + podClient, err := clients.ResourceClient( + clients.Pod, pia.config.currentInputs.GetNamespace(), pia.config.clientSet) if err != nil { - return errors.Wrapf(err, "Couldn't set up watch for Pod object '%s'", + return errors.Wrapf(err, + "Could not make client to watch Pod %q", + pia.config.currentInputs.GetName()) + } + podWatcher, err := podClient.Watch(metav1.ListOptions{}) + if err != nil { + return errors.Wrapf(err, "Couldn't set up watch for Pod object %q", pia.config.currentInputs.GetName()) } defer podWatcher.Stop() @@ -355,9 +363,15 @@ func (pia *podInitAwaiter) Await() error { } func (pia *podInitAwaiter) Read() error { + podClient, err := clients.ResourceClient( + clients.Pod, pia.config.currentInputs.GetNamespace(), pia.config.clientSet) + if err != nil { + return errors.Wrapf(err, + "Could not make client to get Pod %q", + pia.config.currentInputs.GetName()) + } // Get live version of Pod. - pod, err := pia.config.clientForResource.Get(pia.config.currentInputs.GetName(), - metav1.GetOptions{}) + pod, err := podClient.Get(pia.config.currentInputs.GetName(), metav1.GetOptions{}) if err != nil { // IMPORTANT: Do not wrap this error! If this is a 404, the provider need to know so that it // can mark the Pod as having been deleted. @@ -394,8 +408,7 @@ func (pia *podInitAwaiter) await(podWatcher watch.Interface, timeout <-chan time // Else, wait for updates. select { - // TODO: If Pod is added and not making progress on initialization after - // ~30 seconds, report that. + // TODO: If Pod is added and not making progress on initialization after ~30 seconds, report that. case <-pia.config.ctx.Done(): return &cancellationError{ object: pia.pod, @@ -415,7 +428,7 @@ func (pia *podInitAwaiter) await(podWatcher watch.Interface, timeout <-chan time func (pia *podInitAwaiter) processPodEvent(event watch.Event) { pod, isUnstructured := event.Object.(*unstructured.Unstructured) if !isUnstructured { - glog.V(3).Infof("Pod watch received unknown object type '%s'", + glog.V(3).Infof("Pod watch received unknown object type %q", reflect.TypeOf(pod)) return } diff --git a/pkg/await/core_service.go b/pkg/await/core_service.go index f8fefaba6a..6975c94a8e 100644 --- a/pkg/await/core_service.go +++ b/pkg/await/core_service.go @@ -6,15 +6,15 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" ) // ------------------------------------------------------------------------------------------------ @@ -67,7 +67,7 @@ type serviceInitAwaiter struct { } func makeServiceInitAwaiter(c createAwaitConfig) *serviceInitAwaiter { - specType, _ := openapi.Pluck(c.currentInputs.Object, "spec", "type") + specType, _ := openapi.Pluck(c.currentOutputs.Object, "spec", "type") var t string if specTypeString, isString := specType.(string); isString { t = specTypeString @@ -109,8 +109,13 @@ func (sia *serviceInitAwaiter) Await() error { // 4. External IP address is allocated (if we're type `LoadBalancer`). // + serviceClient, endpointsClient, err := sia.makeClients() + if err != nil { + return err + } + // Create service watcher. - serviceWatcher, err := sia.config.clientForResource.Watch(metav1.ListOptions{}) + serviceWatcher, err := serviceClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, "Could set up watch for Service object '%s'", sia.config.currentInputs.GetName()) @@ -118,22 +123,10 @@ func (sia *serviceInitAwaiter) Await() error { defer serviceWatcher.Stop() // Create endpoint watcher. - endpointClient, err := client.FromGVK(sia.config.pool, sia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Endpoints", - }, sia.config.currentInputs.GetNamespace()) + endpointWatcher, err := endpointsClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, - "Could not make client to watch Endpoint object associated with Service '%s'", - sia.config.currentInputs.GetName()) - } - - glog.V(3).Infof("Service Endpoint client namespace: %q", sia.config.currentInputs.GetNamespace()) - endpointWatcher, err := endpointClient.Watch(metav1.ListOptions{}) - if err != nil { - return errors.Wrapf(err, - "Could not create watcher for Endpoint objects associated with Service '%s'", + "Could not create watcher for Endpoint objects associated with Service %q", sia.config.currentInputs.GetName()) } defer endpointWatcher.Stop() @@ -142,8 +135,13 @@ func (sia *serviceInitAwaiter) Await() error { } func (sia *serviceInitAwaiter) Read() error { + serviceClient, endpointsClient, err := sia.makeClients() + if err != nil { + return err + } + // Get live versions of Service and Endpoints. - service, err := sia.config.clientForResource.Get(sia.config.currentInputs.GetName(), + service, err := serviceClient.Get(sia.config.currentOutputs.GetName(), metav1.GetOptions{}) if err != nil { // IMPORTANT: Do not wrap this error! If this is a 404, the provider need to know so that it @@ -158,25 +156,14 @@ func (sia *serviceInitAwaiter) Read() error { // // Create endpoint watcher. - endpointClient, err := client.FromGVK(sia.config.pool, sia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Endpoints", - }, sia.config.currentInputs.GetNamespace()) + endpointList, err := endpointsClient.List(metav1.ListOptions{}) if err != nil { - return errors.Wrapf(err, - "Could not make client to list Endpoint object associated with Service '%s'", - sia.config.currentInputs.GetName()) - } - - endpointList, err := endpointClient.List(metav1.ListOptions{}) - if err != nil { - glog.V(3).Infof("Error retrieving ReplicaSet list for Service '%s': %v", + glog.V(3).Infof("Error retrieving ReplicaSet list for Service %q: %v", service.GetName(), err) endpointList = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}} } - return sia.read(service, endpointList.(*unstructured.UnstructuredList)) + return sia.read(service, endpointList) } func (sia *serviceInitAwaiter) read( @@ -193,7 +180,7 @@ func (sia *serviceInitAwaiter) read( return nil }) if err != nil { - glog.V(3).Infof("Error iterating over ReplicaSet list for Deployment '%s': %v", + glog.V(3).Infof("Error iterating over ReplicaSet list for Deployment %q: %v", service.GetName(), err) } @@ -253,11 +240,11 @@ func (sia *serviceInitAwaiter) await( } func (sia *serviceInitAwaiter) processServiceEvent(event watch.Event) { - inputServiceName := sia.config.currentInputs.GetName() + inputServiceName := sia.config.currentOutputs.GetName() service, isUnstructured := event.Object.(*unstructured.Unstructured) if !isUnstructured { - glog.V(3).Infof("Service watch received unknown object type '%s'", + glog.V(3).Infof("Service watch received unknown object type %q", reflect.TypeOf(service)) return } @@ -282,7 +269,7 @@ func (sia *serviceInitAwaiter) processServiceEvent(event watch.Event) { lbIngress, _ := openapi.Pluck(service.Object, "status", "loadBalancer", "ingress") status, _ := openapi.Pluck(service.Object, "status") - glog.V(3).Infof("Received status for service '%s': %#v", inputServiceName, status) + glog.V(3).Infof("Received status for service %q: %#v", inputServiceName, status) ing, isSlice := lbIngress.([]interface{}) // Update status of service object so that we can check success. @@ -297,12 +284,12 @@ func (sia *serviceInitAwaiter) processServiceEvent(event watch.Event) { } func (sia *serviceInitAwaiter) processEndpointEvent(event watch.Event, settledCh chan<- struct{}) { - inputServiceName := sia.config.currentInputs.GetName() + inputServiceName := sia.config.currentOutputs.GetName() // Get endpoint object. endpoint, isUnstructured := event.Object.(*unstructured.Unstructured) if !isUnstructured { - glog.V(3).Infof("Endpoint watch received unknown object type '%s'", + glog.V(3).Infof("Endpoint watch received unknown object type %q", reflect.TypeOf(endpoint)) return } @@ -388,3 +375,24 @@ func (sia *serviceInitAwaiter) checkAndLogStatus() bool { return success } + +func (sia *serviceInitAwaiter) makeClients() ( + serviceClient, endpointClient dynamic.ResourceInterface, err error, +) { + serviceClient, err = clients.ResourceClient( + clients.Service, sia.config.currentOutputs.GetNamespace(), sia.config.clientSet) + if err != nil { + return nil, nil, errors.Wrapf(err, + "Could not make client to watch Service %q", + sia.config.currentOutputs.GetName()) + } + endpointClient, err = clients.ResourceClient( + clients.Endpoints, sia.config.currentOutputs.GetNamespace(), sia.config.clientSet) + if err != nil { + return nil, nil, errors.Wrapf(err, + "Could not make client to watch Endpoints associated with Service %q", + sia.config.currentOutputs.GetName()) + } + + return serviceClient, endpointClient, nil +} diff --git a/pkg/await/extensions_ingress.go b/pkg/await/extensions_ingress.go index e3817e808d..bd065cb665 100644 --- a/pkg/await/extensions_ingress.go +++ b/pkg/await/extensions_ingress.go @@ -8,16 +8,16 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/diag" "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" ) // ------------------------------------------------------------------------------------------------ @@ -87,23 +87,19 @@ func (iia *ingressInitAwaiter) Await() error { // 3. Ingress entry exists for .status.loadBalancer.ingress. // + ingressClient, endpointsClient, servicesClient, err := iia.makeClients() + if err != nil { + return err + } + // Create ingress watcher. - ingressWatcher, err := iia.config.clientForResource.Watch(metav1.ListOptions{}) + ingressWatcher, err := ingressClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, "Could not set up watch for Ingress object %q", iia.config.currentInputs.GetName()) } defer ingressWatcher.Stop() - endpointsClient, err := client.FromGVK(iia.config.pool, iia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Endpoints", - }, iia.config.currentInputs.GetNamespace()) - if err != nil { - glog.V(3).Infof("Failed to initialize Endpoints client: %v", err) - return err - } endpointWatcher, err := endpointsClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, @@ -112,15 +108,6 @@ func (iia *ingressInitAwaiter) Await() error { } defer endpointWatcher.Stop() - servicesClient, err := client.FromGVK(iia.config.pool, iia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Service", - }, iia.config.currentInputs.GetNamespace()) - if err != nil { - glog.V(3).Infof("Failed to initialize Services client: %v", err) - return err - } serviceWatcher, err := servicesClient.Watch(metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, @@ -132,8 +119,13 @@ func (iia *ingressInitAwaiter) Await() error { } func (iia *ingressInitAwaiter) Read() error { + ingressClient, endpointsClient, servicesClient, err := iia.makeClients() + if err != nil { + return err + } + // Get live versions of Ingress. - ingress, err := iia.config.clientForResource.Get(iia.config.currentInputs.GetName(), metav1.GetOptions{}) + ingress, err := ingressClient.Get(iia.config.currentInputs.GetName(), metav1.GetOptions{}) if err != nil { // IMPORTANT: Do not wrap this error! If this is a 404, the provider need to know so that it // can mark the deployment as having been deleted. @@ -141,37 +133,19 @@ func (iia *ingressInitAwaiter) Read() error { } // Get live version of Endpoints. - endpointsClient, err := client.FromGVK(iia.config.pool, iia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Endpoints", - }, iia.config.currentInputs.GetNamespace()) - if err != nil { - glog.V(3).Infof("Failed to initialize Endpoints client: %v", err) - return err - } endpointList, err := endpointsClient.List(metav1.ListOptions{}) if err != nil { glog.V(3).Infof("Failed to list endpoints needed for Ingress awaiter: %v", err) endpointList = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}} } - servicesClient, err := client.FromGVK(iia.config.pool, iia.config.disco, schema.GroupVersionKind{ - Group: "", - Version: "v1", - Kind: "Service", - }, iia.config.currentInputs.GetNamespace()) - if err != nil { - glog.V(3).Infof("Failed to initialize Services client: %v", err) - return err - } serviceList, err := servicesClient.List(metav1.ListOptions{}) if err != nil { glog.V(3).Infof("Failed to list services needed for Ingress awaiter: %v", err) serviceList = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}} } - return iia.read(ingress, endpointList.(*unstructured.UnstructuredList), serviceList.(*unstructured.UnstructuredList)) + return iia.read(ingress, endpointList, serviceList) } func (iia *ingressInitAwaiter) read(ingress *unstructured.Unstructured, endpoints *unstructured.UnstructuredList, @@ -406,3 +380,31 @@ func (iia *ingressInitAwaiter) checkAndLogStatus() bool { return success } + +func (iia *ingressInitAwaiter) makeClients() ( + ingressClient, endpointsClient, servicesClient dynamic.ResourceInterface, err error, +) { + ingressClient, err = clients.ResourceClient( + clients.Ingress, iia.config.currentInputs.GetNamespace(), iia.config.clientSet) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, + "Could not make client to watch Ingress %q", + iia.config.currentInputs.GetName()) + } + endpointsClient, err = clients.ResourceClient( + clients.Endpoints, iia.config.currentInputs.GetNamespace(), iia.config.clientSet) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, + "Could not make client to watch Endpoints associated with Ingress %q", + iia.config.currentInputs.GetName()) + } + servicesClient, err = clients.ResourceClient( + clients.Service, iia.config.currentInputs.GetNamespace(), iia.config.clientSet) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, + "Could not make client to watch Services associated with Ingress %q", + iia.config.currentInputs.GetName()) + } + + return +} diff --git a/pkg/await/retry.go b/pkg/await/retry.go index 3487f1a18e..77a947b6ea 100644 --- a/pkg/await/retry.go +++ b/pkg/await/retry.go @@ -1,9 +1,24 @@ +// Copyright 2016-2019, 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 await import ( "time" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" ) type retrier struct { @@ -41,7 +56,7 @@ func (r *retrier) Do() error { for r.tries <= r.maxRetries { err = r.try(r.tries) r.tries++ - if errors.IsNotFound(err) { + if errors.IsNotFound(err) || meta.IsNoMatchError(err) { r.sleep(r.waitTime) } else { break diff --git a/pkg/await/retry_test.go b/pkg/await/retry_test.go index 5fd55433c4..2fb8dfc249 100644 --- a/pkg/await/retry_test.go +++ b/pkg/await/retry_test.go @@ -1,3 +1,17 @@ +// Copyright 2016-2019, 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 await import ( diff --git a/pkg/await/util.go b/pkg/await/util.go index 1f29be0ffe..192810b19b 100644 --- a/pkg/await/util.go +++ b/pkg/await/util.go @@ -20,7 +20,7 @@ import ( "sort" "github.com/golang/glog" - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -97,7 +97,7 @@ func getLastWarningsForObject( return nil, err } - items := out.(*unstructured.UnstructuredList).Items + items := out.Items var events []v1.Event for _, item := range items { // Round trip conversion from `Unstructured` to `v1.Event`. There doesn't seem to be a good way diff --git a/pkg/await/util_test.go b/pkg/await/util_test.go index 146f86f66a..79bfa44b1b 100644 --- a/pkg/await/util_test.go +++ b/pkg/await/util_test.go @@ -9,12 +9,10 @@ import ( func mockAwaitConfig(inputs *unstructured.Unstructured) createAwaitConfig { return createAwaitConfig{ - ctx: context.Background(), - pool: nil, - disco: nil, - clientForResource: nil, - currentInputs: inputs, - currentOutputs: inputs, + ctx: context.Background(), + //TODO: complete this mock if needed + currentInputs: inputs, + currentOutputs: inputs, } } @@ -25,7 +23,7 @@ func decodeUnstructured(text string) (*unstructured.Unstructured, error) { } unst, isUnstructured := obj.(*unstructured.Unstructured) if !isUnstructured { - return nil, fmt.Errorf("Could not decode object as *unstructured.Unstructured: %v", unst) + return nil, fmt.Errorf("could not decode object as *unstructured.Unstructured: %v", unst) } return unst, nil } diff --git a/pkg/client/version.go b/pkg/await/version.go similarity index 73% rename from pkg/client/version.go rename to pkg/await/version.go index 491aa2b433..e2cd6bd0ac 100644 --- a/pkg/client/version.go +++ b/pkg/await/version.go @@ -1,10 +1,10 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2019, 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 +// 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, @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package client +package await import ( "fmt" @@ -23,11 +23,10 @@ import ( "github.com/pulumi/pulumi/pkg/diag" "github.com/pulumi/pulumi/pkg/util/cmdutil" "k8s.io/apimachinery/pkg/version" - "k8s.io/client-go/discovery" ) // Format v0.0.0(-master+$Format:%h$) -var gitVersionRe = regexp.MustCompile("v([0-9])+.([0-9])+.[0-9]+.*") +var gitVersionRe = regexp.MustCompile(`v([0-9])+.([0-9])+.[0-9]+.*`) // ServerVersion captures k8s major.minor version in a parsed form type ServerVersion struct { @@ -55,7 +54,7 @@ func DefaultVersion() ServerVersion { func parseGitVersion(gitVersion string) (ServerVersion, error) { parsedVersion := gitVersionRe.FindStringSubmatch(gitVersion) if len(parsedVersion) != 3 { - return ServerVersion{}, fmt.Errorf("Unable to parse git version %s", gitVersion) + return ServerVersion{}, fmt.Errorf("unable to parse git version %s", gitVersion) } var ret ServerVersion var err error @@ -113,11 +112,28 @@ func (v ServerVersion) String() string { return fmt.Sprintf("%d.%d", v.Major, v.Minor) } -// FetchVersion fetches version information from discovery client, and parses -func FetchVersion(v discovery.ServerVersionInterface) (ret ServerVersion, err error) { - version, err := v.ServerVersion() - if err != nil { - return ServerVersion{}, err +// canonicalizeDeploymentAPIVersion unifies the various pre-release apiVerion values for a +// Deployment into "apps/v1". +func canonicalizeDeploymentAPIVersion(ver string) string { + switch ver { + case "extensions/v1beta1", "apps/v1beta1", "apps/v1beta2", "apps/v1": + // Canonicalize all of these to "apps/v1". + return "apps/v1" + default: + // If the input version was not a version we understand, just return it as-is. + return ver + } +} + +// canonicalizeStatefulSetAPIVersion unifies the various pre-release apiVersion values for a +// StatefulSet into "apps/v1". +func canonicalizeStatefulSetAPIVersion(ver string) string { + switch ver { + case "apps/v1beta1", "apps/v1beta2", "apps/v1": + // Canonicalize all of these to "apps/v1". + return "apps/v1" + default: + // If the input version was not a version we understand, just return it as-is. + return ver } - return parseVersion(version) } diff --git a/pkg/client/version_test.go b/pkg/await/version_test.go similarity index 80% rename from pkg/client/version_test.go rename to pkg/await/version_test.go index f578909144..20cddeafdf 100644 --- a/pkg/client/version_test.go +++ b/pkg/await/version_test.go @@ -1,10 +1,10 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2019, 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 +// 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, @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package client +package await import ( "testing" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/version" ) @@ -106,24 +105,3 @@ func TestVersionCompare(t *testing.T) { } } } - -func TestFqName(t *testing.T) { - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "tests/v1alpha1", - "kind": "Test", - "metadata": map[string]interface{}{ - "name": "myname", - }, - }, - } - - if n := FqName(obj.GetNamespace(), obj.GetName()); n != "myname" { - t.Errorf("Got %q for %v", n, obj) - } - - obj.SetNamespace("mynamespace") - if n := FqName(obj.GetNamespace(), obj.GetName()); n != "mynamespace/myname" { - t.Errorf("Got %q for %v", n, obj) - } -} diff --git a/pkg/await/versions.go b/pkg/await/versions.go deleted file mode 100644 index fd23ea5455..0000000000 --- a/pkg/await/versions.go +++ /dev/null @@ -1,28 +0,0 @@ -package await - -// canonicalizeDeploymentAPIVersion unifies the various pre-release apiVerion values for a -// Deployment into "apps/v1". -func canonicalizeDeploymentAPIVersion(ver string) string { - switch ver { - case "extensions/v1beta1", "apps/v1beta1", "apps/v1beta2", "apps/v1": - // Canonicalize all of these to "apps/v1". - return "apps/v1" - default: - // If the input version was not a version we understand, just return it as-is. - return ver - } -} - -// canonicalizeStatefulSetAPIVersion unifies the various pre-release apiVerion values for a -// StatefulSet into "apps/v1". -func canonicalizeStatefulSetAPIVersion(ver string) string { - switch ver { - case "apps/v1beta1", "apps/v1beta2", "apps/v1": - // Canonicalize all of these to "apps/v1". - return "apps/v1" - default: - // If the input version was not a version we understand, just return it as-is. - return ver - } -} - diff --git a/pkg/client/client.go b/pkg/client/client.go deleted file mode 100644 index c3d34b8e76..0000000000 --- a/pkg/client/client.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2016-2018, 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 client - -import ( - "fmt" - "sync" - - "github.com/emicklei/go-restful-swagger12" - "github.com/googleapis/gnostic/OpenAPIv2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - apiVers "k8s.io/apimachinery/pkg/version" - "k8s.io/client-go/discovery" - "k8s.io/client-go/rest" -) - -// -------------------------------------------------------------------------- - -// In-memory, caching Kubernetes discovery client. -// -// The Kubernetes discovery client "discovers" the API server's capabilities, and opaquely handles -// the mapping of unstructured property bag -> typed API objects, regardless (in theory) of the -// version of the API server, greatly simplifying the logic required to interface with the cluster. -// -// This code implements the in-memory caching mechanism for this client, so that we do not have to -// retrieve this information multiple times to satisfy some set of requests. - -// -------------------------------------------------------------------------- - -type memcachedDiscoveryClient struct { - cl discovery.DiscoveryInterface - lock sync.RWMutex - servergroups *metav1.APIGroupList - serverresources map[string]*metav1.APIResourceList - schemas map[string]*swagger.ApiDeclaration - schema *openapi_v2.Document -} - -var _ discovery.CachedDiscoveryInterface = &memcachedDiscoveryClient{} - -// NewMemcachedDiscoveryClient creates a new DiscoveryClient that -// caches results in memory -func NewMemcachedDiscoveryClient(cl discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface { - c := &memcachedDiscoveryClient{cl: cl} - c.Invalidate() - return c -} - -func (c *memcachedDiscoveryClient) Fresh() bool { - return true -} - -func (c *memcachedDiscoveryClient) Invalidate() { - c.lock.Lock() - defer c.lock.Unlock() - - c.servergroups = nil - c.serverresources = make(map[string]*metav1.APIResourceList) - c.schemas = make(map[string]*swagger.ApiDeclaration) -} - -func (c *memcachedDiscoveryClient) RESTClient() rest.Interface { - return c.cl.RESTClient() -} - -func (c *memcachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) { - c.lock.Lock() - defer c.lock.Unlock() - - var err error - if c.servergroups != nil { - return c.servergroups, nil - } - c.servergroups, err = c.cl.ServerGroups() - return c.servergroups, err -} - -func (c *memcachedDiscoveryClient) ServerResourcesForGroupVersion( - groupVersion string, -) (*metav1.APIResourceList, error) { - c.lock.Lock() - defer c.lock.Unlock() - - var err error - if v := c.serverresources[groupVersion]; v != nil { - return v, nil - } - c.serverresources[groupVersion], err = c.cl.ServerResourcesForGroupVersion(groupVersion) - return c.serverresources[groupVersion], err -} - -func (c *memcachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) { - return c.cl.ServerResources() -} - -func (c *memcachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) { - return c.cl.ServerPreferredResources() -} - -func (c *memcachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) { - return c.cl.ServerPreferredNamespacedResources() -} - -func (c *memcachedDiscoveryClient) ServerVersion() (*apiVers.Info, error) { - return c.cl.ServerVersion() -} - -// SwaggerSchema is unimplemented. It's required to implement `discovery.DiscoveryInterface`, but we -// should always use `OpenAPISchema` instead. Since `discovery.DiscoveryInterface` implements -// several other interfaces, we enforce this constraint at the type level by writing functions -// that take as argument a "more specific" interface that describes the actual functionality needed, -// e.g., `discovery.OpenAPISchemaInterface`, which does not define `SwaggerSchema` at all. -func (c *memcachedDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) { - return nil, fmt.Errorf("Not implemented") -} - -func (c *memcachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) { - c.lock.Lock() - defer c.lock.Unlock() - - if c.schema != nil { - return c.schema, nil - } - - schema, err := c.cl.OpenAPISchema() - if err != nil { - return nil, err - } - - c.schema = schema - return schema, nil -} diff --git a/pkg/client/util.go b/pkg/client/util.go deleted file mode 100644 index 8ecf0b1656..0000000000 --- a/pkg/client/util.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2016-2018, 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 client - -import ( - "fmt" - "strings" - - "github.com/golang/glog" - - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" -) - -// -------------------------------------------------------------------------- -// Names and namespaces. -// -------------------------------------------------------------------------- - -// FqObjName returns "namespace.name" -func FqObjName(o metav1.Object) string { - return FqName(o.GetNamespace(), o.GetName()) -} - -// ParseFqName will parse a fully-qualified Kubernetes object name. -func ParseFqName(id string) (namespace, name string) { - split := strings.Split(id, "/") - if len(split) == 1 { - return "", split[0] - } - namespace, name = split[0], split[1] - return -} - -// FqName returns "namespace/name" -func FqName(namespace, name string) string { - if namespace == "" { - return name - } - return fmt.Sprintf("%s/%s", namespace, name) -} - -// NamespaceOrDefault returns `ns` or the the default namespace `"default"` if `ns` is empty. -func NamespaceOrDefault(ns string) string { - if ns == "" { - return "default" - } - return ns -} - -// -------------------------------------------------------------------------- -// Client utilities. -// -------------------------------------------------------------------------- - -// FromResource returns the ResourceClient for a given object -func FromResource( - pool dynamic.ClientPool, disco discovery.ServerResourcesInterface, obj runtime.Object, -) (dynamic.ResourceInterface, error) { - gvk := obj.GetObjectKind().GroupVersionKind() - meta, err := meta.Accessor(obj) - if err != nil { - return nil, err - } - - return FromGVK(pool, disco, gvk, NamespaceOrDefault(meta.GetNamespace())) -} - -// FromGVK returns the ResourceClient for a given object -func FromGVK( - pool dynamic.ClientPool, disco discovery.ServerResourcesInterface, gvk schema.GroupVersionKind, - namespace string, -) (dynamic.ResourceInterface, error) { - client, err := pool.ClientForGroupVersionKind(gvk) - if err != nil { - return nil, err - } - - resource, err := serverResourceForGVK(disco, gvk) - if err != nil { - return nil, err - } - - ns := NamespaceOrDefault(namespace) - glog.V(3).Infof("Fetching client for %s namespace=%s", resource, ns) - rc := client.Resource(resource, ns) - return rc, nil -} - -func serverResourceForGVK( - disco discovery.ServerResourcesInterface, gvk schema.GroupVersionKind, -) (*metav1.APIResource, error) { - resources, err := disco.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) - if err != nil { - return nil, err - } - - for _, r := range resources.APIResources { - if r.Kind == gvk.Kind { - glog.V(3).Infof("Using resource '%s' for %s", r.Name, gvk) - return &r, nil - } - } - - se := &errors.StatusError{ErrStatus: metav1.Status{ - Reason: metav1.StatusReasonNotFound, - Message: fmt.Sprintf("Server does not support kind: %s", gvk)}} - return nil, se -} diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go new file mode 100644 index 0000000000..ac4ccc59fd --- /dev/null +++ b/pkg/clients/clients.go @@ -0,0 +1,390 @@ +// Copyright 2016-2019, 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 clients + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + cacheddiscovery "k8s.io/client-go/discovery/cached" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" +) + +type Kind string + +const ( + APIService Kind = "APIService" + CertificateSigningRequest Kind = "CertificateSigningRequest" + ClusterRole Kind = "ClusterRole" + ClusterRoleBinding Kind = "ClusterRoleBinding" + ControllerRevision Kind = "ControllerRevision" + CustomResourceDefinition Kind = "CustomResourceDefinition" + ConfigMap Kind = "ConfigMap" + CronJob Kind = "CronJob" + DaemonSet Kind = "DaemonSet" + Deployment Kind = "Deployment" + Endpoints Kind = "Endpoints" + Event Kind = "Event" + HorizontalPodAutoscaler Kind = "HorizontalPodAutoscaler" + Ingress Kind = "Ingress" + Job Kind = "Job" + LimitRange Kind = "LimitRange" + MutatingWebhookConfiguration Kind = "MutatingWebhookConfiguration" + Namespace Kind = "Namespace" + NetworkPolicy Kind = "NetworkPolicy" + PersistentVolume Kind = "PersistentVolume" + PersistentVolumeClaim Kind = "PersistentVolumeClaim" + Pod Kind = "Pod" + PodDisruptionBudget Kind = "PodDisruptionBudget" + PodSecurityPolicy Kind = "PodSecurityPolicy" + PodTemplate Kind = "PodTemplate" + PriorityClass Kind = "PriorityClass" + ReplicaSet Kind = "ReplicaSet" + ReplicationController Kind = "ReplicationController" + ResourceQuota Kind = "ResourceQuota" + Role Kind = "Role" + RoleBinding Kind = "RoleBinding" + Secret Kind = "Secret" + Service Kind = "Service" + ServiceAccount Kind = "ServiceAccount" + StatefulSet Kind = "StatefulSet" + StorageClass Kind = "StorageClass" + ValidatingWebhookConfiguration Kind = "ValidatingWebhookConfiguration" +) + +func namespacedKind(k Kind) bool { + switch k { + case APIService, CertificateSigningRequest, ClusterRole, ClusterRoleBinding, CustomResourceDefinition, + MutatingWebhookConfiguration, Namespace, PersistentVolume, PodSecurityPolicy, PriorityClass, + StorageClass, ValidatingWebhookConfiguration: + return false + } + + return true +} + +func ResourceClient(kind Kind, namespace string, client *DynamicClientSet) (dynamic.ResourceInterface, error) { + var gvk schema.GroupVersionKind + switch kind { + case APIService: + gvk = schema.GroupVersionKind{ + Group: "apiregistration.k8s.io", + Version: "v1", + Kind: string(APIService), + } + case CertificateSigningRequest: + gvk = schema.GroupVersionKind{ + Group: "certificates.k8s.io", + Version: "v1beta1", + Kind: string(CertificateSigningRequest), + } + case ClusterRole: + gvk = schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: string(ClusterRole), + } + case ClusterRoleBinding: + gvk = schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: string(ClusterRoleBinding), + } + case ConfigMap: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(ConfigMap), + } + case ControllerRevision: + gvk = schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: string(ControllerRevision), + } + case CronJob: + gvk = schema.GroupVersionKind{ + Group: "batch", + Version: "v1beta1", + Kind: string(CronJob), + } + case CustomResourceDefinition: + gvk = schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1beta1", + Kind: string(CustomResourceDefinition), + } + case Deployment: + gvk = schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: string(Deployment), + } + case DaemonSet: + gvk = schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: string(DaemonSet), + } + case Endpoints: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Endpoints), + } + case Event: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Event), + } + case HorizontalPodAutoscaler: + gvk = schema.GroupVersionKind{ + Group: "autoscaling", + Version: "v1", + Kind: string(HorizontalPodAutoscaler), + } + case Ingress: + gvk = schema.GroupVersionKind{ + Group: "extensions", + Version: "v1beta1", + Kind: string(Ingress), + } + case Job: + gvk = schema.GroupVersionKind{ + Group: "batch", + Version: "v1", + Kind: string(Job), + } + case LimitRange: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(LimitRange), + } + case MutatingWebhookConfiguration: + gvk = schema.GroupVersionKind{ + Group: "admissionregistration.k8s.io", + Version: "v1beta1", + Kind: string(MutatingWebhookConfiguration), + } + case Namespace: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Namespace), + } + case NetworkPolicy: + gvk = schema.GroupVersionKind{ + Group: "networking.k8s.io", + Version: "v1", + Kind: string(NetworkPolicy), + } + case PersistentVolume: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(PersistentVolume), + } + case PersistentVolumeClaim: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(PersistentVolumeClaim), + } + case Pod: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Pod), + } + case PodDisruptionBudget: + gvk = schema.GroupVersionKind{ + Group: "policy", + Version: "v1beta1", + Kind: string(PodDisruptionBudget), + } + case PodSecurityPolicy: + gvk = schema.GroupVersionKind{ + Group: "extensions", + Version: "v1beta1", + Kind: string(PodSecurityPolicy), + } + case PodTemplate: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(PodTemplate), + } + case PriorityClass: + gvk = schema.GroupVersionKind{ + Group: "scheduling.k8s.io", + Version: "v1beta1", + Kind: string(PriorityClass), + } + case ReplicaSet: + gvk = schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: string(ReplicaSet), + } + case ReplicationController: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(ReplicationController), + } + case ResourceQuota: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(ResourceQuota), + } + case Role: + gvk = schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: string(Role), + } + case RoleBinding: + gvk = schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: string(RoleBinding), + } + case Secret: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Secret), + } + case Service: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(Service), + } + case ServiceAccount: + gvk = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: string(ServiceAccount), + } + case StatefulSet: + gvk = schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: string(StatefulSet), + } + case StorageClass: + gvk = schema.GroupVersionKind{ + Group: "storage.k8s.io", + Version: "v1", + Kind: string(StorageClass), + } + case ValidatingWebhookConfiguration: + gvk = schema.GroupVersionKind{ + Group: "admissionregistration.k8s.io", + Version: "v1beta1", + Kind: string(ValidatingWebhookConfiguration), + } + default: + return nil, fmt.Errorf("invalid kind for client: %s", kind) + } + + c, err := client.ResourceClient(gvk, namespace) + if err != nil { + return nil, fmt.Errorf("failed to get client: %v", err) + } + + return c, nil +} + +type DynamicClientSet struct { + GenericClient dynamic.Interface + DiscoveryClientCached discovery.CachedDiscoveryInterface + RESTMapper *restmapper.DeferredDiscoveryRESTMapper +} + +func NewDynamicClientSet(clientConfig *rest.Config) (*DynamicClientSet, error) { + disco, err := discovery.NewDiscoveryClientForConfig(clientConfig) + if err != nil { + return nil, fmt.Errorf("failed to initialize discovery client: %v", err) + } + + // Cache the discovery information (OpenAPI schema, etc.) so we don't have to retrieve it for + // every request. + discoCacheClient := cacheddiscovery.NewMemCacheClient(disco) + discoCacheClient.Invalidate() + + mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoCacheClient) + + // Create dynamic resource client + client, err := dynamic.NewForConfig(clientConfig) + if err != nil { + return nil, fmt.Errorf("failed to initialize dynamic client: %v", err) + } + + return &DynamicClientSet{ + GenericClient: client, + DiscoveryClientCached: discoCacheClient, + RESTMapper: mapper, + }, nil +} + +func (dcs *DynamicClientSet) ResourceClient(gvk schema.GroupVersionKind, namespace string) (dynamic.ResourceInterface, error) { + m, err := dcs.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + // If the REST mapping failed, try refreshing the cache and remapping before giving up. + // This can occur if a CRD is being registered from another resource. + dcs.DiscoveryClientCached.Invalidate() + dcs.RESTMapper.Reset() + m, err = dcs.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + } + + // For namespaced Kinds, create a namespaced client. If no namespace is provided, use the "default" namespace. + if namespacedKind(Kind(gvk.Kind)) { + return dcs.GenericClient.Resource(m.Resource).Namespace(namespaceOrDefault(namespace)), nil + } + + // Return a non-namespaced client for all other Kinds. + return dcs.GenericClient.Resource(m.Resource), nil +} + +func (dcs *DynamicClientSet) ResourceClientForObject(obj *unstructured.Unstructured) (dynamic.ResourceInterface, error) { + return dcs.ResourceClient(obj.GroupVersionKind(), obj.GetNamespace()) +} + +// namespaceOrDefault returns `ns` or the the default namespace `"default"` if `ns` is empty. +func namespaceOrDefault(ns string) string { + if ns == "" { + return "default" + } + return ns +} + +// IsCRD returns true if a Kubernetes resource is a CRD. +func IsCRD(obj *unstructured.Unstructured) bool { + return obj.GetKind() == string(CustomResourceDefinition) && + strings.HasPrefix(obj.GetAPIVersion(), "apiextensions.k8s.io/") +} diff --git a/pkg/openapi/openapi.go b/pkg/openapi/openapi.go index 16e01eb0b6..e7d01d3830 100644 --- a/pkg/openapi/openapi.go +++ b/pkg/openapi/openapi.go @@ -63,7 +63,7 @@ func ValidateAgainstSchema( gvk := obj.GroupVersionKind() resSchema := resources.LookupResource(gvk) if resSchema == nil { - return fmt.Errorf("Cluster does not support resource type '%s'", gvk.String()) + return fmt.Errorf("cluster does not support resource type '%s'", gvk.String()) } // TODO(hausdorff): Come back and make sure that `ValidateBytes` actually reports a list of @@ -77,9 +77,8 @@ func ValidateAgainstSchema( // PatchForResourceUpdate introspects on the OpenAPI spec exposed by some client, and attempts to // generate a strategic merge patch for use in a resource update. If there is no specification of // how to generate a strategic merge patch, we fall back to JSON merge patch. -func PatchForResourceUpdate( - client discovery.OpenAPISchemaInterface, - lastSubmitted, currentSubmitted, liveOldObj *unstructured.Unstructured, +func PatchForResourceUpdate(client discovery.CachedDiscoveryInterface, lastSubmitted, currentSubmitted, + liveOldObj *unstructured.Unstructured, ) ([]byte, types.PatchType, error) { // Create JSON blobs for each of these, preparing to create the three-way merge patch. lastSubmittedJSON, err := lastSubmitted.MarshalJSON() @@ -208,10 +207,10 @@ func jsonMergePatch( func getResourceSchemasForClient( client discovery.OpenAPISchemaInterface, ) (openapi.Resources, error) { - schema, err := client.OpenAPISchema() + document, err := client.OpenAPISchema() if err != nil { return nil, err } - return openapi.NewOpenAPIData(schema) + return openapi.NewOpenAPIData(document) } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 169ae54139..2a25213632 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -23,16 +23,16 @@ import ( "github.com/golang/glog" pbempty "github.com/golang/protobuf/ptypes/empty" - structpb "github.com/golang/protobuf/ptypes/struct" + "github.com/golang/protobuf/ptypes/struct" "github.com/pulumi/pulumi-kubernetes/pkg/await" - "github.com/pulumi/pulumi-kubernetes/pkg/client" + "github.com/pulumi/pulumi-kubernetes/pkg/clients" "github.com/pulumi/pulumi-kubernetes/pkg/openapi" "github.com/pulumi/pulumi/pkg/resource" "github.com/pulumi/pulumi/pkg/resource/plugin" "github.com/pulumi/pulumi/pkg/resource/provider" "github.com/pulumi/pulumi/pkg/util/contract" "github.com/pulumi/pulumi/pkg/util/rpcutil/rpcerror" - pulumirpc "github.com/pulumi/pulumi/sdk/proto/go" + "github.com/pulumi/pulumi/sdk/proto/go" "github.com/yudai/gojsondiff" "google.golang.org/grpc/codes" "k8s.io/apimachinery/pkg/api/errors" @@ -40,8 +40,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/clientcmd" clientapi "k8s.io/client-go/tools/clientcmd/api" ) @@ -66,7 +64,7 @@ type cancellationContext struct { } func makeCancellationContext() *cancellationContext { - var ctx, cancel = context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(context.Background()) return &cancellationContext{ context: ctx, cancel: cancel, @@ -80,12 +78,12 @@ type kubeOpts struct { type kubeProvider struct { host *provider.HostClient canceler *cancellationContext - client discovery.CachedDiscoveryInterface - pool dynamic.ClientPool name string version string providerPrefix string opts kubeOpts + + clientSet *clients.DynamicClientSet } var _ pulumirpc.ResourceProviderServer = (*kubeProvider)(nil) @@ -144,28 +142,17 @@ func (k *kubeProvider) Configure(_ context.Context, req *pulumirpc.ConfigureRequ kubeconfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) } - // Configure the discovery client. conf, err := kubeconfig.ClientConfig() if err != nil { - return nil, fmt.Errorf("Unable to read kubectl config: %v", err) + return nil, fmt.Errorf("unable to read kubectl config: %v", err) } - disco, err := discovery.NewDiscoveryClientForConfig(conf) + cs, err := clients.NewDynamicClientSet(conf) if err != nil { return nil, err } + k.clientSet = cs - // Cache the discovery information (OpenAPI schema, etc.) so we don't have to retrieve it for - // every request. - discoCache := client.NewMemcachedDiscoveryClient(disco) - mapper := discovery.NewDeferredDiscoveryRESTMapper(discoCache, dynamic.VersionInterfaces) - pathresolver := dynamic.LegacyAPIPathResolverFunc - - // Create client pool, reusing one client per API group (e.g., one each for core, extensions, - // apps, etc.) - pool := dynamic.NewClientPool(conf, mapper, pathresolver) - - k.client, k.pool = discoCache, pool return &pbempty.Empty{}, nil } @@ -199,7 +186,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( return true } gv := gvk.GroupVersion() - rls, err := k.client.ServerResourcesForGroupVersion(gv.String()) + rls, err := k.clientSet.DiscoveryClientCached.ServerResourcesForGroupVersion(gv.String()) if err != nil { if !errors.IsNotFound(err) { glog.V(3).Infof("ServerResourcesForGroupVersion(%q) returned unexpected error %v", gv, err) @@ -244,7 +231,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( for k := range newInputs.GetAnnotations() { if strings.HasPrefix(k, annotationInternalPrefix) { failures = append(failures, &pulumirpc.CheckFailure{ - Reason: fmt.Sprintf("annotation '%s' uses illegal prefix `pulumi.com/internal`", k), + Reason: fmt.Sprintf("annotation %q uses illegal prefix `pulumi.com/internal`", k), }) } } @@ -273,7 +260,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( // does not know how to deal with the placeholder values for computed values. if !hasComputedValue(newInputs) { // Get OpenAPI schema for the GVK. - err = openapi.ValidateAgainstSchema(k.client, newInputs) + err = openapi.ValidateAgainstSchema(k.clientSet.DiscoveryClientCached, newInputs) // Validate the object according to the OpenAPI schema. if err != nil { resourceNotFound := errors.IsNotFound(err) || @@ -281,7 +268,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( k8sAPIUnreachable := strings.Contains(err.Error(), "connection refused") if resourceNotFound && gvkExists(gvk) { failures = append(failures, &pulumirpc.CheckFailure{ - Reason: fmt.Sprintf(" Found API Group, but it did not contain a schema for '%s'", gvk), + Reason: fmt.Sprintf(" Found API Group, but it did not contain a schema for %q", gvk), }) } else if k8sAPIUnreachable { k8sURL := "" @@ -297,7 +284,7 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) ( // If the schema doesn't exist, it could still be a CRD (which may not have a // schema). Thus, if we are directed to check resources even if they have unknown // types, we fail here. - return nil, fmt.Errorf("Unable to fetch schema: %v", err) + return nil, fmt.Errorf("unable to fetch schema: %v", err) } } } @@ -378,7 +365,7 @@ func (k *kubeProvider) Diff( // that object MUST have the same name. deleteBeforeReplace := // 1. We know resource must be replaced. - (len(replaces) > 0 && + len(replaces) > 0 && // 2. Object is NOT autonamed (i.e., user manually named it, and therefore we can't // auto-generate the name). !isAutonamed(newInputs) && @@ -386,7 +373,7 @@ func (k *kubeProvider) Diff( newInputs.GetName() == oldInputs.GetName() && // 4. The resource is being deployed to the same namespace (i.e., we aren't creating the // object in a new namespace and then deleting the old one). - canonicalNamespace(newInputs.GetNamespace()) == canonicalNamespace(oldInputs.GetNamespace())) + canonicalNamespace(newInputs.GetNamespace()) == canonicalNamespace(oldInputs.GetNamespace()) return &pulumirpc.DiffResponse{ Changes: hasChanges, @@ -398,7 +385,7 @@ func (k *kubeProvider) Diff( // Create allocates a new instance of the provided resource and returns its unique ID afterwards. // (The input ID must be blank.) If this call fails, the resource must not have been created (i.e., -// it is "transacational"). +// it is "transactional"). func (k *kubeProvider) Create( ctx context.Context, req *pulumirpc.CreateRequest, ) (*pulumirpc.CreateResponse, error) { @@ -418,7 +405,7 @@ func (k *kubeProvider) Create( label := fmt.Sprintf("%s.Create(%s)", k.label(), urn) glog.V(9).Infof("%s executing", label) - // Obtain client from pool for the resource we're creating. + // Parse inputs newResInputs, err := plugin.UnmarshalProperties(req.GetProperties(), plugin.MarshalOptions{ Label: fmt.Sprintf("%s.properties", label), KeepUnknowns: true, SkipNulls: true, }) @@ -427,8 +414,18 @@ func (k *kubeProvider) Create( } newInputs := propMapToUnstructured(newResInputs) - initialized, awaitErr := await.Creation(k.canceler.context, k.host, k.pool, k.client, - resource.URN(req.GetUrn()), newInputs) + config := await.CreateConfig{ + ProviderConfig: await.ProviderConfig{ + Context: k.canceler.context, + Host: k.host, + URN: resource.URN(req.GetUrn()), + ClientSet: k.clientSet, + }, + Inputs: newInputs, + Namespace: canonicalNamespace(newInputs.GetNamespace()), + } + + initialized, awaitErr := await.Creation(config) if awaitErr != nil { initErr, isInitErr := awaitErr.(await.InitializationError) if !isInitErr { @@ -451,18 +448,19 @@ func (k *kubeProvider) Create( if awaitErr != nil { // Resource was created but failed to initialize. Return live version of object so it can be // checkpointed. - return nil, initializationError(client.FqObjName(initialized), awaitErr, inputsAndComputed) + return nil, initializationError(FqObjName(initialized), awaitErr, inputsAndComputed) } // Invalidate the client cache if this was a CRD. This will require subsequent CR creations to // refresh the cache, at which point the CRD definition will be present, so that it doesn't fail // with an `errors.IsNotFound`. - if isCRD(newInputs) { - k.client.Invalidate() + if clients.IsCRD(newInputs) { + k.clientSet.DiscoveryClientCached.Invalidate() + k.clientSet.RESTMapper.Reset() } return &pulumirpc.CreateResponse{ - Id: client.FqObjName(initialized), Properties: inputsAndComputed, + Id: FqObjName(initialized), Properties: inputsAndComputed, }, nil } @@ -497,17 +495,25 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p return nil, err } // Ignore old state; we'll get it from Kubernetes later. - oldInputs, _ := parseCheckpointObject(oldState) + oldInputs, newInputs := parseCheckpointObject(oldState) - gvk, err := k.gvkFromURN(urn) - if err != nil { - return nil, err + if oldInputs.GroupVersionKind().Empty() { + oldInputs.SetGroupVersionKind(newInputs.GroupVersionKind()) } - gvk.Group = schemaGroupName(gvk.Group) - namespace, name := client.ParseFqName(req.GetId()) - liveObj, readErr := await.Read(k.canceler.context, k.host, k.pool, k.client, - resource.URN(req.GetUrn()), gvk, canonicalNamespace(namespace), name, oldInputs) + ns, name := ParseFqName(req.GetId()) + config := await.ReadConfig{ + ProviderConfig: await.ProviderConfig{ + Context: k.canceler.context, + Host: k.host, + URN: resource.URN(req.GetUrn()), + ClientSet: k.clientSet, + }, + Inputs: oldInputs, + Namespace: canonicalNamespace(ns), + Name: name, + } + liveObj, readErr := await.Read(config) if readErr != nil { glog.V(3).Infof("%v", readErr) @@ -533,7 +539,8 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p // initialize. } - id := client.FqObjName(liveObj) + // TODO(lblackstone): not sure why this is needed + id := FqObjName(liveObj) if reqID := req.GetId(); len(reqID) > 0 { id = reqID } @@ -626,8 +633,7 @@ func (k *kubeProvider) Update( label := fmt.Sprintf("%s.Update(%s)", k.label(), urn) glog.V(9).Infof("%s executing", label) - // Obtain new properties, create a Kubernetes `unstructured.Unstructured` that we can pass to the - // validation routines. + // Obtain old properties, create a Kubernetes `unstructured.Unstructured`. oldState, err := plugin.UnmarshalProperties(req.GetOlds(), plugin.MarshalOptions{ Label: fmt.Sprintf("%s.olds", label), KeepUnknowns: true, SkipNulls: true, }) @@ -637,8 +643,7 @@ func (k *kubeProvider) Update( // Ignore old state; we'll get it from Kubernetes later. oldInputs, _ := parseCheckpointObject(oldState) - // Obtain new properties, create a Kubernetes `unstructured.Unstructured` that we can pass to the - // validation routines. + // Obtain new properties, create a Kubernetes `unstructured.Unstructured`. newResInputs, err := plugin.UnmarshalProperties(req.GetNews(), plugin.MarshalOptions{ Label: fmt.Sprintf("%s.news", label), KeepUnknowns: true, SkipNulls: true, }) @@ -647,9 +652,20 @@ func (k *kubeProvider) Update( } newInputs := propMapToUnstructured(newResInputs) + ns, _ := ParseFqName(req.GetId()) + config := await.UpdateConfig{ + ProviderConfig: await.ProviderConfig{ + Context: k.canceler.context, + Host: k.host, + URN: resource.URN(req.GetUrn()), + ClientSet: k.clientSet, + }, + Previous: oldInputs, + Inputs: newInputs, + Namespace: canonicalNamespace(ns), + } // Apply update. - initialized, awaitErr := await.Update(k.canceler.context, k.host, k.pool, k.client, - resource.URN(req.GetUrn()), oldInputs, newInputs) + initialized, awaitErr := await.Update(config) if awaitErr != nil { var getErr error initialized, getErr = k.readLiveObject(newInputs) @@ -673,7 +689,7 @@ func (k *kubeProvider) Update( if awaitErr != nil { // Resource was updated/created but failed to initialize. Return live version of object so it // can be checkpointed. - return nil, initializationError(client.FqObjName(initialized), awaitErr, inputsAndComputed) + return nil, initializationError(FqObjName(initialized), awaitErr, inputsAndComputed) } return &pulumirpc.UpdateResponse{Properties: inputsAndComputed}, nil @@ -690,15 +706,28 @@ func (k *kubeProvider) Delete( // TODO(hausdorff): Propagate other options, like grace period through flags. - gvk, err := k.gvkFromURN(resource.URN(req.GetUrn())) + // Obtain new properties, create a Kubernetes `unstructured.Unstructured`. + oldState, err := plugin.UnmarshalProperties(req.GetProperties(), plugin.MarshalOptions{ + Label: fmt.Sprintf("%s.olds", label), KeepUnknowns: true, SkipNulls: true, + }) if err != nil { return nil, err } - gvk.Group = schemaGroupName(gvk.Group) + _, current := parseCheckpointObject(oldState) - namespace, name := client.ParseFqName(req.GetId()) + _, name := ParseFqName(req.GetId()) + config := await.DeleteConfig{ + ProviderConfig: await.ProviderConfig{ + Context: k.canceler.context, + Host: k.host, + URN: resource.URN(req.GetUrn()), + ClientSet: k.clientSet, + }, + Inputs: current, + Name: name, + } - err = await.Deletion(k.canceler.context, k.host, k.pool, k.client, urn, gvk, namespace, name) + err = await.Deletion(config) if err != nil { return nil, err } @@ -736,7 +765,7 @@ func (k *kubeProvider) label() string { func (k *kubeProvider) gvkFromURN(urn resource.URN) (schema.GroupVersionKind, error) { // Strip prefix. s := string(urn.Type()) - contract.Assertf(strings.HasPrefix(s, k.providerPrefix), "Kubernetes GVK is: '%s'", string(urn)) + contract.Assertf(strings.HasPrefix(s, k.providerPrefix), "Kubernetes GVK is: %q", string(urn)) s = s[len(k.providerPrefix):] // Emit GVK. @@ -744,10 +773,10 @@ func (k *kubeProvider) gvkFromURN(urn resource.URN) (schema.GroupVersionKind, er gv := strings.Split(gvk[0], "/") if len(gvk) < 2 { return schema.GroupVersionKind{}, - fmt.Errorf("GVK must have both an apiVersion and a Kind: '%s'", s) + fmt.Errorf("GVK must have both an apiVersion and a Kind: %q", s) } else if len(gv) != 2 { return schema.GroupVersionKind{}, - fmt.Errorf("apiVersion does not have both a group and a version: '%s'", s) + fmt.Errorf("apiVersion does not have both a group and a version: %q", s) } return schema.GroupVersionKind{ @@ -757,27 +786,15 @@ func (k *kubeProvider) gvkFromURN(urn resource.URN) (schema.GroupVersionKind, er }, nil } -func (k *kubeProvider) readLiveObject( - obj *unstructured.Unstructured, -) (*unstructured.Unstructured, error) { - // Retrieve live version of last submitted version of object. - clientForResource, err := client.FromResource(k.pool, k.client, obj) +func (k *kubeProvider) readLiveObject(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + rc, err := k.clientSet.ResourceClientForObject(obj) if err != nil { return nil, err } // Get the "live" version of the last submitted object. This is necessary because the server may // have populated some fields automatically, updated status fields, and so on. - return clientForResource.Get(obj.GetName(), metav1.GetOptions{}) -} - -func schemaGroupName(group string) string { - switch group { - case "core": - return "" - default: - return group - } + return rc.Get(obj.GetName(), metav1.GetOptions{}) } func propMapToUnstructured(pm resource.PropertyMap) *unstructured.Unstructured { @@ -832,9 +849,3 @@ func canonicalNamespace(ns string) string { } return ns } - -// isCRD returns true if a Kubernetes resource is a CRD. -func isCRD(obj *unstructured.Unstructured) bool { - return obj.GetKind() == "CustomResourceDefinition" && - strings.HasPrefix(obj.GetAPIVersion(), "apiextensions.k8s.io/") -} diff --git a/pkg/provider/util.go b/pkg/provider/util.go index 1dff1b792d..e8afbf078c 100644 --- a/pkg/provider/util.go +++ b/pkg/provider/util.go @@ -1,7 +1,11 @@ package provider import ( + "fmt" + "strings" + "github.com/pulumi/pulumi/pkg/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -30,3 +34,30 @@ func hasComputedValue(obj *unstructured.Unstructured) bool { return false } + +// -------------------------------------------------------------------------- +// Names and namespaces. +// -------------------------------------------------------------------------- + +// FqObjName returns "namespace.name" +func FqObjName(o metav1.Object) string { + return FqName(o.GetNamespace(), o.GetName()) +} + +// ParseFqName will parse a fully-qualified Kubernetes object name. +func ParseFqName(id string) (namespace, name string) { + split := strings.Split(id, "/") + if len(split) == 1 { + return "", split[0] + } + namespace, name = split[0], split[1] + return +} + +// FqName returns "namespace/name" +func FqName(namespace, name string) string { + if namespace == "" { + return name + } + return fmt.Sprintf("%s/%s", namespace, name) +} diff --git a/pkg/provider/util_test.go b/pkg/provider/util_test.go index 617a7cf152..9425e4bb54 100644 --- a/pkg/provider/util_test.go +++ b/pkg/provider/util_test.go @@ -98,3 +98,24 @@ func TestHasComputedValue(t *testing.T) { assert.Equal(t, test.hasComputedValue, hasComputedValue(test.obj), test.name) } } + +func TestFqName(t *testing.T) { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "tests/v1alpha1", + "kind": "Test", + "metadata": map[string]interface{}{ + "name": "myname", + }, + }, + } + + if n := FqName(obj.GetNamespace(), obj.GetName()); n != "myname" { + t.Errorf("Got %q for %v", n, obj) + } + + obj.SetNamespace("mynamespace") + if n := FqName(obj.GetNamespace(), obj.GetName()); n != "mynamespace/myname" { + t.Errorf("Got %q for %v", n, obj) + } +} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index a74e06ae5e..700a2590a5 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -54,7 +54,7 @@ func ForObject( obj, err := clientForResource.Get(name, metav1.GetOptions{}) if err != nil { // Log the error. - glog.V(3).Infof("Received error polling for '%s': %#v", name, err) + glog.V(3).Infof("Received error polling for %q: %#v", name, err) return nil, err } return obj, nil diff --git a/tests/integration/get/get_test.go b/tests/integration/get/get_test.go index 9f7ade6500..93549bbe9d 100644 --- a/tests/integration/get/get_test.go +++ b/tests/integration/get/get_test.go @@ -28,27 +28,27 @@ func TestGet(t *testing.T) { ExpectRefreshChanges: true, ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) { assert.NotNil(t, stackInfo.Deployment) - assert.Equal(t, 6, len(stackInfo.Deployment.Resources)) + assert.Equal(t, 5, len(stackInfo.Deployment.Resources)) tests.SortResourcesByURN(stackInfo) - stackRes := stackInfo.Deployment.Resources[5] + stackRes := stackInfo.Deployment.Resources[4] assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) - provRes := stackInfo.Deployment.Resources[4] + provRes := stackInfo.Deployment.Resources[3] assert.True(t, providers.IsProviderType(provRes.URN.Type())) // - // Assert we can use .get to retrieve the Kubernetes dashboard Service. + // Assert we can use .get to retrieve the kube-api Service. // - pod := stackInfo.Deployment.Resources[1] - assert.Equal(t, "kube-dashboard", string(pod.URN.Name())) - step1Name, _ := openapi.Pluck(pod.Outputs, "metadata", "name") + service := stackInfo.Deployment.Resources[1] + assert.Equal(t, "kube-api", string(service.URN.Name())) + step1Name, _ := openapi.Pluck(service.Outputs, "metadata", "name") assert.Equal(t, "kubernetes", step1Name.(string)) // - // Assert we can use .get to retrieve CRDs. + // Assert that CRD and CR exist // crd := stackInfo.Deployment.Resources[0] @@ -57,9 +57,32 @@ func TestGet(t *testing.T) { ct1 := stackInfo.Deployment.Resources[2] assert.Equal(t, "my-new-cron-object", string(ct1.URN.Name())) - ct2 := stackInfo.Deployment.Resources[3] - assert.Equal(t, "my-new-cron-object-get", string(ct2.URN.Name())) }, + EditDirs: []integration.EditDir{ + { + Dir: "step2", + Additive: true, + ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) { + assert.NotNil(t, stackInfo.Deployment) + assert.Equal(t, 3, len(stackInfo.Deployment.Resources)) + + tests.SortResourcesByURN(stackInfo) + + stackRes := stackInfo.Deployment.Resources[2] + assert.Equal(t, resource.RootStackType, stackRes.URN.Type()) + + provRes := stackInfo.Deployment.Resources[1] + assert.True(t, providers.IsProviderType(provRes.URN.Type())) + + // + // Assert we can use .get to retrieve CRDs. + // + + ct2 := stackInfo.Deployment.Resources[0] + assert.Equal(t, "my-new-cron-object-get", string(ct2.URN.Name())) + }, + }, + }, }) } diff --git a/tests/integration/get/step1/index.ts b/tests/integration/get/step1/index.ts index 142dd4eb39..5c33671420 100644 --- a/tests/integration/get/step1/index.ts +++ b/tests/integration/get/step1/index.ts @@ -3,10 +3,10 @@ import * as k8s from "@pulumi/kubernetes"; // -// `get`s the Kubernetes Dashboard, which is deployed by default in minikube. +// `get`s the Kubernetes API service. // -k8s.core.v1.Service.get("kube-dashboard", "kubernetes"); +k8s.core.v1.Service.get("kube-api", "default/kubernetes"); // // Create a CustomResourceDefinition, a CustomResource, and then `.get` it. @@ -37,9 +37,3 @@ new k8s.apiextensions.CustomResource( }, { dependsOn: ct } ); - -const ctObj = k8s.apiextensions.CustomResource.get("my-new-cron-object-get", { - apiVersion: "stable.example.com/v1", - kind: "CronTab", - id: "my-new-cron-object" -}); diff --git a/tests/integration/get/step2/Pulumi.yaml b/tests/integration/get/step2/Pulumi.yaml new file mode 100644 index 0000000000..d2e909d533 --- /dev/null +++ b/tests/integration/get/step2/Pulumi.yaml @@ -0,0 +1,3 @@ +name: get-tests +description: A program that tests that we can use `CustomResource#get` on Kubernetes resources. +runtime: nodejs diff --git a/tests/integration/get/step2/index.ts b/tests/integration/get/step2/index.ts new file mode 100644 index 0000000000..5d47b74ae5 --- /dev/null +++ b/tests/integration/get/step2/index.ts @@ -0,0 +1,10 @@ +// Copyright 2016-2018, Pulumi Corporation. All rights reserved. + +import * as k8s from "@pulumi/kubernetes"; + + +k8s.apiextensions.CustomResource.get("my-new-cron-object-get", { + apiVersion: "stable.example.com/v1", + kind: "CronTab", + id: "my-new-cron-object", +}); diff --git a/tests/integration/get/step2/package.json b/tests/integration/get/step2/package.json new file mode 100644 index 0000000000..562ce58cde --- /dev/null +++ b/tests/integration/get/step2/package.json @@ -0,0 +1,13 @@ +{ + "name": "steps", + "version": "0.1.0", + "dependencies": { + "@pulumi/pulumi": "^0.15.1" + }, + "devDependencies": { + "typescript": "^2.5.3" + }, + "peerDependencies": { + "@pulumi/kubernetes": "latest" + } +} diff --git a/tests/integration/get/step2/tsconfig.json b/tests/integration/get/step2/tsconfig.json new file mode 100644 index 0000000000..5dacccbd42 --- /dev/null +++ b/tests/integration/get/step2/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "outDir": "bin", + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "stripInternal": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "strictNullChecks": true + }, + "files": [ + "index.ts" + ] +} +