diff --git a/contrib/cmd/aws-actuator-test/main.go b/contrib/cmd/aws-actuator-test/main.go index 7df95fa20..c7cf1db80 100644 --- a/contrib/cmd/aws-actuator-test/main.go +++ b/contrib/cmd/aws-actuator-test/main.go @@ -78,12 +78,12 @@ func deleteClusterMachine(instanceId string) error { } func clusterMachineExists(instanceId string) error { - _, machine := testClusterAPIResources("any") + cluster, machine := testClusterAPIResources("any") machine.Annotations = map[string]string{ instanceIDAnnotation: instanceId, } actuator := aws.NewActuator(nil, nil, log.WithField("example", "delete-machine"), "us-east-1c") - exists, err := actuator.Exists(machine) + exists, err := actuator.Exists(cluster, machine) if err != nil { return err } diff --git a/glide.lock b/glide.lock index 2220131fa..e0e7fce63 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 15d40998b89952af6e466f19c373d9d195fdbbdd6566a567a2b62aa53e57ee65 -updated: 2018-05-08T17:34:33.238841971-04:00 +hash: fd7865846480287d3a1fc4f9d47be4579b0be1a57394189edcfecf056b8635b0 +updated: 2018-05-30T10:42:18.434085638-03:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -172,9 +172,9 @@ imports: - compiler - extensions - name: github.com/gorilla/context - version: 215affda49addc4c8ef7e2534915df2c8c35c6cd + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/mux - version: 8096f47503459bcc74d1f4c487b7e6e42e5746b5 + version: e0b5abaaae35242fa74372f26c24107711702a4e - name: github.com/gregjones/httpcache version: 787624de3eb7bd915c329cba748687a3b22666a6 subpackages: @@ -198,7 +198,7 @@ imports: - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/jmespath/go-jmespath - version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 + version: c2b33e8439af944379acbdd9c3a5fe0bc44bd8a5 - name: github.com/jonboulle/clockwork version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982 - name: github.com/json-iterator/go @@ -213,7 +213,7 @@ imports: - pkg/builders - pkg/controller - name: github.com/kubernetes/repo-infra - version: e26fc85d14a1d3dc25569831acc06919673c545a + version: 3c350a455362b622fe786e63f8f07b2a87f54f7b - name: github.com/mailru/easyjson version: 2f5df55504ebc322e4d52d34df6a1f5b503bf26d subpackages: @@ -269,7 +269,7 @@ imports: - name: github.com/spf13/pflag version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 - name: github.com/stretchr/testify - version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + version: c679ae2cc0cb27ec3293fea7e254e47386f05d69 subpackages: - assert - name: github.com/ugorji/go @@ -694,7 +694,7 @@ imports: - pkg/util - pkg/util/proto - name: sigs.k8s.io/cluster-api - version: 5d5cdec6cf1208bfb4f63e3b2cd48154a12d52a5 + version: 851a4b40eedd60ffaab6ab25d5ba40412523e4e7 subpackages: - pkg/apis - pkg/apis/cluster @@ -702,8 +702,10 @@ imports: - pkg/apis/cluster/install - pkg/apis/cluster/v1alpha1 - pkg/client/clientset_generated/clientset + - pkg/client/clientset_generated/clientset/fake - pkg/client/clientset_generated/clientset/scheme - pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1 + - pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1/fake - pkg/client/informers_generated/externalversions - pkg/client/informers_generated/externalversions/cluster - pkg/client/informers_generated/externalversions/cluster/v1alpha1 @@ -711,6 +713,7 @@ imports: - pkg/client/listers_generated/cluster/v1alpha1 - pkg/controller/config - pkg/controller/machine + - pkg/controller/noderefutil - pkg/controller/sharedinformers - util testImports: diff --git a/glide.yaml b/glide.yaml index 262cbda77..8f9550b5e 100644 --- a/glide.yaml +++ b/glide.yaml @@ -24,3 +24,4 @@ import: - package: github.com/kubernetes-incubator/apiserver-builder version: ^1.9.0-alpha.3 - package: sigs.k8s.io/cluster-api +- package: github.com/stretchr/testify diff --git a/pkg/clusterapi/aws/actuator.go b/pkg/clusterapi/aws/actuator.go index 57a5b5fdf..0b5775c4c 100644 --- a/pkg/clusterapi/aws/actuator.go +++ b/pkg/clusterapi/aws/actuator.go @@ -332,7 +332,7 @@ func (a *Actuator) CreateMachine(cluster *clusterv1.Cluster, machine *clusterv1. } // Delete deletes a machine and updates its finalizer -func (a *Actuator) Delete(machine *clusterv1.Machine) error { +func (a *Actuator) Delete(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { mLog := clustoplog.WithMachine(a.logger, machine) mLog.Debugf("Delete %s/%s", machine.Namespace, machine.Name) if err := a.DeleteMachine(machine); err != nil { @@ -396,7 +396,7 @@ func (a *Actuator) Update(c *clusterv1.Cluster, machine *clusterv1.Machine) erro } // Exists determines if the given machine currently exists. -func (a *Actuator) Exists(machine *clusterv1.Machine) (bool, error) { +func (a *Actuator) Exists(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (bool, error) { mLog := clustoplog.WithMachine(a.logger, machine) mLog.Debugf("checking if machine exists") diff --git a/vendor/github.com/gorilla/context/.travis.yml b/vendor/github.com/gorilla/context/.travis.yml index 6796581fb..6f440f1e4 100644 --- a/vendor/github.com/gorilla/context/.travis.yml +++ b/vendor/github.com/gorilla/context/.travis.yml @@ -1,9 +1,19 @@ language: go +sudo: false -go: - - 1.0 - - 1.1 - - 1.2 - - 1.3 - - 1.4 - - tip +matrix: + include: + - go: 1.3 + - go: 1.4 + - go: 1.5 + - go: 1.6 + - go: 1.7 + - go: tip + allow_failures: + - go: tip + +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d .) + - go vet $(go list ./... | grep -v /vendor/) + - go test -v -race ./... diff --git a/vendor/github.com/gorilla/context/README.md b/vendor/github.com/gorilla/context/README.md index c60a31b05..08f86693b 100644 --- a/vendor/github.com/gorilla/context/README.md +++ b/vendor/github.com/gorilla/context/README.md @@ -4,4 +4,7 @@ context gorilla/context is a general purpose registry for global request variables. +> Note: gorilla/context, having been born well before `context.Context` existed, does not play well +> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`. + Read the full documentation here: http://www.gorillatoolkit.org/pkg/context diff --git a/vendor/github.com/gorilla/context/context_test.go b/vendor/github.com/gorilla/context/context_test.go index 9814c501e..d70e91a23 100644 --- a/vendor/github.com/gorilla/context/context_test.go +++ b/vendor/github.com/gorilla/context/context_test.go @@ -69,7 +69,7 @@ func TestContext(t *testing.T) { // GetAllOk() for empty request values, ok = GetAllOk(emptyR) - assertEqual(value, nil) + assertEqual(len(values), 0) assertEqual(ok, false) // Delete() diff --git a/vendor/github.com/gorilla/context/doc.go b/vendor/github.com/gorilla/context/doc.go index 73c740031..448d1bfca 100644 --- a/vendor/github.com/gorilla/context/doc.go +++ b/vendor/github.com/gorilla/context/doc.go @@ -5,6 +5,12 @@ /* Package context stores values shared during a request lifetime. +Note: gorilla/context, having been born well before `context.Context` existed, +does not play well > with the shallow copying of the request that +[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) +(added to net/http Go 1.7 onwards) performs. You should either use *just* +gorilla/context, or moving forward, the new `http.Request.Context()`. + For example, a router can set variables extracted from the URL and later application handlers can access those values, or it can be used to store sessions values to be saved at the end of a request. There are several diff --git a/vendor/github.com/gorilla/mux/.travis.yml b/vendor/github.com/gorilla/mux/.travis.yml index d87d46576..ad0935dbd 100644 --- a/vendor/github.com/gorilla/mux/.travis.yml +++ b/vendor/github.com/gorilla/mux/.travis.yml @@ -1,7 +1,23 @@ language: go +sudo: false -go: - - 1.0 - - 1.1 - - 1.2 - - tip +matrix: + include: + - go: 1.5.x + - go: 1.6.x + - go: 1.7.x + - go: 1.8.x + - go: 1.9.x + - go: 1.10.x + - go: tip + allow_failures: + - go: tip + +install: + - # Skip + +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md b/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..232be82e4 --- /dev/null +++ b/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +**What version of Go are you running?** (Paste the output of `go version`) + + +**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`) + + +**Describe your problem** (and what you have tried so far) + + +**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it) + diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md index e60301b03..e424397ac 100644 --- a/vendor/github.com/gorilla/mux/README.md +++ b/vendor/github.com/gorilla/mux/README.md @@ -1,7 +1,649 @@ -mux -=== -[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) +# gorilla/mux -gorilla/mux is a powerful URL router and dispatcher. +[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) +[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) +[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) -Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux +![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png) + +http://www.gorillatoolkit.org/pkg/mux + +Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to +their respective handler. + +The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: + +* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. +* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. +* URL hosts, paths and query values can have variables with an optional regular expression. +* Registered URLs can be built, or "reversed", which helps maintaining references to resources. +* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. + +--- + +* [Install](#install) +* [Examples](#examples) +* [Matching Routes](#matching-routes) +* [Static Files](#static-files) +* [Registered URLs](#registered-urls) +* [Walking Routes](#walking-routes) +* [Graceful Shutdown](#graceful-shutdown) +* [Middleware](#middleware) +* [Testing Handlers](#testing-handlers) +* [Full Example](#full-example) + +--- + +## Install + +With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: + +```sh +go get -u github.com/gorilla/mux +``` + +## Examples + +Let's start registering a couple of URL paths and handlers: + +```go +func main() { + r := mux.NewRouter() + r.HandleFunc("/", HomeHandler) + r.HandleFunc("/products", ProductsHandler) + r.HandleFunc("/articles", ArticlesHandler) + http.Handle("/", r) +} +``` + +Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters. + +Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: + +```go +r := mux.NewRouter() +r.HandleFunc("/products/{key}", ProductHandler) +r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) +r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) +``` + +The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: + +```go +func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Category: %v\n", vars["category"]) +} +``` + +And this is all you need to know about the basic usage. More advanced options are explained below. + +### Matching Routes + +Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: + +```go +r := mux.NewRouter() +// Only matches if domain is "www.example.com". +r.Host("www.example.com") +// Matches a dynamic subdomain. +r.Host("{subdomain:[a-z]+}.domain.com") +``` + +There are several other matchers that can be added. To match path prefixes: + +```go +r.PathPrefix("/products/") +``` + +...or HTTP methods: + +```go +r.Methods("GET", "POST") +``` + +...or URL schemes: + +```go +r.Schemes("https") +``` + +...or header values: + +```go +r.Headers("X-Requested-With", "XMLHttpRequest") +``` + +...or query values: + +```go +r.Queries("key", "value") +``` + +...or to use a custom matcher function: + +```go +r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { + return r.ProtoMajor == 0 +}) +``` + +...and finally, it is possible to combine several matchers in a single route: + +```go +r.HandleFunc("/products", ProductsHandler). + Host("www.example.com"). + Methods("GET"). + Schemes("http") +``` + +Routes are tested in the order they were added to the router. If two routes match, the first one wins: + +```go +r := mux.NewRouter() +r.HandleFunc("/specific", specificHandler) +r.PathPrefix("/").Handler(catchAllHandler) +``` + +Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". + +For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it: + +```go +r := mux.NewRouter() +s := r.Host("www.example.com").Subrouter() +``` + +Then register routes in the subrouter: + +```go +s.HandleFunc("/products/", ProductsHandler) +s.HandleFunc("/products/{key}", ProductHandler) +s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) +``` + +The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. + +Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. + +There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: + +```go +r := mux.NewRouter() +s := r.PathPrefix("/products").Subrouter() +// "/products/" +s.HandleFunc("/", ProductsHandler) +// "/products/{key}/" +s.HandleFunc("/{key}/", ProductHandler) +// "/products/{key}/details" +s.HandleFunc("/{key}/details", ProductDetailsHandler) +``` + + +### Static Files + +Note that the path provided to `PathPrefix()` represents a "wildcard": calling +`PathPrefix("/static/").Handler(...)` means that the handler will be passed any +request that matches "/static/\*". This makes it easy to serve static files with mux: + +```go +func main() { + var dir string + + flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") + flag.Parse() + r := mux.NewRouter() + + // This will serve files under http://localhost:8000/static/ + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) + + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) +} +``` + +### Registered URLs + +Now let's see how to build registered URLs. + +Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: + +```go +r := mux.NewRouter() +r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). + Name("article") +``` + +To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: + +```go +url, err := r.Get("article").URL("category", "technology", "id", "42") +``` + +...and the result will be a `url.URL` with the following path: + +``` +"/articles/technology/42" +``` + +This also works for host and query value variables: + +```go +r := mux.NewRouter() +r.Host("{subdomain}.domain.com"). + Path("/articles/{category}/{id:[0-9]+}"). + Queries("filter", "{filter}"). + HandlerFunc(ArticleHandler). + Name("article") + +// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" +url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42", + "filter", "gorilla") +``` + +All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. + +Regex support also exists for matching Headers within a route. For example, we could do: + +```go +r.HeadersRegexp("Content-Type", "application/(text|json)") +``` + +...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` + +There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do: + +```go +// "http://news.domain.com/" +host, err := r.Get("article").URLHost("subdomain", "news") + +// "/articles/technology/42" +path, err := r.Get("article").URLPath("category", "technology", "id", "42") +``` + +And if you use subrouters, host and path defined separately can be built as well: + +```go +r := mux.NewRouter() +s := r.Host("{subdomain}.domain.com").Subrouter() +s.Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + +// "http://news.domain.com/articles/technology/42" +url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") +``` + +### Walking Routes + +The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, +the following prints all of the registered routes: + +```go +package main + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +func handler(w http.ResponseWriter, r *http.Request) { + return +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/", handler) + r.HandleFunc("/products", handler).Methods("POST") + r.HandleFunc("/articles", handler).Methods("GET") + r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT") + r.HandleFunc("/authors", handler).Queries("surname", "{surname}") + err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + pathTemplate, err := route.GetPathTemplate() + if err == nil { + fmt.Println("ROUTE:", pathTemplate) + } + pathRegexp, err := route.GetPathRegexp() + if err == nil { + fmt.Println("Path regexp:", pathRegexp) + } + queriesTemplates, err := route.GetQueriesTemplates() + if err == nil { + fmt.Println("Queries templates:", strings.Join(queriesTemplates, ",")) + } + queriesRegexps, err := route.GetQueriesRegexp() + if err == nil { + fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ",")) + } + methods, err := route.GetMethods() + if err == nil { + fmt.Println("Methods:", strings.Join(methods, ",")) + } + fmt.Println() + return nil + }) + + if err != nil { + fmt.Println(err) + } + + http.Handle("/", r) +} +``` + +### Graceful Shutdown + +Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`: + +```go +package main + +import ( + "context" + "flag" + "log" + "net/http" + "os" + "os/signal" + "time" + + "github.com/gorilla/mux" +) + +func main() { + var wait time.Duration + flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") + flag.Parse() + + r := mux.NewRouter() + // Add your routes as needed + + srv := &http.Server{ + Addr: "0.0.0.0:8080", + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + Handler: r, // Pass our instance of gorilla/mux in. + } + + // Run our server in a goroutine so that it doesn't block. + go func() { + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), wait) + defer cancel() + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} +``` + +### Middleware + +Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters. +Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking. + +Mux middlewares are defined using the de facto standard type: + +```go +type MiddlewareFunc func(http.Handler) http.Handler +``` + +Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers. + +A very basic middleware which logs the URI of the request being handled could be written as: + +```go +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Do stuff here + log.Println(r.RequestURI) + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + }) +} +``` + +Middlewares can be added to a router using `Router.Use()`: + +```go +r := mux.NewRouter() +r.HandleFunc("/", handler) +r.Use(loggingMiddleware) +``` + +A more complex authentication middleware, which maps session token to users, could be written as: + +```go +// Define our struct +type authenticationMiddleware struct { + tokenUsers map[string]string +} + +// Initialize it somewhere +func (amw *authenticationMiddleware) Populate() { + amw.tokenUsers["00000000"] = "user0" + amw.tokenUsers["aaaaaaaa"] = "userA" + amw.tokenUsers["05f717e5"] = "randomUser" + amw.tokenUsers["deadbeef"] = "user0" +} + +// Middleware function, which will be called for each request +func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("X-Session-Token") + + if user, found := amw.tokenUsers[token]; found { + // We found the token in our map + log.Printf("Authenticated user %s\n", user) + // Pass down the request to the next middleware (or final handler) + next.ServeHTTP(w, r) + } else { + // Write an error and stop the handler chain + http.Error(w, "Forbidden", http.StatusForbidden) + } + }) +} +``` + +```go +r := mux.NewRouter() +r.HandleFunc("/", handler) + +amw := authenticationMiddleware{} +amw.Populate() + +r.Use(amw.Middleware) +``` + +Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. + +### Testing Handlers + +Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. + +First, our simple HTTP handler: + +```go +// endpoints.go +package main + +func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { + // A very simple health check. + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + + // In the future we could report back on the status of our DB, or our cache + // (e.g. Redis) by performing a simple PING, and include them in the response. + io.WriteString(w, `{"alive": true}`) +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/health", HealthCheckHandler) + + log.Fatal(http.ListenAndServe("localhost:8080", r)) +} +``` + +Our test code: + +```go +// endpoints_test.go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthCheckHandler(t *testing.T) { + // Create a request to pass to our handler. We don't have any query parameters for now, so we'll + // pass 'nil' as the third parameter. + req, err := http.NewRequest("GET", "/health", nil) + if err != nil { + t.Fatal(err) + } + + // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. + rr := httptest.NewRecorder() + handler := http.HandlerFunc(HealthCheckHandler) + + // Our handlers satisfy http.Handler, so we can call their ServeHTTP method + // directly and pass in our Request and ResponseRecorder. + handler.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + expected := `{"alive": true}` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } +} +``` + +In the case that our routes have [variables](#examples), we can pass those in the request. We could write +[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple +possible route variables as needed. + +```go +// endpoints.go +func main() { + r := mux.NewRouter() + // A route with a route variable: + r.HandleFunc("/metrics/{type}", MetricsHandler) + + log.Fatal(http.ListenAndServe("localhost:8080", r)) +} +``` + +Our test file, with a table-driven test of `routeVariables`: + +```go +// endpoints_test.go +func TestMetricsHandler(t *testing.T) { + tt := []struct{ + routeVariable string + shouldPass bool + }{ + {"goroutines", true}, + {"heap", true}, + {"counters", true}, + {"queries", true}, + {"adhadaeqm3k", false}, + } + + for _, tc := range tt { + path := fmt.Sprintf("/metrics/%s", tc.routeVariable) + req, err := http.NewRequest("GET", path, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + // Need to create a router that we can pass the request through so that the vars will be added to the context + router := mux.NewRouter() + router.HandleFunc("/metrics/{type}", MetricsHandler) + router.ServeHTTP(rr, req) + + // In this case, our MetricsHandler returns a non-200 response + // for a route variable it doesn't know about. + if rr.Code == http.StatusOK && !tc.shouldPass { + t.Errorf("handler should have failed on routeVariable %s: got %v want %v", + tc.routeVariable, rr.Code, http.StatusOK) + } + } +} +``` + +## Full Example + +Here's a complete, runnable example of a small `mux` based server: + +```go +package main + +import ( + "net/http" + "log" + "github.com/gorilla/mux" +) + +func YourHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Gorilla!\n")) +} + +func main() { + r := mux.NewRouter() + // Routes consist of a path and a handler function. + r.HandleFunc("/", YourHandler) + + // Bind to a port and pass our router in + log.Fatal(http.ListenAndServe(":8000", r)) +} +``` + +## License + +BSD licensed. See the LICENSE file for details. diff --git a/vendor/github.com/gorilla/mux/bench_test.go b/vendor/github.com/gorilla/mux/bench_test.go index c5f97b2b2..522156dcc 100644 --- a/vendor/github.com/gorilla/mux/bench_test.go +++ b/vendor/github.com/gorilla/mux/bench_test.go @@ -6,6 +6,7 @@ package mux import ( "net/http" + "net/http/httptest" "testing" ) @@ -19,3 +20,30 @@ func BenchmarkMux(b *testing.B) { router.ServeHTTP(nil, request) } } + +func BenchmarkMuxAlternativeInRegexp(b *testing.B) { + router := new(Router) + handler := func(w http.ResponseWriter, r *http.Request) {} + router.HandleFunc("/v1/{v1:(?:a|b)}", handler) + + requestA, _ := http.NewRequest("GET", "/v1/a", nil) + requestB, _ := http.NewRequest("GET", "/v1/b", nil) + for i := 0; i < b.N; i++ { + router.ServeHTTP(nil, requestA) + router.ServeHTTP(nil, requestB) + } +} + +func BenchmarkManyPathVariables(b *testing.B) { + router := new(Router) + handler := func(w http.ResponseWriter, r *http.Request) {} + router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler) + + matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil) + notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil) + recorder := httptest.NewRecorder() + for i := 0; i < b.N; i++ { + router.ServeHTTP(nil, matchingRequest) + router.ServeHTTP(recorder, notMatchingRequest) + } +} diff --git a/vendor/github.com/gorilla/mux/context_gorilla.go b/vendor/github.com/gorilla/mux/context_gorilla.go new file mode 100644 index 000000000..d7adaa8fa --- /dev/null +++ b/vendor/github.com/gorilla/mux/context_gorilla.go @@ -0,0 +1,26 @@ +// +build !go1.7 + +package mux + +import ( + "net/http" + + "github.com/gorilla/context" +) + +func contextGet(r *http.Request, key interface{}) interface{} { + return context.Get(r, key) +} + +func contextSet(r *http.Request, key, val interface{}) *http.Request { + if val == nil { + return r + } + + context.Set(r, key, val) + return r +} + +func contextClear(r *http.Request) { + context.Clear(r) +} diff --git a/vendor/github.com/gorilla/mux/context_gorilla_test.go b/vendor/github.com/gorilla/mux/context_gorilla_test.go new file mode 100644 index 000000000..ffaf384c0 --- /dev/null +++ b/vendor/github.com/gorilla/mux/context_gorilla_test.go @@ -0,0 +1,40 @@ +// +build !go1.7 + +package mux + +import ( + "net/http" + "testing" + + "github.com/gorilla/context" +) + +// Tests that the context is cleared or not cleared properly depending on +// the configuration of the router +func TestKeepContext(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + res := new(http.ResponseWriter) + r.ServeHTTP(*res, req) + + if _, ok := context.GetOk(req, "t"); ok { + t.Error("Context should have been cleared at end of request") + } + + r.KeepContext = true + + req, _ = http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + r.ServeHTTP(*res, req) + if _, ok := context.GetOk(req, "t"); !ok { + t.Error("Context should NOT have been cleared at end of request") + } + +} diff --git a/vendor/github.com/gorilla/mux/context_native.go b/vendor/github.com/gorilla/mux/context_native.go new file mode 100644 index 000000000..209cbea7d --- /dev/null +++ b/vendor/github.com/gorilla/mux/context_native.go @@ -0,0 +1,24 @@ +// +build go1.7 + +package mux + +import ( + "context" + "net/http" +) + +func contextGet(r *http.Request, key interface{}) interface{} { + return r.Context().Value(key) +} + +func contextSet(r *http.Request, key, val interface{}) *http.Request { + if val == nil { + return r + } + + return r.WithContext(context.WithValue(r.Context(), key, val)) +} + +func contextClear(r *http.Request) { + return +} diff --git a/vendor/github.com/gorilla/mux/context_native_test.go b/vendor/github.com/gorilla/mux/context_native_test.go new file mode 100644 index 000000000..c150edf01 --- /dev/null +++ b/vendor/github.com/gorilla/mux/context_native_test.go @@ -0,0 +1,32 @@ +// +build go1.7 + +package mux + +import ( + "context" + "net/http" + "testing" + "time" +) + +func TestNativeContextMiddleware(t *testing.T) { + withTimeout := func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), time.Minute) + defer cancel() + h.ServeHTTP(w, r.WithContext(ctx)) + }) + } + + r := NewRouter() + r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := Vars(r) + if vars["foo"] != "bar" { + t.Fatal("Expected foo var to be set") + } + }))) + + rec := NewRecorder() + req := newRequest("GET", "/path/bar") + r.ServeHTTP(rec, req) +} diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go index b2deed34c..38957deea 100644 --- a/vendor/github.com/gorilla/mux/doc.go +++ b/vendor/github.com/gorilla/mux/doc.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package gorilla/mux implements a request router and dispatcher. +Package mux implements a request router and dispatcher. The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of @@ -12,8 +12,8 @@ or other conditions. The main features are: * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. - * URL hosts and paths can have variables with an optional regular - expression. + * URL hosts, paths and query values can have variables with an optional + regular expression. * Registered URLs can be built, or "reversed", which helps maintaining references to resources. * Routes can be used as subrouters: nested routes are only tested if the @@ -47,12 +47,21 @@ variable will be anything until the next slash. For example: r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) +Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: + + r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) + The names are used to create a map of route variables which can be retrieved calling mux.Vars(): vars := mux.Vars(request) category := vars["category"] +Note that if any capturing groups are present, mux will panic() during parsing. To prevent +this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to +"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably +when capturing groups were present. + And this is all you need to know about the basic usage. More advanced options are explained below. @@ -60,8 +69,8 @@ Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: r := mux.NewRouter() - // Only matches if domain is "www.domain.com". - r.Host("www.domain.com") + // Only matches if domain is "www.example.com". + r.Host("www.example.com") // Matches a dynamic subdomain. r.Host("{subdomain:[a-z]+}.domain.com") @@ -89,12 +98,12 @@ There are several other matchers that can be added. To match path prefixes: r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 0 - }) + }) ...and finally, it is possible to combine several matchers in a single route: r.HandleFunc("/products", ProductsHandler). - Host("www.domain.com"). + Host("www.example.com"). Methods("GET"). Schemes("http") @@ -103,11 +112,11 @@ a way to group several routes that share the same requirements. We call it "subrouting". For example, let's say we have several URLs that should only match when the -host is "www.domain.com". Create a route for that host and get a "subrouter" +host is "www.example.com". Create a route for that host and get a "subrouter" from it: r := mux.NewRouter() - s := r.Host("www.domain.com").Subrouter() + s := r.Host("www.example.com").Subrouter() Then register routes in the subrouter: @@ -116,7 +125,7 @@ Then register routes in the subrouter: s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) The three URL paths we registered above will only be tested if the domain is -"www.domain.com", because the subrouter is tested first. This is not +"www.example.com", because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. @@ -136,6 +145,31 @@ the inner routes use it as base for their paths: // "/products/{key}/details" s.HandleFunc("/{key}/details", ProductDetailsHandler) +Note that the path provided to PathPrefix() represents a "wildcard": calling +PathPrefix("/static/").Handler(...) means that the handler will be passed any +request that matches "/static/*". This makes it easy to serve static files with mux: + + func main() { + var dir string + + flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") + flag.Parse() + r := mux.NewRouter() + + // This will serve files under http://localhost:8000/static/ + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) + + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) + } + Now let's see how to build registered URLs. Routes can be named. All routes that define a name can have their URLs built, @@ -154,24 +188,33 @@ key/value pairs for the route variables. For the previous route, we would do: "/articles/technology/42" -This also works for host variables: +This also works for host and query value variables: r := mux.NewRouter() r.Host("{subdomain}.domain.com"). Path("/articles/{category}/{id:[0-9]+}"). + Queries("filter", "{filter}"). HandlerFunc(ArticleHandler). Name("article") - // url.String() will be "http://news.domain.com/articles/technology/42" + // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") + "category", "technology", + "id", "42", + "filter", "gorilla") All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. +Regex support also exists for matching Headers within a route. For example, we could do: + + r.HeadersRegexp("Content-Type", "application/(text|json)") + +...and the route will match both requests with a Content-Type of `application/json` as well as +`application/text` + There's also a way to build only the URL host or path for a route: use the methods URLHost() or URLPath() instead. For the previous route, we would do: @@ -193,7 +236,71 @@ as well: // "http://news.domain.com/articles/technology/42" url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") + "category", "technology", + "id", "42") + +Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking. + + type MiddlewareFunc func(http.Handler) http.Handler + +Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). + +A very basic middleware which logs the URI of the request being handled could be written as: + + func simpleMw(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Do stuff here + log.Println(r.RequestURI) + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + }) + } + +Middlewares can be added to a router using `Router.Use()`: + + r := mux.NewRouter() + r.HandleFunc("/", handler) + r.Use(simpleMw) + +A more complex authentication middleware, which maps session token to users, could be written as: + + // Define our struct + type authenticationMiddleware struct { + tokenUsers map[string]string + } + + // Initialize it somewhere + func (amw *authenticationMiddleware) Populate() { + amw.tokenUsers["00000000"] = "user0" + amw.tokenUsers["aaaaaaaa"] = "userA" + amw.tokenUsers["05f717e5"] = "randomUser" + amw.tokenUsers["deadbeef"] = "user0" + } + + // Middleware function, which will be called for each request + func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("X-Session-Token") + + if user, found := amw.tokenUsers[token]; found { + // We found the token in our map + log.Printf("Authenticated user %s\n", user) + next.ServeHTTP(w, r) + } else { + http.Error(w, "Forbidden", http.StatusForbidden) + } + }) + } + + r := mux.NewRouter() + r.HandleFunc("/", handler) + + amw := authenticationMiddleware{} + amw.Populate() + + r.Use(amw.Middleware) + +Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. + */ package mux diff --git a/vendor/github.com/gorilla/mux/example_authentication_middleware_test.go b/vendor/github.com/gorilla/mux/example_authentication_middleware_test.go new file mode 100644 index 000000000..6f2ea86ca --- /dev/null +++ b/vendor/github.com/gorilla/mux/example_authentication_middleware_test.go @@ -0,0 +1,46 @@ +package mux_test + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" +) + +// Define our struct +type authenticationMiddleware struct { + tokenUsers map[string]string +} + +// Initialize it somewhere +func (amw *authenticationMiddleware) Populate() { + amw.tokenUsers["00000000"] = "user0" + amw.tokenUsers["aaaaaaaa"] = "userA" + amw.tokenUsers["05f717e5"] = "randomUser" + amw.tokenUsers["deadbeef"] = "user0" +} + +// Middleware function, which will be called for each request +func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("X-Session-Token") + + if user, found := amw.tokenUsers[token]; found { + // We found the token in our map + log.Printf("Authenticated user %s\n", user) + next.ServeHTTP(w, r) + } else { + http.Error(w, "Forbidden", http.StatusForbidden) + } + }) +} + +func Example_authenticationMiddleware() { + r := mux.NewRouter() + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Do something here + }) + amw := authenticationMiddleware{make(map[string]string)} + amw.Populate() + r.Use(amw.Middleware) +} diff --git a/vendor/github.com/gorilla/mux/example_route_test.go b/vendor/github.com/gorilla/mux/example_route_test.go new file mode 100644 index 000000000..112557071 --- /dev/null +++ b/vendor/github.com/gorilla/mux/example_route_test.go @@ -0,0 +1,51 @@ +package mux_test + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" +) + +// This example demonstrates setting a regular expression matcher for +// the header value. A plain word will match any value that contains a +// matching substring as if the pattern was wrapped with `.*`. +func ExampleRoute_HeadersRegexp() { + r := mux.NewRouter() + route := r.NewRoute().HeadersRegexp("Accept", "html") + + req1, _ := http.NewRequest("GET", "example.com", nil) + req1.Header.Add("Accept", "text/plain") + req1.Header.Add("Accept", "text/html") + + req2, _ := http.NewRequest("GET", "example.com", nil) + req2.Header.Set("Accept", "application/xhtml+xml") + + matchInfo := &mux.RouteMatch{} + fmt.Printf("Match: %v %q\n", route.Match(req1, matchInfo), req1.Header["Accept"]) + fmt.Printf("Match: %v %q\n", route.Match(req2, matchInfo), req2.Header["Accept"]) + // Output: + // Match: true ["text/plain" "text/html"] + // Match: true ["application/xhtml+xml"] +} + +// This example demonstrates setting a strict regular expression matcher +// for the header value. Using the start and end of string anchors, the +// value must be an exact match. +func ExampleRoute_HeadersRegexp_exactMatch() { + r := mux.NewRouter() + route := r.NewRoute().HeadersRegexp("Origin", "^https://example.co$") + + yes, _ := http.NewRequest("GET", "example.co", nil) + yes.Header.Set("Origin", "https://example.co") + + no, _ := http.NewRequest("GET", "example.co.uk", nil) + no.Header.Set("Origin", "https://example.co.uk") + + matchInfo := &mux.RouteMatch{} + fmt.Printf("Match: %v %q\n", route.Match(yes, matchInfo), yes.Header["Origin"]) + fmt.Printf("Match: %v %q\n", route.Match(no, matchInfo), no.Header["Origin"]) + // Output: + // Match: true ["https://example.co"] + // Match: false ["https://example.co.uk"] +} diff --git a/vendor/github.com/gorilla/mux/go.mod b/vendor/github.com/gorilla/mux/go.mod new file mode 100644 index 000000000..cfc8ede58 --- /dev/null +++ b/vendor/github.com/gorilla/mux/go.mod @@ -0,0 +1 @@ +module github.com/gorilla/mux diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go new file mode 100644 index 000000000..ceb812cee --- /dev/null +++ b/vendor/github.com/gorilla/mux/middleware.go @@ -0,0 +1,72 @@ +package mux + +import ( + "net/http" + "strings" +) + +// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. +// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed +// to it, and then calls the handler passed as parameter to the MiddlewareFunc. +type MiddlewareFunc func(http.Handler) http.Handler + +// middleware interface is anything which implements a MiddlewareFunc named Middleware. +type middleware interface { + Middleware(handler http.Handler) http.Handler +} + +// Middleware allows MiddlewareFunc to implement the middleware interface. +func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { + return mw(handler) +} + +// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +func (r *Router) Use(mwf ...MiddlewareFunc) { + for _, fn := range mwf { + r.middlewares = append(r.middlewares, fn) + } +} + +// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. +func (r *Router) useInterface(mw middleware) { + r.middlewares = append(r.middlewares, mw) +} + +// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header +// on a request, by matching routes based only on paths. It also handles +// OPTIONS requests, by settings Access-Control-Allow-Methods, and then +// returning without calling the next http handler. +func CORSMethodMiddleware(r *Router) MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var allMethods []string + + err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { + for _, m := range route.matchers { + if _, ok := m.(*routeRegexp); ok { + if m.Match(req, &RouteMatch{}) { + methods, err := route.GetMethods() + if err != nil { + return err + } + + allMethods = append(allMethods, methods...) + } + break + } + } + return nil + }) + + if err == nil { + w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ",")) + + if req.Method == "OPTIONS" { + return + } + } + + next.ServeHTTP(w, req) + }) + } +} diff --git a/vendor/github.com/gorilla/mux/middleware_test.go b/vendor/github.com/gorilla/mux/middleware_test.go new file mode 100644 index 000000000..acf4e160b --- /dev/null +++ b/vendor/github.com/gorilla/mux/middleware_test.go @@ -0,0 +1,377 @@ +package mux + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" +) + +type testMiddleware struct { + timesCalled uint +} + +func (tm *testMiddleware) Middleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tm.timesCalled++ + h.ServeHTTP(w, r) + }) +} + +func dummyHandler(w http.ResponseWriter, r *http.Request) {} + +func TestMiddlewareAdd(t *testing.T) { + router := NewRouter() + router.HandleFunc("/", dummyHandler).Methods("GET") + + mw := &testMiddleware{} + + router.useInterface(mw) + if len(router.middlewares) != 1 || router.middlewares[0] != mw { + t.Fatal("Middleware was not added correctly") + } + + router.Use(mw.Middleware) + if len(router.middlewares) != 2 { + t.Fatal("MiddlewareFunc method was not added correctly") + } + + banalMw := func(handler http.Handler) http.Handler { + return handler + } + router.Use(banalMw) + if len(router.middlewares) != 3 { + t.Fatal("MiddlewareFunc method was not added correctly") + } +} + +func TestMiddleware(t *testing.T) { + router := NewRouter() + router.HandleFunc("/", dummyHandler).Methods("GET") + + mw := &testMiddleware{} + router.useInterface(mw) + + rw := NewRecorder() + req := newRequest("GET", "/") + + // Test regular middleware call + router.ServeHTTP(rw, req) + if mw.timesCalled != 1 { + t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled) + } + + // Middleware should not be called for 404 + req = newRequest("GET", "/not/found") + router.ServeHTTP(rw, req) + if mw.timesCalled != 1 { + t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled) + } + + // Middleware should not be called if there is a method mismatch + req = newRequest("POST", "/") + router.ServeHTTP(rw, req) + if mw.timesCalled != 1 { + t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled) + } + + // Add the middleware again as function + router.Use(mw.Middleware) + req = newRequest("GET", "/") + router.ServeHTTP(rw, req) + if mw.timesCalled != 3 { + t.Fatalf("Expected %d calls, but got only %d", 3, mw.timesCalled) + } + +} + +func TestMiddlewareSubrouter(t *testing.T) { + router := NewRouter() + router.HandleFunc("/", dummyHandler).Methods("GET") + + subrouter := router.PathPrefix("/sub").Subrouter() + subrouter.HandleFunc("/x", dummyHandler).Methods("GET") + + mw := &testMiddleware{} + subrouter.useInterface(mw) + + rw := NewRecorder() + req := newRequest("GET", "/") + + router.ServeHTTP(rw, req) + if mw.timesCalled != 0 { + t.Fatalf("Expected %d calls, but got only %d", 0, mw.timesCalled) + } + + req = newRequest("GET", "/sub/") + router.ServeHTTP(rw, req) + if mw.timesCalled != 0 { + t.Fatalf("Expected %d calls, but got only %d", 0, mw.timesCalled) + } + + req = newRequest("GET", "/sub/x") + router.ServeHTTP(rw, req) + if mw.timesCalled != 1 { + t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled) + } + + req = newRequest("GET", "/sub/not/found") + router.ServeHTTP(rw, req) + if mw.timesCalled != 1 { + t.Fatalf("Expected %d calls, but got only %d", 1, mw.timesCalled) + } + + router.useInterface(mw) + + req = newRequest("GET", "/") + router.ServeHTTP(rw, req) + if mw.timesCalled != 2 { + t.Fatalf("Expected %d calls, but got only %d", 2, mw.timesCalled) + } + + req = newRequest("GET", "/sub/x") + router.ServeHTTP(rw, req) + if mw.timesCalled != 4 { + t.Fatalf("Expected %d calls, but got only %d", 4, mw.timesCalled) + } +} + +func TestMiddlewareExecution(t *testing.T) { + mwStr := []byte("Middleware\n") + handlerStr := []byte("Logic\n") + + router := NewRouter() + router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }) + + rw := NewRecorder() + req := newRequest("GET", "/") + + // Test handler-only call + router.ServeHTTP(rw, req) + + if bytes.Compare(rw.Body.Bytes(), handlerStr) != 0 { + t.Fatal("Handler response is not what it should be") + } + + // Test middleware call + rw = NewRecorder() + + router.Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(mwStr) + h.ServeHTTP(w, r) + }) + }) + + router.ServeHTTP(rw, req) + if bytes.Compare(rw.Body.Bytes(), append(mwStr, handlerStr...)) != 0 { + t.Fatal("Middleware + handler response is not what it should be") + } +} + +func TestMiddlewareNotFound(t *testing.T) { + mwStr := []byte("Middleware\n") + handlerStr := []byte("Logic\n") + + router := NewRouter() + router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }) + router.Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(mwStr) + h.ServeHTTP(w, r) + }) + }) + + // Test not found call with default handler + rw := NewRecorder() + req := newRequest("GET", "/notfound") + + router.ServeHTTP(rw, req) + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a 404") + } + + // Test not found call with custom handler + rw = NewRecorder() + req = newRequest("GET", "/notfound") + + router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Write([]byte("Custom 404 handler")) + }) + router.ServeHTTP(rw, req) + + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a custom 404") + } +} + +func TestMiddlewareMethodMismatch(t *testing.T) { + mwStr := []byte("Middleware\n") + handlerStr := []byte("Logic\n") + + router := NewRouter() + router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }).Methods("GET") + + router.Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(mwStr) + h.ServeHTTP(w, r) + }) + }) + + // Test method mismatch + rw := NewRecorder() + req := newRequest("POST", "/") + + router.ServeHTTP(rw, req) + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a method mismatch") + } + + // Test not found call + rw = NewRecorder() + req = newRequest("POST", "/") + + router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Write([]byte("Method not allowed")) + }) + router.ServeHTTP(rw, req) + + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a method mismatch") + } +} + +func TestMiddlewareNotFoundSubrouter(t *testing.T) { + mwStr := []byte("Middleware\n") + handlerStr := []byte("Logic\n") + + router := NewRouter() + router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }) + + subrouter := router.PathPrefix("/sub/").Subrouter() + subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }) + + router.Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(mwStr) + h.ServeHTTP(w, r) + }) + }) + + // Test not found call for default handler + rw := NewRecorder() + req := newRequest("GET", "/sub/notfound") + + router.ServeHTTP(rw, req) + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a 404") + } + + // Test not found call with custom handler + rw = NewRecorder() + req = newRequest("GET", "/sub/notfound") + + subrouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Write([]byte("Custom 404 handler")) + }) + router.ServeHTTP(rw, req) + + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a custom 404") + } +} + +func TestMiddlewareMethodMismatchSubrouter(t *testing.T) { + mwStr := []byte("Middleware\n") + handlerStr := []byte("Logic\n") + + router := NewRouter() + router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }) + + subrouter := router.PathPrefix("/sub/").Subrouter() + subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { + w.Write(handlerStr) + }).Methods("GET") + + router.Use(func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(mwStr) + h.ServeHTTP(w, r) + }) + }) + + // Test method mismatch without custom handler + rw := NewRecorder() + req := newRequest("POST", "/sub/") + + router.ServeHTTP(rw, req) + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a method mismatch") + } + + // Test method mismatch with custom handler + rw = NewRecorder() + req = newRequest("POST", "/sub/") + + router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Write([]byte("Method not allowed")) + }) + router.ServeHTTP(rw, req) + + if bytes.Contains(rw.Body.Bytes(), mwStr) { + t.Fatal("Middleware was called for a method mismatch") + } +} + +func TestCORSMethodMiddleware(t *testing.T) { + router := NewRouter() + + cases := []struct { + path string + response string + method string + testURL string + expectedAllowedMethods string + }{ + {"/g/{o}", "a", "POST", "/g/asdf", "POST,PUT,GET,OPTIONS"}, + {"/g/{o}", "b", "PUT", "/g/bla", "POST,PUT,GET,OPTIONS"}, + {"/g/{o}", "c", "GET", "/g/orilla", "POST,PUT,GET,OPTIONS"}, + {"/g", "d", "POST", "/g", "POST,OPTIONS"}, + } + + for _, tt := range cases { + router.HandleFunc(tt.path, stringHandler(tt.response)).Methods(tt.method) + } + + router.Use(CORSMethodMiddleware(router)) + + for _, tt := range cases { + rr := httptest.NewRecorder() + req := newRequest(tt.method, tt.testURL) + + router.ServeHTTP(rr, req) + + if rr.Body.String() != tt.response { + t.Errorf("Expected body '%s', found '%s'", tt.response, rr.Body.String()) + } + + allowedMethods := rr.HeaderMap.Get("Access-Control-Allow-Methods") + + if allowedMethods != tt.expectedAllowedMethods { + t.Errorf("Expected Access-Control-Allow-Methods '%s', found '%s'", tt.expectedAllowedMethods, allowedMethods) + } + } +} diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go index af31d2395..4bbafa51d 100644 --- a/vendor/github.com/gorilla/mux/mux.go +++ b/vendor/github.com/gorilla/mux/mux.go @@ -5,11 +5,19 @@ package mux import ( + "errors" "fmt" "net/http" "path" + "regexp" +) - "github.com/gorilla/context" +var ( + // ErrMethodMismatch is returned when the method in the request does not match + // the method defined against the route. + ErrMethodMismatch = errors.New("method is not allowed") + // ErrNotFound is returned when no route match is found. + ErrNotFound = errors.New("no matching route was found") ) // NewRouter returns a new router instance. @@ -38,6 +46,10 @@ func NewRouter() *Router { type Router struct { // Configurable Handler to be used when no route matches. NotFoundHandler http.Handler + + // Configurable Handler to be used when the request method does not match the route. + MethodNotAllowedHandler http.Handler + // Parent route, if this is a subrouter. parent parentRoute // Routes to be matched, in order. @@ -46,17 +58,59 @@ type Router struct { namedRoutes map[string]*Route // See Router.StrictSlash(). This defines the flag for new routes. strictSlash bool - // If true, do not clear the request context after handling the request + // See Router.SkipClean(). This defines the flag for new routes. + skipClean bool + // If true, do not clear the request context after handling the request. + // This has no effect when go1.7+ is used, since the context is stored + // on the request itself. KeepContext bool + // see Router.UseEncodedPath(). This defines a flag for all routes. + useEncodedPath bool + // Slice of middlewares to be called after a match is found + middlewares []middleware } -// Match matches registered routes against the request. +// Match attempts to match the given request against the router's registered routes. +// +// If the request matches a route of this router or one of its subrouters the Route, +// Handler, and Vars fields of the the match argument are filled and this function +// returns true. +// +// If the request does not match any of this router's or its subrouters' routes +// then this function returns false. If available, a reason for the match failure +// will be filled in the match argument's MatchErr field. If the match failure type +// (eg: not found) has a registered handler, the handler is assigned to the Handler +// field of the match argument. func (r *Router) Match(req *http.Request, match *RouteMatch) bool { for _, route := range r.routes { if route.Match(req, match) { + // Build middleware chain if no error was found + if match.MatchErr == nil { + for i := len(r.middlewares) - 1; i >= 0; i-- { + match.Handler = r.middlewares[i].Middleware(match.Handler) + } + } + return true + } + } + + if match.MatchErr == ErrMethodMismatch { + if r.MethodNotAllowedHandler != nil { + match.Handler = r.MethodNotAllowedHandler return true } + + return false } + + // Closest match for a router (includes sub-routers) + if r.NotFoundHandler != nil { + match.Handler = r.NotFoundHandler + match.MatchErr = ErrNotFound + return true + } + + match.MatchErr = ErrNotFound return false } @@ -65,36 +119,46 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { // When there is a match, the route variables can be retrieved calling // mux.Vars(request). func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Clean path to canonical form and redirect. - if p := cleanPath(req.URL.Path); p != req.URL.Path { - - // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. - // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: - // http://code.google.com/p/go/issues/detail?id=5252 - url := *req.URL - url.Path = p - p = url.String() - - w.Header().Set("Location", p) - w.WriteHeader(http.StatusMovedPermanently) - return + if !r.skipClean { + path := req.URL.Path + if r.useEncodedPath { + path = req.URL.EscapedPath() + } + // Clean path to canonical form and redirect. + if p := cleanPath(path); p != path { + + // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. + // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: + // http://code.google.com/p/go/issues/detail?id=5252 + url := *req.URL + url.Path = p + p = url.String() + + w.Header().Set("Location", p) + w.WriteHeader(http.StatusMovedPermanently) + return + } } var match RouteMatch var handler http.Handler if r.Match(req, &match) { handler = match.Handler - setVars(req, match.Vars) - setCurrentRoute(req, match.Route) + req = setVars(req, match.Vars) + req = setCurrentRoute(req, match.Route) } + + if handler == nil && match.MatchErr == ErrMethodMismatch { + handler = methodNotAllowedHandler() + } + if handler == nil { - handler = r.NotFoundHandler - if handler == nil { - handler = http.NotFoundHandler() - } + handler = http.NotFoundHandler() } + if !r.KeepContext { - defer context.Clear(req) + defer contextClear(req) } + handler.ServeHTTP(w, req) } @@ -112,13 +176,18 @@ func (r *Router) GetRoute(name string) *Route { // StrictSlash defines the trailing slash behavior for new routes. The initial // value is false. // -// When true, if the route path is "/path/", accessing "/path" will redirect +// When true, if the route path is "/path/", accessing "/path" will perform a redirect // to the former and vice versa. In other words, your application will always // see the path as specified in the route. // // When false, if the route path is "/path", accessing "/path/" will not match // this route and vice versa. // +// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for +// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed +// request will be made as a GET by most clients. Use middleware or client settings +// to modify this behaviour as needed. +// // Special case: when a route sets a path prefix using the PathPrefix() method, // strict slash is ignored for that route because the redirect behavior can't // be determined from a prefix alone. However, any subrouters created from that @@ -128,10 +197,41 @@ func (r *Router) StrictSlash(value bool) *Router { return r } +// SkipClean defines the path cleaning behaviour for new routes. The initial +// value is false. Users should be careful about which routes are not cleaned +// +// When true, if the route path is "/path//to", it will remain with the double +// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ +// +// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will +// become /fetch/http/xkcd.com/534 +func (r *Router) SkipClean(value bool) *Router { + r.skipClean = value + return r +} + +// UseEncodedPath tells the router to match the encoded original path +// to the routes. +// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". +// +// If not called, the router will match the unencoded path to the routes. +// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" +func (r *Router) UseEncodedPath() *Router { + r.useEncodedPath = true + return r +} + // ---------------------------------------------------------------------------- // parentRoute // ---------------------------------------------------------------------------- +func (r *Router) getBuildScheme() string { + if r.parent != nil { + return r.parent.getBuildScheme() + } + return "" +} + // getNamedRoutes returns the map where named routes are registered. func (r *Router) getNamedRoutes() map[string]*Route { if r.namedRoutes == nil { @@ -165,7 +265,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string { // NewRoute registers an empty route. func (r *Router) NewRoute() *Route { - route := &Route{parent: r, strictSlash: r.strictSlash} + route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} r.routes = append(r.routes, route) return route } @@ -231,12 +331,59 @@ func (r *Router) Schemes(schemes ...string) *Route { return r.NewRoute().Schemes(schemes...) } -// BuildVars registers a new route with a custom function for modifying +// BuildVarsFunc registers a new route with a custom function for modifying // route variables before building a URL. func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { return r.NewRoute().BuildVarsFunc(f) } +// Walk walks the router and all its sub-routers, calling walkFn for each route +// in the tree. The routes are walked in the order they were added. Sub-routers +// are explored depth-first. +func (r *Router) Walk(walkFn WalkFunc) error { + return r.walk(walkFn, []*Route{}) +} + +// SkipRouter is used as a return value from WalkFuncs to indicate that the +// router that walk is about to descend down to should be skipped. +var SkipRouter = errors.New("skip this router") + +// WalkFunc is the type of the function called for each route visited by Walk. +// At every invocation, it is given the current route, and the current router, +// and a list of ancestor routes that lead to the current route. +type WalkFunc func(route *Route, router *Router, ancestors []*Route) error + +func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { + for _, t := range r.routes { + err := walkFn(t, r, ancestors) + if err == SkipRouter { + continue + } + if err != nil { + return err + } + for _, sr := range t.matchers { + if h, ok := sr.(*Router); ok { + ancestors = append(ancestors, t) + err := h.walk(walkFn, ancestors) + if err != nil { + return err + } + ancestors = ancestors[:len(ancestors)-1] + } + } + if h, ok := t.handler.(*Router); ok { + ancestors = append(ancestors, t) + err := h.walk(walkFn, ancestors) + if err != nil { + return err + } + ancestors = ancestors[:len(ancestors)-1] + } + } + return nil +} + // ---------------------------------------------------------------------------- // Context // ---------------------------------------------------------------------------- @@ -246,6 +393,11 @@ type RouteMatch struct { Route *Route Handler http.Handler Vars map[string]string + + // MatchErr is set to appropriate matching error + // It is set to ErrMethodMismatch if there is a mismatch in + // the request method and route method + MatchErr error } type contextKey int @@ -257,26 +409,30 @@ const ( // Vars returns the route variables for the current request, if any. func Vars(r *http.Request) map[string]string { - if rv := context.Get(r, varsKey); rv != nil { + if rv := contextGet(r, varsKey); rv != nil { return rv.(map[string]string) } return nil } // CurrentRoute returns the matched route for the current request, if any. +// This only works when called inside the handler of the matched route +// because the matched route is stored in the request context which is cleared +// after the handler returns, unless the KeepContext option is set on the +// Router. func CurrentRoute(r *http.Request) *Route { - if rv := context.Get(r, routeKey); rv != nil { + if rv := contextGet(r, routeKey); rv != nil { return rv.(*Route) } return nil } -func setVars(r *http.Request, val interface{}) { - context.Set(r, varsKey, val) +func setVars(r *http.Request, val interface{}) *http.Request { + return contextSet(r, varsKey, val) } -func setCurrentRoute(r *http.Request, val interface{}) { - context.Set(r, routeKey, val) +func setCurrentRoute(r *http.Request, val interface{}) *http.Request { + return contextSet(r, routeKey, val) } // ---------------------------------------------------------------------------- @@ -298,6 +454,7 @@ func cleanPath(p string) string { if p[len(p)-1] == '/' && np != "/" { np += "/" } + return np } @@ -313,13 +470,24 @@ func uniqueVars(s1, s2 []string) error { return nil } -// mapFromPairs converts variadic string parameters to a string map. -func mapFromPairs(pairs ...string) (map[string]string, error) { +// checkPairs returns the count of strings passed in, and an error if +// the count is not an even number. +func checkPairs(pairs ...string) (int, error) { length := len(pairs) if length%2 != 0 { - return nil, fmt.Errorf( + return length, fmt.Errorf( "mux: number of parameters must be multiple of 2, got %v", pairs) } + return length, nil +} + +// mapFromPairsToString converts variadic string parameters to a +// string to string map. +func mapFromPairsToString(pairs ...string) (map[string]string, error) { + length, err := checkPairs(pairs...) + if err != nil { + return nil, err + } m := make(map[string]string, length/2) for i := 0; i < length; i += 2 { m[pairs[i]] = pairs[i+1] @@ -327,6 +495,24 @@ func mapFromPairs(pairs ...string) (map[string]string, error) { return m, nil } +// mapFromPairsToRegex converts variadic string parameters to a +// string to regex map. +func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { + length, err := checkPairs(pairs...) + if err != nil { + return nil, err + } + m := make(map[string]*regexp.Regexp, length/2) + for i := 0; i < length; i += 2 { + regex, err := regexp.Compile(pairs[i+1]) + if err != nil { + return nil, err + } + m[pairs[i]] = regex + } + return m, nil +} + // matchInArray returns true if the given string value is in the array. func matchInArray(arr []string, value string) bool { for _, v := range arr { @@ -337,9 +523,8 @@ func matchInArray(arr []string, value string) bool { return false } -// matchMap returns true if the given key/value pairs exist in a given map. -func matchMap(toCheck map[string]string, toMatch map[string][]string, - canonicalKey bool) bool { +// matchMapWithString returns true if the given key/value pairs exist in a given map. +func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { for k, v := range toCheck { // Check if key exists. if canonicalKey { @@ -364,3 +549,40 @@ func matchMap(toCheck map[string]string, toMatch map[string][]string, } return true } + +// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against +// the given regex +func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { + for k, v := range toCheck { + // Check if key exists. + if canonicalKey { + k = http.CanonicalHeaderKey(k) + } + if values := toMatch[k]; values == nil { + return false + } else if v != nil { + // If value was defined as an empty string we only check that the + // key exists. Otherwise we also check for equality. + valueExists := false + for _, value := range values { + if v.MatchString(value) { + valueExists = true + break + } + } + if !valueExists { + return false + } + } + } + return true +} + +// methodNotAllowed replies to the request with an HTTP status code 405. +func methodNotAllowed(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusMethodNotAllowed) +} + +// methodNotAllowedHandler returns a simple request handler +// that replies to each request with a status code 405. +func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } diff --git a/vendor/github.com/gorilla/mux/mux_test.go b/vendor/github.com/gorilla/mux/mux_test.go index 075dedba4..af21329f2 100644 --- a/vendor/github.com/gorilla/mux/mux_test.go +++ b/vendor/github.com/gorilla/mux/mux_test.go @@ -5,22 +5,46 @@ package mux import ( + "bufio" + "bytes" + "errors" "fmt" "net/http" + "net/url" + "reflect" + "strings" "testing" - - "github.com/gorilla/context" ) +func (r *Route) GoString() string { + matchers := make([]string, len(r.matchers)) + for i, m := range r.matchers { + matchers[i] = fmt.Sprintf("%#v", m) + } + return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", ")) +} + +func (r *routeRegexp) GoString() string { + return fmt.Sprintf("&routeRegexp{template: %q, regexpType: %v, options: %v, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.regexpType, r.options, r.regexp.String(), r.reverse, r.varsN, r.varsR) +} + type routeTest struct { - title string // title of the test - route *Route // the route being tested - request *http.Request // a request to test the route - vars map[string]string // the expected vars of the match - host string // the expected host of the match - path string // the expected path of the match - shouldMatch bool // whether the request is expected to match the route at all - shouldRedirect bool // whether the request should result in a redirect + title string // title of the test + route *Route // the route being tested + request *http.Request // a request to test the route + vars map[string]string // the expected vars of the match + scheme string // the expected scheme of the built URL + host string // the expected host of the built URL + path string // the expected path of the built URL + query string // the expected query string of the built URL + pathTemplate string // the expected path template of the route + hostTemplate string // the expected host template of the route + queriesTemplate string // the expected query template of the route + methods []string // the expected route methods + pathRegexp string // the expected path regexp + queriesRegexp string // the expected query regexp + shouldMatch bool // whether the request is expected to match the route at all + shouldRedirect bool // whether the request should result in a redirect } func TestHost(t *testing.T) { @@ -100,71 +124,89 @@ func TestHost(t *testing.T) { shouldMatch: false, }, { - title: "Host route with pattern, match", - route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), - request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), - vars: map[string]string{"v1": "bbb"}, - host: "aaa.bbb.ccc", - path: "", - shouldMatch: true, - }, - { - title: "Host route with pattern, wrong host in request URL", - route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), - request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), - vars: map[string]string{"v1": "bbb"}, - host: "aaa.bbb.ccc", - path: "", - shouldMatch: false, - }, - { - title: "Host route with multiple patterns, match", - route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), - request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), - vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, - host: "aaa.bbb.ccc", - path: "", - shouldMatch: true, - }, - { - title: "Host route with multiple patterns, wrong host in request URL", - route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), - request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), - vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, - host: "aaa.bbb.ccc", - path: "", - shouldMatch: false, - }, - { - title: "Path route with single pattern with pipe, match", - route: new(Route).Path("/{category:a|b/c}"), - request: newRequest("GET", "http://localhost/a"), - vars: map[string]string{"category": "a"}, - host: "", - path: "/a", - shouldMatch: true, - }, - { - title: "Path route with single pattern with pipe, match", - route: new(Route).Path("/{category:a|b/c}"), - request: newRequest("GET", "http://localhost/b/c"), - vars: map[string]string{"category": "b/c"}, - host: "", - path: "/b/c", - shouldMatch: true, - }, - { - title: "Path route with multiple patterns with pipe, match", - route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), - request: newRequest("GET", "http://localhost/a/product_name/1"), - vars: map[string]string{"category": "a", "product": "product_name", "id": "1"}, - host: "", - path: "/a/product_name/1", - shouldMatch: true, + title: "Host route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`, + shouldMatch: true, + }, + { + title: "Host route with pattern, additional capturing group, match", + route: new(Route).Host("aaa.{v1:[a-z]{2}(?:b|c)}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `aaa.{v1:[a-z]{2}(?:b|c)}.ccc`, + shouldMatch: true, + }, + { + title: "Host route with pattern, wrong host in request URL", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`, + shouldMatch: false, + }, + { + title: "Host route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`, + shouldMatch: true, + }, + { + title: "Host route with multiple patterns, wrong host in request URL", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`, + shouldMatch: false, + }, + { + title: "Host route with hyphenated name and pattern, match", + route: new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v-1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`, + shouldMatch: true, + }, + { + title: "Host route with hyphenated name and pattern, additional capturing group, match", + route: new(Route).Host("aaa.{v-1:[a-z]{2}(?:b|c)}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v-1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `aaa.{v-1:[a-z]{2}(?:b|c)}.ccc`, + shouldMatch: true, + }, + { + title: "Host route with multiple hyphenated names and patterns, match", + route: new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`, + shouldMatch: true, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) } } @@ -189,22 +231,50 @@ func TestPath(t *testing.T) { shouldMatch: true, }, { - title: "Path route, do not match with trailing slash in path", - route: new(Route).Path("/111/"), - request: newRequest("GET", "http://localhost/111"), - vars: map[string]string{}, - host: "", - path: "/111", - shouldMatch: false, - }, - { - title: "Path route, do not match with trailing slash in request", - route: new(Route).Path("/111"), - request: newRequest("GET", "http://localhost/111/"), - vars: map[string]string{}, - host: "", - path: "/111/", - shouldMatch: false, + title: "Path route, do not match with trailing slash in path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + pathTemplate: `/111/`, + pathRegexp: `^/111/$`, + shouldMatch: false, + }, + { + title: "Path route, do not match with trailing slash in request", + route: new(Route).Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + pathTemplate: `/111`, + shouldMatch: false, + }, + { + title: "Path route, match root with no host", + route: new(Route).Path("/"), + request: newRequest("GET", "/"), + vars: map[string]string{}, + host: "", + path: "/", + pathTemplate: `/`, + pathRegexp: `^/$`, + shouldMatch: true, + }, + { + title: "Path route, match root with no host, App Engine format", + route: new(Route).Path("/"), + request: func() *http.Request { + r := newRequest("GET", "http://localhost/") + r.RequestURI = "/" + return r + }(), + vars: map[string]string{}, + host: "", + path: "/", + pathTemplate: `/`, + shouldMatch: true, }, { title: "Path route, wrong path in request in request URL", @@ -216,45 +286,161 @@ func TestPath(t *testing.T) { shouldMatch: false, }, { - title: "Path route with pattern, match", - route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), - request: newRequest("GET", "http://localhost/111/222/333"), - vars: map[string]string{"v1": "222"}, - host: "", - path: "/111/222/333", - shouldMatch: true, - }, - { - title: "Path route with pattern, URL in request does not match", - route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), - request: newRequest("GET", "http://localhost/111/aaa/333"), - vars: map[string]string{"v1": "222"}, - host: "", - path: "/111/222/333", - shouldMatch: false, - }, - { - title: "Path route with multiple patterns, match", - route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/222/333"), - vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, - host: "", - path: "/111/222/333", - shouldMatch: true, - }, - { - title: "Path route with multiple patterns, URL in request does not match", - route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/aaa/333"), - vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, - host: "", - path: "/111/222/333", - shouldMatch: false, + title: "Path route with pattern, match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + pathTemplate: `/111/{v1:[0-9]{3}}/333`, + shouldMatch: true, + }, + { + title: "Path route with pattern, URL in request does not match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + pathTemplate: `/111/{v1:[0-9]{3}}/333`, + pathRegexp: `^/111/(?P[0-9]{3})/333$`, + shouldMatch: false, + }, + { + title: "Path route with multiple patterns, match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`, + pathRegexp: `^/(?P[0-9]{3})/(?P[0-9]{3})/(?P[0-9]{3})$`, + shouldMatch: true, + }, + { + title: "Path route with multiple patterns, URL in request does not match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`, + pathRegexp: `^/(?P[0-9]{3})/(?P[0-9]{3})/(?P[0-9]{3})$`, + shouldMatch: false, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|(?:b/c)}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/a/product_name/1"), + vars: map[string]string{"category": "a", "product": "product_name", "id": "1"}, + host: "", + path: "/a/product_name/1", + pathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`, + pathRegexp: `^/(?Pa|(?:b/c))/(?P[^/]+)/(?P[0-9]+)$`, + shouldMatch: true, + }, + { + title: "Path route with hyphenated name and pattern, match", + route: new(Route).Path("/111/{v-1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v-1": "222"}, + host: "", + path: "/111/222/333", + pathTemplate: `/111/{v-1:[0-9]{3}}/333`, + pathRegexp: `^/111/(?P[0-9]{3})/333$`, + shouldMatch: true, + }, + { + title: "Path route with multiple hyphenated names and patterns, match", + route: new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"}, + host: "", + path: "/111/222/333", + pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`, + pathRegexp: `^/(?P[0-9]{3})/(?P[0-9]{3})/(?P[0-9]{3})$`, + shouldMatch: true, + }, + { + title: "Path route with multiple hyphenated names and patterns with pipe, match", + route: new(Route).Path("/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}"), + request: newRequest("GET", "http://localhost/a/product_name/1"), + vars: map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"}, + host: "", + path: "/a/product_name/1", + pathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`, + pathRegexp: `^/(?Pa|(?:b/c))/(?P[^/]+)/(?P[0-9]+)$`, + shouldMatch: true, + }, + { + title: "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match", + route: new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"), + request: newRequest("GET", "http://localhost/daily-2016-01-01"), + vars: map[string]string{"type": "daily", "date": "2016-01-01"}, + host: "", + path: "/daily-2016-01-01", + pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`, + pathRegexp: `^/(?P(?i:daily|mini|variety))-(?P\d{4,4}-\d{2,2}-\d{2,2})$`, + shouldMatch: true, + }, + { + title: "Path route with empty match right after other match", + route: new(Route).Path(`/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`), + request: newRequest("GET", "http://localhost/111/222"), + vars: map[string]string{"v1": "111", "v2": "", "v3": "222"}, + host: "", + path: "/111/222", + pathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`, + pathRegexp: `^/(?P[0-9]*)(?P[a-z]*)/(?P[0-9]*)$`, + shouldMatch: true, + }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/a"), + vars: map[string]string{"category": "a"}, + host: "", + path: "/a", + pathTemplate: `/{category:a|b/c}`, + shouldMatch: true, + }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/b/c"), + vars: map[string]string{"category": "b/c"}, + host: "", + path: "/b/c", + pathTemplate: `/{category:a|b/c}`, + shouldMatch: true, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/a/product_name/1"), + vars: map[string]string{"category": "a", "product": "product_name", "id": "1"}, + host: "", + path: "/a/product_name/1", + pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`, + shouldMatch: true, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/b/c/product_name/1"), + vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"}, + host: "", + path: "/b/c/product_name/1", + pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`, + shouldMatch: true, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testUseEscapedRoute(t, test) + testRegexp(t, test) } } @@ -288,108 +474,158 @@ func TestPathPrefix(t *testing.T) { shouldMatch: false, }, { - title: "PathPrefix route with pattern, match", - route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/222/333"), - vars: map[string]string{"v1": "222"}, - host: "", - path: "/111/222", - shouldMatch: true, - }, - { - title: "PathPrefix route with pattern, URL prefix in request does not match", - route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/aaa/333"), - vars: map[string]string{"v1": "222"}, - host: "", - path: "/111/222", - shouldMatch: false, - }, - { - title: "PathPrefix route with multiple patterns, match", - route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/222/333"), - vars: map[string]string{"v1": "111", "v2": "222"}, - host: "", - path: "/111/222", - shouldMatch: true, - }, - { - title: "PathPrefix route with multiple patterns, URL prefix in request does not match", - route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), - request: newRequest("GET", "http://localhost/111/aaa/333"), - vars: map[string]string{"v1": "111", "v2": "222"}, - host: "", - path: "/111/222", - shouldMatch: false, + title: "PathPrefix route with pattern, match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + pathTemplate: `/111/{v1:[0-9]{3}}`, + shouldMatch: true, + }, + { + title: "PathPrefix route with pattern, URL prefix in request does not match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + pathTemplate: `/111/{v1:[0-9]{3}}`, + shouldMatch: false, + }, + { + title: "PathPrefix route with multiple patterns, match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`, + shouldMatch: true, + }, + { + title: "PathPrefix route with multiple patterns, URL prefix in request does not match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`, + shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testUseEscapedRoute(t, test) } } -func TestHostPath(t *testing.T) { +func TestSchemeHostPath(t *testing.T) { tests := []routeTest{ { - title: "Host and Path route, match", - route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), - request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Host and Path route, wrong host in request URL", - route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), - request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: false, - }, - { - title: "Host and Path route with pattern, match", - route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), - request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), - vars: map[string]string{"v1": "bbb", "v2": "222"}, - host: "aaa.bbb.ccc", - path: "/111/222/333", - shouldMatch: true, - }, - { - title: "Host and Path route with pattern, URL in request does not match", - route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), - request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), - vars: map[string]string{"v1": "bbb", "v2": "222"}, - host: "aaa.bbb.ccc", - path: "/111/222/333", - shouldMatch: false, - }, - { - title: "Host and Path route with multiple patterns, match", - route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), - request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), - vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, - host: "aaa.bbb.ccc", - path: "/111/222/333", - shouldMatch: true, - }, - { - title: "Host and Path route with multiple patterns, URL in request does not match", - route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), - request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), - vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, - host: "aaa.bbb.ccc", - path: "/111/222/333", - shouldMatch: false, + title: "Host and Path route, match", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/222/333`, + hostTemplate: `aaa.bbb.ccc`, + shouldMatch: true, + }, + { + title: "Scheme, Host, and Path route, match", + route: new(Route).Schemes("https").Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "https://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + scheme: "https", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/222/333`, + hostTemplate: `aaa.bbb.ccc`, + shouldMatch: true, + }, + { + title: "Host and Path route, wrong host in request URL", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/222/333`, + hostTemplate: `aaa.bbb.ccc`, + shouldMatch: false, + }, + { + title: "Host and Path route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/{v2:[0-9]{3}}/333`, + hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`, + shouldMatch: true, + }, + { + title: "Scheme, Host, and Path route with host and path patterns, match", + route: new(Route).Schemes("ftp", "ssss").Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "ssss://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + scheme: "ftp", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/{v2:[0-9]{3}}/333`, + hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`, + shouldMatch: true, + }, + { + title: "Host and Path route with pattern, URL in request does not match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/111/{v2:[0-9]{3}}/333`, + hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`, + shouldMatch: false, + }, + { + title: "Host and Path route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`, + hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`, + shouldMatch: true, + }, + { + title: "Host and Path route with multiple patterns, URL in request does not match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + scheme: "http", + host: "aaa.bbb.ccc", + path: "/111/222/333", + pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`, + hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`, + shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testUseEscapedRoute(t, test) } } @@ -425,12 +661,30 @@ func TestHeaders(t *testing.T) { path: "", shouldMatch: false, }, + { + title: "Headers route, regex header values to match", + route: new(Route).Headers("foo", "ba[zr]"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + { + title: "Headers route, regex header values to match", + route: new(Route).HeadersRegexp("foo", "ba[zr]"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) } - } func TestMethods(t *testing.T) { @@ -442,6 +696,7 @@ func TestMethods(t *testing.T) { vars: map[string]string{}, host: "", path: "", + methods: []string{"GET", "POST"}, shouldMatch: true, }, { @@ -451,6 +706,7 @@ func TestMethods(t *testing.T) { vars: map[string]string{}, host: "", path: "", + methods: []string{"GET", "POST"}, shouldMatch: true, }, { @@ -460,93 +716,334 @@ func TestMethods(t *testing.T) { vars: map[string]string{}, host: "", path: "", + methods: []string{"GET", "POST"}, shouldMatch: false, }, + { + title: "Route without methods", + route: new(Route), + request: newRequest("PUT", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + methods: []string{}, + shouldMatch: true, + }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testMethods(t, test) } } func TestQueries(t *testing.T) { tests := []routeTest{ { - title: "Queries route, match", - route: new(Route).Queries("foo", "bar", "baz", "ding"), - request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route, match with a query string", - route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), - request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route, match with a query string out of order", - route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), - request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route, bad query", - route: new(Route).Queries("foo", "bar", "baz", "ding"), - request: newRequest("GET", "http://localhost?foo=bar&baz=dong"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: false, - }, - { - title: "Queries route with pattern, match", - route: new(Route).Queries("foo", "{v1}"), - request: newRequest("GET", "http://localhost?foo=bar"), - vars: map[string]string{"v1": "bar"}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route with multiple patterns, match", - route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), - request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), - vars: map[string]string{"v1": "bar", "v2": "ding"}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route with regexp pattern, match", - route: new(Route).Queries("foo", "{v1:[0-9]+}"), - request: newRequest("GET", "http://localhost?foo=10"), - vars: map[string]string{"v1": "10"}, - host: "", - path: "", - shouldMatch: true, - }, - { - title: "Queries route with regexp pattern, regexp does not match", - route: new(Route).Queries("foo", "{v1:[0-9]+}"), - request: newRequest("GET", "http://localhost?foo=a"), - vars: map[string]string{}, - host: "", - path: "", - shouldMatch: false, + title: "Queries route, match", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + query: "foo=bar&baz=ding", + queriesTemplate: "foo=bar,baz=ding", + queriesRegexp: "^foo=bar$,^baz=ding$", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + query: "foo=bar&baz=ding", + pathTemplate: `/api`, + hostTemplate: `www.example.com`, + queriesTemplate: "foo=bar,baz=ding", + queriesRegexp: "^foo=bar$,^baz=ding$", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string out of order", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + query: "foo=bar&baz=ding", + pathTemplate: `/api`, + hostTemplate: `www.example.com`, + queriesTemplate: "foo=bar,baz=ding", + queriesRegexp: "^foo=bar$,^baz=ding$", + shouldMatch: true, + }, + { + title: "Queries route, bad query", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=dong"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo=bar,baz=ding", + queriesRegexp: "^foo=bar$,^baz=ding$", + shouldMatch: false, + }, + { + title: "Queries route with pattern, match", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v1": "bar"}, + host: "", + path: "", + query: "foo=bar", + queriesTemplate: "foo={v1}", + queriesRegexp: "^foo=(?P.*)$", + shouldMatch: true, + }, + { + title: "Queries route with multiple patterns, match", + route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "", + query: "foo=bar&baz=ding", + queriesTemplate: "foo={v1},baz={v2}", + queriesRegexp: "^foo=(?P.*)$,^baz=(?P.*)$", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v1": "10"}, + host: "", + path: "", + query: "foo=10", + queriesTemplate: "foo={v1:[0-9]+}", + queriesRegexp: "^foo=(?P[0-9]+)$", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=a"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo={v1:[0-9]+}", + queriesRegexp: "^foo=(?P[0-9]+)$", + shouldMatch: false, + }, + { + title: "Queries route with regexp pattern with quantifier, match", + route: new(Route).Queries("foo", "{v1:[0-9]{1}}"), + request: newRequest("GET", "http://localhost?foo=1"), + vars: map[string]string{"v1": "1"}, + host: "", + path: "", + query: "foo=1", + queriesTemplate: "foo={v1:[0-9]{1}}", + queriesRegexp: "^foo=(?P[0-9]{1})$", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern with quantifier, additional variable in query string, match", + route: new(Route).Queries("foo", "{v1:[0-9]{1}}"), + request: newRequest("GET", "http://localhost?bar=2&foo=1"), + vars: map[string]string{"v1": "1"}, + host: "", + path: "", + query: "foo=1", + queriesTemplate: "foo={v1:[0-9]{1}}", + queriesRegexp: "^foo=(?P[0-9]{1})$", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern with quantifier, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]{1}}"), + request: newRequest("GET", "http://localhost?foo=12"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo={v1:[0-9]{1}}", + queriesRegexp: "^foo=(?P[0-9]{1})$", + shouldMatch: false, + }, + { + title: "Queries route with regexp pattern with quantifier, additional capturing group", + route: new(Route).Queries("foo", "{v1:[0-9]{1}(?:a|b)}"), + request: newRequest("GET", "http://localhost?foo=1a"), + vars: map[string]string{"v1": "1a"}, + host: "", + path: "", + query: "foo=1a", + queriesTemplate: "foo={v1:[0-9]{1}(?:a|b)}", + queriesRegexp: "^foo=(?P[0-9]{1}(?:a|b))$", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]{1}}"), + request: newRequest("GET", "http://localhost?foo=12"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo={v1:[0-9]{1}}", + queriesRegexp: "^foo=(?P[0-9]{1})$", + shouldMatch: false, + }, + { + title: "Queries route with hyphenated name, match", + route: new(Route).Queries("foo", "{v-1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v-1": "bar"}, + host: "", + path: "", + query: "foo=bar", + queriesTemplate: "foo={v-1}", + queriesRegexp: "^foo=(?P.*)$", + shouldMatch: true, + }, + { + title: "Queries route with multiple hyphenated names, match", + route: new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v-1": "bar", "v-2": "ding"}, + host: "", + path: "", + query: "foo=bar&baz=ding", + queriesTemplate: "foo={v-1},baz={v-2}", + queriesRegexp: "^foo=(?P.*)$,^baz=(?P.*)$", + shouldMatch: true, + }, + { + title: "Queries route with hyphenate name and pattern, match", + route: new(Route).Queries("foo", "{v-1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v-1": "10"}, + host: "", + path: "", + query: "foo=10", + queriesTemplate: "foo={v-1:[0-9]+}", + queriesRegexp: "^foo=(?P[0-9]+)$", + shouldMatch: true, + }, + { + title: "Queries route with hyphenated name and pattern with quantifier, additional capturing group", + route: new(Route).Queries("foo", "{v-1:[0-9]{1}(?:a|b)}"), + request: newRequest("GET", "http://localhost?foo=1a"), + vars: map[string]string{"v-1": "1a"}, + host: "", + path: "", + query: "foo=1a", + queriesTemplate: "foo={v-1:[0-9]{1}(?:a|b)}", + queriesRegexp: "^foo=(?P[0-9]{1}(?:a|b))$", + shouldMatch: true, + }, + { + title: "Queries route with empty value, should match", + route: new(Route).Queries("foo", ""), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + query: "foo=", + queriesTemplate: "foo=", + queriesRegexp: "^foo=.*$", + shouldMatch: true, + }, + { + title: "Queries route with empty value and no parameter in request, should not match", + route: new(Route).Queries("foo", ""), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo=", + queriesRegexp: "^foo=.*$", + shouldMatch: false, + }, + { + title: "Queries route with empty value and empty parameter in request, should match", + route: new(Route).Queries("foo", ""), + request: newRequest("GET", "http://localhost?foo="), + vars: map[string]string{}, + host: "", + path: "", + query: "foo=", + queriesTemplate: "foo=", + queriesRegexp: "^foo=.*$", + shouldMatch: true, + }, + { + title: "Queries route with overlapping value, should not match", + route: new(Route).Queries("foo", "bar"), + request: newRequest("GET", "http://localhost?foo=barfoo"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo=bar", + queriesRegexp: "^foo=bar$", + shouldMatch: false, + }, + { + title: "Queries route with no parameter in request, should not match", + route: new(Route).Queries("foo", "{bar}"), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo={bar}", + queriesRegexp: "^foo=(?P.*)$", + shouldMatch: false, + }, + { + title: "Queries route with empty parameter in request, should match", + route: new(Route).Queries("foo", "{bar}"), + request: newRequest("GET", "http://localhost?foo="), + vars: map[string]string{"foo": ""}, + host: "", + path: "", + query: "foo=", + queriesTemplate: "foo={bar}", + queriesRegexp: "^foo=(?P.*)$", + shouldMatch: true, + }, + { + title: "Queries route, bad submatch", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"), + vars: map[string]string{}, + host: "", + path: "", + queriesTemplate: "foo=bar,baz=ding", + queriesRegexp: "^foo=bar$,^baz=ding$", + shouldMatch: false, + }, + { + title: "Queries route with pattern, match, escaped value", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=%25bar%26%20%2F%3D%3F"), + vars: map[string]string{"v1": "%bar& /=?"}, + host: "", + path: "", + query: "foo=%25bar%26+%2F%3D%3F", + queriesTemplate: "foo={v1}", + queriesRegexp: "^foo=(?P.*)$", + shouldMatch: true, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testQueriesTemplates(t, test) + testUseEscapedRoute(t, test) + testQueriesRegexp(t, test) } } @@ -554,35 +1051,49 @@ func TestSchemes(t *testing.T) { tests := []routeTest{ // Schemes { - title: "Schemes route, match https", - route: new(Route).Schemes("https", "ftp"), + title: "Schemes route, default scheme, match http, build http", + route: new(Route).Host("localhost"), + request: newRequest("GET", "http://localhost"), + scheme: "http", + host: "localhost", + shouldMatch: true, + }, + { + title: "Schemes route, match https, build https", + route: new(Route).Schemes("https", "ftp").Host("localhost"), request: newRequest("GET", "https://localhost"), - vars: map[string]string{}, - host: "", - path: "", + scheme: "https", + host: "localhost", shouldMatch: true, }, { - title: "Schemes route, match ftp", - route: new(Route).Schemes("https", "ftp"), + title: "Schemes route, match ftp, build https", + route: new(Route).Schemes("https", "ftp").Host("localhost"), request: newRequest("GET", "ftp://localhost"), - vars: map[string]string{}, - host: "", - path: "", + scheme: "https", + host: "localhost", + shouldMatch: true, + }, + { + title: "Schemes route, match ftp, build ftp", + route: new(Route).Schemes("ftp", "https").Host("localhost"), + request: newRequest("GET", "ftp://localhost"), + scheme: "ftp", + host: "localhost", shouldMatch: true, }, { title: "Schemes route, bad scheme", - route: new(Route).Schemes("https", "ftp"), + route: new(Route).Schemes("https", "ftp").Host("localhost"), request: newRequest("GET", "http://localhost"), - vars: map[string]string{}, - host: "", - path: "", + scheme: "https", + host: "localhost", shouldMatch: false, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) } } @@ -617,6 +1128,7 @@ func TestMatcherFunc(t *testing.T) { for _, test := range tests { testRoute(t, test) + testTemplate(t, test) } } @@ -629,9 +1141,10 @@ func TestBuildVarsFunc(t *testing.T) { vars["v2"] = "a" return vars }), - request: newRequest("GET", "http://localhost/111/2"), - path: "/111/3a", - shouldMatch: true, + request: newRequest("GET", "http://localhost/111/2"), + path: "/111/3a", + pathTemplate: `/111/{v1:\d}{v2:.*}`, + shouldMatch: true, }, { title: "BuildVarsFunc set on route and parent route", @@ -642,58 +1155,148 @@ func TestBuildVarsFunc(t *testing.T) { vars["v2"] = "b" return vars }), - request: newRequest("GET", "http://localhost/1/a"), - path: "/2/b", - shouldMatch: true, + request: newRequest("GET", "http://localhost/1/a"), + path: "/2/b", + pathTemplate: `/{v1:\d}/{v2:\w}`, + shouldMatch: true, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) } } func TestSubRouter(t *testing.T) { subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() + subrouter3 := new(Route).PathPrefix("/foo").Subrouter() + subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter() + subrouter5 := new(Route).PathPrefix("/{category}").Subrouter() tests := []routeTest{ { - route: subrouter1.Path("/{v2:[a-z]+}"), - request: newRequest("GET", "http://aaa.google.com/bbb"), - vars: map[string]string{"v1": "aaa", "v2": "bbb"}, - host: "aaa.google.com", - path: "/bbb", - shouldMatch: true, - }, - { - route: subrouter1.Path("/{v2:[a-z]+}"), - request: newRequest("GET", "http://111.google.com/111"), - vars: map[string]string{"v1": "aaa", "v2": "bbb"}, - host: "aaa.google.com", - path: "/bbb", - shouldMatch: false, - }, - { - route: subrouter2.Path("/baz/{v2}"), - request: newRequest("GET", "http://localhost/foo/bar/baz/ding"), - vars: map[string]string{"v1": "bar", "v2": "ding"}, - host: "", - path: "/foo/bar/baz/ding", - shouldMatch: true, - }, - { - route: subrouter2.Path("/baz/{v2}"), - request: newRequest("GET", "http://localhost/foo/bar"), - vars: map[string]string{"v1": "bar", "v2": "ding"}, - host: "", - path: "/foo/bar/baz/ding", - shouldMatch: false, + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://aaa.google.com/bbb"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + pathTemplate: `/{v2:[a-z]+}`, + hostTemplate: `{v1:[a-z]+}.google.com`, + shouldMatch: true, + }, + { + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://111.google.com/111"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + pathTemplate: `/{v2:[a-z]+}`, + hostTemplate: `{v1:[a-z]+}.google.com`, + shouldMatch: false, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar/baz/ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + pathTemplate: `/foo/{v1}/baz/{v2}`, + shouldMatch: true, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + pathTemplate: `/foo/{v1}/baz/{v2}`, + shouldMatch: false, + }, + { + route: subrouter3.Path("/"), + request: newRequest("GET", "http://localhost/foo/"), + vars: map[string]string{}, + host: "", + path: "/foo/", + pathTemplate: `/foo/`, + shouldMatch: true, + }, + { + route: subrouter3.Path(""), + request: newRequest("GET", "http://localhost/foo"), + vars: map[string]string{}, + host: "", + path: "/foo", + pathTemplate: `/foo`, + shouldMatch: true, + }, + + { + route: subrouter4.Path("/"), + request: newRequest("GET", "http://localhost/foo/bar/"), + vars: map[string]string{}, + host: "", + path: "/foo/bar/", + pathTemplate: `/foo/bar/`, + shouldMatch: true, + }, + { + route: subrouter4.Path(""), + request: newRequest("GET", "http://localhost/foo/bar"), + vars: map[string]string{}, + host: "", + path: "/foo/bar", + pathTemplate: `/foo/bar`, + shouldMatch: true, + }, + { + route: subrouter5.Path("/"), + request: newRequest("GET", "http://localhost/baz/"), + vars: map[string]string{"category": "baz"}, + host: "", + path: "/baz/", + pathTemplate: `/{category}/`, + shouldMatch: true, + }, + { + route: subrouter5.Path(""), + request: newRequest("GET", "http://localhost/baz"), + vars: map[string]string{"category": "baz"}, + host: "", + path: "/baz", + pathTemplate: `/{category}`, + shouldMatch: true, + }, + { + title: "Build with scheme on parent router", + route: new(Route).Schemes("ftp").Host("google.com").Subrouter().Path("/"), + request: newRequest("GET", "ftp://google.com/"), + scheme: "ftp", + host: "google.com", + path: "/", + pathTemplate: `/`, + hostTemplate: `google.com`, + shouldMatch: true, + }, + { + title: "Prefer scheme on child route when building URLs", + route: new(Route).Schemes("https", "ftp").Host("google.com").Subrouter().Schemes("ftp").Path("/"), + request: newRequest("GET", "ftp://google.com/"), + scheme: "ftp", + host: "google.com", + path: "/", + pathTemplate: `/`, + hostTemplate: `google.com`, + shouldMatch: true, }, } for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testUseEscapedRoute(t, test) } } @@ -789,22 +1392,245 @@ func TestStrictSlash(t *testing.T) { for _, test := range tests { testRoute(t, test) + testTemplate(t, test) + testUseEscapedRoute(t, test) + } +} + +func TestUseEncodedPath(t *testing.T) { + r := NewRouter() + r.UseEncodedPath() + + tests := []routeTest{ + { + title: "Router with useEncodedPath, URL with encoded slash does match", + route: r.NewRoute().Path("/v1/{v1}/v2"), + request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), + vars: map[string]string{"v1": "1%2F2"}, + host: "", + path: "/v1/1%2F2/v2", + pathTemplate: `/v1/{v1}/v2`, + shouldMatch: true, + }, + { + title: "Router with useEncodedPath, URL with encoded slash doesn't match", + route: r.NewRoute().Path("/v1/1/2/v2"), + request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), + vars: map[string]string{"v1": "1%2F2"}, + host: "", + path: "/v1/1%2F2/v2", + pathTemplate: `/v1/1/2/v2`, + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + testTemplate(t, test) + } +} + +func TestWalkSingleDepth(t *testing.T) { + r0 := NewRouter() + r1 := NewRouter() + r2 := NewRouter() + + r0.Path("/g") + r0.Path("/o") + r0.Path("/d").Handler(r1) + r0.Path("/r").Handler(r2) + r0.Path("/a") + + r1.Path("/z") + r1.Path("/i") + r1.Path("/l") + r1.Path("/l") + + r2.Path("/i") + r2.Path("/l") + r2.Path("/l") + + paths := []string{"g", "o", "r", "i", "l", "l", "a"} + depths := []int{0, 0, 0, 1, 1, 1, 0} + i := 0 + err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error { + matcher := route.matchers[0].(*routeRegexp) + if matcher.template == "/d" { + return SkipRouter + } + if len(ancestors) != depths[i] { + t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors)) + } + if matcher.template != "/"+paths[i] { + t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template) + } + i++ + return nil + }) + if err != nil { + panic(err) + } + if i != len(paths) { + t.Errorf("Expected %d routes, found %d", len(paths), i) + } +} + +func TestWalkNested(t *testing.T) { + router := NewRouter() + + g := router.Path("/g").Subrouter() + o := g.PathPrefix("/o").Subrouter() + r := o.PathPrefix("/r").Subrouter() + i := r.PathPrefix("/i").Subrouter() + l1 := i.PathPrefix("/l").Subrouter() + l2 := l1.PathPrefix("/l").Subrouter() + l2.Path("/a") + + testCases := []struct { + path string + ancestors []*Route + }{ + {"/g", []*Route{}}, + {"/g/o", []*Route{g.parent.(*Route)}}, + {"/g/o/r", []*Route{g.parent.(*Route), o.parent.(*Route)}}, + {"/g/o/r/i", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route)}}, + {"/g/o/r/i/l", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route)}}, + {"/g/o/r/i/l/l", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route), l1.parent.(*Route)}}, + {"/g/o/r/i/l/l/a", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route), l1.parent.(*Route), l2.parent.(*Route)}}, + } + + idx := 0 + err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error { + path := testCases[idx].path + tpl := route.regexp.path.template + if tpl != path { + t.Errorf(`Expected %s got %s`, path, tpl) + } + currWantAncestors := testCases[idx].ancestors + if !reflect.DeepEqual(currWantAncestors, ancestors) { + t.Errorf(`Expected %+v got %+v`, currWantAncestors, ancestors) + } + idx++ + return nil + }) + if err != nil { + panic(err) + } + if idx != len(testCases) { + t.Errorf("Expected %d routes, found %d", len(testCases), idx) + } +} + +func TestWalkSubrouters(t *testing.T) { + router := NewRouter() + + g := router.Path("/g").Subrouter() + o := g.PathPrefix("/o").Subrouter() + o.Methods("GET") + o.Methods("PUT") + + // all 4 routes should be matched, but final 2 routes do not have path templates + paths := []string{"/g", "/g/o", "", ""} + idx := 0 + err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error { + path := paths[idx] + tpl, _ := route.GetPathTemplate() + if tpl != path { + t.Errorf(`Expected %s got %s`, path, tpl) + } + idx++ + return nil + }) + if err != nil { + panic(err) + } + if idx != len(paths) { + t.Errorf("Expected %d routes, found %d", len(paths), idx) + } +} + +func TestWalkErrorRoute(t *testing.T) { + router := NewRouter() + router.Path("/g") + expectedError := errors.New("error") + err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error { + return expectedError + }) + if err != expectedError { + t.Errorf("Expected %v routes, found %v", expectedError, err) } } +func TestWalkErrorMatcher(t *testing.T) { + router := NewRouter() + expectedError := router.Path("/g").Subrouter().Path("").GetError() + err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error { + return route.GetError() + }) + if err != expectedError { + t.Errorf("Expected %v routes, found %v", expectedError, err) + } +} + +func TestWalkErrorHandler(t *testing.T) { + handler := NewRouter() + expectedError := handler.Path("/path").Subrouter().Path("").GetError() + router := NewRouter() + router.Path("/g").Handler(handler) + err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error { + return route.GetError() + }) + if err != expectedError { + t.Errorf("Expected %v routes, found %v", expectedError, err) + } +} + +func TestSubrouterErrorHandling(t *testing.T) { + superRouterCalled := false + subRouterCalled := false + + router := NewRouter() + router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + superRouterCalled = true + }) + subRouter := router.PathPrefix("/bign8").Subrouter() + subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + subRouterCalled = true + }) + + req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil) + router.ServeHTTP(NewRecorder(), req) + + if superRouterCalled { + t.Error("Super router 404 handler called when sub-router 404 handler is available.") + } + if !subRouterCalled { + t.Error("Sub-router 404 handler was not called.") + } +} + +// See: https://github.com/gorilla/mux/issues/200 +func TestPanicOnCapturingGroups(t *testing.T) { + defer func() { + if recover() == nil { + t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed successfully.\n") + } + }() + NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json") +} + // ---------------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------------- func getRouteTemplate(route *Route) string { - host, path := "none", "none" - if route.regexp != nil { - if route.regexp.host != nil { - host = route.regexp.host.template - } - if route.regexp.path != nil { - path = route.regexp.path.template - } + host, err := route.GetHostTemplate() + if err != nil { + host = "none" + } + path, err := route.GetPathTemplate() + if err != nil { + path = "none" } return fmt.Sprintf("Host: %v, Path: %v", host, path) } @@ -814,10 +1640,16 @@ func testRoute(t *testing.T, test routeTest) { route := test.route vars := test.vars shouldMatch := test.shouldMatch - host := test.host - path := test.path - url := test.host + test.path + query := test.query shouldRedirect := test.shouldRedirect + uri := url.URL{ + Scheme: test.scheme, + Host: test.host, + Path: test.path, + } + if uri.Scheme == "" { + uri.Scheme = "http" + } var match RouteMatch ok := route.Match(request, &match) @@ -830,28 +1662,58 @@ func testRoute(t *testing.T, test routeTest) { return } if shouldMatch { - if test.vars != nil && !stringMapEqual(test.vars, match.Vars) { + if vars != nil && !stringMapEqual(vars, match.Vars) { t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars) return } - if host != "" { - u, _ := test.route.URLHost(mapToPairs(match.Vars)...) - if host != u.Host { - t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route)) + if test.scheme != "" { + u, err := route.URL(mapToPairs(match.Vars)...) + if err != nil { + t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route)) + } + if uri.Scheme != u.Scheme { + t.Errorf("(%v) URLScheme not equal: expected %v, got %v", test.title, uri.Scheme, u.Scheme) + return + } + } + if test.host != "" { + u, err := test.route.URLHost(mapToPairs(match.Vars)...) + if err != nil { + t.Fatalf("(%v) URLHost error: %v -- %v", test.title, err, getRouteTemplate(route)) + } + if uri.Scheme != u.Scheme { + t.Errorf("(%v) URLHost scheme not equal: expected %v, got %v -- %v", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route)) + return + } + if uri.Host != u.Host { + t.Errorf("(%v) URLHost host not equal: expected %v, got %v -- %v", test.title, uri.Host, u.Host, getRouteTemplate(route)) + return + } + } + if test.path != "" { + u, err := route.URLPath(mapToPairs(match.Vars)...) + if err != nil { + t.Fatalf("(%v) URLPath error: %v -- %v", test.title, err, getRouteTemplate(route)) + } + if uri.Path != u.Path { + t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, uri.Path, u.Path, getRouteTemplate(route)) return } } - if path != "" { - u, _ := route.URLPath(mapToPairs(match.Vars)...) - if path != u.Path { - t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route)) + if test.host != "" && test.path != "" { + u, err := route.URL(mapToPairs(match.Vars)...) + if err != nil { + t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route)) + } + if expected, got := uri.String(), u.String(); expected != got { + t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, expected, got, getRouteTemplate(route)) return } } - if url != "" { + if query != "" { u, _ := route.URL(mapToPairs(match.Vars)...) - if url != u.Host+u.Path { - t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route)) + if query != u.RawQuery { + t.Errorf("(%v) URL query not equal: expected %v, got %v", test.title, query, u.RawQuery) return } } @@ -866,34 +1728,65 @@ func testRoute(t *testing.T, test routeTest) { } } -// Tests that the context is cleared or not cleared properly depending on -// the configuration of the router -func TestKeepContext(t *testing.T) { - func1 := func(w http.ResponseWriter, r *http.Request) {} - - r := NewRouter() - r.HandleFunc("/", func1).Name("func1") +func testUseEscapedRoute(t *testing.T, test routeTest) { + test.route.useEncodedPath = true + testRoute(t, test) +} - req, _ := http.NewRequest("GET", "http://localhost/", nil) - context.Set(req, "t", 1) +func testTemplate(t *testing.T, test routeTest) { + route := test.route + pathTemplate := test.pathTemplate + if len(pathTemplate) == 0 { + pathTemplate = test.path + } + hostTemplate := test.hostTemplate + if len(hostTemplate) == 0 { + hostTemplate = test.host + } - res := new(http.ResponseWriter) - r.ServeHTTP(*res, req) + routePathTemplate, pathErr := route.GetPathTemplate() + if pathErr == nil && routePathTemplate != pathTemplate { + t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, pathTemplate, routePathTemplate) + } - if _, ok := context.GetOk(req, "t"); ok { - t.Error("Context should have been cleared at end of request") + routeHostTemplate, hostErr := route.GetHostTemplate() + if hostErr == nil && routeHostTemplate != hostTemplate { + t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, hostTemplate, routeHostTemplate) } +} - r.KeepContext = true +func testMethods(t *testing.T, test routeTest) { + route := test.route + methods, _ := route.GetMethods() + if strings.Join(methods, ",") != strings.Join(test.methods, ",") { + t.Errorf("(%v) GetMethods not equal: expected %v, got %v", test.title, test.methods, methods) + } +} - req, _ = http.NewRequest("GET", "http://localhost/", nil) - context.Set(req, "t", 1) +func testRegexp(t *testing.T, test routeTest) { + route := test.route + routePathRegexp, regexpErr := route.GetPathRegexp() + if test.pathRegexp != "" && regexpErr == nil && routePathRegexp != test.pathRegexp { + t.Errorf("(%v) GetPathRegexp not equal: expected %v, got %v", test.title, test.pathRegexp, routePathRegexp) + } +} - r.ServeHTTP(*res, req) - if _, ok := context.GetOk(req, "t"); !ok { - t.Error("Context should NOT have been cleared at end of request") +func testQueriesRegexp(t *testing.T, test routeTest) { + route := test.route + queries, queriesErr := route.GetQueriesRegexp() + gotQueries := strings.Join(queries, ",") + if test.queriesRegexp != "" && queriesErr == nil && gotQueries != test.queriesRegexp { + t.Errorf("(%v) GetQueriesRegexp not equal: expected %v, got %v", test.title, test.queriesRegexp, gotQueries) } +} +func testQueriesTemplates(t *testing.T, test routeTest) { + route := test.route + queries, queriesErr := route.GetQueriesTemplates() + gotQueries := strings.Join(queries, ",") + if test.queriesTemplate != "" && queriesErr == nil && gotQueries != test.queriesTemplate { + t.Errorf("(%v) GetQueriesTemplates not equal: expected %v, got %v", test.title, test.queriesTemplate, gotQueries) + } } type TestA301ResponseWriter struct { @@ -901,15 +1794,15 @@ type TestA301ResponseWriter struct { status int } -func (ho TestA301ResponseWriter) Header() http.Header { +func (ho *TestA301ResponseWriter) Header() http.Header { return http.Header(ho.hh) } -func (ho TestA301ResponseWriter) Write(b []byte) (int, error) { +func (ho *TestA301ResponseWriter) Write(b []byte) (int, error) { return 0, nil } -func (ho TestA301ResponseWriter) WriteHeader(code int) { +func (ho *TestA301ResponseWriter) WriteHeader(code int) { ho.status = code } @@ -936,6 +1829,24 @@ func Test301Redirect(t *testing.T) { } } +func TestSkipClean(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + func2 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.SkipClean(true) + r.HandleFunc("/api/", func2).Name("func2") + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil) + res := NewRecorder() + r.ServeHTTP(res, req) + + if len(res.HeaderMap["Location"]) != 0 { + t.Errorf("Shouldn't redirect since skip clean is disabled") + } +} + // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW func TestSubrouterHeader(t *testing.T) { expected := "func1 response" @@ -966,6 +1877,417 @@ func TestSubrouterHeader(t *testing.T) { } } +func TestNoMatchMethodErrorHandler(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", func1).Methods("GET", "POST") + + req, _ := http.NewRequest("PUT", "http://localhost/", nil) + match := new(RouteMatch) + matched := r.Match(req, match) + + if matched { + t.Error("Should not have matched route for methods") + } + + if match.MatchErr != ErrMethodMismatch { + t.Error("Should get ErrMethodMismatch error") + } + + resp := NewRecorder() + r.ServeHTTP(resp, req) + if resp.Code != 405 { + t.Errorf("Expecting code %v", 405) + } + + // Add matching route + r.HandleFunc("/", func1).Methods("PUT") + + match = new(RouteMatch) + matched = r.Match(req, match) + + if !matched { + t.Error("Should have matched route for methods") + } + + if match.MatchErr != nil { + t.Error("Should not have any matching error. Found:", match.MatchErr) + } +} + +func TestErrMatchNotFound(t *testing.T) { + emptyHandler := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", emptyHandler) + s := r.PathPrefix("/sub/").Subrouter() + s.HandleFunc("/", emptyHandler) + + // Regular 404 not found + req, _ := http.NewRequest("GET", "/sub/whatever", nil) + match := new(RouteMatch) + matched := r.Match(req, match) + + if matched { + t.Errorf("Subrouter should not have matched that, got %v", match.Route) + } + // Even without a custom handler, MatchErr is set to ErrNotFound + if match.MatchErr != ErrNotFound { + t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr) + } + + // Now lets add a 404 handler to subrouter + s.NotFoundHandler = http.NotFoundHandler() + req, _ = http.NewRequest("GET", "/sub/whatever", nil) + + // Test the subrouter first + match = new(RouteMatch) + matched = s.Match(req, match) + // Now we should get a match + if !matched { + t.Errorf("Subrouter should have matched %s", req.RequestURI) + } + // But MatchErr should be set to ErrNotFound anyway + if match.MatchErr != ErrNotFound { + t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr) + } + + // Now test the parent (MatchErr should propagate) + match = new(RouteMatch) + matched = r.Match(req, match) + + // Now we should get a match + if !matched { + t.Errorf("Router should have matched %s via subrouter", req.RequestURI) + } + // But MatchErr should be set to ErrNotFound anyway + if match.MatchErr != ErrNotFound { + t.Errorf("Expected ErrNotFound MatchErr, but was %v", match.MatchErr) + } +} + +// methodsSubrouterTest models the data necessary for testing handler +// matching for subrouters created after HTTP methods matcher registration. +type methodsSubrouterTest struct { + title string + wantCode int + router *Router + // method is the input into the request and expected response + method string + // input request path + path string + // redirectTo is the expected location path for strict-slash matches + redirectTo string +} + +// methodHandler writes the method string in response. +func methodHandler(method string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(method)) + } +} + +// TestMethodsSubrouterCatchall matches handlers for subrouters where a +// catchall handler is set for a mis-matching method. +func TestMethodsSubrouterCatchall(t *testing.T) { + t.Parallel() + + router := NewRouter() + router.Methods("PATCH").Subrouter().PathPrefix("/").HandlerFunc(methodHandler("PUT")) + router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET")) + router.Methods("POST").Subrouter().HandleFunc("/foo", methodHandler("POST")) + router.Methods("DELETE").Subrouter().HandleFunc("/foo", methodHandler("DELETE")) + + tests := []methodsSubrouterTest{ + { + title: "match GET handler", + router: router, + path: "http://localhost/foo", + method: "GET", + wantCode: http.StatusOK, + }, + { + title: "match POST handler", + router: router, + method: "POST", + path: "http://localhost/foo", + wantCode: http.StatusOK, + }, + { + title: "match DELETE handler", + router: router, + method: "DELETE", + path: "http://localhost/foo", + wantCode: http.StatusOK, + }, + { + title: "disallow PUT method", + router: router, + method: "PUT", + path: "http://localhost/foo", + wantCode: http.StatusMethodNotAllowed, + }, + } + + for _, test := range tests { + testMethodsSubrouter(t, test) + } +} + +// TestMethodsSubrouterStrictSlash matches handlers on subrouters with +// strict-slash matchers. +func TestMethodsSubrouterStrictSlash(t *testing.T) { + t.Parallel() + + router := NewRouter() + sub := router.PathPrefix("/").Subrouter() + sub.StrictSlash(true).Path("/foo").Methods("GET").Subrouter().HandleFunc("", methodHandler("GET")) + sub.StrictSlash(true).Path("/foo/").Methods("PUT").Subrouter().HandleFunc("/", methodHandler("PUT")) + sub.StrictSlash(true).Path("/foo/").Methods("POST").Subrouter().HandleFunc("/", methodHandler("POST")) + + tests := []methodsSubrouterTest{ + { + title: "match POST handler", + router: router, + method: "POST", + path: "http://localhost/foo/", + wantCode: http.StatusOK, + }, + { + title: "match GET handler", + router: router, + method: "GET", + path: "http://localhost/foo", + wantCode: http.StatusOK, + }, + { + title: "match POST handler, redirect strict-slash", + router: router, + method: "POST", + path: "http://localhost/foo", + redirectTo: "http://localhost/foo/", + wantCode: http.StatusMovedPermanently, + }, + { + title: "match GET handler, redirect strict-slash", + router: router, + method: "GET", + path: "http://localhost/foo/", + redirectTo: "http://localhost/foo", + wantCode: http.StatusMovedPermanently, + }, + { + title: "disallow DELETE method", + router: router, + method: "DELETE", + path: "http://localhost/foo", + wantCode: http.StatusMethodNotAllowed, + }, + } + + for _, test := range tests { + testMethodsSubrouter(t, test) + } +} + +// TestMethodsSubrouterPathPrefix matches handlers on subrouters created +// on a router with a path prefix matcher and method matcher. +func TestMethodsSubrouterPathPrefix(t *testing.T) { + t.Parallel() + + router := NewRouter() + router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST")) + router.PathPrefix("/1").Methods("DELETE").Subrouter().HandleFunc("/2", methodHandler("DELETE")) + router.PathPrefix("/1").Methods("PUT").Subrouter().HandleFunc("/2", methodHandler("PUT")) + router.PathPrefix("/1").Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST2")) + + tests := []methodsSubrouterTest{ + { + title: "match first POST handler", + router: router, + method: "POST", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "match DELETE handler", + router: router, + method: "DELETE", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "match PUT handler", + router: router, + method: "PUT", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "disallow PATCH method", + router: router, + method: "PATCH", + path: "http://localhost/1/2", + wantCode: http.StatusMethodNotAllowed, + }, + } + + for _, test := range tests { + testMethodsSubrouter(t, test) + } +} + +// TestMethodsSubrouterSubrouter matches handlers on subrouters produced +// from method matchers registered on a root subrouter. +func TestMethodsSubrouterSubrouter(t *testing.T) { + t.Parallel() + + router := NewRouter() + sub := router.PathPrefix("/1").Subrouter() + sub.Methods("POST").Subrouter().HandleFunc("/2", methodHandler("POST")) + sub.Methods("GET").Subrouter().HandleFunc("/2", methodHandler("GET")) + sub.Methods("PATCH").Subrouter().HandleFunc("/2", methodHandler("PATCH")) + sub.HandleFunc("/2", methodHandler("PUT")).Subrouter().Methods("PUT") + sub.HandleFunc("/2", methodHandler("POST2")).Subrouter().Methods("POST") + + tests := []methodsSubrouterTest{ + { + title: "match first POST handler", + router: router, + method: "POST", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "match GET handler", + router: router, + method: "GET", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "match PATCH handler", + router: router, + method: "PATCH", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "match PUT handler", + router: router, + method: "PUT", + path: "http://localhost/1/2", + wantCode: http.StatusOK, + }, + { + title: "disallow DELETE method", + router: router, + method: "DELETE", + path: "http://localhost/1/2", + wantCode: http.StatusMethodNotAllowed, + }, + } + + for _, test := range tests { + testMethodsSubrouter(t, test) + } +} + +// TestMethodsSubrouterPathVariable matches handlers on matching paths +// with path variables in them. +func TestMethodsSubrouterPathVariable(t *testing.T) { + t.Parallel() + + router := NewRouter() + router.Methods("GET").Subrouter().HandleFunc("/foo", methodHandler("GET")) + router.Methods("POST").Subrouter().HandleFunc("/{any}", methodHandler("POST")) + router.Methods("DELETE").Subrouter().HandleFunc("/1/{any}", methodHandler("DELETE")) + router.Methods("PUT").Subrouter().HandleFunc("/1/{any}", methodHandler("PUT")) + + tests := []methodsSubrouterTest{ + { + title: "match GET handler", + router: router, + method: "GET", + path: "http://localhost/foo", + wantCode: http.StatusOK, + }, + { + title: "match POST handler", + router: router, + method: "POST", + path: "http://localhost/foo", + wantCode: http.StatusOK, + }, + { + title: "match DELETE handler", + router: router, + method: "DELETE", + path: "http://localhost/1/foo", + wantCode: http.StatusOK, + }, + { + title: "match PUT handler", + router: router, + method: "PUT", + path: "http://localhost/1/foo", + wantCode: http.StatusOK, + }, + { + title: "disallow PATCH method", + router: router, + method: "PATCH", + path: "http://localhost/1/foo", + wantCode: http.StatusMethodNotAllowed, + }, + } + + for _, test := range tests { + testMethodsSubrouter(t, test) + } +} + +func ExampleSetURLVars() { + req, _ := http.NewRequest("GET", "/foo", nil) + req = SetURLVars(req, map[string]string{"foo": "bar"}) + + fmt.Println(Vars(req)["foo"]) + + // Output: bar +} + +// testMethodsSubrouter runs an individual methodsSubrouterTest. +func testMethodsSubrouter(t *testing.T, test methodsSubrouterTest) { + // Execute request + req, _ := http.NewRequest(test.method, test.path, nil) + resp := NewRecorder() + test.router.ServeHTTP(resp, req) + + switch test.wantCode { + case http.StatusMethodNotAllowed: + if resp.Code != http.StatusMethodNotAllowed { + t.Errorf(`(%s) Expected "405 Method Not Allowed", but got %d code`, test.title, resp.Code) + } else if matchedMethod := resp.Body.String(); matchedMethod != "" { + t.Errorf(`(%s) Expected "405 Method Not Allowed", but %q handler was called`, test.title, matchedMethod) + } + + case http.StatusMovedPermanently: + if gotLocation := resp.HeaderMap.Get("Location"); gotLocation != test.redirectTo { + t.Errorf("(%s) Expected %q route-match to redirect to %q, but got %q", test.title, test.method, test.redirectTo, gotLocation) + } + + case http.StatusOK: + if matchedMethod := resp.Body.String(); matchedMethod != test.method { + t.Errorf("(%s) Expected %q handler to be called, but %q handler was called", test.title, test.method, matchedMethod) + } + + default: + expectedCodes := []int{http.StatusMethodNotAllowed, http.StatusMovedPermanently, http.StatusOK} + t.Errorf("(%s) Expected wantCode to be one of: %v, but got %d", test.title, expectedCodes, test.wantCode) + } +} + // mapToPairs converts a string map to a slice of string pairs func mapToPairs(m map[string]string) []string { var i int @@ -993,11 +2315,50 @@ func stringMapEqual(m1, m2 map[string]string) bool { return true } -// newRequest is a helper function to create a new request with a method and url +// stringHandler returns a handler func that writes a message 's' to the +// http.ResponseWriter. +func stringHandler(s string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(s)) + } +} + +// newRequest is a helper function to create a new request with a method and url. +// The request returned is a 'server' request as opposed to a 'client' one through +// simulated write onto the wire and read off of the wire. +// The differences between requests are detailed in the net/http package. func newRequest(method, url string) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { panic(err) } + // extract the escaped original host+path from url + // http://localhost/path/here?v=1#frag -> //localhost/path/here + opaque := "" + if i := len(req.URL.Scheme); i > 0 { + opaque = url[i+1:] + } + + if i := strings.LastIndex(opaque, "?"); i > -1 { + opaque = opaque[:i] + } + if i := strings.LastIndex(opaque, "#"); i > -1 { + opaque = opaque[:i] + } + + // Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL + // for < 1.5 client side workaround + req.URL.Opaque = opaque + + // Simulate writing to wire + var buff bytes.Buffer + req.Write(&buff) + ioreader := bufio.NewReader(&buff) + + // Parse request off of 'wire' + req, err = http.ReadRequest(ioreader) + if err != nil { + panic(err) + } return req } diff --git a/vendor/github.com/gorilla/mux/old_test.go b/vendor/github.com/gorilla/mux/old_test.go index 1f7c190c0..b228983c4 100644 --- a/vendor/github.com/gorilla/mux/old_test.go +++ b/vendor/github.com/gorilla/mux/old_test.go @@ -36,10 +36,6 @@ func NewRecorder() *ResponseRecorder { } } -// DefaultRemoteAddr is the default remote address to return in RemoteAddr if -// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. -const DefaultRemoteAddr = "1.2.3.4" - // Header returns the response headers. func (rw *ResponseRecorder) Header() http.Header { return rw.HeaderMap @@ -125,12 +121,7 @@ func TestRouteMatchers(t *testing.T) { var routeMatch RouteMatch matched := router.Match(request, &routeMatch) if matched != shouldMatch { - // Need better messages. :) - if matched { - t.Errorf("Should match.") - } else { - t.Errorf("Should not match.") - } + t.Errorf("Expected: %v\nGot: %v\nRequest: %v %v", shouldMatch, matched, request.Method, url) } if matched { @@ -192,7 +183,6 @@ func TestRouteMatchers(t *testing.T) { match(true) // 2nd route -------------------------------------------------------------- - // Everything match. reset2() match(true) @@ -545,7 +535,7 @@ func TestMatchedRouteName(t *testing.T) { router := NewRouter() route := router.NewRoute().Path("/products/").Name(routeName) - url := "http://www.domain.com/products/" + url := "http://www.example.com/products/" request, _ := http.NewRequest("GET", url, nil) var rv RouteMatch ok := router.Match(request, &rv) @@ -563,10 +553,10 @@ func TestMatchedRouteName(t *testing.T) { func TestSubRouting(t *testing.T) { // Example from docs. router := NewRouter() - subrouter := router.NewRoute().Host("www.domain.com").Subrouter() + subrouter := router.NewRoute().Host("www.example.com").Subrouter() route := subrouter.NewRoute().Path("/products/").Name("products") - url := "http://www.domain.com/products/" + url := "http://www.example.com/products/" request, _ := http.NewRequest("GET", url, nil) var rv RouteMatch ok := router.Match(request, &rv) @@ -576,10 +566,10 @@ func TestSubRouting(t *testing.T) { } u, _ := router.Get("products").URL() - builtUrl := u.String() + builtURL := u.String() // Yay, subroute aware of the domain when building! - if builtUrl != url { - t.Errorf("Expected %q, got %q.", url, builtUrl) + if builtURL != url { + t.Errorf("Expected %q, got %q.", url, builtURL) } } @@ -691,7 +681,7 @@ func TestNewRegexp(t *testing.T) { } for pattern, paths := range tests { - p, _ = newRouteRegexp(pattern, false, false, false, false) + p, _ = newRouteRegexp(pattern, regexpTypePath, routeRegexpOptions{}) for path, result := range paths { matches = p.regexp.FindStringSubmatch(path) if result == nil { diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go index aa3067986..2b57e5627 100644 --- a/vendor/github.com/gorilla/mux/regexp.go +++ b/vendor/github.com/gorilla/mux/regexp.go @@ -10,9 +10,24 @@ import ( "net/http" "net/url" "regexp" + "strconv" "strings" ) +type routeRegexpOptions struct { + strictSlash bool + useEncodedPath bool +} + +type regexpType int + +const ( + regexpTypePath regexpType = 0 + regexpTypeHost regexpType = 1 + regexpTypePrefix regexpType = 2 + regexpTypeQuery regexpType = 3 +) + // newRouteRegexp parses a route template and returns a routeRegexp, // used to match a host, a path or a query string. // @@ -23,7 +38,7 @@ import ( // Previously we accepted only Python-like identifiers for variable // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // name and pattern can't be empty, and names can't contain a colon. -func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { +func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) if errBraces != nil { @@ -33,29 +48,25 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash template := tpl // Now let's parse it. defaultPattern := "[^/]+" - if matchQuery { - defaultPattern = "[^?&]+" - matchPrefix = true - } else if matchHost { + if typ == regexpTypeQuery { + defaultPattern = ".*" + } else if typ == regexpTypeHost { defaultPattern = "[^.]+" - matchPrefix = false } // Only match strict slash if not matching - if matchPrefix || matchHost || matchQuery { - strictSlash = false + if typ != regexpTypePath { + options.strictSlash = false } // Set a flag for strictSlash. endSlash := false - if strictSlash && strings.HasSuffix(tpl, "/") { + if options.strictSlash && strings.HasSuffix(tpl, "/") { tpl = tpl[:len(tpl)-1] endSlash = true } varsN := make([]string, len(idxs)/2) varsR := make([]*regexp.Regexp, len(idxs)/2) pattern := bytes.NewBufferString("") - if !matchQuery { - pattern.WriteByte('^') - } + pattern.WriteByte('^') reverse := bytes.NewBufferString("") var end int var err error @@ -75,9 +86,11 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash tpl[idxs[i]:end]) } // Build the regexp pattern. - fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) + fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) + // Build the reverse template. fmt.Fprintf(reverse, "%s%%s", raw) + // Append variable name and compiled pattern. varsN[i/2] = name varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) @@ -88,10 +101,16 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash // Add the remaining. raw := tpl[end:] pattern.WriteString(regexp.QuoteMeta(raw)) - if strictSlash { + if options.strictSlash { pattern.WriteString("[/]?") } - if !matchPrefix { + if typ == regexpTypeQuery { + // Add the default pattern if the query value is empty + if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { + pattern.WriteString(defaultPattern) + } + } + if typ != regexpTypePrefix { pattern.WriteByte('$') } reverse.WriteString(raw) @@ -103,16 +122,22 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash if errCompile != nil { return nil, errCompile } + + // Check for capturing groups which used to work in older versions + if reg.NumSubexp() != len(idxs)/2 { + panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + + "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") + } + // Done! return &routeRegexp{ - template: template, - matchHost: matchHost, - matchQuery: matchQuery, - strictSlash: strictSlash, - regexp: reg, - reverse: reverse.String(), - varsN: varsN, - varsR: varsR, + template: template, + regexpType: typ, + options: options, + regexp: reg, + reverse: reverse.String(), + varsN: varsN, + varsR: varsR, }, nil } @@ -121,12 +146,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash type routeRegexp struct { // The unmodified template. template string - // True for host match, false for path or query string match. - matchHost bool - // True for query string match, false for path and host match. - matchQuery bool - // The strictSlash value defined on the route, but disabled if PathPrefix was used. - strictSlash bool + // The type of match + regexpType regexpType + // Options for matching + options routeRegexpOptions // Expanded regexp. regexp *regexp.Regexp // Reverse template. @@ -139,13 +162,17 @@ type routeRegexp struct { // Match matches the regexp against the URL host or path. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { - if !r.matchHost { - if r.matchQuery { - return r.regexp.MatchString(req.URL.RawQuery) - } else { - return r.regexp.MatchString(req.URL.Path) + if r.regexpType != regexpTypeHost { + if r.regexpType == regexpTypeQuery { + return r.matchQueryString(req) + } + path := req.URL.Path + if r.options.useEncodedPath { + path = req.URL.EscapedPath() } + return r.regexp.MatchString(path) } + return r.regexp.MatchString(getHost(req)) } @@ -157,6 +184,9 @@ func (r *routeRegexp) url(values map[string]string) (string, error) { if !ok { return "", fmt.Errorf("mux: missing route variable %q", v) } + if r.regexpType == regexpTypeQuery { + value = url.QueryEscape(value) + } urlValues[k] = value } rv := fmt.Sprintf(r.reverse, urlValues...) @@ -175,11 +205,31 @@ func (r *routeRegexp) url(values map[string]string) (string, error) { return rv, nil } +// getURLQuery returns a single query parameter from a request URL. +// For a URL with foo=bar&baz=ding, we return only the relevant key +// value pair for the routeRegexp. +func (r *routeRegexp) getURLQuery(req *http.Request) string { + if r.regexpType != regexpTypeQuery { + return "" + } + templateKey := strings.SplitN(r.template, "=", 2)[0] + for key, vals := range req.URL.Query() { + if key == templateKey && len(vals) > 0 { + return key + "=" + vals[0] + } + } + return "" +} + +func (r *routeRegexp) matchQueryString(req *http.Request) bool { + return r.regexp.MatchString(r.getURLQuery(req)) +} + // braceIndices returns the first level curly brace indices from a string. // It returns an error in case of unbalanced braces. func braceIndices(s string) ([]int, error) { var level, idx int - idxs := make([]int, 0) + var idxs []int for i := 0; i < len(s); i++ { switch s[i] { case '{': @@ -200,6 +250,11 @@ func braceIndices(s string) ([]int, error) { return idxs, nil } +// varGroupName builds a capturing group name for the indexed variable. +func varGroupName(idx int) string { + return "v" + strconv.Itoa(idx) +} + // ---------------------------------------------------------------------------- // routeRegexpGroup // ---------------------------------------------------------------------------- @@ -215,23 +270,24 @@ type routeRegexpGroup struct { func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { // Store host variables. if v.host != nil { - hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) - if hostVars != nil { - for k, v := range v.host.varsN { - m.Vars[v] = hostVars[k+1] - } + host := getHost(req) + matches := v.host.regexp.FindStringSubmatchIndex(host) + if len(matches) > 0 { + extractVars(host, matches, v.host.varsN, m.Vars) } } + path := req.URL.Path + if r.useEncodedPath { + path = req.URL.EscapedPath() + } // Store path variables. if v.path != nil { - pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) - if pathVars != nil { - for k, v := range v.path.varsN { - m.Vars[v] = pathVars[k+1] - } + matches := v.path.regexp.FindStringSubmatchIndex(path) + if len(matches) > 0 { + extractVars(path, matches, v.path.varsN, m.Vars) // Check if we should redirect. - if v.path.strictSlash { - p1 := strings.HasSuffix(req.URL.Path, "/") + if v.path.options.strictSlash { + p1 := strings.HasSuffix(path, "/") p2 := strings.HasSuffix(v.path.template, "/") if p1 != p2 { u, _ := url.Parse(req.URL.String()) @@ -246,13 +302,11 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) } } // Store query string variables. - rawQuery := req.URL.RawQuery for _, q := range v.queries { - queryVars := q.regexp.FindStringSubmatch(rawQuery) - if queryVars != nil { - for k, v := range q.varsN { - m.Vars[v] = queryVars[k+1] - } + queryURL := q.getURLQuery(req) + matches := q.regexp.FindStringSubmatchIndex(queryURL) + if len(matches) > 0 { + extractVars(queryURL, matches, q.varsN, m.Vars) } } } @@ -270,3 +324,9 @@ func getHost(r *http.Request) string { return host } + +func extractVars(input string, matches []int, names []string, output map[string]string) { + for i, name := range names { + output[name] = input[matches[2*i+2]:matches[2*i+3]] + } +} diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go index d4f014688..a591d7354 100644 --- a/vendor/github.com/gorilla/mux/route.go +++ b/vendor/github.com/gorilla/mux/route.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "regexp" "strings" ) @@ -25,6 +26,13 @@ type Route struct { // If true, when the path pattern is "/path/", accessing "/path" will // redirect to the former and vice versa. strictSlash bool + // If true, when the path pattern is "/path//to", accessing "/path//to" + // will not redirect + skipClean bool + // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to" + useEncodedPath bool + // The scheme used when building URLs. + buildScheme string // If true, this route never matches: it is only used to build URLs. buildOnly bool // The name used to build URLs. @@ -35,17 +43,44 @@ type Route struct { buildVarsFunc BuildVarsFunc } +// SkipClean reports whether path cleaning is enabled for this route via +// Router.SkipClean. +func (r *Route) SkipClean() bool { + return r.skipClean +} + // Match matches the route against the request. func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if r.buildOnly || r.err != nil { return false } + + var matchErr error + // Match everything. for _, m := range r.matchers { if matched := m.Match(req, match); !matched { + if _, ok := m.(methodMatcher); ok { + matchErr = ErrMethodMismatch + continue + } + matchErr = nil return false } } + + if matchErr != nil { + match.MatchErr = matchErr + return false + } + + if match.MatchErr == ErrMethodMismatch { + // We found a route which matches request method, clear MatchErr + match.MatchErr = nil + // Then override the mis-matched handler + match.Handler = r.handler + } + // Yay, we have a match. Let's collect some info about it. if match.Route == nil { match.Route = r @@ -56,6 +91,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if match.Vars == nil { match.Vars = make(map[string]string) } + // Set variables. if r.regexp != nil { r.regexp.setMatch(req, match, r) @@ -137,20 +173,23 @@ func (r *Route) addMatcher(m matcher) *Route { } // addRegexpMatcher adds a host or path matcher and builder to a route. -func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { +func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { if r.err != nil { return r.err } r.regexp = r.getRegexpGroup() - if !matchHost && !matchQuery { - if len(tpl) == 0 || tpl[0] != '/' { + if typ == regexpTypePath || typ == regexpTypePrefix { + if len(tpl) > 0 && tpl[0] != '/' { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } if r.regexp.path != nil { tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl } } - rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) + rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ + strictSlash: r.strictSlash, + useEncodedPath: r.useEncodedPath, + }) if err != nil { return err } @@ -159,7 +198,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery return err } } - if matchHost { + if typ == regexpTypeHost { if r.regexp.path != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { return err @@ -172,7 +211,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery return err } } - if matchQuery { + if typ == regexpTypeQuery { r.regexp.queries = append(r.regexp.queries, rr) } else { r.regexp.path = rr @@ -188,7 +227,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery type headerMatcher map[string]string func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMap(m, r.Header, true) + return matchMapWithString(m, r.Header, true) } // Headers adds a matcher for request header values. @@ -199,22 +238,47 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { // "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both request header values match. -// -// It the value is an empty string, it will match any value if the key is set. +// If the value is an empty string, it will match any value if the key is set. func (r *Route) Headers(pairs ...string) *Route { if r.err == nil { var headers map[string]string - headers, r.err = mapFromPairs(pairs...) + headers, r.err = mapFromPairsToString(pairs...) return r.addMatcher(headerMatcher(headers)) } return r } +// headerRegexMatcher matches the request against the route given a regex for the header +type headerRegexMatcher map[string]*regexp.Regexp + +func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchMapWithRegex(m, r.Header, true) +} + +// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex +// support. For example: +// +// r := mux.NewRouter() +// r.HeadersRegexp("Content-Type", "application/(text|json)", +// "X-Requested-With", "XMLHttpRequest") +// +// The above route will only match if both the request header matches both regular expressions. +// If the value is an empty string, it will match any value if the key is set. +// Use the start and end of string anchors (^ and $) to match an exact value. +func (r *Route) HeadersRegexp(pairs ...string) *Route { + if r.err == nil { + var headers map[string]*regexp.Regexp + headers, r.err = mapFromPairsToRegex(pairs...) + return r.addMatcher(headerRegexMatcher(headers)) + } + return r +} + // Host ----------------------------------------------------------------------- // Host adds a matcher for the URL host. // It accepts a template with zero or more URL variables enclosed by {}. -// Variables can define an optional regexp pattern to me matched: +// Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next dot. // @@ -223,14 +287,14 @@ func (r *Route) Headers(pairs ...string) *Route { // For example: // // r := mux.NewRouter() -// r.Host("www.domain.com") +// r.Host("www.example.com") // r.Host("{subdomain}.domain.com") // r.Host("{subdomain:[a-z]+}.domain.com") // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Host(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, true, false, false) + r.err = r.addRegexpMatcher(tpl, regexpTypeHost) return r } @@ -239,6 +303,7 @@ func (r *Route) Host(tpl string) *Route { // MatcherFunc is the function signature used by custom matchers. type MatcherFunc func(*http.Request, *RouteMatch) bool +// Match returns the match for a given request. func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { return m(r, match) } @@ -272,7 +337,7 @@ func (r *Route) Methods(methods ...string) *Route { // Path adds a matcher for the URL path. // It accepts a template with zero or more URL variables enclosed by {}. The // template must start with a "/". -// Variables can define an optional regexp pattern to me matched: +// Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next slash. // @@ -289,7 +354,7 @@ func (r *Route) Methods(methods ...string) *Route { // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Path(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, false, false) + r.err = r.addRegexpMatcher(tpl, regexpTypePath) return r } @@ -305,7 +370,7 @@ func (r *Route) Path(tpl string) *Route { // Also note that the setting of Router.StrictSlash() has no effect on routes // with a PathPrefix matcher. func (r *Route) PathPrefix(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, true, false) + r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) return r } @@ -323,7 +388,7 @@ func (r *Route) PathPrefix(tpl string) *Route { // // It the value is an empty string, it will match any value if the key is set. // -// Variables can define an optional regexp pattern to me matched: +// Variables can define an optional regexp pattern to be matched: // // - {name} matches anything until the next slash. // @@ -336,7 +401,7 @@ func (r *Route) Queries(pairs ...string) *Route { return nil } for i := 0; i < length; i += 2 { - if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { return r } } @@ -359,6 +424,9 @@ func (r *Route) Schemes(schemes ...string) *Route { for k, v := range schemes { schemes[k] = strings.ToLower(v) } + if r.buildScheme == "" && len(schemes) > 0 { + r.buildScheme = schemes[0] + } return r.addMatcher(schemeMatcher(schemes)) } @@ -382,7 +450,7 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { // It will test the inner routes only if the parent route matched. For example: // // r := mux.NewRouter() -// s := r.Host("www.domain.com").Subrouter() +// s := r.Host("www.example.com").Subrouter() // s.HandleFunc("/products/", ProductsHandler) // s.HandleFunc("/products/{key}", ProductHandler) // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) @@ -442,22 +510,33 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) { return nil, err } var scheme, host, path string + queries := make([]string, 0, len(r.regexp.queries)) if r.regexp.host != nil { - // Set a default scheme. - scheme = "http" if host, err = r.regexp.host.url(values); err != nil { return nil, err } + scheme = "http" + if s := r.getBuildScheme(); s != "" { + scheme = s + } } if r.regexp.path != nil { if path, err = r.regexp.path.url(values); err != nil { return nil, err } } + for _, q := range r.regexp.queries { + var query string + if query, err = q.url(values); err != nil { + return nil, err + } + queries = append(queries, query) + } return &url.URL{ - Scheme: scheme, - Host: host, - Path: path, + Scheme: scheme, + Host: host, + Path: path, + RawQuery: strings.Join(queries, "&"), }, nil } @@ -479,10 +558,14 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) { if err != nil { return nil, err } - return &url.URL{ + u := &url.URL{ Scheme: "http", Host: host, - }, nil + } + if s := r.getBuildScheme(); s != "" { + u.Scheme = s + } + return u, nil } // URLPath builds the path part of the URL for a route. See Route.URL(). @@ -508,10 +591,108 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) { }, nil } +// GetPathTemplate returns the template used to build the +// route match. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a path. +func (r *Route) GetPathTemplate() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp == nil || r.regexp.path == nil { + return "", errors.New("mux: route doesn't have a path") + } + return r.regexp.path.template, nil +} + +// GetPathRegexp returns the expanded regular expression used to match route path. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a path. +func (r *Route) GetPathRegexp() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp == nil || r.regexp.path == nil { + return "", errors.New("mux: route does not have a path") + } + return r.regexp.path.regexp.String(), nil +} + +// GetQueriesRegexp returns the expanded regular expressions used to match the +// route queries. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not have queries. +func (r *Route) GetQueriesRegexp() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + var queries []string + for _, query := range r.regexp.queries { + queries = append(queries, query.regexp.String()) + } + return queries, nil +} + +// GetQueriesTemplates returns the templates used to build the +// query matching. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define queries. +func (r *Route) GetQueriesTemplates() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + var queries []string + for _, query := range r.regexp.queries { + queries = append(queries, query.template) + } + return queries, nil +} + +// GetMethods returns the methods the route matches against +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if route does not have methods. +func (r *Route) GetMethods() ([]string, error) { + if r.err != nil { + return nil, r.err + } + for _, m := range r.matchers { + if methods, ok := m.(methodMatcher); ok { + return []string(methods), nil + } + } + return nil, errors.New("mux: route doesn't have methods") +} + +// GetHostTemplate returns the template used to build the +// route match. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An error will be returned if the route does not define a host. +func (r *Route) GetHostTemplate() (string, error) { + if r.err != nil { + return "", r.err + } + if r.regexp == nil || r.regexp.host == nil { + return "", errors.New("mux: route doesn't have a host") + } + return r.regexp.host.template, nil +} + // prepareVars converts the route variable pairs into a map. If the route has a // BuildVarsFunc, it is invoked. func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { - m, err := mapFromPairs(pairs...) + m, err := mapFromPairsToString(pairs...) if err != nil { return nil, err } @@ -534,11 +715,22 @@ func (r *Route) buildVars(m map[string]string) map[string]string { // parentRoute allows routes to know about parent host and path definitions. type parentRoute interface { + getBuildScheme() string getNamedRoutes() map[string]*Route getRegexpGroup() *routeRegexpGroup buildVars(map[string]string) map[string]string } +func (r *Route) getBuildScheme() string { + if r.buildScheme != "" { + return r.buildScheme + } + if r.parent != nil { + return r.parent.getBuildScheme() + } + return "" +} + // getNamedRoutes returns the map where named routes are registered. func (r *Route) getNamedRoutes() map[string]*Route { if r.parent == nil { diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go new file mode 100644 index 000000000..32ecffde4 --- /dev/null +++ b/vendor/github.com/gorilla/mux/test_helpers.go @@ -0,0 +1,19 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import "net/http" + +// SetURLVars sets the URL variables for the given request, to be accessed via +// mux.Vars for testing route behaviour. Arguments are not modified, a shallow +// copy is returned. +// +// This API should only be used for testing purposes; it provides a way to +// inject variables into the request context. Alternatively, URL variables +// can be set by making a route that captures the required variables, +// starting a server and sending the request to that server. +func SetURLVars(r *http.Request, val map[string]string) *http.Request { + return setVars(r, val) +} diff --git a/vendor/github.com/jmespath/go-jmespath/.gitignore b/vendor/github.com/jmespath/go-jmespath/.gitignore index 531fcc11c..5091fb073 100644 --- a/vendor/github.com/jmespath/go-jmespath/.gitignore +++ b/vendor/github.com/jmespath/go-jmespath/.gitignore @@ -1,4 +1,4 @@ -jpgo +/jpgo jmespath-fuzz.zip cpu.out go-jmespath.test diff --git a/vendor/github.com/jmespath/go-jmespath/api.go b/vendor/github.com/jmespath/go-jmespath/api.go index 9cfa988bc..8e26ffeec 100644 --- a/vendor/github.com/jmespath/go-jmespath/api.go +++ b/vendor/github.com/jmespath/go-jmespath/api.go @@ -2,7 +2,7 @@ package jmespath import "strconv" -// JmesPath is the epresentation of a compiled JMES path query. A JmesPath is +// JMESPath is the epresentation of a compiled JMES path query. A JMESPath is // safe for concurrent use by multiple goroutines. type JMESPath struct { ast ASTNode diff --git a/vendor/github.com/jmespath/go-jmespath/util_test.go b/vendor/github.com/jmespath/go-jmespath/util_test.go index 1754b5d3f..195e8b7d3 100644 --- a/vendor/github.com/jmespath/go-jmespath/util_test.go +++ b/vendor/github.com/jmespath/go-jmespath/util_test.go @@ -1,8 +1,9 @@ package jmespath import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestSlicePositiveStep(t *testing.T) { @@ -45,7 +46,7 @@ func TestIsFalseWithUserDefinedStructs(t *testing.T) { func TestIsFalseWithNilInterface(t *testing.T) { assert := assert.New(t) - var a *int = nil + var a *int var nilInterface interface{} nilInterface = a assert.True(isFalse(nilInterface)) diff --git a/vendor/github.com/kubernetes/repo-infra/.gitignore b/vendor/github.com/kubernetes/repo-infra/.gitignore index bf5a9a8c7..d35f6b36c 100644 --- a/vendor/github.com/kubernetes/repo-infra/.gitignore +++ b/vendor/github.com/kubernetes/repo-infra/.gitignore @@ -4,3 +4,4 @@ __pycache__/ *$py.class /bazel-* +/_output diff --git a/vendor/github.com/kubernetes/repo-infra/.travis.yml b/vendor/github.com/kubernetes/repo-infra/.travis.yml index 2670decd6..611710bec 100644 --- a/vendor/github.com/kubernetes/repo-infra/.travis.yml +++ b/vendor/github.com/kubernetes/repo-infra/.travis.yml @@ -1,8 +1,11 @@ dist: trusty sudo: required -# Install Bazel and set up GOPATH. +# Install latest Go and Bazel and set up GOPATH. before_install: +# gimme on travis is too old for .x version resolution, and gimme stable seems broken, too +- LATEST_GO=$(gimme --known | sort -V | tail -1) +- eval "$(gimme ${LATEST_GO})" - mkdir -p $GOPATH/src/k8s.io - mv $TRAVIS_BUILD_DIR $GOPATH/src/k8s.io - cd $GOPATH/src/k8s.io/repo-infra @@ -13,13 +16,13 @@ before_install: install: - sudo apt-get install bazel - go get -u github.com/alecthomas/gometalinter - - go get -u github.com/bazelbuild/buildifier/buildifier + - go get -u github.com/bazelbuild/buildtools/buildifier - gometalinter --install - go install ./... script: - verify/verify-boilerplate.sh --rootdir="$GOPATH/src/k8s.io/repo-infra" -v - verify/verify-go-src.sh --rootdir "$GOPATH/src/k8s.io/repo-infra" -v - - buildifier -mode=check $(find . -name BUILD -o -name '*.bzl' -type f) + - buildifier -mode=check $(find . -name BUILD -o -name '*.bzl' -type f -not -wholename '*/vendor/*') - kazel --print-diff --validate - bazel build //... diff --git a/vendor/github.com/kubernetes/repo-infra/BUILD b/vendor/github.com/kubernetes/repo-infra/BUILD deleted file mode 100644 index ca598fef1..000000000 --- a/vendor/github.com/kubernetes/repo-infra/BUILD +++ /dev/null @@ -1,7 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) - -load("@io_bazel_rules_go//go:def.bzl", "go_prefix") - -go_prefix("k8s.io/repo-infra") diff --git a/vendor/github.com/kubernetes/repo-infra/BUILD.bazel b/vendor/github.com/kubernetes/repo-infra/BUILD.bazel new file mode 100644 index 000000000..b55361e3b --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/BUILD.bazel @@ -0,0 +1,5 @@ +# gazelle:prefix k8s.io/repo-infra + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) diff --git a/vendor/github.com/kubernetes/repo-infra/Gopkg.lock b/vendor/github.com/kubernetes/repo-infra/Gopkg.lock new file mode 100644 index 000000000..9aeecb1c1 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/Gopkg.lock @@ -0,0 +1,59 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/bazelbuild/bazel-gazelle" + packages = [ + "cmd/gazelle", + "internal/config", + "internal/generator", + "internal/label", + "internal/merger", + "internal/packages", + "internal/pathtools", + "internal/repos", + "internal/resolve", + "internal/version", + "internal/wspace" + ] + revision = "7f30ba724af9495b221e2df0f5ac58511179485f" + version = "0.12.0" + +[[projects]] + name = "github.com/bazelbuild/buildtools" + packages = [ + "build", + "tables" + ] + revision = "80c7f0d45d7e40fa1f7362852697d4a03df557b3" + +[[projects]] + branch = "master" + name = "github.com/golang/glog" + packages = ["."] + revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" + +[[projects]] + name = "github.com/pelletier/go-toml" + packages = ["."] + revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" + version = "v1.1.0" + +[[projects]] + branch = "master" + name = "golang.org/x/build" + packages = ["pargzip"] + revision = "125f04e1fc4b4cbfed95e5dd72a435fcb3847608" + +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = ["go/vcs"] + revision = "77106db15f689a60e7d4e085d967ac557b918fb2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "b8912d35b54371947356f238c87883cb08488520bc1b19d6e7a46a4c1ed00968" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/kubernetes/repo-infra/Gopkg.toml b/vendor/github.com/kubernetes/repo-infra/Gopkg.toml new file mode 100644 index 000000000..a4fa0bdbb --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/Gopkg.toml @@ -0,0 +1,30 @@ +required = [ + "github.com/bazelbuild/bazel-gazelle/cmd/gazelle", +] + +[prune] + unused-packages = true + go-tests = true + non-go = true + +[[constraint]] + branch = "master" + name = "github.com/golang/glog" + +[[constraint]] + branch = "master" + name = "golang.org/x/build" + + +# BEGIN gazelle dependencies +# Based on https://github.com/bazelbuild/bazel-gazelle/blob/0.10.1/deps.bzl + +[[constraint]] + name = "github.com/bazelbuild/bazel-gazelle" + version = "0.12.0" + +[[constraint]] + name = "github.com/bazelbuild/buildtools" + revision = "80c7f0d45d7e40fa1f7362852697d4a03df557b3" + +# END gazelle dependencies diff --git a/vendor/github.com/kubernetes/repo-infra/OWNERS b/vendor/github.com/kubernetes/repo-infra/OWNERS new file mode 100644 index 000000000..1df223e3d --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/OWNERS @@ -0,0 +1,3 @@ +approvers: +- ixdy +- mikedanese diff --git a/vendor/github.com/kubernetes/repo-infra/WORKSPACE b/vendor/github.com/kubernetes/repo-infra/WORKSPACE index 17f9b8804..029ca77d1 100644 --- a/vendor/github.com/kubernetes/repo-infra/WORKSPACE +++ b/vendor/github.com/kubernetes/repo-infra/WORKSPACE @@ -1,12 +1,12 @@ workspace(name = "io_kubernetes_build") -git_repository( +http_archive( name = "io_bazel_rules_go", - commit = "7fefcfdf6ad108010bf0dca1b1aaa95e0d2c7ca4", - remote = "https://github.com/bazelbuild/rules_go.git", + sha256 = "c1f52b8789218bb1542ed362c4f7de7052abcf254d865d96fb7ba6d44bc15ee3", + url = "https://github.com/bazelbuild/rules_go/releases/download/0.12.0/rules_go-0.12.0.tar.gz", ) -load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") +load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies") go_rules_dependencies() diff --git a/vendor/github.com/kubernetes/repo-infra/code-of-conduct.md b/vendor/github.com/kubernetes/repo-infra/code-of-conduct.md new file mode 100644 index 000000000..0d15c00cf --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/vendor/github.com/kubernetes/repo-infra/defs/BUILD b/vendor/github.com/kubernetes/repo-infra/defs/BUILD.bazel similarity index 100% rename from vendor/github.com/kubernetes/repo-infra/defs/BUILD rename to vendor/github.com/kubernetes/repo-infra/defs/BUILD.bazel diff --git a/vendor/github.com/kubernetes/repo-infra/defs/bazel_version.bzl b/vendor/github.com/kubernetes/repo-infra/defs/bazel_version.bzl deleted file mode 100644 index ef8b84c2a..000000000 --- a/vendor/github.com/kubernetes/repo-infra/defs/bazel_version.bzl +++ /dev/null @@ -1,32 +0,0 @@ -# Copied from https://github.com/tensorflow/tensorflow/blob/a6510237f06f03babfbcce1bcfa77f07f0d03c50/tensorflow/workspace.bzl - -# Parse the bazel version string from `native.bazel_version`. -def _parse_bazel_version(bazel_version): - # Remove commit from version. - version = bazel_version.split(" ", 1)[0] - - # Split into (release, date) parts and only return the release - # as a tuple of integers. - parts = version.split("-", 1) - - # Turn "release" into a tuple of strings - version_tuple = () - for number in parts[0].split("."): - version_tuple += (str(number),) - return version_tuple - -# Check that a specific bazel version is being used. -def check_version(bazel_version): - if "bazel_version" not in dir(native): - fail("\nCurrent Bazel version is lower than 0.2.1, expected at least %s\n" % - bazel_version) - elif not native.bazel_version: - print("\nCurrent Bazel is not a release version, cannot check for " + - "compatibility.") - print("Make sure that you are running at least Bazel %s.\n" % bazel_version) - else: - current_bazel_version = _parse_bazel_version(native.bazel_version) - minimum_bazel_version = _parse_bazel_version(bazel_version) - if minimum_bazel_version > current_bazel_version: - fail("\nCurrent Bazel version is {}, expected at least {}\n".format( - native.bazel_version, bazel_version)) diff --git a/vendor/github.com/kubernetes/repo-infra/defs/build.bzl b/vendor/github.com/kubernetes/repo-infra/defs/build.bzl index 254707415..65b962d62 100644 --- a/vendor/github.com/kubernetes/repo-infra/defs/build.bzl +++ b/vendor/github.com/kubernetes/repo-infra/defs/build.bzl @@ -1,31 +1,44 @@ +# Copyright 2016 The Kubernetes Authors. +# +# 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. + def _gcs_upload_impl(ctx): - output_lines = [] - for t in ctx.attr.data: - label = str(t.label) - upload_path=ctx.attr.upload_paths.get(label, "") - for f in t.files: - output_lines.append("%s\t%s" % (f.short_path, upload_path)) + output_lines = [] + for t in ctx.attr.data: + label = str(t.label) + upload_path = ctx.attr.upload_paths.get(label, "") + for f in t.files: + output_lines.append("%s\t%s" % (f.short_path, upload_path)) - ctx.file_action( - output = ctx.outputs.targets, - content = "\n".join(output_lines), - ) + ctx.file_action( + output = ctx.outputs.targets, + content = "\n".join(output_lines), + ) - ctx.file_action( - content = "%s --manifest %s --root $PWD -- $@" % ( - ctx.attr.uploader.files_to_run.executable.short_path, - ctx.outputs.targets.short_path, - ), - output = ctx.outputs.executable, - executable = True, - ) + ctx.file_action( + content = "%s --manifest %s --root $PWD -- $@" % ( + ctx.attr.uploader.files_to_run.executable.short_path, + ctx.outputs.targets.short_path, + ), + output = ctx.outputs.executable, + executable = True, + ) - return struct( - runfiles = ctx.runfiles( - files = ctx.files.data + ctx.files.uploader + - [ctx.info_file, ctx.version_file, ctx.outputs.targets] - ) - ) + return struct( + runfiles = ctx.runfiles( + files = ctx.files.data + ctx.files.uploader + [ctx.info_file, ctx.version_file, ctx.outputs.targets], + ), + ) # Adds an executable rule to upload the specified artifacts to GCS. # @@ -58,7 +71,7 @@ gcs_upload = rule( ) # Computes the md5sum of the provided src file, saving it in a file named 'name'. -def md5sum(name, src, visibility=None): +def md5sum(name, src, visibility = None): native.genrule( name = name + "_genmd5sum", srcs = [src], @@ -68,10 +81,10 @@ def md5sum(name, src, visibility=None): cmd = "for f in $(SRCS); do if command -v md5 >/dev/null; then md5 -q $$f>$@; else md5sum $$f | awk '{print $$1}' > $@; fi; done", message = "Computing md5sum", visibility = visibility, -) + ) # Computes the sha1sum of the provided src file, saving it in a file named 'name'. -def sha1sum(name, src, visibility=None): +def sha1sum(name, src, visibility = None): native.genrule( name = name + "_gensha1sum", srcs = [src], @@ -81,14 +94,14 @@ def sha1sum(name, src, visibility=None): cmd = "command -v sha1sum >/dev/null && cmd=sha1sum || cmd='shasum -a1'; for f in $(SRCS); do $$cmd $$f | awk '{print $$1}' > $@; done", message = "Computing sha1sum", visibility = visibility, -) + ) # Creates 3+N rules based on the provided targets: # * A filegroup with just the provided targets (named 'name') # * A filegroup containing all of the md5 and sha1 hash files ('name-hashes') # * A filegroup containing both of the above ('name-and-hashes') # * All of the necessary md5sum and sha1sum rules -def release_filegroup(name, srcs, visibility=None): +def release_filegroup(name, srcs, visibility = None): hashes = [] for src in srcs: parts = src.split(":") @@ -97,9 +110,9 @@ def release_filegroup(name, srcs, visibility=None): else: basename = src.split("/")[-1] - md5sum(name=basename + ".md5", src=src, visibility=visibility) + md5sum(name = basename + ".md5", src = src, visibility = visibility) hashes.append(basename + ".md5") - sha1sum(name=basename + ".sha1", src=src, visibility=visibility) + sha1sum(name = basename + ".sha1", src = src, visibility = visibility) hashes.append(basename + ".sha1") native.filegroup( diff --git a/vendor/github.com/kubernetes/repo-infra/defs/deb.bzl b/vendor/github.com/kubernetes/repo-infra/defs/deb.bzl index cd6a173f0..d93391dcc 100644 --- a/vendor/github.com/kubernetes/repo-infra/defs/deb.bzl +++ b/vendor/github.com/kubernetes/repo-infra/defs/deb.bzl @@ -1,32 +1,47 @@ -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar", "pkg_deb") +# Copyright 2016 The Kubernetes Authors. +# +# 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. + +load("//defs:pkg.bzl", "pkg_tar") +load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_deb") KUBERNETES_AUTHORS = "Kubernetes Authors " KUBERNETES_HOMEPAGE = "http://kubernetes.io" def k8s_deb(name, **kwargs): - pkg_deb( - name = name, - architecture = "amd64", - data = name + "-data", - homepage = KUBERNETES_HOMEPAGE, - maintainer = KUBERNETES_AUTHORS, - package = name, - **kwargs - ) + pkg_deb( + name = name, + architecture = "amd64", + data = name + "-data", + homepage = KUBERNETES_HOMEPAGE, + maintainer = KUBERNETES_AUTHORS, + package = name, + **kwargs + ) def deb_data(name, data = []): - deps = [] - for i, info in enumerate(data): - dname = "%s-deb-data-%s" % (name, i) - deps += [dname] + deps = [] + for i, info in enumerate(data): + dname = "%s-deb-data-%s" % (name, i) + deps += [dname] + pkg_tar( + name = dname, + srcs = info["files"], + mode = info["mode"], + package_dir = info["dir"], + ) pkg_tar( - name = dname, - srcs = info["files"], - mode = info["mode"], - package_dir = info["dir"], + name = name + "-data", + deps = deps, ) - pkg_tar( - name = name + "-data", - deps = deps, - ) diff --git a/vendor/github.com/kubernetes/repo-infra/defs/go.bzl b/vendor/github.com/kubernetes/repo-infra/defs/go.bzl index 243881a20..b3881b1db 100644 --- a/vendor/github.com/kubernetes/repo-infra/defs/go.bzl +++ b/vendor/github.com/kubernetes/repo-infra/defs/go.bzl @@ -1,94 +1,88 @@ -load("@io_bazel_rules_go//go:def.bzl", "GoLibrary") +# Copyright 2016 The Kubernetes Authors. +# +# 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. -go_filetype = ["*.go"] +load("@io_bazel_rules_go//go:def.bzl", "GoPath", "go_context", "go_path", "go_rule") def _compute_genrule_variables(resolved_srcs, resolved_outs): - variables = {"SRCS": cmd_helper.join_paths(" ", resolved_srcs), - "OUTS": cmd_helper.join_paths(" ", resolved_outs)} - if len(resolved_srcs) == 1: - variables["<"] = list(resolved_srcs)[0].path - if len(resolved_outs) == 1: - variables["@"] = list(resolved_outs)[0].path - return variables - -def _compute_genrule_command(ctx): - workspace_root = '$$(pwd)' - if ctx.build_file_path.startswith('external/'): - # We want GO_WORKSPACE to point at the root directory of the Bazel - # workspace containing this go_genrule's BUILD file. If it's being - # included in a different workspace as an external dependency, the - # link target must point to the external subtree instead of the main - # workspace (which contains code we don't care about). - # - # Given a build file path like "external/foo/bar/BUILD", the following - # slash split+join sets external_dep_prefix to "external/foo" and the - # effective workspace root to "$PWD/external/foo/". - external_dep_prefix = '/'.join(ctx.build_file_path.split('/')[:2]) - workspace_root = '$$(pwd)/' + external_dep_prefix - - cmd = [ - 'set -e', - # setup main GOPATH - 'GENRULE_TMPDIR=$$(mktemp -d $${TMPDIR:-/tmp}/bazel_%s_XXXXXXXX)' % ctx.attr.name, - 'export GOPATH=$${GENRULE_TMPDIR}/gopath', - 'export GO_WORKSPACE=$${GOPATH}/src/' + ctx.attr.go_prefix.go_prefix, - 'mkdir -p $${GO_WORKSPACE%/*}', - 'ln -s %s/ $${GO_WORKSPACE}' % (workspace_root,), - 'if [[ ! -e $${GO_WORKSPACE}/external ]]; then ln -s $$(pwd)/external/ $${GO_WORKSPACE}/; fi', - 'if [[ ! -e $${GO_WORKSPACE}/bazel-out ]]; then ln -s $$(pwd)/bazel-out/ $${GO_WORKSPACE}/; fi', - # setup genfile GOPATH - 'export GENGOPATH=$${GENRULE_TMPDIR}/gengopath', - 'export GENGO_WORKSPACE=$${GENGOPATH}/src/' + ctx.attr.go_prefix.go_prefix, - 'mkdir -p $${GENGO_WORKSPACE%/*}', - 'ln -s $$(pwd)/$(GENDIR) $${GENGO_WORKSPACE}', - # drop into WORKSPACE - 'export GOPATH=$${GOPATH}:$${GENGOPATH}', - 'cd $${GO_WORKSPACE}', - # execute user command - ctx.attr.cmd.strip(' \t\n\r'), - ] - return '\n'.join(cmd) + variables = { + "SRCS": cmd_helper.join_paths(" ", resolved_srcs), + "OUTS": cmd_helper.join_paths(" ", resolved_outs), + } + if len(resolved_srcs) == 1: + variables["<"] = list(resolved_srcs)[0].path + if len(resolved_outs) == 1: + variables["@"] = list(resolved_outs)[0].path + return variables def _go_genrule_impl(ctx): - go_toolchain = ctx.toolchains["@io_bazel_rules_go//go:toolchain"] - all_srcs = depset(go_toolchain.data.stdlib) - label_dict = {} + go = go_context(ctx) + + all_srcs = depset(go.stdlib.files) + label_dict = {} + go_paths = [] - for dep in ctx.attr.go_deps: - lib = dep[GoLibrary] - all_srcs += lib.srcs - for transitive_lib in lib.transitive: - all_srcs += transitive_lib.srcs + for dep in ctx.attr.srcs: + all_srcs += dep.files + label_dict[dep.label] = dep.files - for dep in ctx.attr.srcs: - all_srcs += dep.files - label_dict[dep.label] = dep.files + for go_path in ctx.attr.go_paths: + all_srcs += go_path.files + label_dict[go_path.label] = go_path.files - cmd = _compute_genrule_command(ctx) + gp = go_path[GoPath] + ext = gp.gopath_file.extension + if ext == "": + # mode is 'copy' - path is just the gopath + go_paths.append(gp.gopath_file.path) + elif ext == "tag": + # mode is 'link' - path is a tag file in the gopath + go_paths.append(gp.gopath_file.dirname) + else: + fail("Unknown extension on gopath file: '%s'." % ext) - resolved_inputs, argv, runfiles_manifests = ctx.resolve_command( - command=cmd, - attribute="cmd", - expand_locations=True, - make_variables=_compute_genrule_variables(all_srcs, depset(ctx.outputs.outs)), - tools=ctx.attr.tools, - label_dict=label_dict - ) + cmd = [ + "set -e", + "export GOPATH=" + ctx.configuration.host_path_separator.join(["$$(pwd)/" + p for p in go_paths]), + ctx.attr.cmd.strip(" \t\n\r"), + ] + resolved_inputs, argv, runfiles_manifests = ctx.resolve_command( + command = "\n".join(cmd), + attribute = "cmd", + expand_locations = True, + make_variables = _compute_genrule_variables(all_srcs, depset(ctx.outputs.outs)), + tools = ctx.attr.tools, + label_dict = label_dict, + ) - ctx.action( - inputs = list(all_srcs) + resolved_inputs, - outputs = ctx.outputs.outs, - env = ctx.configuration.default_shell_env + go_toolchain.env, - command = argv, - progress_message = "%s %s" % (ctx.attr.message, ctx), - mnemonic = "GoGenrule", - ) + paths = [go.root + "/bin", "/bin", "/usr/bin"] + ctx.action( + inputs = list(all_srcs) + resolved_inputs, + outputs = ctx.outputs.outs, + env = ctx.configuration.default_shell_env + go.env + { + "PATH": ctx.configuration.host_path_separator.join(paths), + }, + command = argv, + progress_message = "%s %s" % (ctx.attr.message, ctx), + mnemonic = "GoGenrule", + ) # We have codegen procedures that depend on the "go/*" stdlib packages -# and thus depend on executing with a valid GOROOT and GOPATH containing -# some amount transitive go src of dependencies. This go_genrule enables -# the creation of these sandboxes. -go_genrule = rule( +# and thus depend on executing with a valid GOROOT. _go_genrule handles +# dependencies on the Go toolchain and environment variables; the +# macro go_genrule handles setting up GOPATH dependencies (using go_path). +_go_genrule = go_rule( + _go_genrule_impl, attrs = { "srcs": attr.label_list(allow_files = True), "tools": attr.label_list( @@ -97,23 +91,33 @@ go_genrule = rule( ), "outs": attr.output_list(mandatory = True), "cmd": attr.string(mandatory = True), - "go_deps": attr.label_list(), + "go_paths": attr.label_list(), + "importpath": attr.string(), "message": attr.string(), "executable": attr.bool(default = False), - # Next rule copied from bazelbuild/rules_go@a9df110cf04e167b33f10473c7e904d780d921e6 - # and then modified a bit. - # I'm not sure if this is correct anymore. - "go_prefix": attr.label( - providers = ["go_prefix"], - default = Label( - "//:go_prefix", - relative_to_caller_repository = True, - ), - allow_files = False, - cfg = "host", - ), }, output_to_genfiles = True, - toolchains = ["@io_bazel_rules_go//go:toolchain"], - implementation = _go_genrule_impl, ) + +# Genrule wrapper for tools which need dependencies in a valid GOPATH +# and access to the Go standard library and toolchain. +# +# Go source dependencies specified through the go_deps argument +# are passed to the rules_go go_path rule to build a GOPATH +# for the provided genrule command. +# +# The command can access the generated GOPATH through the GOPATH +# environment variable. +def go_genrule(name, go_deps, **kw): + go_path_name = "%s~gopath" % name + go_path( + name = go_path_name, + mode = "link", + visibility = ["//visibility:private"], + deps = go_deps, + ) + _go_genrule( + name = name, + go_paths = [":" + go_path_name], + **kw + ) diff --git a/vendor/github.com/kubernetes/repo-infra/defs/pkg.bzl b/vendor/github.com/kubernetes/repo-infra/defs/pkg.bzl new file mode 100644 index 000000000..361134171 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/defs/pkg.bzl @@ -0,0 +1,25 @@ +# Copyright 2017 The Kubernetes Authors. +# +# 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. + +load( + "@bazel_tools//tools/build_defs/pkg:pkg.bzl", + _real_pkg_tar = "pkg_tar", +) + +# pkg_tar wraps the official pkg_tar rule with our faster +# Go-based build_tar binary. +def pkg_tar(**kwargs): + if "build_tar" not in kwargs: + kwargs["build_tar"] = "@io_kubernetes_build//tools/build_tar" + _real_pkg_tar(**kwargs) diff --git a/vendor/github.com/kubernetes/repo-infra/defs/run_in_workspace.bzl b/vendor/github.com/kubernetes/repo-infra/defs/run_in_workspace.bzl new file mode 100644 index 000000000..d07934429 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/defs/run_in_workspace.bzl @@ -0,0 +1,92 @@ +# Copyright 2018 The Kubernetes Authors. +# +# 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. + +# This technique was inspired by the gazelle rule implementation in bazelbuild/rules_go: +# https://github.com/bazelbuild/rules_go/blob/86ade29284ca11deeead86c061e9ba9bd0d157e0/go/private/tools/gazelle.bzl + +# Writes out a script which saves the runfiles directory, +# changes to the workspace root, and then runs a command. +def _workspace_binary_script_impl(ctx): + content = """#!/usr/bin/env bash +set -o errexit +set -o nounset +set -o pipefail + +BASE=$(pwd) +cd $(dirname $(readlink {root_file})) +"$BASE/{cmd}" $@ +""".format( + cmd = ctx.file.cmd.short_path, + root_file = ctx.file.root_file.short_path, + ) + ctx.actions.write( + output = ctx.outputs.executable, + content = content, + is_executable = True, + ) + runfiles = ctx.runfiles( + files = [ + ctx.file.cmd, + ctx.file.root_file, + ], + ) + return [DefaultInfo(runfiles = runfiles)] + +_workspace_binary_script = rule( + attrs = { + "cmd": attr.label( + mandatory = True, + allow_files = True, + single_file = True, + ), + "root_file": attr.label( + mandatory = True, + allow_files = True, + single_file = True, + ), + }, + executable = True, + implementation = _workspace_binary_script_impl, +) + +# Wraps a binary to be run in the workspace root via bazel run. +# +# For example, one might do something like +# +# workspace_binary( +# name = "dep", +# cmd = "//vendor/github.com/golang/dep/cmd/dep", +# ) +# +# which would allow running dep with bazel run. +def workspace_binary( + name, + cmd, + args = None, + visibility = None, + root_file = "//:WORKSPACE"): + script_name = name + "_script" + _workspace_binary_script( + name = script_name, + cmd = cmd, + root_file = root_file, + tags = ["manual"], + ) + native.sh_binary( + name = name, + srcs = [":" + script_name], + args = args, + visibility = visibility, + tags = ["manual"], + ) diff --git a/vendor/github.com/kubernetes/repo-infra/kazel/BUILD b/vendor/github.com/kubernetes/repo-infra/kazel/BUILD.bazel similarity index 66% rename from vendor/github.com/kubernetes/repo-infra/kazel/BUILD rename to vendor/github.com/kubernetes/repo-infra/kazel/BUILD.bazel index 649aeb465..88378e3f1 100644 --- a/vendor/github.com/kubernetes/repo-infra/kazel/BUILD +++ b/vendor/github.com/kubernetes/repo-infra/kazel/BUILD.bazel @@ -10,7 +10,7 @@ load( go_binary( name = "kazel", - library = ":go_default_library", + embed = [":go_default_library"], tags = ["automanaged"], ) @@ -23,9 +23,10 @@ go_library( "kazel.go", "sourcerer.go", ], + importpath = "k8s.io/repo-infra/kazel", tags = ["automanaged"], deps = [ - "//vendor:github.com/bazelbuild/buildifier/core", - "//vendor:github.com/golang/glog", + "//vendor/github.com/bazelbuild/buildtools/build:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", ], ) diff --git a/vendor/github.com/kubernetes/repo-infra/kazel/generator.go b/vendor/github.com/kubernetes/repo-infra/kazel/generator.go index 9ae274337..fa53daef8 100644 --- a/vendor/github.com/kubernetes/repo-infra/kazel/generator.go +++ b/vendor/github.com/kubernetes/repo-infra/kazel/generator.go @@ -17,20 +17,23 @@ limitations under the License. package main import ( - "bytes" "fmt" "io/ioutil" "os" "path/filepath" + "regexp" "sort" "strings" ) const ( - openAPIGenTag = "// +k8s:openapi-gen" + openAPITagName = "openapi-gen" + staging = "staging/src/" +) - baseImport = "k8s.io/kubernetes/" - staging = "staging/src/" +var ( + // Generator tags are specified using the format "// +k8s:name=value" + genTagRe = regexp.MustCompile(`//\s*\+k8s:([^\s=]+)(?:=(\S+))\s*\n`) ) // walkGenerated updates the rule for kubernetes' OpenAPI generated file. @@ -76,8 +79,12 @@ func (v *Vendorer) findOpenAPI(root string) ([]string, error) { if err != nil { return nil, err } - if bytes.Contains(b, []byte(openAPIGenTag)) { - includeMe = true + matches := genTagRe.FindAllSubmatch(b, -1) + for _, m := range matches { + if len(m) >= 2 && string(m[1]) == openAPITagName { + includeMe = true + break + } } } } @@ -96,9 +103,10 @@ func (v *Vendorer) findOpenAPI(root string) ([]string, error) { func (v *Vendorer) addGeneratedOpenAPIRule(paths []string) error { var openAPITargets []string var vendorTargets []string + baseImport := v.cfg.GoPrefix + "/" for _, p := range paths { if !strings.HasPrefix(p, baseImport) { - return fmt.Errorf("openapi-gen path outside of kubernetes: %s", p) + return fmt.Errorf("openapi-gen path outside of %s: %s", v.cfg.GoPrefix, p) } np := p[len(baseImport):] if strings.HasPrefix(np, staging) { diff --git a/vendor/github.com/kubernetes/repo-infra/kazel/kazel.go b/vendor/github.com/kubernetes/repo-infra/kazel/kazel.go index 492587f0b..b1b4e4aad 100644 --- a/vendor/github.com/kubernetes/repo-infra/kazel/kazel.go +++ b/vendor/github.com/kubernetes/repo-infra/kazel/kazel.go @@ -30,7 +30,7 @@ import ( "sort" "strings" - bzl "github.com/bazelbuild/buildifier/core" + bzl "github.com/bazelbuild/buildtools/build" "github.com/golang/glog" ) diff --git a/vendor/github.com/kubernetes/repo-infra/kazel/sourcerer.go b/vendor/github.com/kubernetes/repo-infra/kazel/sourcerer.go index e89b0f87e..e497333be 100644 --- a/vendor/github.com/kubernetes/repo-infra/kazel/sourcerer.go +++ b/vendor/github.com/kubernetes/repo-infra/kazel/sourcerer.go @@ -21,7 +21,7 @@ import ( "io/ioutil" "path/filepath" - bzl "github.com/bazelbuild/buildifier/core" + bzl "github.com/bazelbuild/buildtools/build" ) const ( diff --git a/vendor/github.com/kubernetes/repo-infra/tools/build_tar/BUILD.bazel b/vendor/github.com/kubernetes/repo-infra/tools/build_tar/BUILD.bazel new file mode 100644 index 000000000..a5d3d29e5 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/tools/build_tar/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["buildtar.go"], + importpath = "k8s.io/repo-infra/tools/build_tar", + visibility = ["//visibility:private"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/golang.org/x/build/pargzip:go_default_library", + ], +) + +go_binary( + name = "build_tar", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/kubernetes/repo-infra/tools/build_tar/buildtar.go b/vendor/github.com/kubernetes/repo-infra/tools/build_tar/buildtar.go new file mode 100644 index 000000000..40ad4afe6 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/tools/build_tar/buildtar.go @@ -0,0 +1,532 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +// fast tar builder for bazel +package main + +import ( + "archive/tar" + "bufio" + "compress/bzip2" + "compress/gzip" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/golang/glog" + + "golang.org/x/build/pargzip" +) + +func main() { + var ( + flagfile string + + output string + directory string + compression string + + files multiString + tars multiString + debs multiString + links multiString + + mode string + modes multiString + + owner string + owners multiString + ownerName string + ownerNames multiString + ) + + flag.StringVar(&flagfile, "flagfile", "", "Path to flagfile") + + flag.StringVar(&output, "output", "", "The output file, mandatory") + flag.StringVar(&directory, "directory", "", "Directory in which to store the file inside the layer") + flag.StringVar(&compression, "compression", "", "Compression (`gz` or `bz2`), default is none.") + + flag.Var(&files, "file", "A file to add to the layer") + flag.Var(&tars, "tar", "A tar file to add to the layer") + flag.Var(&debs, "deb", "A debian package to add to the layer") + flag.Var(&links, "link", "Add a symlink a inside the layer ponting to b if a:b is specified") + + flag.StringVar(&mode, "mode", "", "Force the mode on the added files (in octal).") + flag.Var(&modes, "modes", "Specific mode to apply to specific file (from the file argument), e.g., path/to/file=0455.") + + flag.StringVar(&owner, "owner", "0.0", "Specify the numeric default owner of all files, e.g., 0.0") + flag.Var(&owners, "owners", "Specify the numeric owners of individual files, e.g. path/to/file=0.0.") + flag.StringVar(&ownerName, "owner_name", "", "Specify the owner name of all files, e.g. root.root.") + flag.Var(&ownerNames, "owner_names", "Specify the owner names of individual files, e.g. path/to/file=root.root.") + + flag.Set("alsologtostderr", "true") + + flag.Parse() + + if flagfile != "" { + b, err := ioutil.ReadFile(flagfile) + if err != nil { + glog.Fatalf("couldn't read flagfile: %v", err) + } + cmdline := strings.Split(string(b), "\n") + flag.CommandLine.Parse(cmdline) + } + + if output == "" { + glog.Fatalf("--output flag is required") + } + + meta := newFileMeta(mode, modes, owner, owners, ownerName, ownerNames) + + tf, err := newTarFile(output, directory, compression, meta) + if err != nil { + glog.Fatalf("couldn't build tar: %v", err) + } + defer tf.Close() + + for _, file := range files { + parts := strings.SplitN(file, "=", 2) + if len(parts) != 2 { + glog.Fatalf("bad parts length for file %q", file) + } + if err := tf.addFile(parts[0], parts[1]); err != nil { + glog.Fatalf("couldn't add file: %v", err) + } + } + + for _, tar := range tars { + if err := tf.addTar(tar); err != nil { + glog.Fatalf("couldn't add tar: %v", err) + } + } + + for _, deb := range debs { + if err := tf.addDeb(deb); err != nil { + glog.Fatalf("couldn't add deb: %v", err) + } + } + + for _, link := range links { + parts := strings.SplitN(link, ":", 2) + if len(parts) != 2 { + glog.Fatalf("bad parts length for link %q", link) + } + if err := tf.addLink(parts[0], parts[1]); err != nil { + glog.Fatalf("couldn't add link: %v", err) + } + } +} + +type tarFile struct { + directory string + + tw *tar.Writer + + meta fileMeta + dirsMade map[string]struct{} + + closers []func() +} + +func newTarFile(output, directory, compression string, meta fileMeta) (*tarFile, error) { + var ( + w io.Writer + closers []func() + ) + f, err := os.Create(output) + if err != nil { + return nil, err + } + closers = append(closers, func() { + f.Close() + }) + w = f + + buf := bufio.NewWriter(w) + closers = append(closers, func() { buf.Flush() }) + w = buf + + switch compression { + case "": + case "gz": + gzw := pargzip.NewWriter(w) + closers = append(closers, func() { gzw.Close() }) + w = gzw + case "bz2", "xz": + return nil, fmt.Errorf("%q compression is not supported yet", compression) + default: + return nil, fmt.Errorf("unknown compression %q", compression) + } + + tw := tar.NewWriter(w) + closers = append(closers, func() { tw.Close() }) + + return &tarFile{ + directory: directory, + tw: tw, + closers: closers, + meta: meta, + dirsMade: map[string]struct{}{}, + }, nil +} + +func (f *tarFile) addFile(file, dest string) error { + dest = strings.TrimLeft(dest, "/") + dest = filepath.Clean(dest) + + uid := f.meta.getUID(dest) + gid := f.meta.getGID(dest) + uname := f.meta.getUname(dest) + gname := f.meta.getGname(dest) + + dest = filepath.Join(strings.TrimLeft(f.directory, "/"), dest) + dest = filepath.Clean(dest) + + info, err := os.Stat(file) + if err != nil { + return err + } + + mode := f.meta.getMode(dest) + // If mode is unspecified, derive the mode from the file's mode. + if mode == 0 { + mode = os.FileMode(0644) + if info.Mode().Perm()&os.FileMode(0111) != 0 { + mode = os.FileMode(0755) + } + } + + header := tar.Header{ + Name: dest, + Mode: int64(mode), + Uid: uid, + Gid: gid, + Size: 0, + Uname: uname, + Gname: gname, + } + + if err := f.makeDirs(header); err != nil { + return err + } + + switch { + case info.Mode()&os.ModeSymlink != 0: + return fmt.Errorf("addFile: didn't expect symlink: %s", file) + case info.Mode()&os.ModeNamedPipe != 0: + return fmt.Errorf("addFile: didn't expect named pipe: %s", file) + case info.Mode()&os.ModeSocket != 0: + return fmt.Errorf("addFile: didn't expect socket: %s", file) + case info.Mode()&os.ModeDevice != 0: + return fmt.Errorf("addFile: didn't expect device: %s", file) + case info.Mode()&os.ModeDir != 0: + header.Typeflag = tar.TypeDir + if err := f.tw.WriteHeader(&header); err != nil { + return err + } + default: + //regular file + header.Typeflag = tar.TypeReg + b, err := ioutil.ReadFile(file) + if err != nil { + return err + } + header.Size = int64(len(b)) + if err := f.tw.WriteHeader(&header); err != nil { + return err + } + if _, err := f.tw.Write(b); err != nil { + return err + } + } + return nil +} + +func (f *tarFile) addLink(symlink, target string) error { + header := tar.Header{ + Name: symlink, + Typeflag: tar.TypeSymlink, + Linkname: target, + } + if err := f.makeDirs(header); err != nil { + return err + } + return f.tw.WriteHeader(&header) +} + +func (f *tarFile) addTar(toAdd string) error { + root := "" + if f.directory != "/" { + root = f.directory + } + + var r io.Reader + + file, err := os.Open(toAdd) + if err != nil { + return err + } + defer file.Close() + r = file + + r = bufio.NewReader(r) + + switch { + case strings.HasSuffix(toAdd, "gz"): + gzr, err := gzip.NewReader(r) + if err != nil { + return err + } + r = gzr + case strings.HasSuffix(toAdd, "bz2"): + bz2r := bzip2.NewReader(r) + r = bz2r + case strings.HasSuffix(toAdd, "xz"): + return fmt.Errorf("%q decompression is not supported yet", toAdd) + default: + } + + tr := tar.NewReader(r) + + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + header.Name = filepath.Join(root, header.Name) + if header.Typeflag == tar.TypeDir && !strings.HasSuffix(header.Name, "/") { + header.Name = header.Name + "/" + } + err = f.tw.WriteHeader(header) + if err != nil { + return err + } + if _, err = io.Copy(f.tw, tr); err != nil { + return err + } + } + return nil +} + +func (f *tarFile) addDeb(toAdd string) error { + return fmt.Errorf("addDeb unimplemented") +} + +func (f *tarFile) makeDirs(header tar.Header) error { + dirToMake := []string{} + dir := header.Name + for { + dir = filepath.Dir(dir) + if dir == "." || dir == "/" { + break + } + dirToMake = append(dirToMake, dir) + } + for i := len(dirToMake) - 1; i >= 0; i-- { + dir := dirToMake[i] + if _, ok := f.dirsMade[dir]; ok { + continue + } + dh := header + dh.Typeflag = tar.TypeDir + dh.Name = dir + "/" + if err := f.tw.WriteHeader(&dh); err != nil { + return err + } + + f.dirsMade[dir] = struct{}{} + } + return nil +} + +func (f *tarFile) Close() { + for i := len(f.closers) - 1; i >= 0; i-- { + f.closers[i]() + } +} + +func newFileMeta( + mode string, + modes multiString, + owner string, + owners multiString, + ownerName string, + ownerNames multiString, +) fileMeta { + var meta fileMeta + + if mode != "" { + i, err := strconv.ParseUint(mode, 8, 32) + if err != nil { + glog.Fatalf("couldn't parse mode: %v", mode) + } + meta.defaultMode = os.FileMode(i) + } + + meta.modeMap = map[string]os.FileMode{} + for _, filemode := range modes { + parts := strings.SplitN(filemode, "=", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", filemode) + } + if parts[0][0] == '/' { + parts[0] = parts[0][1:] + } + i, err := strconv.ParseUint(parts[1], 8, 32) + if err != nil { + glog.Fatalf("couldn't parse mode: %v", filemode) + } + meta.modeMap[parts[0]] = os.FileMode(i) + } + + if ownerName != "" { + parts := strings.SplitN(ownerName, ".", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", ownerName) + } + meta.defaultUname = parts[0] + meta.defaultGname = parts[1] + } + + meta.unameMap = map[string]string{} + meta.gnameMap = map[string]string{} + for _, name := range ownerNames { + parts := strings.SplitN(name, "=", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q %v", name, parts) + } + filename, ownername := parts[0], parts[1] + + parts = strings.SplitN(ownername, ".", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", name) + } + uname, gname := parts[0], parts[1] + + meta.unameMap[filename] = uname + meta.gnameMap[filename] = gname + } + + if owner != "" { + parts := strings.SplitN(owner, ".", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", owner) + } + uid, err := strconv.Atoi(parts[0]) + if err != nil { + glog.Fatalf("could not parse uid: %q", parts[0]) + } + gid, err := strconv.Atoi(parts[1]) + if err != nil { + glog.Fatalf("could not parse gid: %q", parts[1]) + } + meta.defaultUID = uid + meta.defaultGID = gid + + } + + meta.uidMap = map[string]int{} + meta.gidMap = map[string]int{} + for _, owner := range owners { + parts := strings.SplitN(owner, "=", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", owner) + } + filename, owner := parts[0], parts[1] + + parts = strings.SplitN(parts[1], ".", 2) + if len(parts) != 2 { + glog.Fatalf("expected two parts to %q", owner) + } + uid, err := strconv.Atoi(parts[0]) + if err != nil { + glog.Fatalf("could not parse uid: %q", parts[0]) + } + gid, err := strconv.Atoi(parts[1]) + if err != nil { + glog.Fatalf("could not parse gid: %q", parts[1]) + } + meta.uidMap[filename] = uid + meta.gidMap[filename] = gid + } + + return meta +} + +type fileMeta struct { + defaultGID, defaultUID int + gidMap, uidMap map[string]int + + defaultGname, defaultUname string + gnameMap, unameMap map[string]string + + defaultMode os.FileMode + modeMap map[string]os.FileMode +} + +func (f *fileMeta) getGID(fname string) int { + if id, ok := f.gidMap[fname]; ok { + return id + } + return f.defaultGID +} + +func (f *fileMeta) getUID(fname string) int { + if id, ok := f.uidMap[fname]; ok { + return id + } + return f.defaultUID +} + +func (f *fileMeta) getGname(fname string) string { + if name, ok := f.gnameMap[fname]; ok { + return name + } + return f.defaultGname +} + +func (f *fileMeta) getUname(fname string) string { + if name, ok := f.unameMap[fname]; ok { + return name + } + return f.defaultUname +} + +func (f *fileMeta) getMode(fname string) os.FileMode { + if mode, ok := f.modeMap[fname]; ok { + return mode + } + return f.defaultMode +} + +type multiString []string + +func (ms *multiString) String() string { + return strings.Join(*ms, ",") +} + +func (ms *multiString) Set(v string) error { + *ms = append(*ms, v) + return nil +} diff --git a/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/BUILD b/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/BUILD.bazel similarity index 100% rename from vendor/github.com/kubernetes/repo-infra/verify/boilerplate/BUILD rename to vendor/github.com/kubernetes/repo-infra/verify/boilerplate/BUILD.bazel diff --git a/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/boilerplate.bzl.txt b/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/boilerplate.bzl.txt new file mode 100644 index 000000000..384f325ab --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/boilerplate.bzl.txt @@ -0,0 +1,14 @@ +# Copyright YEAR The Kubernetes Authors. +# +# 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. + diff --git a/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD b/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD.bazel similarity index 81% rename from vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD rename to vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD.bazel index 15a633704..79396a8c8 100644 --- a/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD +++ b/vendor/github.com/kubernetes/repo-infra/verify/boilerplate/test/BUILD.bazel @@ -13,5 +13,6 @@ go_library( "fail.go", "pass.go", ], + importpath = "k8s.io/repo-infra/verify/boilerplate/test", tags = ["automanaged"], ) diff --git a/vendor/github.com/kubernetes/repo-infra/verify/go_install_from_commit.sh b/vendor/github.com/kubernetes/repo-infra/verify/go_install_from_commit.sh new file mode 100755 index 000000000..ee6fd0d9c --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/verify/go_install_from_commit.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Copyright 2017 The Kubernetes Authors. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +PKG=$1 +COMMIT=$2 +export GOPATH=$3 +export GOBIN="$GOPATH/bin" + +go get -d -u "${PKG}" +cd "${GOPATH}/src/${PKG}" +git checkout -q "${COMMIT}" +go install "${PKG}" diff --git a/vendor/github.com/kubernetes/repo-infra/verify/update-bazel.sh b/vendor/github.com/kubernetes/repo-infra/verify/update-bazel.sh new file mode 100755 index 000000000..03ab54700 --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/verify/update-bazel.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Copyright 2016 The Kubernetes Authors. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +REPOINFRA_ROOT=$(git rev-parse --show-toplevel) +# https://github.com/kubernetes/test-infra/issues/5699#issuecomment-348350792 +cd ${REPOINFRA_ROOT} + +OUTPUT_GOBIN="${REPOINFRA_ROOT}/_output/bin" +GOBIN="${OUTPUT_GOBIN}" go install ./vendor/github.com/bazelbuild/bazel-gazelle/cmd/gazelle +GOBIN="${OUTPUT_GOBIN}" go install ./kazel + +touch "${REPOINFRA_ROOT}/vendor/BUILD.bazel" + +"${OUTPUT_GOBIN}/gazelle" fix \ + -external=vendored \ + -mode=fix + +"${OUTPUT_GOBIN}/kazel" diff --git a/vendor/github.com/kubernetes/repo-infra/verify/verify-bazel.sh b/vendor/github.com/kubernetes/repo-infra/verify/verify-bazel.sh new file mode 100755 index 000000000..159a9815a --- /dev/null +++ b/vendor/github.com/kubernetes/repo-infra/verify/verify-bazel.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Copyright 2016 The Kubernetes Authors. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +REPOINFRA_ROOT=$(git rev-parse --show-toplevel) +# https://github.com/kubernetes/test-infra/issues/5699#issuecomment-348350792 +cd ${REPOINFRA_ROOT} + +OUTPUT_GOBIN="${REPOINFRA_ROOT}/_output/bin" +GOBIN="${OUTPUT_GOBIN}" go install ./vendor/github.com/bazelbuild/bazel-gazelle/cmd/gazelle +GOBIN="${OUTPUT_GOBIN}" go install ./kazel + +touch "${REPOINFRA_ROOT}/vendor/BUILD.bazel" + +gazelle_diff=$("${OUTPUT_GOBIN}/gazelle" fix \ + -external=vendored \ + -mode=diff) + +kazel_diff=$("${OUTPUT_GOBIN}/kazel" \ + -dry-run \ + -print-diff) + +if [[ -n "${gazelle_diff}" || -n "${kazel_diff}" ]]; then + echo "${gazelle_diff}" + echo "${kazel_diff}" + echo + echo "Run ./verify/update-bazel.sh" + exit 1 +fi diff --git a/vendor/github.com/stretchr/testify/.travis.gofmt.sh b/vendor/github.com/stretchr/testify/.travis.gofmt.sh new file mode 100755 index 000000000..bfffdca8b --- /dev/null +++ b/vendor/github.com/stretchr/testify/.travis.gofmt.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ -n "$(gofmt -l .)" ]; then + echo "Go code is not formatted:" + gofmt -d . + exit 1 +fi diff --git a/vendor/github.com/stretchr/testify/.travis.gogenerate.sh b/vendor/github.com/stretchr/testify/.travis.gogenerate.sh new file mode 100755 index 000000000..161b449cd --- /dev/null +++ b/vendor/github.com/stretchr/testify/.travis.gogenerate.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [[ "$TRAVIS_GO_VERSION" =~ ^1\.[45](\..*)?$ ]]; then + exit 0 +fi + +go get github.com/ernesto-jimenez/gogen/imports +go generate ./... +if [ -n "$(git diff)" ]; then + echo "Go generate had not been run" + git diff + exit 1 +fi diff --git a/vendor/github.com/stretchr/testify/.travis.govet.sh b/vendor/github.com/stretchr/testify/.travis.govet.sh new file mode 100755 index 000000000..f8fbba7a1 --- /dev/null +++ b/vendor/github.com/stretchr/testify/.travis.govet.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd "$(dirname $0)" +DIRS=". assert require mock _codegen" +set -e +for subdir in $DIRS; do + pushd $subdir + go vet + popd +done diff --git a/vendor/github.com/stretchr/testify/.travis.yml b/vendor/github.com/stretchr/testify/.travis.yml index ffb9e0ddb..f408b4cc8 100644 --- a/vendor/github.com/stretchr/testify/.travis.yml +++ b/vendor/github.com/stretchr/testify/.travis.yml @@ -3,14 +3,13 @@ language: go sudo: false go: - - 1.1 - - 1.2 - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - 1.7 + - "1.8" + - "1.9" + - "1.10" - tip script: - - go test -v ./... + - ./.travis.gogenerate.sh + - ./.travis.gofmt.sh + - ./.travis.govet.sh + - go test -v -race $(go list ./... | grep -v vendor) diff --git a/vendor/github.com/stretchr/testify/Godeps/Godeps.json b/vendor/github.com/stretchr/testify/Godeps/Godeps.json deleted file mode 100644 index df032ac31..000000000 --- a/vendor/github.com/stretchr/testify/Godeps/Godeps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "ImportPath": "github.com/stretchr/testify", - "GoVersion": "go1.5", - "GodepVersion": "v74", - "Packages": [ - "./..." - ], - "Deps": [ - { - "ImportPath": "github.com/davecgh/go-spew/spew", - "Comment": "v1.0.0-3-g6d21280", - "Rev": "6d212800a42e8ab5c146b8ace3490ee17e5225f9" - }, - { - "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d" - }, - { - "ImportPath": "github.com/stretchr/objx", - "Rev": "cbeaeb16a013161a98496fad62933b1d21786672" - } - ] -} diff --git a/vendor/github.com/stretchr/testify/Godeps/Readme b/vendor/github.com/stretchr/testify/Godeps/Readme deleted file mode 100644 index 4cdaa53d5..000000000 --- a/vendor/github.com/stretchr/testify/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/vendor/github.com/stretchr/testify/LICENCE.txt b/vendor/github.com/stretchr/testify/LICENCE.txt deleted file mode 100644 index 473b670a7..000000000 --- a/vendor/github.com/stretchr/testify/LICENCE.txt +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell - -Please consider promoting this project if you find it useful. - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/stretchr/testify/README.md b/vendor/github.com/stretchr/testify/README.md index e57b1811f..51b6df347 100644 --- a/vendor/github.com/stretchr/testify/README.md +++ b/vendor/github.com/stretchr/testify/README.md @@ -9,7 +9,6 @@ Features include: * [Easy assertions](#assert-package) * [Mocking](#mock-package) - * [HTTP response trapping](#http-package) * [Testing suite interfaces and functions](#suite-package) Get started: @@ -106,14 +105,6 @@ The `require` package provides same global functions as the `assert` package, bu See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details. - -[`http`](http://godoc.org/github.com/stretchr/testify/http "API documentation") package ---------------------------------------------------------------------------------------- - -The `http` package contains test objects useful for testing code that relies on the `net/http` package. Check out the [(deprecated) API documentation for the `http` package](http://godoc.org/github.com/stretchr/testify/http). - -We recommend you use [httptest](http://golang.org/pkg/net/http/httptest) instead. - [`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package ---------------------------------------------------------------------------------------- @@ -173,6 +164,29 @@ func TestSomething(t *testing.T) { // assert that the expectations were met testObj.AssertExpectations(t) + +} + +// TestSomethingElse is a second example of how to use our test object to +// make assertions about some target code we are testing. +// This time using a placeholder. Placeholders might be used when the +// data being passed in is normally dynamically generated and cannot be +// predicted beforehand (eg. containing hashes that are time sensitive) +func TestSomethingElse(t *testing.T) { + + // create an instance of our test object + testObj := new(MyMockedObject) + + // setup expectations with a placeholder in the argument list + testObj.On("DoSomething", mock.Anything).Return(true, nil) + + // call the code we are testing + targetFuncThatDoesSomethingWithObj(testObj) + + // assert that the expectations were met + testObj.AssertExpectations(t) + + } ``` @@ -268,8 +282,7 @@ Installation To install Testify, use `go get`: - * Latest version: go get github.com/stretchr/testify - * Specific version: go get gopkg.in/stretchr/testify.v1 + go get github.com/stretchr/testify This will then make the following packages available to you: @@ -303,10 +316,10 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test ------ -Version History -=============== +Supported go versions +================== - * 1.0 - New package versioning strategy adopted. +We support the three major Go versions, which are 1.8, 1.9 and 1.10 at the moment. ------ @@ -316,17 +329,3 @@ Contributing Please feel free to submit issues, fork the repository and send pull requests! When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it. - ------- - -Licence -======= -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell - -Please consider promoting this project if you find it useful. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/stretchr/testify/_codegen/main.go b/vendor/github.com/stretchr/testify/_codegen/main.go index 328009f84..2e5e8124f 100644 --- a/vendor/github.com/stretchr/testify/_codegen/main.go +++ b/vendor/github.com/stretchr/testify/_codegen/main.go @@ -1,5 +1,5 @@ // This program reads all assertion functions from the assert package and -// automatically generates the corersponding requires and forwarded assertions +// automatically generates the corresponding requires and forwarded assertions package main @@ -10,6 +10,7 @@ import ( "go/ast" "go/build" "go/doc" + "go/format" "go/importer" "go/parser" "go/token" @@ -19,6 +20,7 @@ import ( "log" "os" "path" + "regexp" "strings" "text/template" @@ -27,6 +29,7 @@ import ( var ( pkg = flag.String("assert-path", "github.com/stretchr/testify/assert", "Path to the assert package") + includeF = flag.Bool("include-format-funcs", false, "include format functions such as Errorf and Equalf") outputPkg = flag.String("output-package", "", "package for the resulting code") tmplFile = flag.String("template", "", "What file to load the function template from") out = flag.String("out", "", "What file to write the source code to") @@ -77,13 +80,18 @@ func generateCode(importer imports.Importer, funcs []testFunc) error { } } + code, err := format.Source(buff.Bytes()) + if err != nil { + return err + } + // Write file output, err := outputFile() if err != nil { return err } defer output.Close() - _, err = io.Copy(output, buff) + _, err = io.Copy(output, bytes.NewReader(code)) return err } @@ -133,7 +141,7 @@ func analyzeCode(scope *types.Scope, docs *doc.Package) (imports.Importer, []tes if !ok { continue } - // Check function signatuer has at least two arguments + // Check function signature has at least two arguments sig := fn.Type().(*types.Signature) if sig.Params().Len() < 2 { continue @@ -151,13 +159,18 @@ func analyzeCode(scope *types.Scope, docs *doc.Package) (imports.Importer, []tes continue } + // Skip functions ending with f + if strings.HasSuffix(fdocs.Name, "f") && !*includeF { + continue + } + funcs = append(funcs, testFunc{*outputPkg, fdocs, fn}) importer.AddImportsFrom(sig.Params()) } return importer, funcs, nil } -// parsePackageSource returns the types scope and the package documentation from the pa +// parsePackageSource returns the types scope and the package documentation from the package func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) { pd, err := build.Import(pkg, ".", 0) if err != nil { @@ -258,10 +271,26 @@ func (f *testFunc) ForwardedParams() string { return p } +func (f *testFunc) ParamsFormat() string { + return strings.Replace(f.Params(), "msgAndArgs", "msg string, args", 1) +} + +func (f *testFunc) ForwardedParamsFormat() string { + return strings.Replace(f.ForwardedParams(), "msgAndArgs", "append([]interface{}{msg}, args...)", 1) +} + func (f *testFunc) Comment() string { return "// " + strings.Replace(strings.TrimSpace(f.DocInfo.Doc), "\n", "\n// ", -1) } +func (f *testFunc) CommentFormat() string { + search := fmt.Sprintf("%s", f.DocInfo.Name) + replace := fmt.Sprintf("%sf", f.DocInfo.Name) + comment := strings.Replace(f.Comment(), search, replace, -1) + exp := regexp.MustCompile(replace + `\(((\(\)|[^)])+)\)`) + return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`) +} + func (f *testFunc) CommentWithoutT(receiver string) string { search := fmt.Sprintf("assert.%s(t, ", f.DocInfo.Name) replace := fmt.Sprintf("%s.%s(", receiver, f.DocInfo.Name) diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go new file mode 100644 index 000000000..677e19b20 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -0,0 +1,484 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package assert + +import ( + http "net/http" + url "net/url" + time "time" +) + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Condition(t, comp, append([]interface{}{msg}, args...)...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Contains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return DirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Empty(t, object, append([]interface{}{msg}, args...)...) +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Equal(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Error(t, err, append([]interface{}{msg}, args...)...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Fail(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return False(t, value, append([]interface{}{msg}, args...)...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return FileExists(t, path, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Len(t, object, length, append([]interface{}{msg}, args...)...) +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Nil(t, object, append([]interface{}{msg}, args...)...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NoError(t, err, append([]interface{}{msg}, args...)...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotEmpty(t, object, append([]interface{}{msg}, args...)...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotNil(t, object, append([]interface{}{msg}, args...)...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotPanics(t, f, append([]interface{}{msg}, args...)...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotZero(t, i, append([]interface{}{msg}, args...)...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Panics(t, f, append([]interface{}{msg}, args...)...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Subset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return True(t, value, append([]interface{}{msg}, args...)...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Zero(t, i, append([]interface{}{msg}, args...)...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl new file mode 100644 index 000000000..d2bb0b817 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentFormat}} +func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { + if h, ok := t.(tHelper); ok { h.Helper() } + return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index e6a796046..8adb11194 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -1,387 +1,956 @@ /* * CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen * THIS FILE MUST NOT BE EDITED BY HAND -*/ + */ package assert import ( - http "net/http" url "net/url" time "time" ) - // Condition uses a Comparison to assert a complex condition. func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Condition(a.t, comp, msgAndArgs...) } +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Conditionf(a.t, comp, msg, args...) +} // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. -// -// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Contains(a.t, s, contains, msgAndArgs...) } +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatchf(a.t, listA, listB, msg, args...) +} // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Empty(a.t, object, msgAndArgs...) } +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Emptyf(a.t, object, msg, args...) +} // Equal asserts that two objects are equal. -// -// a.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Equal(a.t, expected, actual, msgAndArgs...) } - // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. -// +// // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). +// a.EqualError(err, expectedErrorString) func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualError(a.t, theError, errString, msgAndArgs...) } +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualErrorf(a.t, theError, errString, msg, args...) +} // EqualValues asserts that two objects are equal or convertable to the same types // and equal. -// -// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return EqualValues(a.t, expected, actual, msgAndArgs...) } +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Equalf(a.t, expected, actual, msg, args...) +} // Error asserts that a function returned an error (i.e. not `nil`). -// +// // actualObj, err := SomeFunction() -// if a.Error(err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if a.Error(err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Error(a.t, err, msgAndArgs...) } +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Errorf(a.t, err, msg, args...) +} -// Exactly asserts that two objects are equal is value and type. -// -// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Exactly(a.t, expected, actual, msgAndArgs...) } +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Exactlyf(a.t, expected, actual, msg, args...) +} // Fail reports a failure through func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Fail(a.t, failureMessage, msgAndArgs...) } - // FailNow fails test func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return FailNow(a.t, failureMessage, msgAndArgs...) } +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Failf(a.t, failureMessage, msg, args...) +} // False asserts that the specified value is false. -// -// a.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.False(myBool) func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return False(a.t, value, msgAndArgs...) } +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExistsf(a.t, path, msg, args...) +} // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. -// +// // a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. -// +// // a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPError asserts that a specified handler returns an error status code. -// +// // a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPError(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} // HTTPRedirect asserts that a specified handler returns a redirect status code. -// +// // a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} // HTTPSuccess asserts that a specified handler returns a success status code. -// +// // a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} // Implements asserts that an object is implemented by the specified interface. -// -// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +// +// a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Implements(a.t, interfaceObject, object, msgAndArgs...) } +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Implementsf(a.t, interfaceObject, object, msg, args...) +} // InDelta asserts that the two numerals are within delta of each other. -// +// // a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDelta(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} // InDeltaSlice is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaf(a.t, expected, actual, delta, msg, args...) +} // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) } +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) } +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} // IsType asserts that the specified objects are of the same type. func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return IsType(a.t, expectedType, object, msgAndArgs...) } +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsTypef(a.t, expectedType, object, msg, args...) +} // JSONEq asserts that two JSON strings are equivalent. -// +// // a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return JSONEq(a.t, expected, actual, msgAndArgs...) } +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return JSONEqf(a.t, expected, actual, msg, args...) +} // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. -// -// a.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Len(mySlice, 3) func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Len(a.t, object, length, msgAndArgs...) } +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Lenf(a.t, object, length, msg, args...) +} // Nil asserts that the specified object is nil. -// -// a.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Nil(err) func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Nil(a.t, object, msgAndArgs...) } +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Nilf(a.t, object, msg, args...) +} // NoError asserts that a function returned no error (i.e. `nil`). -// +// // actualObj, err := SomeFunction() // if a.NoError(err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NoError(a.t, err, msgAndArgs...) } +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoErrorf(a.t, err, msg, args...) +} // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. -// -// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotContains(a.t, s, contains, msgAndArgs...) } +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotContainsf(a.t, s, contains, msg, args...) +} // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEmpty(a.t, object, msgAndArgs...) } +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEmptyf(a.t, object, msg, args...) +} // NotEqual asserts that the specified values are NOT equal. -// -// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotEqual(a.t, expected, actual, msgAndArgs...) } +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEqualf(a.t, expected, actual, msg, args...) +} // NotNil asserts that the specified object is not nil. -// -// a.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotNil(err) func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotNil(a.t, object, msgAndArgs...) } +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotNilf(a.t, object, msg, args...) +} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// a.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotPanics(func(){ RemainCalm() }) func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotPanics(a.t, f, msgAndArgs...) } +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotPanicsf(a.t, f, msg, args...) +} // NotRegexp asserts that a specified regexp does not match a string. -// +// // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") // a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotRegexp(a.t, rx, str, msgAndArgs...) } - -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return NotZero(a.t, i, msgAndArgs...) } +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotZerof(a.t, i, msg, args...) +} // Panics asserts that the code inside the specified PanicTestFunc panics. -// -// a.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Panics(func(){ GoCrazy() }) func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Panics(a.t, f, msgAndArgs...) } +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Panicsf(a.t, f, msg, args...) +} // Regexp asserts that a specified regexp matches a string. -// +// // a.Regexp(regexp.MustCompile("start"), "it's starting") // a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Regexp(a.t, rx, str, msgAndArgs...) } +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Regexpf(a.t, rx, str, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Subsetf(a.t, list, subset, msg, args...) +} // True asserts that the specified value is true. -// -// a.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.True(myBool) func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return True(a.t, value, msgAndArgs...) } +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Truef(a.t, value, msg, args...) +} // WithinDuration asserts that the two times are within duration delta of each other. -// -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) } +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return WithinDurationf(a.t, expected, actual, delta, msg, args...) +} -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } return Zero(a.t, i, msgAndArgs...) } + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl index 99f9acfbb..188bb9e17 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl @@ -1,4 +1,5 @@ {{.CommentWithoutT "a"}} func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { + if h, ok := a.t.(tHelper); ok { h.Helper() } return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index b3f4e170d..94781ea33 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -4,8 +4,10 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" "math" + "os" "reflect" "regexp" "runtime" @@ -18,15 +20,29 @@ import ( "github.com/pmezard/go-difflib/difflib" ) -func init() { - spew.Config.SortKeys = true -} +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl // TestingT is an interface wrapper around *testing.T type TestingT interface { Errorf(format string, args ...interface{}) } +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) bool + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool + +// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool + // Comparison a custom function that returns true on success and false on failure type Comparison func() (success bool) @@ -42,7 +58,15 @@ func ObjectsAreEqual(expected, actual interface{}) bool { if expected == nil || actual == nil { return expected == actual } - + if exp, ok := expected.([]byte); ok { + act, ok := actual.([]byte) + if !ok { + return false + } else if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) + } return reflect.DeepEqual(expected, actual) } @@ -112,10 +136,12 @@ func CallerInfo() []string { } parts := strings.Split(file, "/") - dir := parts[len(parts)-2] file = parts[len(parts)-1] - if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + if len(parts) > 1 { + dir := parts[len(parts)-2] + if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } } // Drop the package @@ -146,21 +172,6 @@ func isTest(name, prefix string) bool { return !unicode.IsLower(rune) } -// getWhitespaceString returns a string that is long enough to overwrite the default -// output from the go testing framework. -func getWhitespaceString() string { - - _, file, line, ok := runtime.Caller(1) - if !ok { - return "" - } - parts := strings.Split(file, "/") - file = parts[len(parts)-1] - - return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) - -} - func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { if len(msgAndArgs) == 0 || msgAndArgs == nil { return "" @@ -174,22 +185,18 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { return "" } -// Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's -// test printing (see inner comment for specifics) -func indentMessageLines(message string, tabs int) string { +// Aligns the provided message so that all lines after the first line start at the same location as the first line. +// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab). +// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the +// basis on which the alignment occurs). +func indentMessageLines(message string, longestLabelLen int) string { outBuf := new(bytes.Buffer) for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { + // no need to align first line because it starts at the correct location (after the label) if i != 0 { - outBuf.WriteRune('\n') - } - for ii := 0; ii < tabs; ii++ { - outBuf.WriteRune('\t') - // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter - // by 1 prematurely. - if ii == 0 && i > 0 { - ii++ - } + // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab + outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") } outBuf.WriteString(scanner.Text()) } @@ -203,6 +210,9 @@ type failNower interface { // FailNow fails test func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } Fail(t, failureMessage, msgAndArgs...) // We cannot extend TestingT with FailNow() and @@ -221,46 +231,83 @@ func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + content := []labeledContent{ + {"Error Trace", strings.Join(CallerInfo(), "\n\t\t\t")}, + {"Error", failureMessage}, + } - message := messageFromMsgAndArgs(msgAndArgs...) + // Add test name if the Go version supports it + if n, ok := t.(interface { + Name() string + }); ok { + content = append(content, labeledContent{"Test", n.Name()}) + } - errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t") + message := messageFromMsgAndArgs(msgAndArgs...) if len(message) > 0 { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n"+ - "\r\tMessages:\t%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2), - message) - } else { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2)) + content = append(content, labeledContent{"Messages", message}) } + t.Errorf("\n%s", ""+labeledOutput(content...)) + return false } +type labeledContent struct { + label string + content string +} + +// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner: +// +// \t{{label}}:{{align_spaces}}\t{{content}}\n +// +// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label. +// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this +// alignment is achieved, "\t{{content}}\n" is added for the output. +// +// If the content of the labeledOutput contains line breaks, the subsequent lines are aligned so that they start at the same location as the first line. +func labeledOutput(content ...labeledContent) string { + longestLabel := 0 + for _, v := range content { + if len(v.label) > longestLabel { + longestLabel = len(v.label) + } + } + var output string + for _, v := range content { + output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" + } + return output +} + // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - + if h, ok := t.(tHelper); ok { + h.Helper() + } interfaceType := reflect.TypeOf(interfaceObject).Elem() + if object == nil { + return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) + } if !reflect.TypeOf(object).Implements(interfaceType) { return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) } return true - } // IsType asserts that the specified objects are of the same type. func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) @@ -271,16 +318,26 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// assert.Equal(t, 123, 123) // -// Returns whether the assertion was successful (true) or not (false). +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) + } if !ObjectsAreEqual(expected, actual) { diff := diff(expected, actual) expected, actual = formatUnequalValues(expected, actual) - return Fail(t, fmt.Sprintf("Not equal: %s (expected)\n"+ - " != %s (actual)%s", expected, actual, diff), msgAndArgs...) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true @@ -294,60 +351,49 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) // with the type name, and the value will be enclosed in parenthesis similar // to a type conversion in the Go grammar. func formatUnequalValues(expected, actual interface{}) (e string, a string) { - aType := reflect.TypeOf(expected) - bType := reflect.TypeOf(actual) - - if aType != bType && isNumericType(aType) && isNumericType(bType) { - return fmt.Sprintf("%v(%#v)", aType, expected), - fmt.Sprintf("%v(%#v)", bType, actual) + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + return fmt.Sprintf("%T(%#v)", expected, expected), + fmt.Sprintf("%T(%#v)", actual, actual) } return fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual) } -func isNumericType(t reflect.Type) bool { - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - case reflect.Float32, reflect.Float64: - return true - } - - return false -} - // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ - " != %#v (actual)", expected, actual), msgAndArgs...) + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } -// Exactly asserts that two objects are equal is value and type. -// -// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") +// Exactly asserts that two objects are equal in value and type. // -// Returns whether the assertion was successful (true) or not (false). +// assert.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } aType := reflect.TypeOf(expected) bType := reflect.TypeOf(actual) if aType != bType { - return Fail(t, fmt.Sprintf("Types expected to match exactly\n\r\t%v != %v", aType, bType), msgAndArgs...) + return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) } return Equal(t, expected, actual, msgAndArgs...) @@ -356,10 +402,11 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if !isNil(object) { return true } @@ -383,85 +430,53 @@ func isNil(object interface{}) bool { // Nil asserts that the specified object is nil. // -// assert.Nil(t, err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if isNil(object) { return true } return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) } -var numericZeros = []interface{}{ - int(0), - int8(0), - int16(0), - int32(0), - int64(0), - uint(0), - uint8(0), - uint16(0), - uint32(0), - uint64(0), - float32(0), - float64(0), -} - // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { + // get nil case out of the way if object == nil { return true - } else if object == "" { - return true - } else if object == false { - return true - } - - for _, v := range numericZeros { - if object == v { - return true - } } objValue := reflect.ValueOf(object) switch objValue.Kind() { - case reflect.Map: - fallthrough - case reflect.Slice, reflect.Chan: - { - return (objValue.Len() == 0) - } - case reflect.Struct: - switch object.(type) { - case time.Time: - return object.(time.Time).IsZero() - } + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: - { - if objValue.IsNil() { - return true - } - switch object.(type) { - case *time.Time: - return object.(*time.Time).IsZero() - default: - return false - } + if objValue.IsNil() { + return true } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) } - return false } // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } pass := isEmpty(object) if !pass { @@ -478,9 +493,10 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } pass := !isEmpty(object) if !pass { @@ -506,10 +522,11 @@ func getLen(x interface{}) (ok bool, length int) { // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, l := getLen(object) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) @@ -523,10 +540,16 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // True asserts that the specified value is true. // -// assert.True(t, myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if h, ok := t.(interface { + Helper() + }); ok { + h.Helper() + } if value != true { return Fail(t, "Should be true", msgAndArgs...) @@ -538,10 +561,11 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { // False asserts that the specified value is false. // -// assert.False(t, myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if value != false { return Fail(t, "Should be false", msgAndArgs...) @@ -553,10 +577,18 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// assert.NotEqual(t, obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", + expected, actual, err), msgAndArgs...) + } if ObjectsAreEqual(expected, actual) { return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) @@ -607,12 +639,13 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) { // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") -// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, found := includeElement(s, contains) if !ok { @@ -629,12 +662,13 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } ok, found := includeElement(s, contains) if !ok { @@ -648,8 +682,156 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) } +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if subset == nil { + return true // we consider nil to be equal to the nil set + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...) + } + } + + return true +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if subset == nil { + return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return true + } + } + + return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return true + } + + aKind := reflect.TypeOf(listA).Kind() + bKind := reflect.TypeOf(listB).Kind() + + if aKind != reflect.Array && aKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...) + } + + if bKind != reflect.Array && bKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...) + } + + aValue := reflect.ValueOf(listA) + bValue := reflect.ValueOf(listB) + + aLen := aValue.Len() + bLen := bValue.Len() + + if aLen != bLen { + return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...) + } + + // Mark indexes in bValue that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := aValue.Index(i).Interface() + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if ObjectsAreEqual(bValue.Index(j).Interface(), element) { + visited[j] = true + found = true + break + } + } + if !found { + return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...) + } + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } result := comp() if !result { Fail(t, "Condition failed!", msgAndArgs...) @@ -685,31 +867,49 @@ func didPanic(f PanicTestFunc) (bool, interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%v", f, panicValue), msgAndArgs...) } return true } -// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. // -// assert.NotPanics(t, func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + funcDidPanic, panicValue := didPanic(f) + if !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + if panicValue != expected { + return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%v\n\tPanic value:\t%v", f, expected, panicValue), msgAndArgs...) + } + + return true +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// Returns whether the assertion was successful (true) or not (false). +// assert.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if funcDidPanic, panicValue := didPanic(f); funcDidPanic { - return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + return Fail(t, fmt.Sprintf("func %#v should not panic\n\tPanic value:\t%v", f, panicValue), msgAndArgs...) } return true @@ -717,10 +917,11 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } dt := expected.Sub(actual) if dt < -delta || dt > delta { @@ -757,6 +958,8 @@ func toFloat(x interface{}) (float64, bool) { xf = float64(xn) case float64: xf = float64(xn) + case time.Duration: + xf = float64(xn) default: xok = false } @@ -767,9 +970,10 @@ func toFloat(x interface{}) (float64, bool) { // InDelta asserts that the two numerals are within delta of each other. // // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } af, aok := toFloat(expected) bf, bok := toFloat(actual) @@ -779,7 +983,7 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs } if math.IsNaN(af) { - return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...) + return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...) } if math.IsNaN(bf) { @@ -796,6 +1000,9 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs // InDeltaSlice is the same as InDelta, except it compares two slices. func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { @@ -806,7 +1013,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn expectedSlice := reflect.ValueOf(expected) for i := 0; i < actualSlice.Len(); i++ { - result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) + result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta, msgAndArgs...) if !result { return result } @@ -815,6 +1022,50 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn return true } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) if !aok { @@ -825,23 +1076,24 @@ func calcRelativeError(expected, actual interface{}) (float64, error) { } bf, bok := toFloat(actual) if !bok { - return 0, fmt.Errorf("expected value %q cannot be converted to float", actual) + return 0, fmt.Errorf("actual value %q cannot be converted to float", actual) } return math.Abs(af-bf) / math.Abs(af), nil } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } actualEpsilon, err := calcRelativeError(expected, actual) if err != nil { return Fail(t, err.Error(), msgAndArgs...) } if actualEpsilon > epsilon { return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ - " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...) + " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...) } return true @@ -849,6 +1101,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd // InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { @@ -876,13 +1131,14 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m // // actualObj, err := SomeFunction() // if assert.NoError(t, err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err != nil { - return Fail(t, fmt.Sprintf("Received unexpected error %+v", err), msgAndArgs...) + return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) } return true @@ -891,12 +1147,13 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // Error asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if err == nil { return Fail(t, "An error is expected but got nil.", msgAndArgs...) @@ -909,18 +1166,23 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString, "An error was expected") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { - - message := messageFromMsgAndArgs(msgAndArgs...) - if !NotNil(t, theError, "An error is expected but got nil. %s", message) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !Error(t, theError, msgAndArgs...) { return false } - s := "An error with value \"%s\" is expected but got \"%s\". %s" - return Equal(t, errString, theError.Error(), - s, errString, theError.Error(), message) + expected := errString + actual := theError.Error() + // don't need to use deep equals here, we know they are both strings + if expected != actual { + return Fail(t, fmt.Sprintf("Error message not equal:\n"+ + "expected: %q\n"+ + "actual : %q", expected, actual), msgAndArgs...) + } + return true } // matchRegexp return true if a specified regexp matches a string. @@ -941,9 +1203,10 @@ func matchRegexp(rx interface{}, str interface{}) bool { // // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } match := matchRegexp(rx, str) @@ -958,9 +1221,10 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } match := matchRegexp(rx, str) if match { @@ -971,28 +1235,71 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) } return true } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) } return true } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...) + } + return true +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if !info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...) + } + return true +} + // JSONEq asserts that two JSON strings are equivalent. // // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } var expectedJSONAsInterface, actualJSONAsInterface interface{} if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { @@ -1035,8 +1342,8 @@ func diff(expected interface{}, actual interface{}) string { return "" } - e := spew.Sdump(expected) - a := spew.Sdump(actual) + e := spewConfig.Sdump(expected) + a := spewConfig.Sdump(actual) diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(e), @@ -1050,3 +1357,30 @@ func diff(expected interface{}, actual interface{}) string { return "\n\nDiff:\n" + diff } + +// validateEqualArgs checks whether provided arguments can be safely used in the +// Equal/NotEqual functions. +func validateEqualArgs(expected, actual interface{}) error { + if isFunction(expected) || isFunction(actual) { + return errors.New("cannot take func type as argument") + } + return nil +} + +func isFunction(arg interface{}) bool { + if arg == nil { + return false + } + return reflect.TypeOf(arg).Kind() == reflect.Func +} + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +type tHelper interface { + Helper() +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions_test.go b/vendor/github.com/stretchr/testify/assert/assertions_test.go index ac9b70172..80da4dc4c 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions_test.go +++ b/vendor/github.com/stretchr/testify/assert/assertions_test.go @@ -1,12 +1,17 @@ package assert import ( + "bytes" + "encoding/json" "errors" + "fmt" "io" "math" "os" "reflect" "regexp" + "runtime" + "strings" "testing" "time" ) @@ -151,6 +156,9 @@ func TestImplements(t *testing.T) { if Implements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) { t.Error("Implements method should return false: AssertionTesterNonConformingObject does not implements AssertionTesterInterface") } + if Implements(mockT, (*AssertionTesterInterface)(nil), nil) { + t.Error("Implements method should return false: nil does not implement AssertionTesterInterface") + } } @@ -192,7 +200,71 @@ func TestEqual(t *testing.T) { if !Equal(mockT, uint64(123), uint64(123)) { t.Error("Equal should return true") } + if !Equal(mockT, &struct{}{}, &struct{}{}) { + t.Error("Equal should return true (pointer equality is based on equality of underlying value)") + } + var m map[string]interface{} + if Equal(mockT, m["bar"], "something") { + t.Error("Equal should return false") + } +} + +// bufferT implements TestingT. Its implementation of Errorf writes the output that would be produced by +// testing.T.Errorf to an internal bytes.Buffer. +type bufferT struct { + buf bytes.Buffer +} +func (t *bufferT) Errorf(format string, args ...interface{}) { + // implementation of decorate is copied from testing.T + decorate := func(s string) string { + _, file, line, ok := runtime.Caller(3) // decorate + log + public function. + if ok { + // Truncate file name at last file name separator. + if index := strings.LastIndex(file, "/"); index >= 0 { + file = file[index+1:] + } else if index = strings.LastIndex(file, "\\"); index >= 0 { + file = file[index+1:] + } + } else { + file = "???" + line = 1 + } + buf := new(bytes.Buffer) + // Every line is indented at least one tab. + buf.WriteByte('\t') + fmt.Fprintf(buf, "%s:%d: ", file, line) + lines := strings.Split(s, "\n") + if l := len(lines); l > 1 && lines[l-1] == "" { + lines = lines[:l-1] + } + for i, line := range lines { + if i > 0 { + // Second and subsequent lines are indented an extra tab. + buf.WriteString("\n\t\t") + } + buf.WriteString(line) + } + buf.WriteByte('\n') + return buf.String() + } + t.buf.WriteString(decorate(fmt.Sprintf(format, args...))) +} + +func TestEqualFormatting(t *testing.T) { + for i, currCase := range []struct { + equalWant string + equalGot string + msgAndArgs []interface{} + want string + }{ + {equalWant: "want", equalGot: "got", want: "\tassertions.go:[0-9]+: \n\t\t\tError Trace:\t\n\t\t\tError: \tNot equal: \n\t\t\t \texpected: \"want\"\n\t\t\t \tactual : \"got\"\n"}, + {equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{"hello, %v!", "world"}, want: "\tassertions.go:[0-9]+: \n\t\t\tError Trace:\t\n\t\t\tError: \tNot equal: \n\t\t\t \texpected: \"want\"\n\t\t\t \tactual : \"got\"\n\t\t\tMessages: \thello, world!\n"}, + } { + mockT := &bufferT{} + Equal(mockT, currCase.equalWant, currCase.equalGot, currCase.msgAndArgs...) + Regexp(t, regexp.MustCompile(currCase.want), mockT.buf.String(), "Case %d", i) + } } func TestFormatUnequalValues(t *testing.T) { @@ -208,6 +280,10 @@ func TestFormatUnequalValues(t *testing.T) { Equal(t, `int64(123)`, expected, "value should include type") Equal(t, `int32(123)`, actual, "value should include type") + expected, actual = formatUnequalValues(int64(123), nil) + Equal(t, `int64(123)`, expected, "value should include type") + Equal(t, `()`, actual, "value should include type") + type testStructType struct { Val string } @@ -324,8 +400,8 @@ func TestNotEqual(t *testing.T) { } funcA := func() int { return 23 } funcB := func() int { return 42 } - if !NotEqual(mockT, funcA, funcB) { - t.Error("NotEqual should return true") + if NotEqual(mockT, funcA, funcB) { + t.Error("NotEqual should return false") } if NotEqual(mockT, "Hello World", "Hello World") { @@ -343,6 +419,9 @@ func TestNotEqual(t *testing.T) { if NotEqual(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) { t.Error("NotEqual should return false") } + if NotEqual(mockT, &struct{}{}, &struct{}{}) { + t.Error("NotEqual should return false") + } } type A struct { @@ -418,6 +497,74 @@ func TestNotContains(t *testing.T) { } } +func TestSubset(t *testing.T) { + mockT := new(testing.T) + + if !Subset(mockT, []int{1, 2, 3}, nil) { + t.Error("Subset should return true: given subset is nil") + } + if !Subset(mockT, []int{1, 2, 3}, []int{}) { + t.Error("Subset should return true: any set contains the nil set") + } + if !Subset(mockT, []int{1, 2, 3}, []int{1, 2}) { + t.Error("Subset should return true: [1, 2, 3] contains [1, 2]") + } + if !Subset(mockT, []int{1, 2, 3}, []int{1, 2, 3}) { + t.Error("Subset should return true: [1, 2, 3] contains [1, 2, 3]") + } + if !Subset(mockT, []string{"hello", "world"}, []string{"hello"}) { + t.Error("Subset should return true: [\"hello\", \"world\"] contains [\"hello\"]") + } + + if Subset(mockT, []string{"hello", "world"}, []string{"hello", "testify"}) { + t.Error("Subset should return false: [\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]") + } + if Subset(mockT, []int{1, 2, 3}, []int{4, 5}) { + t.Error("Subset should return false: [1, 2, 3] does not contain [4, 5]") + } + if Subset(mockT, []int{1, 2, 3}, []int{1, 5}) { + t.Error("Subset should return false: [1, 2, 3] does not contain [1, 5]") + } +} + +func TestNotSubset(t *testing.T) { + mockT := new(testing.T) + + if NotSubset(mockT, []int{1, 2, 3}, nil) { + t.Error("NotSubset should return false: given subset is nil") + } + if NotSubset(mockT, []int{1, 2, 3}, []int{}) { + t.Error("NotSubset should return false: any set contains the nil set") + } + if NotSubset(mockT, []int{1, 2, 3}, []int{1, 2}) { + t.Error("NotSubset should return false: [1, 2, 3] contains [1, 2]") + } + if NotSubset(mockT, []int{1, 2, 3}, []int{1, 2, 3}) { + t.Error("NotSubset should return false: [1, 2, 3] contains [1, 2, 3]") + } + if NotSubset(mockT, []string{"hello", "world"}, []string{"hello"}) { + t.Error("NotSubset should return false: [\"hello\", \"world\"] contains [\"hello\"]") + } + + if !NotSubset(mockT, []string{"hello", "world"}, []string{"hello", "testify"}) { + t.Error("NotSubset should return true: [\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]") + } + if !NotSubset(mockT, []int{1, 2, 3}, []int{4, 5}) { + t.Error("NotSubset should return true: [1, 2, 3] does not contain [4, 5]") + } + if !NotSubset(mockT, []int{1, 2, 3}, []int{1, 5}) { + t.Error("NotSubset should return true: [1, 2, 3] does not contain [1, 5]") + } +} + +func TestNotSubsetNil(t *testing.T) { + mockT := new(testing.T) + NotSubset(mockT, []string{"foo"}, nil) + if !mockT.Failed() { + t.Error("NotSubset on nil set should have failed the test") + } +} + func Test_includeElement(t *testing.T) { list1 := []string{"Foo", "Bar"} @@ -469,6 +616,57 @@ func Test_includeElement(t *testing.T) { False(t, found) } +func TestElementsMatch(t *testing.T) { + mockT := new(testing.T) + + if !ElementsMatch(mockT, nil, nil) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{}, []int{}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{1}, []int{1}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{1, 1}, []int{1, 1}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{1, 2}, []int{1, 2}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{1, 2}, []int{2, 1}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, [2]int{1, 2}, [2]int{2, 1}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []string{"hello", "world"}, []string{"world", "hello"}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []string{"hello", "hello"}, []string{"hello", "hello"}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []string{"hello", "hello", "world"}, []string{"hello", "world", "hello"}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, [3]string{"hello", "hello", "world"}, [3]string{"hello", "world", "hello"}) { + t.Error("ElementsMatch should return true") + } + if !ElementsMatch(mockT, []int{}, nil) { + t.Error("ElementsMatch should return true") + } + + if ElementsMatch(mockT, []int{1}, []int{1, 1}) { + t.Error("ElementsMatch should return false") + } + if ElementsMatch(mockT, []int{1, 2}, []int{2, 2}) { + t.Error("ElementsMatch should return false") + } + if ElementsMatch(mockT, []string{"hello", "hello"}, []string{"hello"}) { + t.Error("ElementsMatch should return false") + } +} + func TestCondition(t *testing.T) { mockT := new(testing.T) @@ -514,6 +712,28 @@ func TestPanics(t *testing.T) { } +func TestPanicsWithValue(t *testing.T) { + + mockT := new(testing.T) + + if !PanicsWithValue(mockT, "Panic!", func() { + panic("Panic!") + }) { + t.Error("PanicsWithValue should return true") + } + + if PanicsWithValue(mockT, "Panic!", func() { + }) { + t.Error("PanicsWithValue should return false") + } + + if PanicsWithValue(mockT, "at the disco", func() { + panic("Panic!") + }) { + t.Error("PanicsWithValue should return false") + } +} + func TestNotPanics(t *testing.T) { mockT := new(testing.T) @@ -555,7 +775,7 @@ func TestNoError(t *testing.T) { }() if err == nil { // err is not nil here! - t.Errorf("Error should be nil due to empty interface", err) + t.Errorf("Error should be nil due to empty interface: %s", err) } False(t, NoError(mockT, err), "NoError should fail with empty error interface") @@ -579,6 +799,9 @@ func TestError(t *testing.T) { True(t, Error(mockT, err), "Error with error should return True") + // go vet check + True(t, Errorf(mockT, err, "example with %s", "formatted message"), "Errorf with error should rturn True") + // returning an empty error interface err = func() error { var err *customError @@ -589,7 +812,7 @@ func TestError(t *testing.T) { }() if err == nil { // err is not nil here! - t.Errorf("Error should be nil due to empty interface", err) + t.Errorf("Error should be nil due to empty interface: %s", err) } True(t, Error(mockT, err), "Error should pass with empty error interface") @@ -646,6 +869,15 @@ func TestEmpty(t *testing.T) { var tiNP time.Time var s *string var f *os.File + sP := &s + x := 1 + xP := &x + + type TString string + type TStruct struct { + x int + s []int + } True(t, Empty(mockT, ""), "Empty string is empty") True(t, Empty(mockT, nil), "Nil is empty") @@ -657,6 +889,9 @@ func TestEmpty(t *testing.T) { True(t, Empty(mockT, f), "Nil os.File pointer is empty") True(t, Empty(mockT, tiP), "Nil time.Time pointer is empty") True(t, Empty(mockT, tiNP), "time.Time is empty") + True(t, Empty(mockT, TStruct{}), "struct with zero values is empty") + True(t, Empty(mockT, TString("")), "empty aliased string is empty") + True(t, Empty(mockT, sP), "ptr to nil value is empty") False(t, Empty(mockT, "something"), "Non Empty string is not empty") False(t, Empty(mockT, errors.New("something")), "Non nil object is not empty") @@ -664,6 +899,9 @@ func TestEmpty(t *testing.T) { False(t, Empty(mockT, 1), "Non-zero int value is not empty") False(t, Empty(mockT, true), "True value is not empty") False(t, Empty(mockT, chWithValue), "Channel with values is not empty") + False(t, Empty(mockT, TStruct{x: 1}), "struct with initialized values is empty") + False(t, Empty(mockT, TString("abc")), "non-empty aliased string is empty") + False(t, Empty(mockT, xP), "ptr to non-nil value is not empty") } func TestNotEmpty(t *testing.T) { @@ -870,6 +1108,82 @@ func TestInDeltaSlice(t *testing.T) { False(t, InDeltaSlice(mockT, "", nil, 1), "Expected non numeral slices to fail") } +func TestInDeltaMapValues(t *testing.T) { + mockT := new(testing.T) + + for _, tc := range []struct { + title string + expect interface{} + actual interface{} + f func(TestingT, bool, ...interface{}) bool + delta float64 + }{ + { + title: "Within delta", + expect: map[string]float64{ + "foo": 1.0, + "bar": 2.0, + }, + actual: map[string]float64{ + "foo": 1.01, + "bar": 1.99, + }, + delta: 0.1, + f: True, + }, + { + title: "Within delta", + expect: map[int]float64{ + 1: 1.0, + 2: 2.0, + }, + actual: map[int]float64{ + 1: 1.0, + 2: 1.99, + }, + delta: 0.1, + f: True, + }, + { + title: "Different number of keys", + expect: map[int]float64{ + 1: 1.0, + 2: 2.0, + }, + actual: map[int]float64{ + 1: 1.0, + }, + delta: 0.1, + f: False, + }, + { + title: "Within delta with zero value", + expect: map[string]float64{ + "zero": 0.0, + }, + actual: map[string]float64{ + "zero": 0.0, + }, + delta: 0.1, + f: True, + }, + { + title: "With missing key with zero value", + expect: map[string]float64{ + "zero": 0.0, + "foo": 0.0, + }, + actual: map[string]float64{ + "zero": 0.0, + "bar": 0.0, + }, + f: False, + }, + } { + tc.f(t, InDeltaMapValues(mockT, tc.expect, tc.actual, tc.delta), tc.title+"\n"+diff(tc.expect, tc.actual)) + } +} + func TestInEpsilon(t *testing.T) { mockT := new(testing.T) @@ -885,6 +1199,7 @@ func TestInEpsilon(t *testing.T) { {uint64(100), uint8(101), 0.01}, {0.1, -0.1, 2}, {0.1, 0, 2}, + {time.Second, time.Second + time.Millisecond, 0.002}, } for _, tc := range cases { @@ -903,6 +1218,7 @@ func TestInEpsilon(t *testing.T) { {2.1, "bla-bla", 0}, {0.1, -0.1, 1.99}, {0, 0.1, 2}, // expected must be different to zero + {time.Second, time.Second + 10*time.Millisecond, 0.002}, } for _, tc := range cases { @@ -1006,6 +1322,28 @@ func TestNotZero(t *testing.T) { } } +func TestFileExists(t *testing.T) { + mockT := new(testing.T) + True(t, FileExists(mockT, "assertions.go")) + + mockT = new(testing.T) + False(t, FileExists(mockT, "random_file")) + + mockT = new(testing.T) + False(t, FileExists(mockT, "../_codegen")) +} + +func TestDirExists(t *testing.T) { + mockT := new(testing.T) + False(t, DirExists(mockT, "assertions.go")) + + mockT = new(testing.T) + False(t, DirExists(mockT, "random_dir")) + + mockT = new(testing.T) + True(t, DirExists(mockT, "../_codegen")) +} + func TestJSONEq_EqualSONString(t *testing.T) { mockT := new(testing.T) True(t, JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)) @@ -1208,3 +1546,233 @@ func TestFailNowWithFullTestingT(t *testing.T) { FailNow(mockT, "failed") }, "should call mockT.FailNow() rather than panicking") } + +func TestBytesEqual(t *testing.T) { + var cases = []struct { + a, b []byte + }{ + {make([]byte, 2), make([]byte, 2)}, + {make([]byte, 2), make([]byte, 2, 3)}, + {nil, make([]byte, 0)}, + } + for i, c := range cases { + Equal(t, reflect.DeepEqual(c.a, c.b), ObjectsAreEqual(c.a, c.b), "case %d failed", i+1) + } +} + +func BenchmarkBytesEqual(b *testing.B) { + const size = 1024 * 8 + s := make([]byte, size) + for i := range s { + s[i] = byte(i % 255) + } + s2 := make([]byte, size) + copy(s2, s) + + mockT := &mockFailNowTestingT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + Equal(mockT, s, s2) + } +} + +func TestEqualArgsValidation(t *testing.T) { + err := validateEqualArgs(time.Now, time.Now) + EqualError(t, err, "cannot take func type as argument") +} + +func ExampleComparisonAssertionFunc() { + t := &testing.T{} // provided by test + + adder := func(x, y int) int { + return x + y + } + + type args struct { + x int + y int + } + + tests := []struct { + name string + args args + expect int + assertion ComparisonAssertionFunc + }{ + {"2+2=4", args{2, 2}, 4, Equal}, + {"2+2!=5", args{2, 2}, 5, NotEqual}, + {"2+3==5", args{2, 3}, 5, Exactly}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.expect, adder(tt.args.x, tt.args.y)) + }) + } +} + +func TestComparisonAssertionFunc(t *testing.T) { + type iface interface { + Name() string + } + + tests := []struct { + name string + expect interface{} + got interface{} + assertion ComparisonAssertionFunc + }{ + {"implements", (*iface)(nil), t, Implements}, + {"isType", (*testing.T)(nil), t, IsType}, + {"equal", t, t, Equal}, + {"equalValues", t, t, EqualValues}, + {"exactly", t, t, Exactly}, + {"notEqual", t, nil, NotEqual}, + {"notContains", []int{1, 2, 3}, 4, NotContains}, + {"subset", []int{1, 2, 3, 4}, []int{2, 3}, Subset}, + {"notSubset", []int{1, 2, 3, 4}, []int{0, 3}, NotSubset}, + {"elementsMatch", []byte("abc"), []byte("bac"), ElementsMatch}, + {"regexp", "^t.*y$", "testify", Regexp}, + {"notRegexp", "^t.*y$", "Testify", NotRegexp}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.expect, tt.got) + }) + } +} + +func ExampleValueAssertionFunc() { + t := &testing.T{} // provided by test + + dumbParse := func(input string) interface{} { + var x interface{} + json.Unmarshal([]byte(input), &x) + return x + } + + tests := []struct { + name string + arg string + assertion ValueAssertionFunc + }{ + {"true is not nil", "true", NotNil}, + {"empty string is nil", "", Nil}, + {"zero is not nil", "0", NotNil}, + {"zero is zero", "0", Zero}, + {"false is zero", "false", Zero}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, dumbParse(tt.arg)) + }) + } +} + +func TestValueAssertionFunc(t *testing.T) { + tests := []struct { + name string + value interface{} + assertion ValueAssertionFunc + }{ + {"notNil", true, NotNil}, + {"nil", nil, Nil}, + {"empty", []int{}, Empty}, + {"notEmpty", []int{1}, NotEmpty}, + {"zero", false, Zero}, + {"notZero", 42, NotZero}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.value) + }) + } +} + +func ExampleBoolAssertionFunc() { + t := &testing.T{} // provided by test + + isOkay := func(x int) bool { + return x >= 42 + } + + tests := []struct { + name string + arg int + assertion BoolAssertionFunc + }{ + {"-1 is bad", -1, False}, + {"42 is good", 42, True}, + {"41 is bad", 41, False}, + {"45 is cool", 45, True}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, isOkay(tt.arg)) + }) + } +} + +func TestBoolAssertionFunc(t *testing.T) { + tests := []struct { + name string + value bool + assertion BoolAssertionFunc + }{ + {"true", true, True}, + {"false", false, False}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.value) + }) + } +} + +func ExampleErrorAssertionFunc() { + t := &testing.T{} // provided by test + + dumbParseNum := func(input string, v interface{}) error { + return json.Unmarshal([]byte(input), v) + } + + tests := []struct { + name string + arg string + assertion ErrorAssertionFunc + }{ + {"1.2 is number", "1.2", NoError}, + {"1.2.3 not number", "1.2.3", Error}, + {"true is not number", "true", Error}, + {"3 is number", "3", NoError}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var x float64 + tt.assertion(t, dumbParseNum(tt.arg, &x)) + }) + } +} + +func TestErrorAssertionFunc(t *testing.T) { + tests := []struct { + name string + err error + assertion ErrorAssertionFunc + }{ + {"noError", nil, NoError}, + {"error", errors.New("whoops"), Error}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.err) + }) + } +} diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go index b867e95ea..9ad56851d 100644 --- a/vendor/github.com/stretchr/testify/assert/forward_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/forward_assertions.go @@ -13,4 +13,4 @@ func New(t TestingT) *Assertions { } } -//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index fa7ab89b1..a87c2e1da 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -8,16 +8,17 @@ import ( "strings" ) -// httpCode is a helper that returns HTTP code of the response. It returns -1 -// if building a new request fails. -func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { +// httpCode is a helper that returns HTTP code of the response. It returns -1 and +// an error if building a new request fails. +func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { w := httptest.NewRecorder() - req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) + req, err := http.NewRequest(method, url, nil) if err != nil { - return -1 + return -1, err } + req.URL.RawQuery = values.Encode() handler(w, req) - return w.Code + return w.Code, nil } // HTTPSuccess asserts that a specified handler returns a success status code. @@ -25,12 +26,22 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) i // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusOK && code <= http.StatusPartialContent + + isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent + if !isSuccessCode { + Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isSuccessCode } // HTTPRedirect asserts that a specified handler returns a redirect status code. @@ -38,12 +49,22 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect + + isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect + if !isRedirectCode { + Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isRedirectCode } // HTTPError asserts that a specified handler returns an error status code. @@ -51,12 +72,22 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusBadRequest + + isErrorCode := code >= http.StatusBadRequest + if !isErrorCode { + Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isErrorCode } // HTTPBody is a helper that returns HTTP body of the response. It returns @@ -77,7 +108,10 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) @@ -94,7 +128,10 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions_test.go b/vendor/github.com/stretchr/testify/assert/http_assertions_test.go index 684c2d5d1..112beaf64 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions_test.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions_test.go @@ -19,21 +19,52 @@ func httpError(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) } -func TestHTTPStatuses(t *testing.T) { +func TestHTTPSuccess(t *testing.T) { assert := New(t) - mockT := new(testing.T) - assert.Equal(HTTPSuccess(mockT, httpOK, "GET", "/", nil), true) - assert.Equal(HTTPSuccess(mockT, httpRedirect, "GET", "/", nil), false) - assert.Equal(HTTPSuccess(mockT, httpError, "GET", "/", nil), false) + mockT1 := new(testing.T) + assert.Equal(HTTPSuccess(mockT1, httpOK, "GET", "/", nil), true) + assert.False(mockT1.Failed()) - assert.Equal(HTTPRedirect(mockT, httpOK, "GET", "/", nil), false) - assert.Equal(HTTPRedirect(mockT, httpRedirect, "GET", "/", nil), true) - assert.Equal(HTTPRedirect(mockT, httpError, "GET", "/", nil), false) + mockT2 := new(testing.T) + assert.Equal(HTTPSuccess(mockT2, httpRedirect, "GET", "/", nil), false) + assert.True(mockT2.Failed()) - assert.Equal(HTTPError(mockT, httpOK, "GET", "/", nil), false) - assert.Equal(HTTPError(mockT, httpRedirect, "GET", "/", nil), false) - assert.Equal(HTTPError(mockT, httpError, "GET", "/", nil), true) + mockT3 := new(testing.T) + assert.Equal(HTTPSuccess(mockT3, httpError, "GET", "/", nil), false) + assert.True(mockT3.Failed()) +} + +func TestHTTPRedirect(t *testing.T) { + assert := New(t) + + mockT1 := new(testing.T) + assert.Equal(HTTPRedirect(mockT1, httpOK, "GET", "/", nil), false) + assert.True(mockT1.Failed()) + + mockT2 := new(testing.T) + assert.Equal(HTTPRedirect(mockT2, httpRedirect, "GET", "/", nil), true) + assert.False(mockT2.Failed()) + + mockT3 := new(testing.T) + assert.Equal(HTTPRedirect(mockT3, httpError, "GET", "/", nil), false) + assert.True(mockT3.Failed()) +} + +func TestHTTPError(t *testing.T) { + assert := New(t) + + mockT1 := new(testing.T) + assert.Equal(HTTPError(mockT1, httpOK, "GET", "/", nil), false) + assert.True(mockT1.Failed()) + + mockT2 := new(testing.T) + assert.Equal(HTTPError(mockT2, httpRedirect, "GET", "/", nil), false) + assert.True(mockT2.Failed()) + + mockT3 := new(testing.T) + assert.Equal(HTTPError(mockT3, httpError, "GET", "/", nil), true) + assert.False(mockT3.Failed()) } func TestHTTPStatusesWrapper(t *testing.T) { @@ -58,6 +89,35 @@ func httpHelloName(w http.ResponseWriter, r *http.Request) { w.Write([]byte(fmt.Sprintf("Hello, %s!", name))) } +func TestHTTPRequestWithNoParams(t *testing.T) { + var got *http.Request + handler := func(w http.ResponseWriter, r *http.Request) { + got = r + w.WriteHeader(http.StatusOK) + } + + True(t, HTTPSuccess(t, handler, "GET", "/url", nil)) + + Empty(t, got.URL.Query()) + Equal(t, "/url", got.URL.RequestURI()) +} + +func TestHTTPRequestWithParams(t *testing.T) { + var got *http.Request + handler := func(w http.ResponseWriter, r *http.Request) { + got = r + w.WriteHeader(http.StatusOK) + } + params := url.Values{} + params.Add("id", "12345") + + True(t, HTTPSuccess(t, handler, "GET", "/url", params)) + + Equal(t, url.Values{"id": []string{"12345"}}, got.URL.Query()) + Equal(t, "/url?id=12345", got.URL.String()) + Equal(t, "/url?id=12345", got.URL.RequestURI()) +} + func TestHttpBody(t *testing.T) { assert := New(t) mockT := new(testing.T) diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go index 20d7b8b1f..4dde47d89 100644 --- a/vendor/github.com/stretchr/testify/mock/mock.go +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -1,6 +1,7 @@ package mock import ( + "errors" "fmt" "reflect" "regexp" @@ -15,10 +16,6 @@ import ( "github.com/stretchr/testify/assert" ) -func inin() { - spew.Config.SortKeys = true -} - // TestingT is an interface wrapper around *testing.T type TestingT interface { Logf(format string, args ...interface{}) @@ -45,6 +42,9 @@ type Call struct { // this method is called. ReturnArguments Arguments + // Holds the caller info for the On() call + callerInfo []string + // The number of times to return the return arguments when setting // expectations. 0 means to always return the value. Repeatability int @@ -52,22 +52,28 @@ type Call struct { // Amount of times this call has been called totalCalls int + // Call to this method can be optional + optional bool + // Holds a channel that will be used to block the Return until it either // receives a message or is closed. nil means it returns immediately. WaitFor <-chan time.Time + waitTime time.Duration + // Holds a handler used to manipulate arguments content that are passed by // reference. It's useful when mocking methods such as unmarshalers or // decoders. RunFn func(Arguments) } -func newCall(parent *Mock, methodName string, methodArguments ...interface{}) *Call { +func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call { return &Call{ Parent: parent, Method: methodName, Arguments: methodArguments, ReturnArguments: make([]interface{}, 0), + callerInfo: callerInfo, Repeatability: 0, WaitFor: nil, RunFn: nil, @@ -134,7 +140,10 @@ func (c *Call) WaitUntil(w <-chan time.Time) *Call { // // Mock.On("MyMethod", arg1, arg2).After(time.Second) func (c *Call) After(d time.Duration) *Call { - return c.WaitUntil(time.After(d)) + c.lock() + defer c.unlock() + c.waitTime = d + return c } // Run sets a handler to be called before returning. It can be used when @@ -145,13 +154,22 @@ func (c *Call) After(d time.Duration) *Call { // arg := args.Get(0).(*map[string]interface{}) // arg["foo"] = "bar" // }) -func (c *Call) Run(fn func(Arguments)) *Call { +func (c *Call) Run(fn func(args Arguments)) *Call { c.lock() defer c.unlock() c.RunFn = fn return c } +// Maybe allows the method call to be optional. Not calling an optional method +// will not cause an error while asserting expectations +func (c *Call) Maybe() *Call { + c.lock() + defer c.unlock() + c.optional = true + return c +} + // On chains a new expectation description onto the mocked interface. This // allows syntax like. // @@ -173,6 +191,10 @@ type Mock struct { // Holds the calls that were made to this mocked object. Calls []Call + // test is An optional variable that holds the test struct, to be used when an + // invalid mock call was made. + test TestingT + // TestData holds any data that might be useful for testing. Testify ignores // this data completely allowing you to do whatever you like with it. testData objx.Map @@ -195,6 +217,27 @@ func (m *Mock) TestData() objx.Map { Setting expectations */ +// Test sets the test struct variable of the mock object +func (m *Mock) Test(t TestingT) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.test = t +} + +// fail fails the current test with the given formatted format and args. +// In case that a test was defined, it uses the test APIs for failing a test, +// otherwise it uses panic. +func (m *Mock) fail(format string, args ...interface{}) { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.test == nil { + panic(fmt.Sprintf(format, args...)) + } + m.test.Errorf(format, args...) + m.test.FailNow() +} + // On starts a description of an expectation of the specified method // being called. // @@ -208,7 +251,7 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { m.mutex.Lock() defer m.mutex.Unlock() - c := newCall(m, methodName, arguments...) + c := newCall(m, methodName, assert.CallerInfo(), arguments...) m.ExpectedCalls = append(m.ExpectedCalls, c) return c } @@ -218,8 +261,6 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { // */ func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { - m.mutex.Lock() - defer m.mutex.Unlock() for i, call := range m.ExpectedCalls { if call.Method == method && call.Repeatability > -1 { @@ -233,27 +274,25 @@ func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, * return -1, nil } -func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) { - diffCount := 0 +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) { + var diffCount int var closestCall *Call + var err string for _, call := range m.expectedCalls() { if call.Method == method { - _, tempDiffCount := call.Arguments.Diff(arguments) + errInfo, tempDiffCount := call.Arguments.Diff(arguments) if tempDiffCount < diffCount || diffCount == 0 { diffCount = tempDiffCount closestCall = call + err = errInfo } } } - if closestCall == nil { - return false, nil - } - - return true, closestCall + return closestCall, err } func callString(method string, arguments Arguments, includeArgumentValues bool) string { @@ -283,7 +322,7 @@ func (m *Mock) Called(arguments ...interface{}) Arguments { functionPath := runtime.FuncForPC(pc).Name() //Next four lines are required to use GCCGO function naming conventions. //For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock - //uses inteface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree + //uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree //With GCCGO we need to remove interface information starting from pN
. re := regexp.MustCompile("\\.pN\\d+_") if re.MatchString(functionPath) { @@ -291,8 +330,17 @@ func (m *Mock) Called(arguments ...interface{}) Arguments { } parts := strings.Split(functionPath, ".") functionName := parts[len(parts)-1] + return m.MethodCalled(functionName, arguments...) +} - found, call := m.findExpectedCall(functionName, arguments...) +// MethodCalled tells the mock object that the given method has been called, and gets +// an array of arguments to return. Panics if the call is unexpected (i.e. not preceded +// by appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments { + m.mutex.Lock() + //TODO: could combine expected and closes in single loop + found, call := m.findExpectedCall(methodName, arguments...) if found < 0 { // we have to fail here - because we don't know what to do @@ -302,45 +350,52 @@ func (m *Mock) Called(arguments ...interface{}) Arguments { // b) the arguments are not what was expected, or // c) the developer has forgotten to add an accompanying On...Return pair. - closestFound, closestCall := m.findClosestCall(functionName, arguments...) + closestCall, mismatch := m.findClosestCall(methodName, arguments...) + m.mutex.Unlock() - if closestFound { - panic(fmt.Sprintf("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\n", callString(functionName, arguments, true), callString(functionName, closestCall.Arguments, true), diffArguments(arguments, closestCall.Arguments))) + if closestCall != nil { + m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s", + callString(methodName, arguments, true), + callString(methodName, closestCall.Arguments, true), + diffArguments(closestCall.Arguments, arguments), + strings.TrimSpace(mismatch), + ) } else { - panic(fmt.Sprintf("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", functionName, functionName, callString(functionName, arguments, true), assert.CallerInfo())) - } - } else { - m.mutex.Lock() - switch { - case call.Repeatability == 1: - call.Repeatability = -1 - call.totalCalls++ - - case call.Repeatability > 1: - call.Repeatability-- - call.totalCalls++ - - case call.Repeatability == 0: - call.totalCalls++ + m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) } - m.mutex.Unlock() } + if call.Repeatability == 1 { + call.Repeatability = -1 + } else if call.Repeatability > 1 { + call.Repeatability-- + } + call.totalCalls++ + // add the call - m.mutex.Lock() - m.Calls = append(m.Calls, *newCall(m, functionName, arguments...)) + m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...)) m.mutex.Unlock() // block if specified if call.WaitFor != nil { <-call.WaitFor + } else { + time.Sleep(call.waitTime) } - if call.RunFn != nil { - call.RunFn(arguments) + m.mutex.Lock() + runFn := call.RunFn + m.mutex.Unlock() + + if runFn != nil { + runFn(arguments) } - return call.ReturnArguments + m.mutex.Lock() + returnArgs := call.ReturnArguments + m.mutex.Unlock() + + return returnArgs } /* @@ -356,6 +411,9 @@ type assertExpectationser interface { // // Calls may have occurred in any order. func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } for _, obj := range testObjects { if m, ok := obj.(Mock); ok { t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)") @@ -363,6 +421,7 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { } m := obj.(assertExpectationser) if !m.AssertExpectations(t) { + t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m)) return false } } @@ -372,25 +431,29 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { // AssertExpectations asserts that everything specified with On and Return was // in fact called as expected. Calls may have occurred in any order. func (m *Mock) AssertExpectations(t TestingT) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() var somethingMissing bool var failedExpectations int // iterate through each expectation expectedCalls := m.expectedCalls() for _, expectedCall := range expectedCalls { - if !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { + if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { somethingMissing = true failedExpectations++ - t.Logf("\u274C\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) } else { - m.mutex.Lock() if expectedCall.Repeatability > 0 { somethingMissing = true failedExpectations++ + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) } else { - t.Logf("\u2705\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) } - m.mutex.Unlock() } } @@ -403,6 +466,11 @@ func (m *Mock) AssertExpectations(t TestingT) bool { // AssertNumberOfCalls asserts that the method was called expectedCalls times. func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() var actualCalls int for _, call := range m.calls() { if call.Method == methodName { @@ -415,9 +483,22 @@ func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls // AssertCalled asserts that the method was called. // It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { - if !assert.True(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method should have been called with %d argument(s), but was not.", methodName, len(arguments))) { - t.Logf("%v", m.expectedCalls()) - return false + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if !m.methodWasCalled(methodName, arguments) { + var calledWithArgs []string + for _, call := range m.calls() { + calledWithArgs = append(calledWithArgs, fmt.Sprintf("%v", call.Arguments)) + } + if len(calledWithArgs) == 0 { + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut no actual calls happened", methodName, arguments)) + } + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut actual calls were:\n %v", methodName, arguments, strings.Join(calledWithArgs, "\n"))) } return true } @@ -425,9 +506,14 @@ func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interfac // AssertNotCalled asserts that the method was not called. // It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { - if !assert.False(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method was called with %d argument(s), but should NOT have been.", methodName, len(arguments))) { - t.Logf("%v", m.expectedCalls()) - return false + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if m.methodWasCalled(methodName, arguments) { + return assert.Fail(t, "Should not have called with given arguments", + fmt.Sprintf("Expected %q to not have been called with:\n%v\nbut actually it was.", methodName, arguments)) } return true } @@ -450,14 +536,10 @@ func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { } func (m *Mock) expectedCalls() []*Call { - m.mutex.Lock() - defer m.mutex.Unlock() return append([]*Call{}, m.ExpectedCalls...) } func (m *Mock) calls() []Call { - m.mutex.Lock() - defer m.mutex.Unlock() return append([]Call{}, m.Calls...) } @@ -471,7 +553,7 @@ type Arguments []interface{} const ( // Anything is used in Diff and Assert when the argument being tested // shouldn't be taken into consideration. - Anything string = "mock.Anything" + Anything = "mock.Anything" ) // AnythingOfTypeArgument is a string that contains the type of an argument @@ -496,9 +578,25 @@ type argumentMatcher struct { func (f argumentMatcher) Matches(argument interface{}) bool { expectType := f.fn.Type().In(0) + expectTypeNilSupported := false + switch expectType.Kind() { + case reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Ptr: + expectTypeNilSupported = true + } - if reflect.TypeOf(argument).AssignableTo(expectType) { - result := f.fn.Call([]reflect.Value{reflect.ValueOf(argument)}) + argType := reflect.TypeOf(argument) + var arg reflect.Value + if argType == nil { + arg = reflect.New(expectType).Elem() + } else { + arg = reflect.ValueOf(argument) + } + + if argType == nil && !expectTypeNilSupported { + panic(errors.New("attempting to call matcher with nil for non-nil expected type")) + } + if argType == nil || argType.AssignableTo(expectType) { + result := f.fn.Call([]reflect.Value{arg}) return result[0].Bool() } return false @@ -518,7 +616,7 @@ func (f argumentMatcher) String() string { // // |fn|, must be a function accepting a single argument (of the expected type) // which returns a bool. If |fn| doesn't match the required signature, -// MathedBy() panics. +// MatchedBy() panics. func MatchedBy(fn interface{}) argumentMatcher { fnType := reflect.TypeOf(fn) @@ -558,6 +656,7 @@ func (args Arguments) Is(objects ...interface{}) bool { // // Returns the diff string and number of differences found. func (args Arguments) Diff(objects []interface{}) (string, int) { + //TODO: could return string as error and nil for No difference var output = "\n" var differences int @@ -584,10 +683,10 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { if matcher, ok := expected.(argumentMatcher); ok { if matcher.Matches(actual) { - output = fmt.Sprintf("%s\t%d: \u2705 %s matched by %s\n", output, i, actual, matcher) + output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actual, matcher) } else { differences++ - output = fmt.Sprintf("%s\t%d: \u2705 %s not matched by %s\n", output, i, actual, matcher) + output = fmt.Sprintf("%s\t%d: PASS: %s not matched by %s\n", output, i, actual, matcher) } } else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { @@ -595,7 +694,7 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { // not match differences++ - output = fmt.Sprintf("%s\t%d: \u274C type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual) + output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual) } } else { @@ -604,11 +703,11 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { // match - output = fmt.Sprintf("%s\t%d: \u2705 %s == %s\n", output, i, actual, expected) + output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actual, expected) } else { // not match differences++ - output = fmt.Sprintf("%s\t%d: \u274C %s != %s\n", output, i, actual, expected) + output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actual, expected) } } @@ -625,6 +724,9 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { // Assert compares the arguments with the specified objects and fails if // they do not exactly match. func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } // get the differences diff, diffCount := args.Diff(objects) @@ -719,6 +821,10 @@ func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { } func diffArguments(expected Arguments, actual Arguments) string { + if len(expected) != len(actual) { + return fmt.Sprintf("Provided %v arguments, mocked for %v arguments", len(expected), len(actual)) + } + for x := range expected { if diffString := diff(expected[x], actual[x]); diffString != "" { return fmt.Sprintf("Difference found in argument %v:\n\n%s", x, diffString) @@ -746,8 +852,8 @@ func diff(expected interface{}, actual interface{}) string { return "" } - e := spew.Sdump(expected) - a := spew.Sdump(actual) + e := spewConfig.Sdump(expected) + a := spewConfig.Sdump(actual) diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(e), @@ -761,3 +867,14 @@ func diff(expected interface{}, actual interface{}) string { return diff } + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +type tHelper interface { + Helper() +} diff --git a/vendor/github.com/stretchr/testify/mock/mock_test.go b/vendor/github.com/stretchr/testify/mock/mock_test.go index 8cb4615db..96b0c8556 100644 --- a/vendor/github.com/stretchr/testify/mock/mock_test.go +++ b/vendor/github.com/stretchr/testify/mock/mock_test.go @@ -2,10 +2,15 @@ package mock import ( "errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "fmt" + "regexp" + "runtime" + "sync" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) /* @@ -40,6 +45,26 @@ func (i *TestExampleImplementation) TheExampleMethod3(et *ExampleType) error { return args.Error(0) } +func (i *TestExampleImplementation) TheExampleMethod4(v ExampleInterface) error { + args := i.Called(v) + return args.Error(0) +} + +func (i *TestExampleImplementation) TheExampleMethod5(ch chan struct{}) error { + args := i.Called(ch) + return args.Error(0) +} + +func (i *TestExampleImplementation) TheExampleMethod6(m map[string]bool) error { + args := i.Called(m) + return args.Error(0) +} + +func (i *TestExampleImplementation) TheExampleMethod7(slice []bool) error { + args := i.Called(slice) + return args.Error(0) +} + func (i *TestExampleImplementation) TheExampleMethodFunc(fn func(string) error) error { args := i.Called(fn) return args.Error(0) @@ -55,6 +80,11 @@ func (i *TestExampleImplementation) TheExampleMethodVariadicInterface(a ...inter return args.Error(0) } +func (i *TestExampleImplementation) TheExampleMethodMixedVariadic(a int, b ...int) error { + args := i.Called(a, b) + return args.Error(0) +} + type ExampleFuncType func(string) error func (i *TestExampleImplementation) TheExampleMethodFuncType(fn ExampleFuncType) error { @@ -62,6 +92,34 @@ func (i *TestExampleImplementation) TheExampleMethodFuncType(fn ExampleFuncType) return args.Error(0) } +// MockTestingT mocks a test struct +type MockTestingT struct { + logfCount, errorfCount, failNowCount int +} + +const mockTestingTFailNowCalled = "FailNow was called" + +func (m *MockTestingT) Logf(string, ...interface{}) { + m.logfCount++ +} + +func (m *MockTestingT) Errorf(string, ...interface{}) { + m.errorfCount++ +} + +// FailNow mocks the FailNow call. +// It panics in order to mimic the FailNow behavior in the sense that +// the execution stops. +// When expecting this method, the call that invokes it should use the following code: +// +// assert.PanicsWithValue(t, mockTestingTFailNowCalled, func() {...}) +func (m *MockTestingT) FailNow() { + m.failNowCount++ + + // this function should panic now to stop the execution as expected + panic(mockTestingTFailNowCalled) +} + /* Mock */ @@ -91,6 +149,8 @@ func Test_Mock_Chained_On(t *testing.T) { // make a test impl object var mockedService = new(TestExampleImplementation) + // determine our current line number so we can assert the expected calls callerInfo properly + _, _, line, _ := runtime.Caller(0) mockedService. On("TheExampleMethod", 1, 2, 3). Return(0). @@ -98,17 +158,19 @@ func Test_Mock_Chained_On(t *testing.T) { Return(nil) expectedCalls := []*Call{ - &Call{ + { Parent: &mockedService.Mock, Method: "TheExampleMethod", Arguments: []interface{}{1, 2, 3}, ReturnArguments: []interface{}{0}, + callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+2)}, }, - &Call{ + { Parent: &mockedService.Mock, Method: "TheExampleMethod3", Arguments: []interface{}{AnythingOfType("*mock.ExampleType")}, ReturnArguments: []interface{}{nil}, + callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+4)}, }, } assert.Equal(t, expectedCalls, mockedService.ExpectedCalls) @@ -170,19 +232,52 @@ func Test_Mock_On_WithIntArgMatcher(t *testing.T) { }) } +func TestMock_WithTest(t *testing.T) { + var ( + mockedService TestExampleImplementation + mockedTest MockTestingT + ) + + mockedService.Test(&mockedTest) + mockedService.On("TheExampleMethod", 1, 2, 3).Return(0, nil) + + // Test that on an expected call, the test was not failed + + mockedService.TheExampleMethod(1, 2, 3) + + // Assert that Errorf and FailNow were not called + assert.Equal(t, 0, mockedTest.errorfCount) + assert.Equal(t, 0, mockedTest.failNowCount) + + // Test that on unexpected call, the mocked test was called to fail the test + + assert.PanicsWithValue(t, mockTestingTFailNowCalled, func() { + mockedService.TheExampleMethod(1, 1, 1) + }) + + // Assert that Errorf and FailNow were called once + assert.Equal(t, 1, mockedTest.errorfCount) + assert.Equal(t, 1, mockedTest.failNowCount) +} + func Test_Mock_On_WithPtrArgMatcher(t *testing.T) { var mockedService TestExampleImplementation mockedService.On("TheExampleMethod3", - MatchedBy(func(a *ExampleType) bool { return a.ran == true }), + MatchedBy(func(a *ExampleType) bool { return a != nil && a.ran == true }), ).Return(nil) mockedService.On("TheExampleMethod3", - MatchedBy(func(a *ExampleType) bool { return a.ran == false }), + MatchedBy(func(a *ExampleType) bool { return a != nil && a.ran == false }), ).Return(errors.New("error")) + mockedService.On("TheExampleMethod3", + MatchedBy(func(a *ExampleType) bool { return a == nil }), + ).Return(errors.New("error2")) + assert.Equal(t, mockedService.TheExampleMethod3(&ExampleType{true}), nil) assert.EqualError(t, mockedService.TheExampleMethod3(&ExampleType{false}), "error") + assert.EqualError(t, mockedService.TheExampleMethod3(nil), "error2") } func Test_Mock_On_WithFuncArgMatcher(t *testing.T) { @@ -191,17 +286,62 @@ func Test_Mock_On_WithFuncArgMatcher(t *testing.T) { fixture1, fixture2 := errors.New("fixture1"), errors.New("fixture2") mockedService.On("TheExampleMethodFunc", - MatchedBy(func(a func(string) error) bool { return a("string") == fixture1 }), + MatchedBy(func(a func(string) error) bool { return a != nil && a("string") == fixture1 }), ).Return(errors.New("fixture1")) mockedService.On("TheExampleMethodFunc", - MatchedBy(func(a func(string) error) bool { return a("string") == fixture2 }), + MatchedBy(func(a func(string) error) bool { return a != nil && a("string") == fixture2 }), ).Return(errors.New("fixture2")) + mockedService.On("TheExampleMethodFunc", + MatchedBy(func(a func(string) error) bool { return a == nil }), + ).Return(errors.New("fixture3")) + assert.EqualError(t, mockedService.TheExampleMethodFunc( func(string) error { return fixture1 }), "fixture1") assert.EqualError(t, mockedService.TheExampleMethodFunc( func(string) error { return fixture2 }), "fixture2") + assert.EqualError(t, mockedService.TheExampleMethodFunc(nil), "fixture3") +} + +func Test_Mock_On_WithInterfaceArgMatcher(t *testing.T) { + var mockedService TestExampleImplementation + + mockedService.On("TheExampleMethod4", + MatchedBy(func(a ExampleInterface) bool { return a == nil }), + ).Return(errors.New("fixture1")) + + assert.EqualError(t, mockedService.TheExampleMethod4(nil), "fixture1") +} + +func Test_Mock_On_WithChannelArgMatcher(t *testing.T) { + var mockedService TestExampleImplementation + + mockedService.On("TheExampleMethod5", + MatchedBy(func(ch chan struct{}) bool { return ch == nil }), + ).Return(errors.New("fixture1")) + + assert.EqualError(t, mockedService.TheExampleMethod5(nil), "fixture1") +} + +func Test_Mock_On_WithMapArgMatcher(t *testing.T) { + var mockedService TestExampleImplementation + + mockedService.On("TheExampleMethod6", + MatchedBy(func(m map[string]bool) bool { return m == nil }), + ).Return(errors.New("fixture1")) + + assert.EqualError(t, mockedService.TheExampleMethod6(nil), "fixture1") +} + +func Test_Mock_On_WithSliceArgMatcher(t *testing.T) { + var mockedService TestExampleImplementation + + mockedService.On("TheExampleMethod7", + MatchedBy(func(slice []bool) bool { return slice == nil }), + ).Return(errors.New("fixture1")) + + assert.EqualError(t, mockedService.TheExampleMethod7(nil), "fixture1") } func Test_Mock_On_WithVariadicFunc(t *testing.T) { @@ -226,6 +366,29 @@ func Test_Mock_On_WithVariadicFunc(t *testing.T) { } +func Test_Mock_On_WithMixedVariadicFunc(t *testing.T) { + + // make a test impl object + var mockedService = new(TestExampleImplementation) + + c := mockedService. + On("TheExampleMethodMixedVariadic", 1, []int{2, 3, 4}). + Return(nil) + + assert.Equal(t, []*Call{c}, mockedService.ExpectedCalls) + assert.Equal(t, 2, len(c.Arguments)) + assert.Equal(t, 1, c.Arguments[0]) + assert.Equal(t, []int{2, 3, 4}, c.Arguments[1]) + + assert.NotPanics(t, func() { + mockedService.TheExampleMethodMixedVariadic(1, 2, 3, 4) + }) + assert.Panics(t, func() { + mockedService.TheExampleMethodMixedVariadic(1, 2, 3, 5) + }) + +} + func Test_Mock_On_WithVariadicFuncWithInterface(t *testing.T) { // make a test impl object @@ -726,7 +889,7 @@ func Test_AssertExpectationsForObjects_Helper(t *testing.T) { mockedService2.Called(2) mockedService3.Called(3) - assert.True(t, AssertExpectationsForObjects(t, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock)) + assert.True(t, AssertExpectationsForObjects(t, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock)) assert.True(t, AssertExpectationsForObjects(t, mockedService1, mockedService2, mockedService3)) } @@ -745,7 +908,7 @@ func Test_AssertExpectationsForObjects_Helper_Failed(t *testing.T) { mockedService3.Called(3) tt := new(testing.T) - assert.False(t, AssertExpectationsForObjects(tt, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock)) + assert.False(t, AssertExpectationsForObjects(tt, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock)) assert.False(t, AssertExpectationsForObjects(tt, mockedService1, mockedService2, mockedService3)) } @@ -969,6 +1132,31 @@ func Test_Mock_AssertNotCalled(t *testing.T) { } +func Test_Mock_AssertOptional(t *testing.T) { + // Optional called + var ms1 = new(TestExampleImplementation) + ms1.On("TheExampleMethod", 1, 2, 3).Maybe().Return(4, nil) + ms1.TheExampleMethod(1, 2, 3) + + tt1 := new(testing.T) + assert.Equal(t, true, ms1.AssertExpectations(tt1)) + + // Optional not called + var ms2 = new(TestExampleImplementation) + ms2.On("TheExampleMethod", 1, 2, 3).Maybe().Return(4, nil) + + tt2 := new(testing.T) + assert.Equal(t, true, ms2.AssertExpectations(tt2)) + + // Non-optional called + var ms3 = new(TestExampleImplementation) + ms3.On("TheExampleMethod", 1, 2, 3).Return(4, nil) + ms3.TheExampleMethod(1, 2, 3) + + tt3 := new(testing.T) + assert.Equal(t, true, ms3.AssertExpectations(tt3)) +} + /* Arguments helper methods */ @@ -1130,3 +1318,180 @@ func Test_Arguments_Bool(t *testing.T) { assert.Equal(t, true, args.Bool(2)) } + +func Test_WaitUntil_Parallel(t *testing.T) { + + // make a test impl object + var mockedService = new(TestExampleImplementation) + + ch1 := make(chan time.Time) + ch2 := make(chan time.Time) + + mockedService.Mock.On("TheExampleMethod2", true).Return().WaitUntil(ch2).Run(func(args Arguments) { + ch1 <- time.Now() + }) + + mockedService.Mock.On("TheExampleMethod2", false).Return().WaitUntil(ch1) + + // Lock both goroutines on the .WaitUntil method + go func() { + mockedService.TheExampleMethod2(false) + }() + go func() { + mockedService.TheExampleMethod2(true) + }() + + // Allow the first call to execute, so the second one executes afterwards + ch2 <- time.Now() +} + +func Test_MockMethodCalled(t *testing.T) { + m := new(Mock) + m.On("foo", "hello").Return("world") + + retArgs := m.MethodCalled("foo", "hello") + require.True(t, len(retArgs) == 1) + require.Equal(t, "world", retArgs[0]) + m.AssertExpectations(t) +} + +// Test to validate fix for racy concurrent call access in MethodCalled() +func Test_MockReturnAndCalledConcurrent(t *testing.T) { + iterations := 1000 + m := &Mock{} + call := m.On("ConcurrencyTestMethod") + + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + for i := 0; i < iterations; i++ { + call.Return(10) + } + wg.Done() + }() + go func() { + for i := 0; i < iterations; i++ { + ConcurrencyTestMethod(m) + } + wg.Done() + }() + wg.Wait() +} + +type timer struct{ Mock } + +func (s *timer) GetTime(i int) string { + return s.Called(i).Get(0).(string) +} + +type tCustomLogger struct { + *testing.T + logs []string + errs []string +} + +func (tc *tCustomLogger) Logf(format string, args ...interface{}) { + tc.T.Logf(format, args...) + tc.logs = append(tc.logs, fmt.Sprintf(format, args...)) +} + +func (tc *tCustomLogger) Errorf(format string, args ...interface{}) { + tc.errs = append(tc.errs, fmt.Sprintf(format, args...)) +} + +func (tc *tCustomLogger) FailNow() {} + +func TestLoggingAssertExpectations(t *testing.T) { + m := new(timer) + m.On("GetTime", 0).Return("") + tcl := &tCustomLogger{t, []string{}, []string{}} + + AssertExpectationsForObjects(tcl, m, new(TestExampleImplementation)) + + require.Equal(t, 1, len(tcl.errs)) + assert.Regexp(t, regexp.MustCompile("(?s)FAIL: 0 out of 1 expectation\\(s\\) were met.*The code you are testing needs to make 1 more call\\(s\\).*"), tcl.errs[0]) + require.Equal(t, 2, len(tcl.logs)) + assert.Regexp(t, regexp.MustCompile("(?s)FAIL:\tGetTime\\(int\\).*"), tcl.logs[0]) + require.Equal(t, "Expectations didn't match for Mock: *mock.timer", tcl.logs[1]) +} + +func TestAfterTotalWaitTimeWhileExecution(t *testing.T) { + waitDuration := 1 + total, waitMs := 5, time.Millisecond*time.Duration(waitDuration) + aTimer := new(timer) + for i := 0; i < total; i++ { + aTimer.On("GetTime", i).After(waitMs).Return(fmt.Sprintf("Time%d", i)).Once() + } + time.Sleep(waitMs) + start := time.Now() + var results []string + + for i := 0; i < total; i++ { + results = append(results, aTimer.GetTime(i)) + } + + end := time.Now() + elapsedTime := end.Sub(start) + assert.True(t, elapsedTime > waitMs, fmt.Sprintf("Total elapsed time:%v should be atleast greater than %v", elapsedTime, waitMs)) + assert.Equal(t, total, len(results)) + for i := range results { + assert.Equal(t, fmt.Sprintf("Time%d", i), results[i], "Return value of method should be same") + } +} + +func TestArgumentMatcherToPrintMismatch(t *testing.T) { + defer func() { + if r := recover(); r != nil { + matchingExp := regexp.MustCompile( + `\s+mock: Unexpected Method Call\s+-*\s+GetTime\(int\)\s+0: 1\s+The closest call I have is:\s+GetTime\(mock.argumentMatcher\)\s+0: mock.argumentMatcher\{.*?\}\s+Diff:.*\(int=1\) not matched by func\(int\) bool`) + assert.Regexp(t, matchingExp, r) + } + }() + + m := new(timer) + m.On("GetTime", MatchedBy(func(i int) bool { return false })).Return("SomeTime").Once() + + res := m.GetTime(1) + require.Equal(t, "SomeTime", res) + m.AssertExpectations(t) +} + +func TestClosestCallMismatchedArgumentInformationShowsTheClosest(t *testing.T) { + defer func() { + if r := recover(); r != nil { + matchingExp := regexp.MustCompile(unexpectedCallRegex(`TheExampleMethod(int,int,int)`, `0: 1\s+1: 1\s+2: 2`, `0: 1\s+1: 1\s+2: 1`, `0: PASS: %!s\(int=1\) == %!s\(int=1\)\s+1: PASS: %!s\(int=1\) == %!s\(int=1\)\s+2: FAIL: %!s\(int=2\) != %!s\(int=1\)`)) + assert.Regexp(t, matchingExp, r) + } + }() + + m := new(TestExampleImplementation) + m.On("TheExampleMethod", 1, 1, 1).Return(1, nil).Once() + m.On("TheExampleMethod", 2, 2, 2).Return(2, nil).Once() + + m.TheExampleMethod(1, 1, 2) +} + +func TestClosestCallMismatchedArgumentValueInformation(t *testing.T) { + defer func() { + if r := recover(); r != nil { + matchingExp := regexp.MustCompile(unexpectedCallRegex(`GetTime(int)`, "0: 1", "0: 999", `0: FAIL: %!s\(int=1\) != %!s\(int=999\)`)) + assert.Regexp(t, matchingExp, r) + } + }() + + m := new(timer) + m.On("GetTime", 999).Return("SomeTime").Once() + + _ = m.GetTime(1) +} + +func unexpectedCallRegex(method, calledArg, expectedArg, diff string) string { + rMethod := regexp.QuoteMeta(method) + return fmt.Sprintf(`\s+mock: Unexpected Method Call\s+-*\s+%s\s+%s\s+The closest call I have is:\s+%s\s+%s\s+Diff: %s`, + rMethod, calledArg, rMethod, expectedArg, diff) +} + +func ConcurrencyTestMethod(m *Mock) { + m.Called() +} diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go index d3c2ab9bc..ac71d4058 100644 --- a/vendor/github.com/stretchr/testify/require/forward_requirements.go +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -13,4 +13,4 @@ func New(t TestingT) *Assertions { } } -//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl +//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go index 1bcfcb0d9..2110ea89c 100644 --- a/vendor/github.com/stretchr/testify/require/require.go +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -1,464 +1,1137 @@ /* * CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen * THIS FILE MUST NOT BE EDITED BY HAND -*/ + */ package require import ( - assert "github.com/stretchr/testify/assert" http "net/http" url "net/url" time "time" ) - // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { - if !assert.Condition(t, comp, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Condition(t, comp, msgAndArgs...) { + t.FailNow() + } } +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Conditionf(t, comp, msg, args...) { + t.FailNow() + } +} // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. -// -// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") -// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { - if !assert.Contains(t, s, contains, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Contains(t, s, contains, msgAndArgs...) { + t.FailNow() + } } +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Containsf(t, s, contains, msg, args...) { + t.FailNow() + } +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.DirExists(t, path, msgAndArgs...) { + t.FailNow() + } +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.DirExistsf(t, path, msg, args...) { + t.FailNow() + } +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + t.FailNow() + } +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.ElementsMatchf(t, listA, listB, msg, args...) { + t.FailNow() + } +} // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.Empty(t, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Empty(t, object, msgAndArgs...) { + t.FailNow() + } } +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Emptyf(t, object, msg, args...) { + t.FailNow() + } +} // Equal asserts that two objects are equal. -// -// assert.Equal(t, 123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.Equal(t, expected, actual, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Equal(t, expected, actual, msgAndArgs...) { + t.FailNow() + } } - // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. -// +// // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). +// assert.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { - if !assert.EqualError(t, theError, errString, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.EqualError(t, theError, errString, msgAndArgs...) { + t.FailNow() + } } +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.EqualErrorf(t, theError, errString, msg, args...) { + t.FailNow() + } +} // EqualValues asserts that two objects are equal or convertable to the same types // and equal. -// -// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.EqualValues(t, expected, actual, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.EqualValues(t, expected, actual, msgAndArgs...) { + t.FailNow() + } } +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.EqualValuesf(t, expected, actual, msg, args...) { + t.FailNow() + } +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Equalf(t, expected, actual, msg, args...) { + t.FailNow() + } +} // Error asserts that a function returned an error (i.e. not `nil`). -// +// // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) { - if !assert.Error(t, err, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Error(t, err, msgAndArgs...) { + t.FailNow() + } } +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Errorf(t, err, msg, args...) { + t.FailNow() + } +} -// Exactly asserts that two objects are equal is value and type. -// -// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.Exactly(t, expected, actual, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Exactly(t, expected, actual, msgAndArgs...) { + t.FailNow() + } } +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Exactlyf(t, expected, actual, msg, args...) { + t.FailNow() + } +} // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { - if !assert.Fail(t, failureMessage, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Fail(t, failureMessage, msgAndArgs...) { + t.FailNow() + } } - // FailNow fails test func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { - if !assert.FailNow(t, failureMessage, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.FailNow(t, failureMessage, msgAndArgs...) { + t.FailNow() + } } +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.FailNowf(t, failureMessage, msg, args...) { + t.FailNow() + } +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Failf(t, failureMessage, msg, args...) { + t.FailNow() + } +} // False asserts that the specified value is false. -// -// assert.False(t, myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) { - if !assert.False(t, value, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.False(t, value, msgAndArgs...) { + t.FailNow() + } } +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Falsef(t, value, msg, args...) { + t.FailNow() + } +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.FileExists(t, path, msgAndArgs...) { + t.FailNow() + } +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.FileExistsf(t, path, msg, args...) { + t.FailNow() + } +} // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. -// +// // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - if !assert.HTTPBodyContains(t, handler, method, url, values, str) { - t.FailNow() - } +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + t.FailNow() + } } +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + t.FailNow() + } +} // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. -// +// // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - if !assert.HTTPBodyNotContains(t, handler, method, url, values, str) { - t.FailNow() - } +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + t.FailNow() + } } +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + t.FailNow() + } +} // HTTPError asserts that a specified handler returns an error status code. -// +// // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPError(t, handler, method, url, values) { - t.FailNow() - } +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + t.FailNow() + } } +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + t.FailNow() + } +} // HTTPRedirect asserts that a specified handler returns a redirect status code. -// +// // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPRedirect(t, handler, method, url, values) { - t.FailNow() - } +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + t.FailNow() + } } +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + t.FailNow() + } +} // HTTPSuccess asserts that a specified handler returns a success status code. -// +// // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) -// +// // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values) { - if !assert.HTTPSuccess(t, handler, method, url, values) { - t.FailNow() - } +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + t.FailNow() + } } +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + t.FailNow() + } +} // Implements asserts that an object is implemented by the specified interface. -// -// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { - if !assert.Implements(t, interfaceObject, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Implements(t, interfaceObject, object, msgAndArgs...) { + t.FailNow() + } } +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Implementsf(t, interfaceObject, object, msg, args...) { + t.FailNow() + } +} // InDelta asserts that the two numerals are within delta of each other. -// +// // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } } +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + t.FailNow() + } +} // InDeltaSlice is the same as InDelta, except it compares two slices. func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + t.FailNow() + } } +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InDeltaf(t, expected, actual, delta, msg, args...) { + t.FailNow() + } +} // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { - if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + t.FailNow() + } } +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + t.FailNow() + } +} -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - if !assert.InEpsilonSlice(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + t.FailNow() + } } +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + t.FailNow() + } +} // IsType asserts that the specified objects are of the same type. func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { - if !assert.IsType(t, expectedType, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.IsType(t, expectedType, object, msgAndArgs...) { + t.FailNow() + } } +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.IsTypef(t, expectedType, object, msg, args...) { + t.FailNow() + } +} // JSONEq asserts that two JSON strings are equivalent. -// +// // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { - if !assert.JSONEq(t, expected, actual, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.JSONEq(t, expected, actual, msgAndArgs...) { + t.FailNow() + } } +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.JSONEqf(t, expected, actual, msg, args...) { + t.FailNow() + } +} // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. -// -// assert.Len(t, mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { - if !assert.Len(t, object, length, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Len(t, object, length, msgAndArgs...) { + t.FailNow() + } } +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Lenf(t, object, length, msg, args...) { + t.FailNow() + } +} // Nil asserts that the specified object is nil. -// -// assert.Nil(t, err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.Nil(t, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Nil(t, object, msgAndArgs...) { + t.FailNow() + } } +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Nilf(t, object, msg, args...) { + t.FailNow() + } +} // NoError asserts that a function returned no error (i.e. `nil`). -// +// // actualObj, err := SomeFunction() // if assert.NoError(t, err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) { - if !assert.NoError(t, err, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NoError(t, err, msgAndArgs...) { + t.FailNow() + } } +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NoErrorf(t, err, msg, args...) { + t.FailNow() + } +} // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. -// -// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { - if !assert.NotContains(t, s, contains, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotContains(t, s, contains, msgAndArgs...) { + t.FailNow() + } } +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotContainsf(t, s, contains, msg, args...) { + t.FailNow() + } +} // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.NotEmpty(t, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotEmpty(t, object, msgAndArgs...) { + t.FailNow() + } } +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotEmptyf(t, object, msg, args...) { + t.FailNow() + } +} // NotEqual asserts that the specified values are NOT equal. -// -// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { - if !assert.NotEqual(t, expected, actual, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotEqual(t, expected, actual, msgAndArgs...) { + t.FailNow() + } } +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotEqualf(t, expected, actual, msg, args...) { + t.FailNow() + } +} // NotNil asserts that the specified object is not nil. -// -// assert.NotNil(t, err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { - if !assert.NotNil(t, object, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotNil(t, object, msgAndArgs...) { + t.FailNow() + } } +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotNilf(t, object, msg, args...) { + t.FailNow() + } +} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// assert.NotPanics(t, func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { - if !assert.NotPanics(t, f, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotPanics(t, f, msgAndArgs...) { + t.FailNow() + } } +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotPanicsf(t, f, msg, args...) { + t.FailNow() + } +} // NotRegexp asserts that a specified regexp does not match a string. -// +// // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { - if !assert.NotRegexp(t, rx, str, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotRegexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotRegexpf(t, rx, str, msg, args...) { + t.FailNow() + } } +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotSubset(t, list, subset, msgAndArgs...) { + t.FailNow() + } +} -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotSubsetf(t, list, subset, msg, args...) { + t.FailNow() + } +} + +// NotZero asserts that i is not the zero value for its type. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { - if !assert.NotZero(t, i, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotZero(t, i, msgAndArgs...) { + t.FailNow() + } } +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.NotZerof(t, i, msg, args...) { + t.FailNow() + } +} // Panics asserts that the code inside the specified PanicTestFunc panics. -// -// assert.Panics(t, func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { - if !assert.Panics(t, f, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Panics(t, f, msgAndArgs...) { + t.FailNow() + } } +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + t.FailNow() + } +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.PanicsWithValuef(t, expected, f, msg, args...) { + t.FailNow() + } +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Panicsf(t, f, msg, args...) { + t.FailNow() + } +} // Regexp asserts that a specified regexp matches a string. -// +// // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { - if !assert.Regexp(t, rx, str, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Regexp(t, rx, str, msgAndArgs...) { + t.FailNow() + } +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Regexpf(t, rx, str, msg, args...) { + t.FailNow() + } +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Subset(t, list, subset, msgAndArgs...) { + t.FailNow() + } } +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Subsetf(t, list, subset, msg, args...) { + t.FailNow() + } +} // True asserts that the specified value is true. -// -// assert.True(t, myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) { - if !assert.True(t, value, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.True(t, value, msgAndArgs...) { + t.FailNow() + } } +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Truef(t, value, msg, args...) { + t.FailNow() + } +} // WithinDuration asserts that the two times are within duration delta of each other. -// -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { - if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + t.FailNow() + } } +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + t.FailNow() + } +} -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { - if !assert.Zero(t, i, msgAndArgs...) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Zero(t, i, msgAndArgs...) { + t.FailNow() + } +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !assert.Zerof(t, i, msg, args...) { + t.FailNow() + } } diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl index ab1b1e9fd..9fb2577d4 100644 --- a/vendor/github.com/stretchr/testify/require/require.go.tmpl +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -1,6 +1,7 @@ {{.Comment}} func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { - if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { - t.FailNow() - } + if h, ok := t.(tHelper); ok { h.Helper() } + if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { + t.FailNow() + } } diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go index 58324f105..9c66cd3c3 100644 --- a/vendor/github.com/stretchr/testify/require/require_forward.go +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -1,388 +1,957 @@ /* * CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen * THIS FILE MUST NOT BE EDITED BY HAND -*/ + */ package require import ( - assert "github.com/stretchr/testify/assert" http "net/http" url "net/url" time "time" ) - // Condition uses a Comparison to assert a complex condition. func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Condition(a.t, comp, msgAndArgs...) } +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. -// -// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Contains(a.t, s, contains, msgAndArgs...) } +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Empty(a.t, object, msgAndArgs...) } +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} // Equal asserts that two objects are equal. -// -// a.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Equal(a.t, expected, actual, msgAndArgs...) } - // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. -// +// // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). +// a.EqualError(err, expectedErrorString) func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } EqualError(a.t, theError, errString, msgAndArgs...) } +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} // EqualValues asserts that two objects are equal or convertable to the same types // and equal. -// -// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } EqualValues(a.t, expected, actual, msgAndArgs...) } +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} // Error asserts that a function returned an error (i.e. not `nil`). -// +// // actualObj, err := SomeFunction() -// if a.Error(err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if a.Error(err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Error(a.t, err, msgAndArgs...) } +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} -// Exactly asserts that two objects are equal is value and type. -// -// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Exactly(a.t, expected, actual, msgAndArgs...) } +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} // Fail reports a failure through func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Fail(a.t, failureMessage, msgAndArgs...) } - // FailNow fails test func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } FailNow(a.t, failureMessage, msgAndArgs...) } +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} // False asserts that the specified value is false. -// -// a.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.False(myBool) func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } False(a.t, value, msgAndArgs...) } +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. -// +// // a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - HTTPBodyContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. -// +// // a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) { - HTTPBodyNotContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPError asserts that a specified handler returns an error status code. -// +// // a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPError(a.t, handler, method, url, values) +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} // HTTPRedirect asserts that a specified handler returns a redirect status code. -// +// // a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPRedirect(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} // HTTPSuccess asserts that a specified handler returns a success status code. -// +// // a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) { - HTTPSuccess(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} // Implements asserts that an object is implemented by the specified interface. -// -// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +// +// a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Implements(a.t, interfaceObject, object, msgAndArgs...) } +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} // InDelta asserts that the two numerals are within delta of each other. -// +// // a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } InDelta(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} // InDeltaSlice is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) } +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { - InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) } +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} // IsType asserts that the specified objects are of the same type. func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } IsType(a.t, expectedType, object, msgAndArgs...) } +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} // JSONEq asserts that two JSON strings are equivalent. -// +// // a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } JSONEq(a.t, expected, actual, msgAndArgs...) } +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. -// -// a.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Len(mySlice, 3) func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Len(a.t, object, length, msgAndArgs...) } +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} // Nil asserts that the specified object is nil. -// -// a.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Nil(err) func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Nil(a.t, object, msgAndArgs...) } +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} // NoError asserts that a function returned no error (i.e. `nil`). -// +// // actualObj, err := SomeFunction() // if a.NoError(err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NoError(a.t, err, msgAndArgs...) } +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. -// -// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotContains(a.t, s, contains, msgAndArgs...) } +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotEmpty(a.t, object, msgAndArgs...) } +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} // NotEqual asserts that the specified values are NOT equal. -// -// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotEqual(a.t, expected, actual, msgAndArgs...) } +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} // NotNil asserts that the specified object is not nil. -// -// a.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotNil(err) func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotNil(a.t, object, msgAndArgs...) } +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// a.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotPanics(func(){ RemainCalm() }) func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotPanics(a.t, f, msgAndArgs...) } +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} // NotRegexp asserts that a specified regexp does not match a string. -// +// // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") // a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotRegexp(a.t, rx, str, msgAndArgs...) } - -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } NotZero(a.t, i, msgAndArgs...) } +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} // Panics asserts that the code inside the specified PanicTestFunc panics. -// -// a.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Panics(func(){ GoCrazy() }) func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Panics(a.t, f, msgAndArgs...) } +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} // Regexp asserts that a specified regexp matches a string. -// +// // a.Regexp(regexp.MustCompile("start"), "it's starting") // a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Regexp(a.t, rx, str, msgAndArgs...) } +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} // True asserts that the specified value is true. -// -// a.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.True(myBool) func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } True(a.t, value, msgAndArgs...) } +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} // WithinDuration asserts that the two times are within duration delta of each other. -// -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } WithinDuration(a.t, expected, actual, delta, msgAndArgs...) } +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } Zero(a.t, i, msgAndArgs...) } + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl index b93569e0a..54124df1d 100644 --- a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -1,4 +1,5 @@ {{.CommentWithoutT "a"}} func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) } diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go index 41147562d..690583a8e 100644 --- a/vendor/github.com/stretchr/testify/require/requirements.go +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -6,4 +6,24 @@ type TestingT interface { FailNow() } -//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl +type tHelper interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs diff --git a/vendor/github.com/stretchr/testify/require/requirements_test.go b/vendor/github.com/stretchr/testify/require/requirements_test.go index d2ccc99c9..39467d9a5 100644 --- a/vendor/github.com/stretchr/testify/require/requirements_test.go +++ b/vendor/github.com/stretchr/testify/require/requirements_test.go @@ -1,6 +1,7 @@ package require import ( + "encoding/json" "errors" "testing" "time" @@ -367,3 +368,199 @@ func TestJSONEq_ArraysOfDifferentOrder(t *testing.T) { t.Error("Check should fail") } } + +func ExampleComparisonAssertionFunc() { + t := &testing.T{} // provided by test + + adder := func(x, y int) int { + return x + y + } + + type args struct { + x int + y int + } + + tests := []struct { + name string + args args + expect int + assertion ComparisonAssertionFunc + }{ + {"2+2=4", args{2, 2}, 4, Equal}, + {"2+2!=5", args{2, 2}, 5, NotEqual}, + {"2+3==5", args{2, 3}, 5, Exactly}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.expect, adder(tt.args.x, tt.args.y)) + }) + } +} + +func TestComparisonAssertionFunc(t *testing.T) { + type iface interface { + Name() string + } + + tests := []struct { + name string + expect interface{} + got interface{} + assertion ComparisonAssertionFunc + }{ + {"implements", (*iface)(nil), t, Implements}, + {"isType", (*testing.T)(nil), t, IsType}, + {"equal", t, t, Equal}, + {"equalValues", t, t, EqualValues}, + {"exactly", t, t, Exactly}, + {"notEqual", t, nil, NotEqual}, + {"notContains", []int{1, 2, 3}, 4, NotContains}, + {"subset", []int{1, 2, 3, 4}, []int{2, 3}, Subset}, + {"notSubset", []int{1, 2, 3, 4}, []int{0, 3}, NotSubset}, + {"elementsMatch", []byte("abc"), []byte("bac"), ElementsMatch}, + {"regexp", "^t.*y$", "testify", Regexp}, + {"notRegexp", "^t.*y$", "Testify", NotRegexp}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.expect, tt.got) + }) + } +} + +func ExampleValueAssertionFunc() { + t := &testing.T{} // provided by test + + dumbParse := func(input string) interface{} { + var x interface{} + json.Unmarshal([]byte(input), &x) + return x + } + + tests := []struct { + name string + arg string + assertion ValueAssertionFunc + }{ + {"true is not nil", "true", NotNil}, + {"empty string is nil", "", Nil}, + {"zero is not nil", "0", NotNil}, + {"zero is zero", "0", Zero}, + {"false is zero", "false", Zero}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, dumbParse(tt.arg)) + }) + } +} + +func TestValueAssertionFunc(t *testing.T) { + tests := []struct { + name string + value interface{} + assertion ValueAssertionFunc + }{ + {"notNil", true, NotNil}, + {"nil", nil, Nil}, + {"empty", []int{}, Empty}, + {"notEmpty", []int{1}, NotEmpty}, + {"zero", false, Zero}, + {"notZero", 42, NotZero}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.value) + }) + } +} + +func ExampleBoolAssertionFunc() { + t := &testing.T{} // provided by test + + isOkay := func(x int) bool { + return x >= 42 + } + + tests := []struct { + name string + arg int + assertion BoolAssertionFunc + }{ + {"-1 is bad", -1, False}, + {"42 is good", 42, True}, + {"41 is bad", 41, False}, + {"45 is cool", 45, True}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, isOkay(tt.arg)) + }) + } +} + +func TestBoolAssertionFunc(t *testing.T) { + tests := []struct { + name string + value bool + assertion BoolAssertionFunc + }{ + {"true", true, True}, + {"false", false, False}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.value) + }) + } +} + +func ExampleErrorAssertionFunc() { + t := &testing.T{} // provided by test + + dumbParseNum := func(input string, v interface{}) error { + return json.Unmarshal([]byte(input), v) + } + + tests := []struct { + name string + arg string + assertion ErrorAssertionFunc + }{ + {"1.2 is number", "1.2", NoError}, + {"1.2.3 not number", "1.2.3", Error}, + {"true is not number", "true", Error}, + {"3 is number", "3", NoError}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var x float64 + tt.assertion(t, dumbParseNum(tt.arg, &x)) + }) + } +} + +func TestErrorAssertionFunc(t *testing.T) { + tests := []struct { + name string + err error + assertion ErrorAssertionFunc + }{ + {"noError", nil, NoError}, + {"error", errors.New("whoops"), Error}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertion(t, tt.err) + }) + } +} diff --git a/vendor/github.com/stretchr/testify/suite/interfaces.go b/vendor/github.com/stretchr/testify/suite/interfaces.go index 20969472c..b37cb0409 100644 --- a/vendor/github.com/stretchr/testify/suite/interfaces.go +++ b/vendor/github.com/stretchr/testify/suite/interfaces.go @@ -32,3 +32,15 @@ type TearDownAllSuite interface { type TearDownTestSuite interface { TearDownTest() } + +// BeforeTest has a function to be executed right before the test +// starts and receives the suite and test names as input +type BeforeTest interface { + BeforeTest(suiteName, testName string) +} + +// AfterTest has a function to be executed right after the test +// finishes and receives the suite and test names as input +type AfterTest interface { + AfterTest(suiteName, testName string) +} diff --git a/vendor/github.com/stretchr/testify/suite/suite.go b/vendor/github.com/stretchr/testify/suite/suite.go index db7413000..e20afbc21 100644 --- a/vendor/github.com/stretchr/testify/suite/suite.go +++ b/vendor/github.com/stretchr/testify/suite/suite.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" ) +var allTestsFilter = func(_, _ string) (bool, error) { return true, nil } var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run") // Suite is a basic testing suite with methods for storing and @@ -86,7 +87,13 @@ func Run(t *testing.T, suite TestingSuite) { if setupTestSuite, ok := suite.(SetupTestSuite); ok { setupTestSuite.SetupTest() } + if beforeTestSuite, ok := suite.(BeforeTest); ok { + beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name) + } defer func() { + if afterTestSuite, ok := suite.(AfterTest); ok { + afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name) + } if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok { tearDownTestSuite.TearDownTest() } @@ -98,10 +105,20 @@ func Run(t *testing.T, suite TestingSuite) { tests = append(tests, test) } } + runTests(t, tests) +} + +func runTests(t testing.TB, tests []testing.InternalTest) { + r, ok := t.(runner) + if !ok { // backwards compatibility with Go 1.6 and below + if !testing.RunTests(allTestsFilter, tests) { + t.Fail() + } + return + } - if !testing.RunTests(func(_, _ string) (bool, error) { return true, nil }, - tests) { - t.Fail() + for _, test := range tests { + r.Run(test.Name, test.F) } } @@ -113,3 +130,7 @@ func methodFilter(name string) (bool, error) { } return regexp.MatchString(*matchMethod, name) } + +type runner interface { + Run(name string, f func(t *testing.T)) bool +} diff --git a/vendor/github.com/stretchr/testify/suite/suite_test.go b/vendor/github.com/stretchr/testify/suite/suite_test.go index c7c4e88f7..b75fa4ac1 100644 --- a/vendor/github.com/stretchr/testify/suite/suite_test.go +++ b/vendor/github.com/stretchr/testify/suite/suite_test.go @@ -5,8 +5,10 @@ import ( "io/ioutil" "os" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // SuiteRequireTwice is intended to test the usage of suite.Require in two @@ -18,7 +20,7 @@ type SuiteRequireTwice struct{ Suite } // A regression would result on these tests panicking rather than failing. func TestSuiteRequireTwice(t *testing.T) { ok := testing.RunTests( - func(_, _ string) (bool, error) { return true, nil }, + allTestsFilter, []testing.InternalTest{{ Name: "TestSuiteRequireTwice", F: func(t *testing.T) { @@ -58,6 +60,15 @@ type SuiteTester struct { TestOneRunCount int TestTwoRunCount int NonTestMethodRunCount int + + SuiteNameBefore []string + TestNameBefore []string + + SuiteNameAfter []string + TestNameAfter []string + + TimeBefore []time.Time + TimeAfter []time.Time } type SuiteSkipTester struct { @@ -75,6 +86,18 @@ func (suite *SuiteTester) SetupSuite() { suite.SetupSuiteRunCount++ } +func (suite *SuiteTester) BeforeTest(suiteName, testName string) { + suite.SuiteNameBefore = append(suite.SuiteNameBefore, suiteName) + suite.TestNameBefore = append(suite.TestNameBefore, testName) + suite.TimeBefore = append(suite.TimeBefore, time.Now()) +} + +func (suite *SuiteTester) AfterTest(suiteName, testName string) { + suite.SuiteNameAfter = append(suite.SuiteNameAfter, suiteName) + suite.TestNameAfter = append(suite.TestNameAfter, testName) + suite.TimeAfter = append(suite.TimeAfter, time.Now()) +} + func (suite *SuiteSkipTester) SetupSuite() { suite.SetupSuiteRunCount++ suite.T().Skip() @@ -145,6 +168,35 @@ func TestRunSuite(t *testing.T) { assert.Equal(t, suiteTester.SetupSuiteRunCount, 1) assert.Equal(t, suiteTester.TearDownSuiteRunCount, 1) + assert.Equal(t, len(suiteTester.SuiteNameAfter), 3) + assert.Equal(t, len(suiteTester.SuiteNameBefore), 3) + assert.Equal(t, len(suiteTester.TestNameAfter), 3) + assert.Equal(t, len(suiteTester.TestNameBefore), 3) + + assert.Contains(t, suiteTester.TestNameAfter, "TestOne") + assert.Contains(t, suiteTester.TestNameAfter, "TestTwo") + assert.Contains(t, suiteTester.TestNameAfter, "TestSkip") + + assert.Contains(t, suiteTester.TestNameBefore, "TestOne") + assert.Contains(t, suiteTester.TestNameBefore, "TestTwo") + assert.Contains(t, suiteTester.TestNameBefore, "TestSkip") + + for _, suiteName := range suiteTester.SuiteNameAfter { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, suiteName := range suiteTester.SuiteNameBefore { + assert.Equal(t, "SuiteTester", suiteName) + } + + for _, when := range suiteTester.TimeAfter { + assert.False(t, when.IsZero()) + } + + for _, when := range suiteTester.TimeBefore { + assert.False(t, when.IsZero()) + } + // There are three test methods (TestOne, TestTwo, and TestSkip), so // the SetupTest and TearDownTest methods (which should be run once for // each test) should have been run three times. @@ -216,16 +268,19 @@ func (sc *StdoutCapture) StopCapture() (string, error) { } func TestSuiteLogging(t *testing.T) { - testT := testing.T{} - suiteLoggingTester := new(SuiteLoggingTester) - capture := StdoutCapture{} + internalTest := testing.InternalTest{ + Name: "SomeTest", + F: func(subT *testing.T) { + Run(subT, suiteLoggingTester) + }, + } capture.StartCapture() - Run(&testT, suiteLoggingTester) + testing.RunTests(allTestsFilter, []testing.InternalTest{internalTest}) output, err := capture.StopCapture() - - assert.Nil(t, err, "Got an error trying to capture stdout!") + require.NoError(t, err, "Got an error trying to capture stdout and stderr!") + require.NotEmpty(t, output, "output content must not be empty") // Failed tests' output is always printed assert.Contains(t, output, "TESTLOGFAIL") diff --git a/vendor/sigs.k8s.io/cluster-api/Gopkg.lock b/vendor/sigs.k8s.io/cluster-api/Gopkg.lock index 1d2220630..074403001 100644 --- a/vendor/sigs.k8s.io/cluster-api/Gopkg.lock +++ b/vendor/sigs.k8s.io/cluster-api/Gopkg.lock @@ -201,6 +201,12 @@ packages = ["."] revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" +[[projects]] + branch = "master" + name = "github.com/golang/groupcache" + packages = ["lru"] + revision = "66deaeb636dff1ac7d938ce666d090556056a4b0" + [[projects]] name = "github.com/golang/protobuf" packages = [ @@ -901,12 +907,16 @@ "tools/clientcmd/api", "tools/clientcmd/api/latest", "tools/clientcmd/api/v1", + "tools/leaderelection", + "tools/leaderelection/resourcelock", "tools/metrics", "tools/pager", + "tools/record", "tools/reference", "transport", "util/buffer", "util/cert", + "util/cert/triple", "util/flowcontrol", "util/homedir", "util/integer", @@ -951,6 +961,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "cd83d550d0ee64c0847a31ab37fce90f980e5762b76ee5a9963959b0dd020eed" + inputs-digest = "e3e5af3f93950e21d1b9d087811efb8520757396d819e6ec5f163c659b0da4a2" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/sigs.k8s.io/cluster-api/OWNERS_ALIASES b/vendor/sigs.k8s.io/cluster-api/OWNERS_ALIASES index ca393d0c7..845bbe480 100644 --- a/vendor/sigs.k8s.io/cluster-api/OWNERS_ALIASES +++ b/vendor/sigs.k8s.io/cluster-api/OWNERS_ALIASES @@ -14,9 +14,11 @@ aliases: - kris-nova cluster-api-maintainers: - jessicaochen + - k4leung4 - karan - kris-nova - krousey - medinatiger - roberthbailey - rsdcastro + - spew diff --git a/vendor/sigs.k8s.io/cluster-api/README.md b/vendor/sigs.k8s.io/cluster-api/README.md index 06e15cdaa..41e9554bd 100644 --- a/vendor/sigs.k8s.io/cluster-api/README.md +++ b/vendor/sigs.k8s.io/cluster-api/README.md @@ -17,7 +17,7 @@ To learn more, see the [Cluster API KEP][cluster-api-kep]. ## Get involved! * Join our Cluster API working group sessions - * Weekly on Wednesdays @ 11:00 PT (19:00 UTC) on [Zoom][zoomMeeting] + * Weekly on Wednesdays @ 10:00 PT on [Zoom][zoomMeeting] * Previous meetings: \[ [notes][notes] | [recordings][recordings] \] * Chat with us on [Slack](http://slack.k8s.io/): #cluster-api diff --git a/vendor/sigs.k8s.io/cluster-api/SECURITY_CONTACTS b/vendor/sigs.k8s.io/cluster-api/SECURITY_CONTACTS new file mode 100644 index 000000000..686757663 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/SECURITY_CONTACTS @@ -0,0 +1,16 @@ +# Defined below are the security contacts for this repo. +# +# They are the contact point for the Product Security Team to reach out +# to for triaging and handling of incoming issues. +# +# The below names agree to abide by the +# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) +# and will be removed and replaced if they violate that agreement. +# +# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE +# INSTRUCTIONS AT https://kubernetes.io/security/ + +lukemarsden +luxas +roberthbailey +timothysc diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Dockerfile b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Dockerfile index fce55d405..dc93becf9 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Dockerfile +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Dockerfile @@ -23,8 +23,8 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller # Final container -FROM alpine:3.7 -RUN apk --no-cache add ca-certificates bash +FROM debian:stretch-slim +RUN apt-get update && apt-get install -y ca-certificates openssh-server && rm -rf /var/lib/apt/lists/* COPY --from=builder /go/bin/gce-machine-controller . diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Makefile b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Makefile index c689a9954..0edb3ced1 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Makefile +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/Makefile @@ -18,7 +18,7 @@ GCR_BUCKET = k8s-cluster-api PREFIX = gcr.io/$(GCR_BUCKET) DEV_PREFIX ?= gcr.io/$(shell gcloud config get-value project) NAME = gce-machine-controller -TAG = 0.0.7 +TAG = 0.0.11 image: docker build -t "$(PREFIX)/$(NAME):$(TAG)" -f ./Dockerfile ../../../.. diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/README.md b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/README.md new file mode 100644 index 000000000..8e4cbe5c3 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/README.md @@ -0,0 +1,28 @@ +# GCE Machine Controller + +The GCE Machine Controller is a machine controller implementation for clusters running on Google Compute Engine. + +## Development +The instructions below will enable you to get started with running your own version of the gce-machine-controller on a GCE Kubernetes cluster. + +### Prerequisites + +1. Follow the "Before you begin" section from the google cloud container registry [pushing and pulling images documentation](https://cloud.google.com/container-registry/docs/pushing-and-pulling). +1. From this directory, run `make dev_push`. +1. Note from the ouput of the above step the name of the image. It should be in this format, `gcr.io/MY-GCP-PROJECT-NAME/gce-machine-controller:VERSION-dev`. +1. From the same terminal in which you will create your cluster using gcp-deployer, run `export MACHINE_CONTROLLER_IMAGE=gcr.io/MY-GCP-PROJECT-NAME/gce-machine-controller:VERSION-dev`. Note that the value should be equal to the name of the image you noted in the output from the previous step. +1. Follow the steps listed at [gcp-deployer](../../../../gcp-deployer/) and create a new cluster. +1. Run the following command to ensure that new images are fetched for every new gce-machine-controller container, `kubectl patch deployment clusterapi -p "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"gce-machine-controller\",\"imagePullPolicy\":\"Always\"}]}}}}"`. + +### Running a Custom GCE Machine Controller + +1. Make a change to gce-machine-controller. For example, edit main.go and insert the following print statement `glog.Error("Hello World!")` below `logs.InitLogs()`. +1. From this folder, run `make dev_push`. +1. Run `kubectl patch deployment clusterapi -p "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"gce-machine-controller\",\"env\":[{\"name\":\"DATE\",\"value\":\"$(date +'%s')\"}]}]}}}}"`. This command inserts or updates an environment variable named `DATE` which triggers a new deployment. + +### Verifying Your Environment + +1. Install `jq`. Instructions can be found [here](https://stedolan.github.io/jq/download/). +1. Run the following, `kubectl get pods -o json | jq '.items[].status.containerStatuses[] | select(.name=="gce-machine-controller")'`. Validate the the hash in the `imageID` field matches the image you built above. +1. Run the following, it will store, in `${POD_NAME}`, the name of your main clusterapi pod, `POD_NAME=$(kubectl get pods -o json | jq '.items[] | select(.status.containerStatuses[].name=="gce-machine-controller") | .metadata.name' --raw-output)`. +1. Run `kubectl logs --namespace=default ${POD_NAME} -c gce-machine-controller`. Look for the output or change that you added to gce-machine-controller. \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/controller.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/controller.go new file mode 100644 index 000000000..e26e5b2be --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/controller.go @@ -0,0 +1,165 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 app + +import ( + "os" + + "github.com/golang/glog" + "github.com/kubernetes-incubator/apiserver-builder/pkg/controller" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/cluster-api/cloud/google" + "sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/options" + "sigs.k8s.io/cluster-api/cloud/google/machinesetup" + "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" + clusterapiclientsetscheme "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/scheme" + "sigs.k8s.io/cluster-api/pkg/controller/config" + "sigs.k8s.io/cluster-api/pkg/controller/machine" + "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" +) + +const ( + gceMachineControllerName = "gce-machine-controller" +) + +func StartMachineController(server *options.MachineControllerServer, shutdown <-chan struct{}) { + config, err := controller.GetConfig(server.CommonConfig.Kubeconfig) + if err != nil { + glog.Fatalf("Could not create Config for talking to the apiserver: %v", err) + } + + client, err := clientset.NewForConfig(config) + if err != nil { + glog.Fatalf("Could not create client for talking to the apiserver: %v", err) + } + + configWatch, err := machinesetup.NewConfigWatch(server.MachineSetupConfigsPath) + if err != nil { + glog.Fatalf("Could not create config watch: %v", err) + } + params := google.MachineActuatorParams{ + KubeadmToken: server.KubeadmToken, + MachineClient: client.ClusterV1alpha1().Machines(corev1.NamespaceDefault), + MachineSetupConfigGetter: configWatch, + } + actuator, err := google.NewMachineActuator(params) + if err != nil { + glog.Fatalf("Could not create Google machine actuator: %v", err) + } + + si := sharedinformers.NewSharedInformers(config, shutdown) + // If this doesn't compile, the code generator probably + // overwrote the customized NewMachineController function. + c := machine.NewMachineController(config, si, actuator) + c.RunAsync(shutdown) + + select {} +} + +func Run(server *options.MachineControllerServer) error { + kubeConfig, err := controller.GetConfig(server.CommonConfig.Kubeconfig) + if err != nil { + glog.Errorf("Could not create Config for talking to the apiserver: %v", err) + return err + } + + kubeClientControl, err := kubernetes.NewForConfig( + rest.AddUserAgent(kubeConfig, "machine-controller-manager"), + ) + if err != nil { + glog.Errorf("Invalid API configuration for kubeconfig-control: %v", err) + return err + } + + recorder, err := createRecorder(kubeClientControl) + if err != nil { + glog.Errorf("Could not create event recorder : %v", err) + return err + } + + // run function will block and never return. + run := func(stop <-chan struct{}) { + StartMachineController(server, stop) + } + + leaderElectConfig := config.GetLeaderElectionConfig() + if !leaderElectConfig.LeaderElect { + run(make(<-chan (struct{}))) + } + + // Identity used to distinguish between multiple machine controller instances. + id, err := os.Hostname() + if err != nil { + return err + } + + leaderElectionClient := kubernetes.NewForConfigOrDie(rest.AddUserAgent(kubeConfig, "machine-leader-election")) + + id = id + "-" + string(uuid.NewUUID()) + // Lock required for leader election + rl, err := resourcelock.New( + leaderElectConfig.ResourceLock, + metav1.NamespaceSystem, + gceMachineControllerName, + leaderElectionClient.CoreV1(), + resourcelock.ResourceLockConfig{ + Identity: id + "-" + gceMachineControllerName, + EventRecorder: recorder, + }) + if err != nil { + return err + } + + // Try and become the leader and start machine controller loops + leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{ + Lock: rl, + LeaseDuration: leaderElectConfig.LeaseDuration.Duration, + RenewDeadline: leaderElectConfig.RenewDeadline.Duration, + RetryPeriod: leaderElectConfig.RetryPeriod.Duration, + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: run, + OnStoppedLeading: func() { + glog.Fatalf("leaderelection lost") + }, + }, + }) + panic("unreachable") +} + +func createRecorder(kubeClient *kubernetes.Clientset) (record.EventRecorder, error) { + + eventsScheme := runtime.NewScheme() + if err := corev1.AddToScheme(eventsScheme); err != nil { + return nil, err + } + // We also emit events for our own types + clusterapiclientsetscheme.AddToScheme(eventsScheme) + + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) + return eventBroadcaster.NewRecorder(eventsScheme, corev1.EventSource{Component: gceMachineControllerName}), nil +} diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/options/options.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/options/options.go new file mode 100644 index 000000000..fbc7f2fbe --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/options/options.go @@ -0,0 +1,42 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 options + +import ( + "github.com/spf13/pflag" + "sigs.k8s.io/cluster-api/pkg/controller/config" +) + +type MachineControllerServer struct { + CommonConfig *config.Configuration + KubeadmToken string + MachineSetupConfigsPath string +} + +func NewMachineControllerServer() *MachineControllerServer { + s := MachineControllerServer{ + CommonConfig: &config.ControllerConfig, + } + return &s +} + +func (s *MachineControllerServer) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&s.KubeadmToken, "token", s.KubeadmToken, "Kubeadm token to use to join new machines") + fs.StringVar(&s.MachineSetupConfigsPath, "machinesetup", s.MachineSetupConfigsPath, "path to machine setup configs file") + + config.ControllerConfig.AddFlags(pflag.CommandLine) +} diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/main.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/main.go index fb5adf62a..0a2d18f54 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/main.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/main.go @@ -18,53 +18,26 @@ package main import ( "github.com/golang/glog" - "github.com/kubernetes-incubator/apiserver-builder/pkg/controller" "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" "k8s.io/apiserver/pkg/util/logs" - - "sigs.k8s.io/cluster-api/cloud/google" - "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" - "sigs.k8s.io/cluster-api/pkg/controller/config" - "sigs.k8s.io/cluster-api/pkg/controller/machine" - "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" + "sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app" + "sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller/app/options" + "flag" ) -var ( - kubeadmToken = pflag.String("token", "", "Kubeadm token to use to join new machines") - machineSetupConfigsPath = pflag.String("machinesetup", "", "path to machine setup configs file") -) +func main() { -func init() { - config.ControllerConfig.AddFlags(pflag.CommandLine) -} + s := options.NewMachineControllerServer() + s.AddFlags(pflag.CommandLine) -func main() { pflag.Parse() + // the following line exists to make glog happy, for more information, see: https://github.com/kubernetes/kubernetes/issues/17162 + flag.CommandLine.Parse([]string{}) logs.InitLogs() defer logs.FlushLogs() - config, err := controller.GetConfig(config.ControllerConfig.Kubeconfig) - if err != nil { - glog.Fatalf("Could not create Config for talking to the apiserver: %v", err) + if err := app.Run(s); err != nil { + glog.Errorf("Failed to start machine controller. Err: %v", err) } - - client, err := clientset.NewForConfig(config) - if err != nil { - glog.Fatalf("Could not create client for talking to the apiserver: %v", err) - } - - actuator, err := google.NewMachineActuator(*kubeadmToken, client.ClusterV1alpha1().Machines(corev1.NamespaceDefault), *machineSetupConfigsPath) - if err != nil { - glog.Fatalf("Could not create Google machine actuator: %v", err) - } - - shutdown := make(chan struct{}) - si := sharedinformers.NewSharedInformers(config, shutdown) - // If this doesn't compile, the code generator probably - // overwrote the customized NewMachineController function. - c := machine.NewMachineController(config, si, actuator) - c.RunAsync(shutdown) - select {} } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/config/configtemplate.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/config/configtemplate.go index 81c600706..c6f00bd6b 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/config/configtemplate.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/config/configtemplate.go @@ -282,3 +282,202 @@ data: tls.crt: {{ .TLSCrt }} tls.key: {{ .TLSKey }} ` + +const StorageClassConfigTemplate = ` +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: standard + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: kubernetes.io/gce-pd +parameters: + type: pd-standard +` + +const IngressControllerConfigTemplate = ` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: glbc + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/ +kind: ClusterRole +metadata: + name: system:controller:glbc +rules: +- apiGroups: [""] + resources: ["secrets", "endpoints", "services", "pods", "nodes", "namespaces"] + verbs: ["describe", "get", "list", "watch"] +- apiGroups: [""] + resources: ["events", "configmaps"] + verbs: ["describe", "get", "list", "watch", "update", "create", "patch"] +- apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "update"] +- apiGroups: ["extensions"] + resources: ["ingresses/status"] + verbs: ["update"] +--- +apiVersion: rbac.authorization.k8s.io/ +kind: ClusterRoleBinding +metadata: + name: system:controller:glbc +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:glbc +subjects: +- kind: ServiceAccount + name: glbc + namespace: kube-system +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: l7-default-backend + namespace: kube-system + labels: + k8s-app: glbc + kubernetes.io/name: "GLBC" + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: glbc + template: + metadata: + labels: + k8s-app: glbc + name: glbc + spec: + containers: + - name: default-http-backend + # Any image is permissible as long as: + # 1. It serves a 404 page at / + # 2. It serves 200 on a /healthz endpoint + image: gcr.io/google_containers/defaultbackend:1.4 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + ports: + - containerPort: 8080 + resources: + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ingress-controller-config + namespace: kube-system +data: + gce.conf: | + [global] + token-url = nil + network = default + project-id = {{ .Project }} + node-tags = {{ .NodeTag }} +--- +apiVersion: v1 +kind: Service +metadata: + # This must match the --default-backend-service argument of the l7 lb + # controller and is required because GCE mandates a default backend. + name: default-http-backend + namespace: kube-system + labels: + k8s-app: glbc + kubernetes.io/cluster-service: "true" + addonmanager.kubernetes.io/mode: Reconcile + kubernetes.io/name: "GLBCDefaultBackend" +spec: + # The default backend must be of type NodePort. + type: NodePort + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + k8s-app: glbc +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + namespace: kube-system + name: l7-lb-controller + annotations: + scheduler.alpha.kubernetes.io/critical-pod: '' + labels: + k8s-app: glbc + version: v1.1.1 + kubernetes.io/name: "GLBC" +spec: + # There should never be more than 1 controller alive simultaneously. + replicas: 1 + selector: + matchLabels: + k8s-app: glbc + version: v1.1.1 + template: + metadata: + labels: + k8s-app: glbc + version: v1.1.1 + name: glbc + spec: + serviceAccountName: glbc + terminationGracePeriodSeconds: 600 + containers: + - image: k8s.gcr.io/ingress-gce-glbc-amd64:v1.1.1 + livenessProbe: + httpGet: + path: /healthz + port: 8086 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + env: + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /etc/credentials/service-account.json + name: l7-lb-controller + resources: + limits: + cpu: 100m + memory: 100Mi + requests: + cpu: 100m + memory: 50Mi + command: + - sh + - -c + - 'exec /glbc --gce-ratelimit=ga.Operations.Get,qps,10,100 --gce-ratelimit=alpha.Operations.Get,qps,10,100 --gce-ratelimit=ga.BackendServices.Get,qps,1.8,1 --gce-ratelimit=ga.HealthChecks.Get,qps,1.8,1 --gce-ratelimit=alpha.HealthChecks.Get,qps,1.8,1 --verbose --default-backend-service=kube-system/default-http-backend --sync-period=600s --running-in-cluster=true --use-real-cloud=true --config-file-path=/etc/ingress-config/gce.conf --healthz-port=8086 2>&1' + volumeMounts: + - mountPath: /etc/ingress-config + name: cloudconfig + readOnly: true + - mountPath: /etc/credentials + name: credentials + readOnly: true + volumes: + - name: cloudconfig + configMap: + name: ingress-controller-config + - name: credentials + secret: + secretName: glbc-gcp-key +` diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/register.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/register.go index 52bb3ed1f..161871cc2 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/register.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/register.go @@ -45,7 +45,10 @@ func Resource(resource string) schema.GroupResource { func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &GCEProviderConfig{}, + &GCEMachineProviderConfig{}, + ) + scheme.AddKnownTypes(SchemeGroupVersion, + &GCEClusterProviderConfig{}, ) return nil } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/types.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/types.go index 1ca0f76f6..e9de3bb48 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/types.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/types.go @@ -21,13 +21,29 @@ import ( ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type GCEProviderConfig struct { +type GCEMachineProviderConfig struct { metav1.TypeMeta `json:",inline"` - Project string `json:"project"` Zone string `json:"zone"` MachineType string `json:"machineType"` // The name of the OS to be installed on the machine. - OS string `json:"os"` + OS string `json:"os"` + Disks []Disk `json:"disks"` +} + +type Disk struct { + InitializeParams DiskInitializeParams `json:"initializeParams"` +} + +type DiskInitializeParams struct { + DiskSizeGb int64 `json:"diskSizeGb"` + DiskType string `json:"diskType"` } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type GCEClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + + Project string `json:"project"` +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/register.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/register.go index 01c4f4de8..a588328f8 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/register.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/register.go @@ -1,12 +1,9 @@ /* Copyright 2017 The Kubernetes Authors. - 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. @@ -17,12 +14,20 @@ limitations under the License. package v1alpha1 import ( + "bytes" + "fmt" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig" + clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" ) +type GCEProviderConfigCodec struct { + encoder runtime.Encoder + decoder runtime.Decoder +} + const GroupName = "gceproviderconfig" var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} @@ -39,19 +44,65 @@ func init() { func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &GCEProviderConfig{}, + &GCEMachineProviderConfig{}, + ) + scheme.AddKnownTypes(SchemeGroupVersion, + &GCEClusterProviderConfig{}, ) return nil } -func NewSchemeAndCodecs() (*runtime.Scheme, *serializer.CodecFactory, error) { +func NewScheme() (*runtime.Scheme, error) { scheme := runtime.NewScheme() if err := AddToScheme(scheme); err != nil { - return nil, nil, err + return nil, err } if err := gceproviderconfig.AddToScheme(scheme); err != nil { - return nil, nil, err + return nil, err } - codecs := serializer.NewCodecFactory(scheme) - return scheme, &codecs, nil + return scheme, nil } + +func NewCodec() (*GCEProviderConfigCodec, error) { + scheme, err := NewScheme() + if err != nil { + return nil, err + } + codecFactory := serializer.NewCodecFactory(scheme) + encoder, err := newEncoder(&codecFactory) + if err != nil { + return nil, err + } + codec := GCEProviderConfigCodec{ + encoder: encoder, + decoder: codecFactory.UniversalDecoder(SchemeGroupVersion), + } + return &codec, nil +} + +func (codec *GCEProviderConfigCodec) DecodeFromProviderConfig(providerConfig clusterv1.ProviderConfig, out runtime.Object) (error) { + _, _, err := codec.decoder.Decode(providerConfig.Value.Raw, nil, out) + if err != nil { + return fmt.Errorf("decoding failure: %v", err) + } + return nil +} + +func (codec *GCEProviderConfigCodec) EncodeToProviderConfig(in runtime.Object) (*clusterv1.ProviderConfig, error) { + var buf bytes.Buffer + if err := codec.encoder.Encode(in, &buf); err != nil { + return nil, fmt.Errorf("encoding failed: %v", err) + } + return &clusterv1.ProviderConfig{ + Value: &runtime.RawExtension{Raw: buf.Bytes()}, + }, nil +} + +func newEncoder(codecFactory *serializer.CodecFactory) (runtime.Encoder, error) { + serializerInfos := codecFactory.SupportedMediaTypes() + if len(serializerInfos) == 0 { + return nil, fmt.Errorf("unable to find any serlializers") + } + encoder := codecFactory.EncoderForVersion(serializerInfos[0].Serializer, SchemeGroupVersion) + return encoder, nil +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/types.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/types.go index b81885cf4..f85be9c10 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/types.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/types.go @@ -21,13 +21,30 @@ import ( ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type GCEProviderConfig struct { +type GCEMachineProviderConfig struct { metav1.TypeMeta `json:",inline"` - Project string `json:"project"` Zone string `json:"zone"` MachineType string `json:"machineType"` // The name of the OS to be installed on the machine. - OS string `json:"os"` + OS string `json:"os"` + Disks []Disk `json:"disks"` +} + +type Disk struct { + InitializeParams DiskInitializeParams `json:"initializeParams"` } + +type DiskInitializeParams struct { + DiskSizeGb int64 `json:"diskSizeGb"` + DiskType string `json:"diskType"` +} + + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type GCEClusterProviderConfig struct { + metav1.TypeMeta `json:",inline"` + + Project string `json:"project"` +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/zz_generated.deepcopy.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/zz_generated.deepcopy.go index 78e70d2ac..3d7d54d08 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/zz_generated.deepcopy.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2018 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// This file was autogenerated by deepcopy-gen. Do not edit it manually! +// Code generated by deepcopy-gen. DO NOT EDIT. package v1alpha1 @@ -25,27 +25,51 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCEProviderConfig) DeepCopyInto(out *GCEProviderConfig) { +func (in *GCEClusterProviderConfig) DeepCopyInto(out *GCEClusterProviderConfig) { *out = *in out.TypeMeta = in.TypeMeta return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEProviderConfig. -func (in *GCEProviderConfig) DeepCopy() *GCEProviderConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEClusterProviderConfig. +func (in *GCEClusterProviderConfig) DeepCopy() *GCEClusterProviderConfig { if in == nil { return nil } - out := new(GCEProviderConfig) + out := new(GCEClusterProviderConfig) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCEProviderConfig) DeepCopyObject() runtime.Object { +func (in *GCEClusterProviderConfig) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c - } else { + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCEMachineProviderConfig) DeepCopyInto(out *GCEMachineProviderConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEMachineProviderConfig. +func (in *GCEMachineProviderConfig) DeepCopy() *GCEMachineProviderConfig { + if in == nil { return nil } + out := new(GCEMachineProviderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCEMachineProviderConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/zz_generated.deepcopy.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/zz_generated.deepcopy.go index 26319e3d5..f655f8857 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/zz_generated.deepcopy.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2018 The Kubernetes Authors. +Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// This file was autogenerated by deepcopy-gen. Do not edit it manually! +// Code generated by deepcopy-gen. DO NOT EDIT. package gceproviderconfig @@ -25,27 +25,51 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCEProviderConfig) DeepCopyInto(out *GCEProviderConfig) { +func (in *GCEClusterProviderConfig) DeepCopyInto(out *GCEClusterProviderConfig) { *out = *in out.TypeMeta = in.TypeMeta return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEProviderConfig. -func (in *GCEProviderConfig) DeepCopy() *GCEProviderConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEClusterProviderConfig. +func (in *GCEClusterProviderConfig) DeepCopy() *GCEClusterProviderConfig { if in == nil { return nil } - out := new(GCEProviderConfig) + out := new(GCEClusterProviderConfig) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCEProviderConfig) DeepCopyObject() runtime.Object { +func (in *GCEClusterProviderConfig) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c - } else { + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCEMachineProviderConfig) DeepCopyInto(out *GCEMachineProviderConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCEMachineProviderConfig. +func (in *GCEMachineProviderConfig) DeepCopy() *GCEMachineProviderConfig { + if in == nil { return nil } + out := new(GCEMachineProviderConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCEMachineProviderConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator.go index 90a4882fd..4f8018ed6 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator.go @@ -1,12 +1,9 @@ /* Copyright 2018 The Kubernetes Authors. - 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. @@ -34,10 +31,10 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" "regexp" + "encoding/base64" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "sigs.k8s.io/cluster-api/cloud/google/clients" @@ -45,6 +42,7 @@ import ( "sigs.k8s.io/cluster-api/cloud/google/machinesetup" apierrors "sigs.k8s.io/cluster-api/errors" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/cert" client "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" "sigs.k8s.io/cluster-api/util" ) @@ -66,6 +64,10 @@ type SshCreds struct { privateKeyPath string } +type GCEClientMachineSetupConfigGetter interface { + GetMachineSetupConfig() (machinesetup.MachineSetupConfig, error) +} + type GCEClientComputeService interface { ImagesGet(project string, image string) (*compute.Image, error) ImagesGetFromFamily(project string, family string) (*compute.Image, error) @@ -76,13 +78,22 @@ type GCEClientComputeService interface { } type GCEClient struct { - computeService GCEClientComputeService - scheme *runtime.Scheme - codecFactory *serializer.CodecFactory - kubeadmToken string - sshCreds SshCreds - machineClient client.MachineInterface - configWatch *machinesetup.ConfigWatch + certificateAuthority *cert.CertificateAuthority + computeService GCEClientComputeService + gceProviderConfigCodec *gceconfigv1.GCEProviderConfigCodec + scheme *runtime.Scheme + kubeadmToken string + sshCreds SshCreds + machineClient client.MachineInterface + machineSetupConfigGetter GCEClientMachineSetupConfigGetter +} + +type MachineActuatorParams struct { + CertificateAuthority *cert.CertificateAuthority + ComputeService GCEClientComputeService + KubeadmToken string + MachineClient client.MachineInterface + MachineSetupConfigGetter GCEClientMachineSetupConfigGetter } const ( @@ -90,20 +101,17 @@ const ( gceWaitSleep = time.Second * 5 ) -func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterface, configListPath string) (*GCEClient, error) { - // The default GCP client expects the environment variable - // GOOGLE_APPLICATION_CREDENTIALS to point to a file with service credentials. - client, err := google.DefaultClient(context.TODO(), compute.ComputeScope) +func NewMachineActuator(params MachineActuatorParams) (*GCEClient, error) { + computeService, err := getOrNewComputeService(params) if err != nil { return nil, err } - computeService, err := clients.NewComputeService(client) + scheme, err := gceconfigv1.NewScheme() if err != nil { return nil, err } - - scheme, codecFactory, err := gceconfigv1.NewSchemeAndCodecs() + codec, err := gceconfigv1.NewCodec() if err != nil { return nil, err } @@ -121,36 +129,31 @@ func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterfa } } - // TODO: get rid of empty string check when we switch to the new bootstrapping method. - var configWatch *machinesetup.ConfigWatch - if configListPath != "" { - configWatch, err = machinesetup.NewConfigWatch(configListPath) - if err != nil { - glog.Errorf("Error creating config watch: %v", err) - } - } - return &GCEClient{ - computeService: computeService, - scheme: scheme, - codecFactory: codecFactory, - kubeadmToken: kubeadmToken, + certificateAuthority: params.CertificateAuthority, + computeService: computeService, + scheme: scheme, + gceProviderConfigCodec: codec, + kubeadmToken: params.KubeadmToken, sshCreds: SshCreds{ privateKeyPath: privateKeyPath, user: user, }, - machineClient: machineClient, - configWatch: configWatch, + machineClient: params.MachineClient, + machineSetupConfigGetter: params.MachineSetupConfigGetter, }, nil } func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine, clientSet kubernetes.Clientset) error { + if gce.machineSetupConfigGetter == nil { + return errors.New("a valid machineSetupConfigGetter is required") + } if err := gce.CreateMachineControllerServiceAccount(cluster, initialMachines); err != nil { return err } // Setup SSH access to master VM - if err := gce.setupSSHAccess(util.GetMaster(initialMachines)); err != nil { + if err := gce.setupSSHAccess(cluster, util.GetMaster(initialMachines)); err != nil { return err } @@ -158,13 +161,11 @@ func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initia return err } - // Create the configmap so the machine setup configs can be mounted into the node. - // TODO: create the configmap during bootstrapping instead of being buried in the machine actuator code. - machineSetupConfigs, err := gce.configWatch.ValidConfigs() + machineSetupConfig, err := gce.machineSetupConfigGetter.GetMachineSetupConfig() if err != nil { return err } - yaml, err := machineSetupConfigs.GetYaml() + yaml, err := machineSetupConfig.GetYaml() if err != nil { return err } @@ -185,80 +186,61 @@ func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initia return nil } +func (gce *GCEClient) ProvisionClusterDependencies(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { + err := gce.CreateWorkerNodeServiceAccount(cluster, initialMachines) + if err != nil { + return err + } + + return gce.CreateMasterNodeServiceAccount(cluster, initialMachines) +} + func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { - config, err := gce.providerconfig(machine.Spec.ProviderConfig) + if gce.machineSetupConfigGetter == nil { + return errors.New("a valid machineSetupConfigGetter is required") + } + machineConfig, err := gce.machineproviderconfig(machine.Spec.ProviderConfig) if err != nil { return gce.handleMachineError(machine, apierrors.InvalidMachineConfiguration( - "Cannot unmarshal providerConfig field: %v", err)) + "Cannot unmarshal machine's providerConfig field: %v", err)) } - - if verr := gce.validateMachine(machine, config); verr != nil { - return gce.handleMachineError(machine, verr) + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return gce.handleMachineError(machine, apierrors.InvalidMachineConfiguration( + "Cannot unmarshal cluster's providerConfig field: %v", err)) } - var metadata map[string]string - if machine.Spec.Versions.Kubelet == "" { - return errors.New("invalid master configuration: missing Machine.Spec.Versions.Kubelet") + if verr := gce.validateMachine(machine, machineConfig); verr != nil { + return gce.handleMachineError(machine, verr) } - machineSetupConfigs, err := gce.configWatch.ValidConfigs() - if err != nil { - return err - } configParams := &machinesetup.ConfigParams{ - OS: config.OS, + OS: machineConfig.OS, Roles: machine.Spec.Roles, Versions: machine.Spec.Versions, } + machineSetupConfigs, err := gce.machineSetupConfigGetter.GetMachineSetupConfig() + if err != nil { + return err + } image, err := machineSetupConfigs.GetImage(configParams) if err != nil { return err } imagePath := gce.getImagePath(image) - - machineSetupMetadata, err := machineSetupConfigs.GetMetadata(configParams) + metadata, err := gce.getMetadata(cluster, machine, clusterConfig, configParams) if err != nil { return err } - if util.IsMaster(machine) { - if machine.Spec.Versions.ControlPlane == "" { - return gce.handleMachineError(machine, apierrors.InvalidMachineConfiguration( - "invalid master configuration: missing Machine.Spec.Versions.ControlPlane")) - } - var err error - metadata, err = masterMetadata(gce.kubeadmToken, cluster, machine, config.Project, &machineSetupMetadata) - if err != nil { - return err - } - } else { - if len(cluster.Status.APIEndpoints) == 0 { - return errors.New("invalid cluster state: cannot create a Kubernetes node without an API endpoint") - } - var err error - metadata, err = nodeMetadata(gce.kubeadmToken, cluster, machine, &machineSetupMetadata) - if err != nil { - return err - } - } - - var metadataItems []*compute.MetadataItems - for k, v := range metadata { - v := v // rebind scope to avoid loop aliasing below - metadataItems = append(metadataItems, &compute.MetadataItems{ - Key: k, - Value: &v, - }) - } - instance, err := gce.instanceIfExists(machine) + instance, err := gce.instanceIfExists(cluster, machine) if err != nil { return err } name := machine.ObjectMeta.Name - project := config.Project - zone := config.Zone - diskSize := int64(30) + project := clusterConfig.Project + zone := machineConfig.Zone if instance == nil { labels := map[string]string{} @@ -266,21 +248,9 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach labels[BootstrapLabelKey] = "true" } - // The service account is needed for the Kubernetes GCE cloud provider code. It is needed on the master VM. - serviceAccounts := []*compute.ServiceAccount{nil} - if util.IsMaster(machine) { - serviceAccounts = append(serviceAccounts, - &compute.ServiceAccount{ - Email: "default", - Scopes: []string{ - "https://www.googleapis.com/auth/cloud-platform", - }, - }) - } - op, err := gce.computeService.InstancesInsert(project, zone, &compute.Instance{ Name: name, - MachineType: fmt.Sprintf("zones/%s/machineTypes/%s", zone, config.MachineType), + MachineType: fmt.Sprintf("zones/%s/machineTypes/%s", zone, machineConfig.MachineType), CanIpForward: true, NetworkInterfaces: []*compute.NetworkInterface{ { @@ -293,30 +263,26 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach }, }, }, - Disks: []*compute.AttachedDisk{ - { - AutoDelete: true, - Boot: true, - InitializeParams: &compute.AttachedDiskInitializeParams{ - SourceImage: imagePath, - DiskSizeGb: diskSize, - }, - }, - }, - Metadata: &compute.Metadata{ - Items: metadataItems, - }, + Disks: newDisks(machineConfig, zone, imagePath, int64(30)), + Metadata: metadata, Tags: &compute.Tags{ Items: []string{ "https-server", fmt.Sprintf("%s-worker", cluster.Name)}, }, - Labels: labels, - ServiceAccounts: serviceAccounts, + Labels: labels, + ServiceAccounts: []*compute.ServiceAccount{ + { + Email: gce.GetDefaultServiceAccountForMachine(cluster, machine), + Scopes: []string{ + compute.CloudPlatformScope, + }, + }, + }, }) if err == nil { - err = gce.waitForOperation(config, op) + err = gce.waitForOperation(clusterConfig, op) } if err != nil { @@ -327,7 +293,7 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach // If we have a machineClient, then annotate the machine so that we // remember exactly what VM we created for it. if gce.machineClient != nil { - return gce.updateAnnotations(machine) + return gce.updateAnnotations(cluster, machine) } } else { glog.Infof("Skipped creating a VM that already exists.\n") @@ -336,8 +302,8 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach return nil } -func (gce *GCEClient) Delete(machine *clusterv1.Machine) error { - instance, err := gce.instanceIfExists(machine) +func (gce *GCEClient) Delete(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { + instance, err := gce.instanceIfExists(cluster, machine) if err != nil { return err } @@ -347,13 +313,19 @@ func (gce *GCEClient) Delete(machine *clusterv1.Machine) error { return nil } - config, err := gce.providerconfig(machine.Spec.ProviderConfig) + machineConfig, err := gce.machineproviderconfig(machine.Spec.ProviderConfig) + if err != nil { + return gce.handleMachineError(machine, + apierrors.InvalidMachineConfiguration("Cannot unmarshal machine's providerConfig field: %v", err)) + } + + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) if err != nil { return gce.handleMachineError(machine, - apierrors.InvalidMachineConfiguration("Cannot unmarshal providerConfig field: %v", err)) + apierrors.InvalidMachineConfiguration("Cannot unmarshal cluster's providerConfig field: %v", err)) } - if verr := gce.validateMachine(machine, config); verr != nil { + if verr := gce.validateMachine(machine, machineConfig); verr != nil { return gce.handleMachineError(machine, verr) } @@ -367,14 +339,14 @@ func (gce *GCEClient) Delete(machine *clusterv1.Machine) error { // If the annotations are missing, fall back on providerConfig if project == "" || zone == "" || name == "" { - project = config.Project - zone = config.Zone + project = clusterConfig.Project + zone = machineConfig.Zone name = machine.ObjectMeta.Name } op, err := gce.computeService.InstancesDelete(project, zone, name) if err == nil { - err = gce.waitForOperation(config, op) + err = gce.waitForOperation(clusterConfig, op) } if err != nil { return gce.handleMachineError(machine, apierrors.DeleteMachine( @@ -390,16 +362,51 @@ func (gce *GCEClient) Delete(machine *clusterv1.Machine) error { return err } +func (gce *GCEClient) PostCreate(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { + err := CreateDefaultStorageClass() + if err != nil { + return fmt.Errorf("error creating default storage class: %v", err) + } + + err = gce.CreateIngressControllerServiceAccount(cluster, machines) + if err != nil { + return fmt.Errorf("error creating service account for ingress controller: %v", err) + } + + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return fmt.Errorf("Cannot unmarshal cluster's providerConfig field: %v", err) + } + err = CreateIngressController(clusterConfig.Project, cluster.Name) + if err != nil { + return fmt.Errorf("error creating ingress controller: %v", err) + } + + return nil +} + func (gce *GCEClient) PostDelete(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { - return gce.DeleteMachineControllerServiceAccount(cluster, machines) + if err := gce.DeleteMasterNodeServiceAccount(cluster, machines); err != nil { + return fmt.Errorf("error deleting master node service account: %v", err) + } + if err := gce.DeleteWorkerNodeServiceAccount(cluster, machines); err != nil { + return fmt.Errorf("error deleting worker node service account: %v", err) + } + if err := gce.DeleteIngressControllerServiceAccount(cluster, machines); err != nil { + return fmt.Errorf("error deleting ingress controller service account: %v", err) + } + if err := gce.DeleteMachineControllerServiceAccount(cluster, machines); err != nil { + return fmt.Errorf("error deleting machine controller service account: %v", err) + } + return nil } func (gce *GCEClient) Update(cluster *clusterv1.Cluster, goalMachine *clusterv1.Machine) error { // Before updating, do some basic validation of the object first. - config, err := gce.providerconfig(goalMachine.Spec.ProviderConfig) + config, err := gce.machineproviderconfig(goalMachine.Spec.ProviderConfig) if err != nil { return gce.handleMachineError(goalMachine, - apierrors.InvalidMachineConfiguration("Cannot unmarshal providerConfig field: %v", err)) + apierrors.InvalidMachineConfiguration("Cannot unmarshal machine's providerConfig field: %v", err)) } if verr := gce.validateMachine(goalMachine, config); verr != nil { return gce.handleMachineError(goalMachine, verr) @@ -412,13 +419,13 @@ func (gce *GCEClient) Update(cluster *clusterv1.Cluster, goalMachine *clusterv1. currentMachine := (*clusterv1.Machine)(status) if currentMachine == nil { - instance, err := gce.instanceIfExists(goalMachine) + instance, err := gce.instanceIfExists(cluster, goalMachine) if err != nil { return err } if instance != nil && instance.Labels[BootstrapLabelKey] != "" { glog.Infof("Populating current state for boostrap machine %v", goalMachine.ObjectMeta.Name) - return gce.updateAnnotations(goalMachine) + return gce.updateAnnotations(cluster, goalMachine) } else { return fmt.Errorf("Cannot retrieve current state to update machine %v", goalMachine.ObjectMeta.Name) } @@ -430,13 +437,14 @@ func (gce *GCEClient) Update(cluster *clusterv1.Cluster, goalMachine *clusterv1. if util.IsMaster(currentMachine) { glog.Infof("Doing an in-place upgrade for master.\n") - err = gce.updateMasterInplace(currentMachine, goalMachine) + // TODO: should we support custom CAs here? + err = gce.updateMasterInplace(cluster, currentMachine, goalMachine) if err != nil { glog.Errorf("master inplace update failed: %v", err) } } else { glog.Infof("re-creating machine %s for update.", currentMachine.ObjectMeta.Name) - err = gce.Delete(currentMachine) + err = gce.Delete(cluster, currentMachine) if err != nil { glog.Errorf("delete machine %s for update failed: %v", currentMachine.ObjectMeta.Name, err) } else { @@ -453,21 +461,26 @@ func (gce *GCEClient) Update(cluster *clusterv1.Cluster, goalMachine *clusterv1. return err } -func (gce *GCEClient) Exists(machine *clusterv1.Machine) (bool, error) { - i, err := gce.instanceIfExists(machine) +func (gce *GCEClient) Exists(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (bool, error) { + i, err := gce.instanceIfExists(cluster, machine) if err != nil { return false, err } return (i != nil), err } -func (gce *GCEClient) GetIP(machine *clusterv1.Machine) (string, error) { - config, err := gce.providerconfig(machine.Spec.ProviderConfig) +func (gce *GCEClient) GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { + machineConfig, err := gce.machineproviderconfig(machine.Spec.ProviderConfig) if err != nil { return "", err } - instance, err := gce.computeService.InstancesGet(config.Project, config.Zone, machine.ObjectMeta.Name) + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return "", err + } + + instance, err := gce.computeService.InstancesGet(clusterConfig.Project, machineConfig.Zone, machine.ObjectMeta.Name) if err != nil { return "", err } @@ -484,32 +497,38 @@ func (gce *GCEClient) GetIP(machine *clusterv1.Machine) (string, error) { return publicIP, nil } -func (gce *GCEClient) GetKubeConfig(master *clusterv1.Machine) (string, error) { - config, err := gce.providerconfig(master.Spec.ProviderConfig) +func (gce *GCEClient) GetKubeConfig(cluster *clusterv1.Cluster, master *clusterv1.Machine) (string, error) { + machineConfig, err := gce.machineproviderconfig(master.Spec.ProviderConfig) if err != nil { return "", err } - command := "echo STARTFILE; sudo cat /etc/kubernetes/admin.conf" - result := strings.TrimSpace(util.ExecCommand( - "gcloud", "compute", "ssh", "--project", config.Project, - "--zone", config.Zone, master.ObjectMeta.Name, "--command", command)) - parts := strings.Split(result, "STARTFILE") - if len(parts) != 2 { - return "", nil + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return "", err } - return strings.TrimSpace(parts[1]), nil + + command := "sudo cat /etc/kubernetes/admin.conf" + result := strings.TrimSpace(util.ExecCommand( + "gcloud", "compute", "ssh", "--project", clusterConfig.Project, + "--zone", machineConfig.Zone, master.ObjectMeta.Name, "--command", command, "--", "-q")) + return result, nil } -func (gce *GCEClient) updateAnnotations(machine *clusterv1.Machine) error { - config, err := gce.providerconfig(machine.Spec.ProviderConfig) +func (gce *GCEClient) updateAnnotations(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { + machineConfig, err := gce.machineproviderconfig(machine.Spec.ProviderConfig) name := machine.ObjectMeta.Name - project := config.Project - zone := config.Zone + zone := machineConfig.Zone + if err != nil { + return gce.handleMachineError(machine, + apierrors.InvalidMachineConfiguration("Cannot unmarshal machine's providerConfig field: %v", err)) + } + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + project := clusterConfig.Project if err != nil { return gce.handleMachineError(machine, - apierrors.InvalidMachineConfiguration("Cannot unmarshal providerConfig field: %v", err)) + apierrors.InvalidMachineConfiguration("Cannot unmarshal cluster's providerConfig field: %v", err)) } if machine.ObjectMeta.Annotations == nil { @@ -537,7 +556,7 @@ func (gce *GCEClient) requiresUpdate(a *clusterv1.Machine, b *clusterv1.Machine) } // Gets the instance represented by the given machine -func (gce *GCEClient) instanceIfExists(machine *clusterv1.Machine) (*compute.Instance, error) { +func (gce *GCEClient) instanceIfExists(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (*compute.Instance, error) { identifyingMachine := machine // Try to use the last saved status locating the machine @@ -552,12 +571,17 @@ func (gce *GCEClient) instanceIfExists(machine *clusterv1.Machine) (*compute.Ins } // Get the VM via specified location and name - config, err := gce.providerconfig(identifyingMachine.Spec.ProviderConfig) + machineConfig, err := gce.machineproviderconfig(identifyingMachine.Spec.ProviderConfig) if err != nil { return nil, err } - instance, err := gce.computeService.InstancesGet(config.Project, config.Zone, identifyingMachine.ObjectMeta.Name) + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return nil, err + } + + instance, err := gce.computeService.InstancesGet(clusterConfig.Project, machineConfig.Zone, identifyingMachine.ObjectMeta.Name) if err != nil { // TODO: Use formal way to check for error code 404 if strings.Contains(err.Error(), "Error 404") { @@ -569,20 +593,25 @@ func (gce *GCEClient) instanceIfExists(machine *clusterv1.Machine) (*compute.Ins return instance, nil } -func (gce *GCEClient) providerconfig(providerConfig clusterv1.ProviderConfig) (*gceconfigv1.GCEProviderConfig, error) { - obj, gvk, err := gce.codecFactory.UniversalDecoder(gceconfigv1.SchemeGroupVersion).Decode(providerConfig.Value.Raw, nil, nil) +func (gce *GCEClient) machineproviderconfig(providerConfig clusterv1.ProviderConfig) (*gceconfigv1.GCEMachineProviderConfig, error) { + var config gceconfigv1.GCEMachineProviderConfig + err := gce.gceProviderConfigCodec.DecodeFromProviderConfig(providerConfig, &config) if err != nil { - return nil, fmt.Errorf("decoding failure: %v", err) - } - config, ok := obj.(*gceconfigv1.GCEProviderConfig) - if !ok { - return nil, fmt.Errorf("failure to cast to gce; type: %v", gvk) + return nil, err } + return &config, nil +} - return config, nil +func (gce *GCEClient) clusterproviderconfig(providerConfig clusterv1.ProviderConfig) (*gceconfigv1.GCEClusterProviderConfig, error) { + var config gceconfigv1.GCEClusterProviderConfig + err := gce.gceProviderConfigCodec.DecodeFromProviderConfig(providerConfig, &config) + if err != nil { + return nil, err + } + return &config, nil } -func (gce *GCEClient) waitForOperation(c *gceconfigv1.GCEProviderConfig, op *compute.Operation) error { +func (gce *GCEClient) waitForOperation(c *gceconfigv1.GCEClusterProviderConfig, op *compute.Operation) error { glog.Infof("Wait for %v %q...", op.OperationType, op.Name) defer glog.Infof("Finish wait for %v %q...", op.OperationType, op.Name) @@ -606,7 +635,7 @@ func (gce *GCEClient) waitForOperation(c *gceconfigv1.GCEProviderConfig, op *com } // getOp returns an updated operation. -func (gce *GCEClient) getOp(c *gceconfigv1.GCEProviderConfig, op *compute.Operation) (*compute.Operation, error) { +func (gce *GCEClient) getOp(c *gceconfigv1.GCEClusterProviderConfig, op *compute.Operation) (*compute.Operation, error) { return gce.computeService.ZoneOperationsGet(c.Project, path.Base(op.Zone), op.Name) } @@ -623,13 +652,13 @@ func (gce *GCEClient) checkOp(op *compute.Operation, err error) error { return errors.New(errs.String()) } -func (gce *GCEClient) updateMasterInplace(oldMachine *clusterv1.Machine, newMachine *clusterv1.Machine) error { +func (gce *GCEClient) updateMasterInplace(cluster *clusterv1.Cluster, oldMachine *clusterv1.Machine, newMachine *clusterv1.Machine) error { if oldMachine.Spec.Versions.ControlPlane != newMachine.Spec.Versions.ControlPlane { // First pull off the latest kubeadm. cmd := "export KUBEADM_VERSION=$(curl -sSL https://dl.k8s.io/release/stable.txt); " + "curl -sSL https://dl.k8s.io/release/${KUBEADM_VERSION}/bin/linux/amd64/kubeadm | sudo tee /usr/bin/kubeadm > /dev/null; " + "sudo chmod a+rx /usr/bin/kubeadm" - _, err := gce.remoteSshCommand(newMachine, cmd) + _, err := gce.remoteSshCommand(cluster, newMachine, cmd) if err != nil { glog.Infof("remotesshcomand error: %v", err) return err @@ -638,7 +667,7 @@ func (gce *GCEClient) updateMasterInplace(oldMachine *clusterv1.Machine, newMach // TODO: We might want to upgrade kubeadm if the target control plane version is newer. // Upgrade control plan. cmd = fmt.Sprintf("sudo kubeadm upgrade apply %s -y", "v"+newMachine.Spec.Versions.ControlPlane) - _, err = gce.remoteSshCommand(newMachine, cmd) + _, err = gce.remoteSshCommand(cluster, newMachine, cmd) if err != nil { glog.Infof("remotesshcomand error: %v", err) return err @@ -649,16 +678,16 @@ func (gce *GCEClient) updateMasterInplace(oldMachine *clusterv1.Machine, newMach if oldMachine.Spec.Versions.Kubelet != newMachine.Spec.Versions.Kubelet { cmd := fmt.Sprintf("sudo kubectl drain %s --kubeconfig /etc/kubernetes/admin.conf --ignore-daemonsets", newMachine.Name) // The errors are intentionally ignored as master has static pods. - gce.remoteSshCommand(newMachine, cmd) + gce.remoteSshCommand(cluster, newMachine, cmd) // Upgrade kubelet to desired version. cmd = fmt.Sprintf("sudo apt-get install kubelet=%s", newMachine.Spec.Versions.Kubelet+"-00") - _, err := gce.remoteSshCommand(newMachine, cmd) + _, err := gce.remoteSshCommand(cluster, newMachine, cmd) if err != nil { glog.Infof("remotesshcomand error: %v", err) return err } cmd = fmt.Sprintf("sudo kubectl uncordon %s --kubeconfig /etc/kubernetes/admin.conf", newMachine.Name) - _, err = gce.remoteSshCommand(newMachine, cmd) + _, err = gce.remoteSshCommand(cluster, newMachine, cmd) if err != nil { glog.Infof("remotesshcomand error: %v", err) return err @@ -668,7 +697,7 @@ func (gce *GCEClient) updateMasterInplace(oldMachine *clusterv1.Machine, newMach return nil } -func (gce *GCEClient) validateMachine(machine *clusterv1.Machine, config *gceconfigv1.GCEProviderConfig) *apierrors.MachineError { +func (gce *GCEClient) validateMachine(machine *clusterv1.Machine, config *gceconfigv1.GCEMachineProviderConfig) *apierrors.MachineError { if machine.Spec.Versions.Kubelet == "" { return apierrors.InvalidMachineConfiguration("spec.versions.kubelet can't be empty") } @@ -720,6 +749,30 @@ func (gce *GCEClient) getImagePath(img string) (imagePath string) { return defaultImg } +func newDisks(config *gceconfigv1.GCEMachineProviderConfig, zone string, imagePath string, minDiskSizeGb int64) []*compute.AttachedDisk { + var disks []*compute.AttachedDisk + for idx, disk := range config.Disks { + diskSizeGb := disk.InitializeParams.DiskSizeGb + d := compute.AttachedDisk{ + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + DiskSizeGb: diskSizeGb, + DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone, disk.InitializeParams.DiskType), + }, + } + if idx == 0 { + d.InitializeParams.SourceImage = imagePath + d.Boot = true + if diskSizeGb < minDiskSizeGb { + glog.Info("increasing disk size to %v gb, the supplied disk size of %v gb is below the minimum", minDiskSizeGb, diskSizeGb) + d.InitializeParams.DiskSizeGb = minDiskSizeGb + } + } + disks = append(disks, &d) + } + return disks +} + // Just a temporary hack to grab a single range from the config. func getSubnet(netRange clusterv1.NetworkRanges) string { if len(netRange.CIDRBlocks) == 0 { @@ -728,6 +781,75 @@ func getSubnet(netRange clusterv1.NetworkRanges) string { return netRange.CIDRBlocks[0] } +func getOrNewComputeService(params MachineActuatorParams) (GCEClientComputeService, error) { + if params.ComputeService != nil { + return params.ComputeService, nil + } + // The default GCP client expects the environment variable + // GOOGLE_APPLICATION_CREDENTIALS to point to a file with service credentials. + client, err := google.DefaultClient(context.TODO(), compute.ComputeScope) + if err != nil { + return nil, err + } + computeService, err := clients.NewComputeService(client) + if err != nil { + return nil, err + } + return computeService, nil +} + +func (gce *GCEClient) getMetadata(cluster *clusterv1.Cluster, machine *clusterv1.Machine, clusterConfig *gceconfigv1.GCEClusterProviderConfig, configParams *machinesetup.ConfigParams) (*compute.Metadata, error) { + var metadataMap map[string]string + if machine.Spec.Versions.Kubelet == "" { + return nil, errors.New("invalid master configuration: missing Machine.Spec.Versions.Kubelet") + } + machineSetupConfigs, err := gce.machineSetupConfigGetter.GetMachineSetupConfig() + if err != nil { + return nil, err + } + machineSetupMetadata, err := machineSetupConfigs.GetMetadata(configParams) + if err != nil { + return nil, err + } + if util.IsMaster(machine) { + if machine.Spec.Versions.ControlPlane == "" { + return nil, gce.handleMachineError(machine, apierrors.InvalidMachineConfiguration( + "invalid master configuration: missing Machine.Spec.Versions.ControlPlane")) + } + var err error + metadataMap, err = masterMetadata(gce.kubeadmToken, cluster, machine, clusterConfig.Project, &machineSetupMetadata) + if err != nil { + return nil, err + } + ca := gce.certificateAuthority + if ca != nil { + metadataMap["ca-cert"] = base64.StdEncoding.EncodeToString(ca.Certificate) + metadataMap["ca-key"] = base64.StdEncoding.EncodeToString(ca.PrivateKey) + } + } else { + if len(cluster.Status.APIEndpoints) == 0 { + return nil, errors.New("invalid cluster state: cannot create a Kubernetes node without an API endpoint") + } + var err error + metadataMap, err = nodeMetadata(gce.kubeadmToken, cluster, machine, clusterConfig.Project, &machineSetupMetadata) + if err != nil { + return nil, err + } + } + var metadataItems []*compute.MetadataItems + for k, v := range metadataMap { + v := v // rebind scope to avoid loop aliasing below + metadataItems = append(metadataItems, &compute.MetadataItems{ + Key: k, + Value: &v, + }) + } + metadata := compute.Metadata{ + Items: metadataItems, + } + return &metadata, nil +} + // TODO: We need to change this when we create dedicated service account for apiserver/controller // pod. // @@ -735,4 +857,4 @@ func CreateExtApiServerRoleBinding() error { return run("kubectl", "create", "rolebinding", "-n", "kube-system", "machine-controller", "--role=extension-apiserver-authentication-reader", "--serviceaccount=default:default") -} +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator_test.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator_test.go new file mode 100644 index 000000000..b510c84e5 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/machineactuator_test.go @@ -0,0 +1,365 @@ +package google_test + +import ( + "encoding/base64" + compute "google.golang.org/api/compute/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/cluster-api/cloud/google" + gceconfigv1 "sigs.k8s.io/cluster-api/cloud/google/gceproviderconfig/v1alpha1" + "sigs.k8s.io/cluster-api/cloud/google/machinesetup" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "strings" + "sigs.k8s.io/cluster-api/pkg/cert" + "testing" +) + +type GCEClientComputeServiceMock struct { + mockImagesGet func(project string, image string) (*compute.Image, error) + mockImagesGetFromFamily func(project string, family string) (*compute.Image, error) + mockInstancesDelete func(project string, zone string, targetInstance string) (*compute.Operation, error) + mockInstancesGet func(project string, zone string, instance string) (*compute.Instance, error) + mockInstancesInsert func(project string, zone string, instance *compute.Instance) (*compute.Operation, error) + mockZoneOperationsGet func(project string, zone string, operation string) (*compute.Operation, error) +} + +func (c *GCEClientComputeServiceMock) ImagesGet(project string, image string) (*compute.Image, error) { + if c.mockImagesGet == nil { + return nil, nil + } + return c.mockImagesGet(project, image) +} + +func (c *GCEClientComputeServiceMock) ImagesGetFromFamily(project string, family string) (*compute.Image, error) { + if c.mockImagesGetFromFamily == nil { + return nil, nil + } + return c.mockImagesGetFromFamily(project, family) +} + +func (c *GCEClientComputeServiceMock) InstancesDelete(project string, zone string, targetInstance string) (*compute.Operation, error) { + if c.mockInstancesDelete == nil { + return nil, nil + } + return c.mockInstancesDelete(project, zone, targetInstance) +} + +func (c *GCEClientComputeServiceMock) InstancesGet(project string, zone string, instance string) (*compute.Instance, error) { + if c.mockInstancesGet == nil { + return nil, nil + } + return c.mockInstancesGet(project, zone, instance) +} + +func (c *GCEClientComputeServiceMock) InstancesInsert(project string, zone string, instance *compute.Instance) (*compute.Operation, error) { + if c.mockInstancesInsert == nil { + return nil, nil + } + return c.mockInstancesInsert(project, zone, instance) +} + +func (c *GCEClientComputeServiceMock) ZoneOperationsGet(project string, zone string, operation string) (*compute.Operation, error) { + if c.mockZoneOperationsGet == nil { + return nil, nil + } + return c.mockZoneOperationsGet(project, zone, operation) +} + +type GCEClientMachineSetupConfigMock struct { + mockGetYaml func() (string, error) + mockGetImage func(params *machinesetup.ConfigParams) (string, error) + mockGetMetadata func(params *machinesetup.ConfigParams) (machinesetup.Metadata, error) +} + +func (m *GCEClientMachineSetupConfigMock) GetYaml() (string, error) { + if m.mockGetYaml == nil { + return "", nil + } + return m.mockGetYaml() +} + +func (m *GCEClientMachineSetupConfigMock) GetImage(params *machinesetup.ConfigParams) (string, error) { + if m.mockGetYaml == nil { + return "", nil + } + return m.mockGetImage(params) +} + +func (m *GCEClientMachineSetupConfigMock) GetMetadata(params *machinesetup.ConfigParams) (machinesetup.Metadata, error) { + if m.mockGetYaml == nil { + return machinesetup.Metadata{}, nil + } + return m.mockGetMetadata(params) +} + +func TestNoDisks(t *testing.T) { + config := newGCEMachineProviderConfigFixture() + config.Disks = make([]gceconfigv1.Disk, 0) + receivedInstance, computeServiceMock := newInsertInstanceCapturingMock() + createCluster(t, config, computeServiceMock, nil) + checkInstanceValues(t, receivedInstance, 0) +} + +func TestMinimumSizeShouldBeEnforced(t *testing.T) { + config := newGCEMachineProviderConfigFixture() + config.Disks = []gceconfigv1.Disk{ + { + InitializeParams: gceconfigv1.DiskInitializeParams{ + DiskType: "pd-ssd", + DiskSizeGb: int64(6), + }, + }, + } + receivedInstance, computeServiceMock := newInsertInstanceCapturingMock() + createCluster(t, config, computeServiceMock, nil) + checkInstanceValues(t, receivedInstance, 1) + checkDiskValues(t, receivedInstance.Disks[0], true, 30, "pd-ssd", "projects/ubuntu-os-cloud/global/images/family/ubuntu-1710") +} + +func TestOneDisk(t *testing.T) { + config := newGCEMachineProviderConfigFixture() + config.Disks = []gceconfigv1.Disk{ + { + InitializeParams: gceconfigv1.DiskInitializeParams{ + DiskType: "pd-ssd", + DiskSizeGb: 37, + }, + }, + } + receivedInstance, computeServiceMock := newInsertInstanceCapturingMock() + createCluster(t, config, computeServiceMock, nil) + checkInstanceValues(t, receivedInstance, 1) + checkDiskValues(t, receivedInstance.Disks[0], true, 37, "pd-ssd", "projects/ubuntu-os-cloud/global/images/family/ubuntu-1710") +} + +func TestTwoDisks(t *testing.T) { + config := newGCEMachineProviderConfigFixture() + config.Disks = []gceconfigv1.Disk{ + { + InitializeParams: gceconfigv1.DiskInitializeParams{ + DiskType: "pd-ssd", + DiskSizeGb: 32, + }, + }, + { + InitializeParams: gceconfigv1.DiskInitializeParams{ + DiskType: "pd-standard", + DiskSizeGb: 45, + }, + }, + } + receivedInstance, computeServiceMock := newInsertInstanceCapturingMock() + createCluster(t, config, computeServiceMock, nil) + checkInstanceValues(t, receivedInstance, 2) + checkDiskValues(t, receivedInstance.Disks[0], true, 32, "pd-ssd", "projects/ubuntu-os-cloud/global/images/family/ubuntu-1710") + checkDiskValues(t, receivedInstance.Disks[1], false, 45, "pd-standard", "") +} + +func checkInstanceValues(t *testing.T, instance *compute.Instance, diskCount int) { + t.Helper() + if instance == nil { + t.Error("expected a valid instance") + } + if len(instance.Disks) != diskCount { + t.Errorf("invalid disk count: expected '%v' got '%v'", diskCount, len(instance.Disks)) + } +} + +func checkDiskValues(t *testing.T, disk *compute.AttachedDisk, boot bool, sizeGb int64, diskType string, image string) { + t.Helper() + if disk.Boot != boot { + t.Errorf("invalid disk.Boot value: expected '%v' got '%v'", boot, disk.Boot) + } + if disk.InitializeParams.DiskSizeGb != sizeGb { + t.Errorf("invalid disk size: expected '%v' got '%v'", sizeGb, disk.InitializeParams.DiskSizeGb) + } + if !strings.Contains(disk.InitializeParams.DiskType, diskType) { + t.Errorf("invalid disk type '%v': expected it to contain '%v'", disk.InitializeParams.DiskType, diskType) + } + if disk.InitializeParams.SourceImage != image { + t.Errorf("invalid image: expected '%v' got '%v'", image, disk.InitializeParams.SourceImage) + } +} + +func TestCreateWithCAShouldPopulateMetadata(t *testing.T) { + config := newGCEMachineProviderConfigFixture() + receivedInstance, computeServiceMock := newInsertInstanceCapturingMock() + ca, err := cert.Load("testdata/ca") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + createCluster(t, config, computeServiceMock, ca) + if receivedInstance.Metadata.Items == nil { + t.Fatalf("expected the instance to have valid metadata items") + } + checkMetadataItem(t, receivedInstance.Metadata, "ca-cert", string(ca.Certificate)) + checkMetadataItem(t, receivedInstance.Metadata, "ca-key", string(ca.PrivateKey)) +} + +func checkMetadataItem(t *testing.T, metadata *compute.Metadata, key string, expectedValue string) { + item := getMetadataItem(t, metadata, key) + value, err := base64.StdEncoding.DecodeString(*item.Value) + if err != nil { + t.Fatalf("unable to base64 decode %v's value: %v", item.Key, *item.Value) + } + if string(value) != expectedValue { + t.Errorf("invalid value for %v, expected %v got %v", key, expectedValue, value) + } +} + +func getMetadataItem(t *testing.T, metadata *compute.Metadata, itemKey string) *compute.MetadataItems { + for _, i := range metadata.Items { + if i.Key == itemKey { + return i + } + } + t.Fatalf("missing metadata item with key: %v", itemKey) + return nil +} + +func createCluster(t *testing.T, config gceconfigv1.GCEMachineProviderConfig, computeServiceMock *GCEClientComputeServiceMock, ca *cert.CertificateAuthority) { + cluster := newDefaultClusterFixture(t) + machine := newMachine(t, config) + configWatch := newMachineSetupConfigWatcher() + params := google.MachineActuatorParams{ + CertificateAuthority: ca, + ComputeService: computeServiceMock, + MachineSetupConfigGetter: configWatch, + } + gce, err := google.NewMachineActuator(params) + if err != nil { + t.Fatalf("unable to create machine actuator: %v", err) + } + err = gce.Create(cluster, machine) + if err != nil { + t.Fatalf("unable to create cluster: %v", err) + } +} + +func newInsertInstanceCapturingMock() (*compute.Instance, *GCEClientComputeServiceMock) { + var receivedInstance compute.Instance + computeServiceMock := GCEClientComputeServiceMock{ + mockInstancesInsert: func(project string, zone string, instance *compute.Instance) (*compute.Operation, error) { + receivedInstance = *instance + return &compute.Operation{ + Status: "DONE", + }, nil + }, + } + return &receivedInstance, &computeServiceMock +} + +func newMachineSetupConfigMock() *GCEClientMachineSetupConfigMock { + return &GCEClientMachineSetupConfigMock{ + mockGetYaml: func() (string, error) { + return "", nil + }, + mockGetMetadata: func(params *machinesetup.ConfigParams) (machinesetup.Metadata, error) { + metadata := machinesetup.Metadata{} + return metadata, nil + }, + mockGetImage: func(params *machinesetup.ConfigParams) (string, error) { + return "image-name", nil + }, + } +} + +type TestMachineSetupConfigWatcher struct { + machineSetupConfigMock *GCEClientMachineSetupConfigMock +} + +func newMachineSetupConfigWatcher() *TestMachineSetupConfigWatcher { + return &TestMachineSetupConfigWatcher{ + machineSetupConfigMock: newMachineSetupConfigMock(), + } +} + +func (cw *TestMachineSetupConfigWatcher) GetMachineSetupConfig() (machinesetup.MachineSetupConfig, error) { + return cw.machineSetupConfigMock, nil +} + +func newMachine(t *testing.T, gceProviderConfig gceconfigv1.GCEMachineProviderConfig) *v1alpha1.Machine { + gceProviderConfigCodec, err := gceconfigv1.NewCodec() + if err != nil { + t.Fatalf("unable to create GCE provider config codec: %v", err) + } + providerConfig, err := gceProviderConfigCodec.EncodeToProviderConfig(&gceProviderConfig) + if err != nil { + t.Fatalf("unable to encode provider config: %v", err) + } + + return &v1alpha1.Machine{ + Spec: v1alpha1.MachineSpec{ + ProviderConfig: *providerConfig, + Versions: v1alpha1.MachineVersionInfo{ + Kubelet: "1.9.4", + ControlPlane: "1.9.4", + ContainerRuntime: v1alpha1.ContainerRuntimeInfo{ + Name: "docker", + Version: "1.12.0", + }, + }, + Roles: []common.MachineRole{ + common.MasterRole, + }, + }, + } +} + +func newGCEMachineProviderConfigFixture() gceconfigv1.GCEMachineProviderConfig { + return gceconfigv1.GCEMachineProviderConfig{ + TypeMeta: v1.TypeMeta{ + APIVersion: "gceproviderconfig/v1alpha1", + Kind: "GCEMachineProviderConfig", + }, + Zone: "us-west5-f", + OS: "os-name", + Disks: make([]gceconfigv1.Disk, 0), + } +} + +func newGCEClusterProviderConfigFixture() gceconfigv1.GCEClusterProviderConfig { + return gceconfigv1.GCEClusterProviderConfig{ + TypeMeta: v1.TypeMeta{ + APIVersion: "gceproviderconfig/v1alpha1", + Kind: "GCEClusterProviderConfig", + }, + Project: "project-name-2000", + } +} + +func newDefaultClusterFixture(t *testing.T) *v1alpha1.Cluster { + gceProviderConfigCodec, err := gceconfigv1.NewCodec() + if err != nil { + t.Fatalf("unable to create GCE provider config codec: %v", err) + } + gceProviderConfig := newGCEClusterProviderConfigFixture() + providerConfig, err := gceProviderConfigCodec.EncodeToProviderConfig(&gceProviderConfig) + if err != nil { + t.Fatalf("unable to encode provider config: %v", err) + } + + return &v1alpha1.Cluster{ + TypeMeta: v1.TypeMeta{ + Kind: "Cluster", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "cluster-test", + }, + Spec: v1alpha1.ClusterSpec{ + ClusterNetwork: v1alpha1.ClusterNetworkingConfig{ + Services: v1alpha1.NetworkRanges{ + CIDRBlocks: []string{ + "10.96.0.0/12", + }, + }, + Pods: v1alpha1.NetworkRanges{ + CIDRBlocks: []string{ + "192.168.0.0/16", + }, + }, + }, + ProviderConfig: *providerConfig, + }, + } +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types.go index 97dd16318..802ab085f 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types.go @@ -26,6 +26,12 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" ) +type MachineSetupConfig interface { + GetYaml() (string, error) + GetImage(params *ConfigParams) (string, error) + GetMetadata(params *ConfigParams) (Metadata, error) +} + // Config Watch holds the path to the machine setup configs yaml file. // This works directly with a yaml file is used instead of a ConfigMap object so that we don't take a dependency on the API Server. type ConfigWatch struct { @@ -71,7 +77,7 @@ func NewConfigWatch(path string) (*ConfigWatch, error) { return &ConfigWatch{path: path}, nil } -func (cw *ConfigWatch) ValidConfigs() (*ValidConfigs, error) { +func (cw *ConfigWatch) GetMachineSetupConfig() (MachineSetupConfig, error) { file, err := os.Open(cw.path) if err != nil { return nil, err diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types_test.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types_test.go index 259beb3da..fd818d4c0 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types_test.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/machinesetup/config_types_test.go @@ -90,7 +90,7 @@ func TestParseMachineSetupYaml(t *testing.T) { t.Errorf("An error was not received as expected.") } if validConfigs != nil { - t.Errorf("ValidConfigs should be nil, got %v", validConfigs) + t.Errorf("GetMachineSetupConfigs should be nil, got %v", validConfigs) } } if !table.expectedErr { @@ -98,7 +98,7 @@ func TestParseMachineSetupYaml(t *testing.T) { t.Errorf("Got unexpected error: %s", err) } if validConfigs == nil { - t.Errorf("ValidConfigs should have been parsed, but was nil") + t.Errorf("GetMachineSetupConfigs should have been parsed, but was nil") } } } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/metadata.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/metadata.go index ae25c80e9..5e6f09c2e 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/metadata.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/metadata.go @@ -39,11 +39,12 @@ type metadataParams struct { MasterEndpoint string } -func nodeMetadata(token string, cluster *clusterv1.Cluster, machine *clusterv1.Machine, metadata *machinesetup.Metadata) (map[string]string, error) { +func nodeMetadata(token string, cluster *clusterv1.Cluster, machine *clusterv1.Machine, project string, metadata *machinesetup.Metadata) (map[string]string, error) { params := metadataParams{ Token: token, Cluster: cluster, Machine: machine, + Project: project, Metadata: metadata, PodCIDR: getSubnet(cluster.Spec.ClusterNetwork.Pods), ServiceCIDR: getSubnet(cluster.Spec.ClusterNetwork.Services), @@ -123,5 +124,11 @@ MASTER={{ .MasterEndpoint }} MACHINE={{ .Machine.ObjectMeta.Name }} CLUSTER_DNS_DOMAIN={{ .Cluster.Spec.ClusterNetwork.ServiceDomain }} POD_CIDR={{ .PodCIDR }} -SERVICE_CIDER={{ .ServiceCIDR }} +SERVICE_CIDR={{ .ServiceCIDR }} +# Environment variables for GCE cloud config +PROJECT={{ .Project }} +NETWORK=default +SUBNETWORK=kubernetes +CLUSTER_NAME={{ .Cluster.Name }} +NODE_TAG="$CLUSTER_NAME-worker" ` diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/pods.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/pods.go index 6000d0fe4..c23d82abf 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/pods.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/pods.go @@ -20,21 +20,21 @@ import ( "bytes" "encoding/base64" "fmt" - "io/ioutil" "os" "os/exec" - "path/filepath" "text/template" "time" "github.com/golang/glog" corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/cert/triple" "sigs.k8s.io/cluster-api/cloud/google/config" ) -var apiServerImage = "gcr.io/k8s-cluster-api/cluster-apiserver:0.0.3" -var controllerManagerImage = "gcr.io/k8s-cluster-api/controller-manager:0.0.2" -var machineControllerImage = "gcr.io/k8s-cluster-api/gce-machine-controller:0.0.7" +var apiServerImage = "gcr.io/k8s-cluster-api/cluster-apiserver:0.0.4" +var controllerManagerImage = "gcr.io/k8s-cluster-api/controller-manager:0.0.4" +var machineControllerImage = "gcr.io/k8s-cluster-api/gce-machine-controller:0.0.11" func init() { if img, ok := os.LookupEnv("MACHINE_CONTROLLER_IMAGE"); ok { @@ -56,69 +56,34 @@ type caCertParams struct { tlsKey string } -func getBase64(file string) string { - buff := bytes.Buffer{} - enc := base64.NewEncoder(base64.StdEncoding, &buff) - data, err := ioutil.ReadFile(file) - if err != nil { - glog.Fatalf("Could not read file %s: %v", file, err) - } - - _, err = enc.Write(data) - if err != nil { - glog.Fatalf("Could not write bytes: %v", err) - } - enc.Close() - return buff.String() -} - func getApiServerCerts() (*caCertParams, error) { const name = "clusterapi" const namespace = corev1.NamespaceDefault - configDir, err := ioutil.TempDir("", "cert") - if err != nil { - return nil, err - } - defer os.RemoveAll(configDir) // clean up - if err := run("openssl", "req", "-x509", - "-newkey", "rsa:2048", - "-keyout", filepath.Join(configDir, "apiserver_ca.key"), - "-out", filepath.Join(configDir, "apiserver_ca.crt"), - "-days", "365", - "-nodes", - "-subj", fmt.Sprintf("/C=un/ST=st/L=l/O=o/OU=ou/CN=%s-certificate-authority", name)); err != nil { - return nil, err - } - - // Use ..svc as the domain Name for the certificate - if err = run("openssl", "req", - "-out", filepath.Join(configDir, "apiserver.csr"), - "-new", - "-newkey", "rsa:2048", - "-nodes", - "-keyout", filepath.Join(configDir, "apiserver.key"), - "-subj", fmt.Sprintf("/C=un/ST=st/L=l/O=o/OU=ou/CN=%s.%s.svc", name, namespace)); err != nil { - return nil, err + caKeyPair, err := triple.NewCA(fmt.Sprintf("%s-certificate-authority", name)) + if err != nil { + return nil, fmt.Errorf("failed to create root-ca: %v", err) } - if err = run("openssl", "x509", "-req", - "-days", "365", - "-in", filepath.Join(configDir, "apiserver.csr"), - "-CA", filepath.Join(configDir, "apiserver_ca.crt"), - "-CAkey", filepath.Join(configDir, "apiserver_ca.key"), - "-CAcreateserial", - "-out", filepath.Join(configDir, "apiserver.crt")); err != nil { - return nil, err + apiServerKeyPair, err := triple.NewServerKeyPair( + caKeyPair, + fmt.Sprintf("%s.%s.svc", name, namespace), + name, + namespace, + "cluster.local", + []string{}, + []string{}) + if err != nil { + return nil, fmt.Errorf("failed to create apiserver key pair: %v", err) } - certParms := &caCertParams{ - caBundle: getBase64(filepath.Join(configDir, "apiserver_ca.crt")), - tlsCrt: getBase64(filepath.Join(configDir, "apiserver.crt")), - tlsKey: getBase64(filepath.Join(configDir, "apiserver.key")), + certParams := &caCertParams{ + caBundle: base64.StdEncoding.EncodeToString(cert.EncodeCertPEM(caKeyPair.Cert)), + tlsKey: base64.StdEncoding.EncodeToString(cert.EncodePrivateKeyPEM(apiServerKeyPair.Key)), + tlsCrt: base64.StdEncoding.EncodeToString(cert.EncodeCertPEM(apiServerKeyPair.Cert)), } - return certParms, nil + return certParams, nil } func CreateApiServerAndController(token string) error { @@ -177,6 +142,77 @@ func CreateApiServerAndController(token string) error { } } +func CreateIngressController(project string, clusterName string) error { + tmpl, err := template.New("config").Parse(config.IngressControllerConfigTemplate) + if err != nil { + return err + } + + type params struct { + Project string + NodeTag string + } + + var tmplBuf bytes.Buffer + err = tmpl.Execute(&tmplBuf, params{ + Project: project, + NodeTag: clusterName + "-worker", + }) + if err != nil { + return err + } + + maxTries := 5 + for tries := 0; tries < maxTries; tries++ { + err = deployConfig(tmplBuf.Bytes()) + if err == nil { + return nil + } else { + if tries < maxTries-1 { + glog.Infof("Error scheduling ingress controller. Will retry... %v\n", err) + time.Sleep(3 * time.Second) + } + } + } + + if err != nil { + return fmt.Errorf("couldn't start ingress controller: %v\n", err) + } else { + return nil + } +} + +func CreateDefaultStorageClass() error { + tmpl, err := template.New("config").Parse(config.StorageClassConfigTemplate) + if err != nil { + return err + } + var tmplBuf bytes.Buffer + err = tmpl.Execute(&tmplBuf, nil) + if err != nil { + return err + } + + maxTries := 5 + for tries := 0; tries < maxTries; tries++ { + err = deployConfig(tmplBuf.Bytes()) + if err == nil { + return nil + } else { + if tries < maxTries-1 { + glog.Info("Error creating default storage class. Will retry...\n") + time.Sleep(3 * time.Second) + } + } + } + + if err != nil { + return fmt.Errorf("couldn't create default storage class: %v\n", err) + } else { + return nil + } +} + func deployConfig(manifest []byte) error { cmd := exec.Command("kubectl", "create", "-f", "-") stdin, err := cmd.StdinPipe() diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/serviceaccount.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/serviceaccount.go index 6844194ad..057edaf80 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/serviceaccount.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/serviceaccount.go @@ -1,12 +1,9 @@ /* Copyright 2017 The Kubernetes Authors. - 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. @@ -26,81 +23,175 @@ import ( ) const ( - ServiceAccountPrefix = "k8s-machine-controller-" - ServiceAccount = "service-account" + MasterNodeServiceAccountPrefix = "k8s-master" + WorkerNodeServiceAccountPrefix = "k8s-worker" + IngressControllerServiceAccountPrefix = "k8s-ingress-controller" + MachineControllerServiceAccountPrefix = "k8s-machine-controller" + + IngressControllerSecret = "glbc-gcp-key" MachineControllerSecret = "machine-controller-credential" + + ClusterAnnotationPrefix = "gce.clusterapi.k8s.io/service-account-" ) -// Creates a GCP service account for the machine controller, granted the -// permissions to manage compute instances, and stores its credentials as a -// Kubernetes secret. -func (gce *GCEClient) CreateMachineControllerServiceAccount(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { +var ( + MasterNodeRoles = []string{ + "compute.instanceAdmin", + "compute.networkAdmin", + "compute.securityAdmin", + "compute.viewer", + "iam.serviceAccountUser", + "storage.admin", + "storage.objectViewer", + } + WorkerNodeRoles = []string{} + IngressControllerRoles = []string{ + "compute.instanceAdmin.v1", + "compute.networkAdmin", + "compute.securityAdmin", + "iam.serviceAccountActor", + } + MachineControllerRoles = []string{ + "compute.instanceAdmin.v1", + "iam.serviceAccountActor", + } +) - if len(initialMachines) == 0 { - return fmt.Errorf("machine count is zero, cannot create service a/c") +// Returns the email address of the service account that should be used +// as the default service account for this machine +func (gce *GCEClient) GetDefaultServiceAccountForMachine(cluster *clusterv1.Cluster, machine *clusterv1.Machine) string { + if util.IsMaster(machine) { + return cluster.ObjectMeta.Annotations[ClusterAnnotationPrefix+MasterNodeServiceAccountPrefix] + } else { + return cluster.ObjectMeta.Annotations[ClusterAnnotationPrefix+WorkerNodeServiceAccountPrefix] } +} + +// Creates a GCP service account for the master node, granted permissions +// that allow the control plane to provision disks and networking resources +func (gce *GCEClient) CreateMasterNodeServiceAccount(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { + _, _, err := gce.createServiceAccount(MasterNodeServiceAccountPrefix, MasterNodeRoles, cluster, initialMachines) + + return err +} + +// Creates a GCP service account for the worker node +func (gce *GCEClient) CreateWorkerNodeServiceAccount(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { + _, _, err := gce.createServiceAccount(WorkerNodeServiceAccountPrefix, WorkerNodeRoles, cluster, initialMachines) + + return err +} - // TODO: use real go bindings - // Figure out what projects the service account needs permission to. - projects, err := gce.getProjects(initialMachines) +// Creates a GCP service account for the ingress controller +func (gce *GCEClient) CreateIngressControllerServiceAccount(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { + accountId, project, err := gce.createServiceAccount(IngressControllerServiceAccountPrefix, IngressControllerRoles, cluster, initialMachines) if err != nil { return err } - // The service account needs to be created in a single project, so just - // use the first one, but grant permission to all projects in the list. - project := projects[0] - accountId := ServiceAccountPrefix + util.RandomString(5) + return gce.createSecretForServiceAccountKey(accountId, project, IngressControllerSecret, "kube-system") +} - err = run("gcloud", "--project", project, "iam", "service-accounts", "create", "--display-name=k8s machines controller", accountId) +// Creates a GCP service account for the machine controller, granted the +// permissions to manage compute instances, and stores its credentials as a +// Kubernetes secret. +func (gce *GCEClient) CreateMachineControllerServiceAccount(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { + accountId, project, err := gce.createServiceAccount(MachineControllerServiceAccountPrefix, MachineControllerRoles, cluster, initialMachines) if err != nil { - return fmt.Errorf("couldn't create service account: %v", err) + return err } - email := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project) - localFile := accountId + "-key.json" + return gce.createSecretForServiceAccountKey(accountId, project, MachineControllerSecret, "default") +} - for _, project := range projects { - err = run("gcloud", "projects", "add-iam-policy-binding", project, "--member=serviceAccount:"+email, "--role=roles/compute.instanceAdmin.v1") - if err != nil { - return fmt.Errorf("couldn't grant permissions to service account: %v", err) - } - } +func (gce *GCEClient) createSecretForServiceAccountKey(accountId string, project string, secretName string, namespace string) error { + email := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project) - err = run("gcloud", "--project", project, "iam", "service-accounts", "keys", "create", localFile, "--iam-account", email) + localFile := accountId + "-key.json" + err := run("gcloud", "--project", project, "iam", "service-accounts", "keys", "create", localFile, "--iam-account", email) if err != nil { return fmt.Errorf("couldn't create service account key: %v", err) } - err = run("kubectl", "create", "secret", "generic", MachineControllerSecret, "--from-file=service-account.json="+localFile) + err = run("kubectl", "create", "secret", "generic", secretName, "--from-file=service-account.json="+localFile, "--namespace="+namespace) if err != nil { return fmt.Errorf("couldn't import service account key as credential: %v", err) } + if err := run("rm", localFile); err != nil { glog.Error(err) } + return nil +} + +// creates a service account with the roles specifed. Returns the account id +// of the created account and the project it belongs to. +func (gce *GCEClient) createServiceAccount(serviceAccountPrefix string, roles []string, cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) (string, string, error) { + if len(initialMachines) == 0 { + return "", "", fmt.Errorf("machine count is zero, cannot create service a/c") + } + + config, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return "", "", err + } + + accountId := serviceAccountPrefix + "-" + util.RandomString(5) + + err = run("gcloud", "--project", config.Project, "iam", "service-accounts", "create", "--display-name="+serviceAccountPrefix+" service account", accountId) + if err != nil { + return "", "", fmt.Errorf("couldn't create service account: %v", err) + } + + email := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, config.Project) + + for _, role := range roles { + err = run("gcloud", "projects", "add-iam-policy-binding", config.Project, "--member=serviceAccount:"+email, "--role=roles/"+role) + if err != nil { + return "", "", fmt.Errorf("couldn't grant permissions to service account: %v", err) + } + } + if cluster.ObjectMeta.Annotations == nil { cluster.ObjectMeta.Annotations = make(map[string]string) } - cluster.ObjectMeta.Annotations[ServiceAccount] = email - return nil + cluster.ObjectMeta.Annotations[ClusterAnnotationPrefix+serviceAccountPrefix] = email + + return accountId, config.Project, nil +} + +func (gce *GCEClient) DeleteMasterNodeServiceAccount(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { + return gce.deleteServiceAccount(MasterNodeServiceAccountPrefix, MasterNodeRoles, cluster, machines) +} + +func (gce *GCEClient) DeleteWorkerNodeServiceAccount(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { + return gce.deleteServiceAccount(WorkerNodeServiceAccountPrefix, WorkerNodeRoles, cluster, machines) +} + +func (gce *GCEClient) DeleteIngressControllerServiceAccount(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { + return gce.deleteServiceAccount(IngressControllerServiceAccountPrefix, IngressControllerRoles, cluster, machines) } func (gce *GCEClient) DeleteMachineControllerServiceAccount(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { + return gce.deleteServiceAccount(MachineControllerServiceAccountPrefix, MachineControllerRoles, cluster, machines) +} + +func (gce *GCEClient) deleteServiceAccount(serviceAccountPrefix string, roles []string, cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { if len(machines) == 0 { glog.Info("machine count is zero, cannot determine project for service a/c deletion") return nil } - projects, err := gce.getProjects(machines) + config, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) if err != nil { - return err + glog.Info("cannot parse cluster providerConfig field") + return nil } - project := projects[0] + var email string if cluster.ObjectMeta.Annotations != nil { - email = cluster.ObjectMeta.Annotations[ServiceAccount] + email = cluster.ObjectMeta.Annotations[ClusterAnnotationPrefix+serviceAccountPrefix] } if email == "" { @@ -108,33 +199,21 @@ func (gce *GCEClient) DeleteMachineControllerServiceAccount(cluster *clusterv1.C return nil } - err = run("gcloud", "projects", "remove-iam-policy-binding", project, "--member=serviceAccount:"+email, "--role=roles/compute.instanceAdmin.v1") + for _, role := range roles { + err = run("gcloud", "projects", "remove-iam-policy-binding", config.Project, "--member=serviceAccount:"+email, "--role=roles/"+role) + } if err != nil { return fmt.Errorf("couldn't remove permissions to service account: %v", err) } - err = run("gcloud", "--project", project, "iam", "service-accounts", "delete", email) + err = run("gcloud", "--project", config.Project, "iam", "service-accounts", "delete", email) if err != nil { return fmt.Errorf("couldn't delete service account: %v", err) } return nil } -func (gce *GCEClient) getProjects(machines []*clusterv1.Machine) ([]string, error) { - // Figure out what projects the service account needs permission to. - var projects []string - for _, machine := range machines { - config, err := gce.providerconfig(machine.Spec.ProviderConfig) - if err != nil { - return nil, err - } - - projects = append(projects, config.Project) - } - return projects, nil -} - func run(cmd string, args ...string) error { c := exec.Command(cmd, args...) if out, err := c.CombinedOutput(); err != nil { diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/ssh.go b/vendor/sigs.k8s.io/cluster-api/cloud/google/ssh.go index acf868a85..24b3cbd06 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/google/ssh.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/ssh.go @@ -64,21 +64,26 @@ func cleanupSshKeyPairs() { } // It creates secret to store private key. -func (gce *GCEClient) setupSSHAccess(m *clusterv1.Machine) error { +func (gce *GCEClient) setupSSHAccess(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { // Create public/private key pairs err := createSshKeyPairs() if err != nil { return err } - config, err := gce.providerconfig(m.Spec.ProviderConfig) + machineConfig, err := gce.machineproviderconfig(machine.Spec.ProviderConfig) if err != nil { return err } - err = run("gcloud", "compute", "instances", "add-metadata", m.Name, + clusterConfig, err := gce.clusterproviderconfig(cluster.Spec.ProviderConfig) + if err != nil { + return err + } + + err = run("gcloud", "compute", "instances", "add-metadata", machine.Name, "--metadata-from-file", "ssh-keys="+SshKeyFile+".pub.gcloud", - "--project", config.Project, "--zone", config.Zone) + "--project", clusterConfig.Project, "--zone", machineConfig.Zone) if err != nil { return err } @@ -94,25 +99,20 @@ func (gce *GCEClient) setupSSHAccess(m *clusterv1.Machine) error { return err } -func (gce *GCEClient) remoteSshCommand(m *clusterv1.Machine, cmd string) (string, error) { - glog.Infof("Remote SSH execution '%s' on %s", cmd, m.ObjectMeta.Name) +func (gce *GCEClient) remoteSshCommand(cluster *clusterv1.Cluster, machine *clusterv1.Machine, cmd string) (string, error) { + glog.Infof("Remote SSH execution '%s' on %s", cmd, machine.ObjectMeta.Name) - publicIP, err := gce.GetIP(m) + publicIP, err := gce.GetIP(cluster, machine) if err != nil { return "", err } - command := fmt.Sprintf("echo STARTFILE; %s", cmd) - c := exec.Command("ssh", "-i", gce.sshCreds.privateKeyPath, gce.sshCreds.user+"@"+publicIP, command) + command := fmt.Sprintf("%s", cmd) + c := exec.Command("ssh", "-i", gce.sshCreds.privateKeyPath, "-q", gce.sshCreds.user+"@"+publicIP, command) out, err := c.CombinedOutput() if err != nil { return "", fmt.Errorf("error: %v, output: %s", err, string(out)) } result := strings.TrimSpace(string(out)) - parts := strings.Split(result, "STARTFILE") - if len(parts) != 2 { - return "", nil - } - // TODO: Check error. - return strings.TrimSpace(parts[1]), nil + return result, nil } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.crt b/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.crt new file mode 100644 index 000000000..b0b173a69 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTE4MDUwMzE3MTk0OVoXDTI4MDQzMDE3MTk0OVowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALkG +6Aygr2E/6zY9RlLB8gzu2vH0uL10bPi15Mu0p0Xu1caR7+iy9d16oHZvHAUB8pDm +o2cjA8QKpqbT2ffTQs1Khd+NVI0+Lm2qTQHSY60dnnc7KqX08pTFzIzz6UNxID0H +0Tw0e8nz1SSRmzmsnAOb6ib2qruamJgojBdzpDSguaM5b0Mj/uit5G5iMtM/YhLv +n+0T/011ep9S661P86BeTRQct0XuTq+nDuEgAdH1c9KcIQJfzTnvGjOZxVtGdnla +Qi3lDwIGd69k1rYXyVtiyPjmG3rDUJwLdBFCVEOiKfZO4QTFMIyKSgBv6bMS5QKv +6VY/btqNx5MjjDL+46sCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJCaNTZCYnuIZ0yRBSsC3n9+q6+N +s9oXi3ogS74aBh++cKIWDbM1u6teNMgldyOdgZJ5IxZjgE/Fp6ZtYtFyAB30NlzE +Mx7gP84EnRgki0N11PpCZ32+Yl0p/R6rzx64HNrf+TQISkXJ2DRrC+P3Ft8oyNrh +r6/+jxds1TLEmmsjQs6JLqlqsuYD6O30Rl72tvTT4yOjpvkkk3xRFmjjAUg59M0P +ZpGn+Gn550KvgSyMriRrepwNOKF1u/EIQDQ45m8x4w6b9uLDl2wsuEMeddRD5dNs +I3uChxe0PmkUXISTBdCBFwoqV5ADbLEZkJceN2qvQX+IKELDE7hGrupN1Ec= +-----END CERTIFICATE----- diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.key b/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.key new file mode 100644 index 000000000..bd184e819 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/cloud/google/testdata/ca/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuQboDKCvYT/rNj1GUsHyDO7a8fS4vXRs+LXky7SnRe7VxpHv +6LL13Xqgdm8cBQHykOajZyMDxAqmptPZ99NCzUqF341UjT4ubapNAdJjrR2edzsq +pfTylMXMjPPpQ3EgPQfRPDR7yfPVJJGbOaycA5vqJvaqu5qYmCiMF3OkNKC5ozlv +QyP+6K3kbmIy0z9iEu+f7RP/TXV6n1LrrU/zoF5NFBy3Re5Or6cO4SAB0fVz0pwh +Al/NOe8aM5nFW0Z2eVpCLeUPAgZ3r2TWthfJW2LI+OYbesNQnAt0EUJUQ6Ip9k7h +BMUwjIpKAG/psxLlAq/pVj9u2o3HkyOMMv7jqwIDAQABAoIBAHqyBMES8RnZLB3M +pkyMxfkjj1bxhGxFv1lyLSWUR3RNe79w2RmDSx0yiyPebEM51mOWPfdPtdiTg42P +YLRBiMPfzkS/ULlU6a7ZRrWVkXmj8Yh5WL+yXgaf4BP8TsvlQY6XF34IA6qZIddd +IwUs3ExcYPqzieZcDyKfiL6oddRbO7NMOAWvBNRhPtQIjYZEi9EMhHE0te7rXF1L +7a1eBUeUx9HqahtXr8K11YBGlMKU0hpDI0GygfXALSj0X0Fjfapdz7CKynkutWwf +jV3C3QzyXKBDGm5gh58dxb9Ty7QJlanREhuge/PtqZHTcJyPxR4beSJ0RkEJ3t3v +EbjBR6kCgYEA0F+QaEYQBACS3ol7zk7d5nxTzj/uxY887tI0ucBOaQp119+3KecQ +jFd9CWR6+0BSBgBBVbctcHO872a8l5yHRuczq2LI5Xw4jmU8oYd65bmhVNASdntt +Votu9YR9WOJPgYB8pei1g9gCVS+hnzb9yV95TjodO5tX5K+MuoLOlK8CgYEA41FK +pubLK00zI1DrcwtenREpLFEZDIFegOZVfAQiY7Q6BdIoWUH938iHqEXzKJMN+u5G +VT2yZBO4soxvbHI3rw5qjfeZVOg8QCjUASYC3lfch423gEE9ftXeKeU1Ny7AE/Ac +oTac0/CrqwtvXHf9+7qVcXs8LfMM0Uh73yAxV8UCgYEAuqKd6Ye3mJ4a7pAeAC/L +uUHqnnXNG86sOQy2hRmmIZdo7a5HlJSN0eYa7Gq/bIOz9AQOgQPNA0fnFlbi3Lkq +wWsccInOfx3AWmp64ANFJLYbtqeXod/zkuzYubetvgsWQ1weSUwiKLE1ha9gCAdt +UQzezh5nSJhfJUnHttaqyakCgYA3A7SSgibHmw52TJ4/IJBMaRvS2um6ChFKFvS3 +Z/S+tp6YTA1x1LPRliGmyw8XUce/ZC1nceAbsl4v8CcDUv1BA7csFrFoSDBUwszU +XlOxZgg8UkMH6kIs0SvMbemcKldYO/Nh3OrFrGiPo9vWwD+azcRfS5j78ee3Otin +3o2ylQKBgQDFr6TLYNd5ygOQQs2TAB2SDUywz5bl1uHqyBv5+2xgOlaCthg4GQUb +EcvN7sqq0Re2o2lbPbpwLZPrxSf306rqSoHbxjIaZUZjf/lWs6TtZdWx020ZuImv +vWi9YCVEAL08emdMrMgAM3KJIVUujmzHWkkgH2iYAK9NLjqiDOtKoA== +-----END RSA PRIVATE KEY----- diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/bin/terraform b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/bin/terraform deleted file mode 100755 index c94ba1f5f..000000000 Binary files a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/bin/terraform and /dev/null differ diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Dockerfile b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Dockerfile index 1c132fcd2..54e4fe4fd 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Dockerfile +++ b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Dockerfile @@ -23,7 +23,20 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller # Final container -FROM alpine:3.7 -RUN apk --no-cache add --update ca-certificates bash openssh terraform -RUN echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' >> ~/.terraformrc +FROM debian:stretch-slim + +ENV TERRAFORM_VERSION=0.11.7 +ENV TERRAFORM_ZIP=terraform_${TERRAFORM_VERSION}_linux_amd64.zip +ENV TERRAFORM_SHA256SUM=6b8ce67647a59b2a3f70199c304abca0ddec0e49fd060944c26f666298e23418 +ENV TERRAFORM_SHAFILE=terraform_${TERRAFORM_VERSION}_SHA256SUMS + +RUN apt-get update && apt-get install -y ca-certificates curl openssh-server unzip && \ + curl https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/${TERRAFORM_ZIP} > ${TERRAFORM_ZIP} && \ + echo "${TERRAFORM_SHA256SUM} ${TERRAFORM_ZIP}" > ${TERRAFORM_SHAFILE} && \ + sha256sum --quiet -c ${TERRAFORM_SHAFILE} && \ + unzip ${TERRAFORM_ZIP} -d /bin && \ + rm -f ${TERRAFORM_ZIP} ${TERRAFORM_SHAFILE} && \ + echo 'plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"' >> ~/.terraformrc && \ + rm -rf /var/lib/apt/lists/* + COPY --from=builder /go/bin/terraform-machine-controller . diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Makefile b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Makefile index 800a7f53c..54bcdc225 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Makefile +++ b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/cmd/terraform-machine-controller/Makefile @@ -14,16 +14,22 @@ .PHONY: image push dev_image dev_push -PREFIX = gcr.io/k8s-cluster-api +GCR_BUCKET = k8s-cluster-api +PREFIX = gcr.io/$(GCR_BUCKET) DEV_PREFIX ?= gcr.io/$(shell gcloud config get-value project) NAME = terraform-machine-controller -TAG = 0.0.2 +TAG = 0.0.5 image: docker build -t "$(PREFIX)/$(NAME):$(TAG)" -f ./Dockerfile ../../../.. push: image docker push "$(PREFIX)/$(NAME):$(TAG)" + $(MAKE) fix_gcs_permissions + +fix_gcs_permissions: + gsutil acl ch -u AllUsers:READ gs://artifacts.$(GCR_BUCKET).appspot.com + gsutil -m acl ch -r -u AllUsers:READ gs://artifacts.$(GCR_BUCKET).appspot.com dev_image: docker build -t "$(DEV_PREFIX)/$(NAME):$(TAG)-dev" -f ./Dockerfile ../../../.. diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/machineactuator.go b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/machineactuator.go index 8b16d7dd9..f66471f0d 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/machineactuator.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/machineactuator.go @@ -1,12 +1,9 @@ /* Copyright 2017 The Kubernetes Authors. - 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. @@ -48,8 +45,10 @@ import ( ) const ( - MasterIpAnnotationKey = "master-ip" - TerraformConfigAnnotationKey = "tf-config" + MasterIpAnnotationKey = "master-ip" + TerraformConfigAnnotationKey = "tf-config" + ControlPlaneVersionAnnotationKey = "control-plane-version" + KubeletVersionAnnotationKey = "kubelet-version" // Filename in which named machines are saved using a ConfigMap (in master). NamedMachinesFilename = "vsphere_named_machines.yaml" @@ -240,13 +239,13 @@ func (tf *TerraformClient) Create(cluster *clusterv1.Cluster, machine *clusterv1 // Check if we need to terraform init tfInitExists, e := pathExists(path.Join(tfConfigDir, ".terraform/")) if e != nil { - return errors.New(fmt.Sprintf("Could not get the path of the file: ", e)) + return errors.New(fmt.Sprintf("Could not get the path of the file: %+v", e)) } if !tfInitExists { glog.Infof("Terraform not initialized.. Running terraform init.") _, initErr := runTerraformCmd(true, tfConfigDir, "init") if initErr != nil { - return errors.New(fmt.Sprintf("Could not run terraform: ", initErr)) + return errors.New(fmt.Sprintf("Could not run terraform: %+v", initErr)) } } @@ -341,9 +340,43 @@ func runTerraformCmd(stdout bool, workingDir string, arg ...string) (bytes.Buffe return out, nil } -func (tf *TerraformClient) Delete(machine *clusterv1.Machine) error { - glog.Infof("TERRAFORM DELETE.\n") - return nil +func (tf *TerraformClient) Delete(_ *clusterv1.Cluster, machine *clusterv1.Machine) error { + // Check if the instance exists, return if it doesn't + instance, err := tf.instanceIfExists(machine) + if err != nil { + return err + } + + if instance == nil { + return nil + } + + homedir, err := getHomeDir() + if err != nil { + return err + } + machinePath := path.Join(homedir, fmt.Sprintf(".terraform.d/kluster/machines/%s", machine.ObjectMeta.Name)) + + // destroy it + args := []string{ + "destroy", + "-auto-approve", + "-input=false", + "-var", fmt.Sprintf("vm_name=%s", machine.ObjectMeta.Name), + "-var-file=variables.tfvars", + } + _, err = runTerraformCmd(false, machinePath, args...) + if err != nil { + return fmt.Errorf("could not run terraform: %s", err) + } + + // remove finalizer + if tf.machineClient != nil { + machine.ObjectMeta.Finalizers = util.Filter(machine.ObjectMeta.Finalizers, clusterv1.MachineFinalizer) + _, err = tf.machineClient.Update(machine) + } + + return err } func (tf *TerraformClient) PostDelete(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { @@ -351,11 +384,118 @@ func (tf *TerraformClient) PostDelete(cluster *clusterv1.Cluster, machines []*cl } func (tf *TerraformClient) Update(cluster *clusterv1.Cluster, goalMachine *clusterv1.Machine) error { - glog.Infof("TERRAFORM UPDATE.\n") + // Try to get the annotations for the versions. If they don't exist, update the annotation and return. + // This can only happen right after bootstrapping. + if goalMachine.ObjectMeta.Annotations == nil { + ip, _ := tf.GetIP(goalMachine) + glog.Info("Annotations do not exist. Populating existing state for bootstrapped machine.") + return tf.updateAnnotations(goalMachine, ip) + } + + // Check if the annotations we want to track exist, if not, the user likely created a master machine with their own annotation. + if _, ok := goalMachine.ObjectMeta.Annotations[ControlPlaneVersionAnnotationKey]; !ok { + ip, _ := tf.GetIP(goalMachine) + glog.Info("Version annotations do not exist. Populating existing state for bootstrapped machine.") + return tf.updateAnnotations(goalMachine, ip) + } + + if util.IsMaster(goalMachine) { + glog.Info("Upgrade for master machine.. Check if upgrade needed.") + // Get the versions for the new object. + goalMachineControlPlaneVersion := goalMachine.Spec.Versions.ControlPlane + goalMachineKubeletVersion := goalMachine.Spec.Versions.Kubelet + + // If the saved versions and new versions differ, do in-place upgrade. + if goalMachineControlPlaneVersion != goalMachine.Annotations[ControlPlaneVersionAnnotationKey] || + goalMachineKubeletVersion != goalMachine.Annotations[KubeletVersionAnnotationKey] { + glog.Infof("Doing in-place upgrade for master from %s to %s", goalMachine.Annotations[ControlPlaneVersionAnnotationKey], goalMachineControlPlaneVersion) + err := tf.updateMasterInPlace(goalMachine) + if err != nil { + glog.Errorf("Master inplace update failed: %+v", err) + } + } else { + glog.Info("UPDATE TYPE NOT SUPPORTED") + return nil + } + } else { + glog.Info("NODE UPDATES NOT SUPPORTED") + return nil + } + return nil } -func (tf *TerraformClient) Exists(machine *clusterv1.Machine) (bool, error) { +// Assumes that update is needed. +// For now support only K8s control plane upgrades. +func (tf *TerraformClient) updateMasterInPlace(goalMachine *clusterv1.Machine) error { + goalMachineControlPlaneVersion := goalMachine.Spec.Versions.ControlPlane + goalMachineKubeletVersion := goalMachine.Spec.Versions.Kubelet + + currentControlPlaneVersion := goalMachine.Annotations[ControlPlaneVersionAnnotationKey] + currentKubeletVersion := goalMachine.Annotations[KubeletVersionAnnotationKey] + + // Control plane upgrade + if goalMachineControlPlaneVersion != currentControlPlaneVersion { + // Pull the kudeadm for target version K8s. + cmd := fmt.Sprintf("curl -sSL https://dl.k8s.io/release/v%s/bin/linux/amd64/kubeadm | sudo tee /usr/bin/kubeadm > /dev/null; "+ + "sudo chmod a+rx /usr/bin/kubeadm", goalMachineControlPlaneVersion) + _, err := tf.remoteSshCommand(goalMachine, cmd, "~/.ssh/id_rsa", "ubuntu") + if err != nil { + glog.Infof("remoteSshCommand failed while downloading new kubeadm: %+v", err) + return err + } + + // Next upgrade control plane + cmd = fmt.Sprintf("sudo kubeadm upgrade apply %s -y", "v"+goalMachine.Spec.Versions.ControlPlane) + _, err = tf.remoteSshCommand(goalMachine, cmd, "~/.ssh/id_rsa", "ubuntu") + if err != nil { + glog.Infof("remoteSshCommand failed while upgrading control plane: %+v", err) + return err + } + } + + // Upgrade kubelet + if goalMachineKubeletVersion != currentKubeletVersion { + // Upgrade kubelet to desired version. + cmd := fmt.Sprintf("sudo apt-get install kubelet=%s", goalMachine.Spec.Versions.Kubelet+"-00") + _, err := tf.remoteSshCommand(goalMachine, cmd, "~/.ssh/id_rsa", "ubuntu") + if err != nil { + glog.Infof("remoteSshCommand while installing new kubelet version: %v", err) + return err + } + } + + return nil +} + +func (tf *TerraformClient) remoteSshCommand(m *clusterv1.Machine, cmd, privateKeyPath, sshUser string) (string, error) { + glog.Infof("Remote SSH execution '%s' on %s", cmd, m.ObjectMeta.Name) + + publicIP, err := tf.GetIP(m) + if err != nil { + return "", err + } + + command := fmt.Sprintf("echo STARTFILE; %s", cmd) + c := exec.Command("ssh", "-i", privateKeyPath, sshUser+"@"+publicIP, + "-o", "StrictHostKeyChecking no", + "-o", "UserKnownHostsFile /dev/null", + command) + out, err := c.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error: %v, output: %s", err, string(out)) + } + result := strings.TrimSpace(string(out)) + parts := strings.Split(result, "STARTFILE") + glog.Infof("\t Result of command %s ========= %+v", cmd, parts) + if len(parts) != 2 { + return "", nil + } + // TODO: Check error. + return strings.TrimSpace(parts[1]), nil +} + +func (tf *TerraformClient) Exists(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (bool, error) { i, err := tf.instanceIfExists(machine) if err != nil { return false, err @@ -394,19 +534,16 @@ func (tf *TerraformClient) GetKubeConfig(master *clusterv1.Machine) (string, err cmd := exec.Command( // TODO: this is taking my private key and username for now. "ssh", "-i", "~/.ssh/vsphere_tmp", + "-q", "-o", "StrictHostKeyChecking no", "-o", "UserKnownHostsFile /dev/null", fmt.Sprintf("ubuntu@%s", ip), - "echo STARTFILE; sudo cat /etc/kubernetes/admin.conf") + "sudo cat /etc/kubernetes/admin.conf") cmd.Stdout = &out cmd.Stderr = os.Stderr cmd.Run() result := strings.TrimSpace(out.String()) - parts := strings.Split(result, "STARTFILE") - if len(parts) != 2 { - return "", nil - } - return strings.TrimSpace(parts[1]), nil + return result, nil } // After master created, move the plugins folder from local to @@ -448,7 +585,7 @@ func (tf *TerraformClient) SetupRemoteMaster(master *clusterv1.Machine) error { "-o", "StrictHostKeyChecking no", "-o", "UserKnownHostsFile /dev/null", fmt.Sprintf("ubuntu@%s", ip), - fmt.Sprintf("source ~/.profile; cd ~/.terraform.d/kluster/machines/%s; ~/.terraform.d/terraform init; cp -r ~/.terraform.d/kluster/machines/%s/.terraform/plugins/* ~/.terraform.d/plugins/", machineName, machineName)) + fmt.Sprintf("source ~/.profile; cd ~/.terraform.d/kluster/machines/%s; ~/.terraform.d/bin/terraform init; cp -r ~/.terraform.d/kluster/machines/%s/.terraform/plugins/* ~/.terraform.d/plugins/", machineName, machineName)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Run() @@ -461,12 +598,15 @@ func (tf *TerraformClient) updateAnnotations(machine *clusterv1.Machine, masterE machine.ObjectMeta.Annotations = make(map[string]string) } machine.ObjectMeta.Annotations[MasterIpAnnotationKey] = masterEndpointIp + machine.ObjectMeta.Annotations[ControlPlaneVersionAnnotationKey] = machine.Spec.Versions.ControlPlane + machine.ObjectMeta.Annotations[KubeletVersionAnnotationKey] = machine.Spec.Versions.Kubelet + _, err := tf.machineClient.Update(machine) if err != nil { return err } - err = tf.updateInstanceStatus(machine) - return err + //err = tf.updateInstanceStatus(machine) + return nil } func (tf *TerraformClient) instanceIfExists(machine *clusterv1.Machine) (*clusterv1.Machine, error) { @@ -578,4 +718,4 @@ func pathExists(path string) (bool, error) { return false, nil } return true, err -} +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/pods.go b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/pods.go index 4b3862db9..aa6d884a2 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/pods.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/pods.go @@ -32,9 +32,9 @@ import ( "sigs.k8s.io/cluster-api/cloud/terraform/config" ) -var apiServerImage = "gcr.io/k8s-cluster-api/cluster-apiserver:0.0.2" -var controllerManagerImage = "gcr.io/k8s-cluster-api/controller-manager:0.0.2" -var machineControllerImage = "gcr.io/karangoel-gke-1/terraform-machine-controller:0.0.1-dev" +var apiServerImage = "gcr.io/k8s-cluster-api/cluster-apiserver:0.0.4" +var controllerManagerImage = "gcr.io/k8s-cluster-api/controller-manager:0.0.4" +var machineControllerImage = "gcr.io/k8s-cluster-api/terraform-machine-controller:0.0.5" func init() { if img, ok := os.LookupEnv("MACHINE_CONTROLLER_IMAGE"); ok { @@ -159,8 +159,9 @@ func CreateApiServerAndController(token string) error { ioutil.WriteFile("/tmp/pods.yaml", tmplBuf.Bytes(), 0644) - maxTries := 5 + maxTries := 30 for tries := 0; tries < maxTries; tries++ { + glog.Infof("Attempting to deploy cluster api config.") err = deployConfig(tmplBuf.Bytes()) if err == nil { return nil @@ -168,6 +169,8 @@ func CreateApiServerAndController(token string) error { if tries < maxTries-1 { glog.Info("Error scheduling machine controller. Will retry...\n") time.Sleep(3 * time.Second) + } else { + glog.Info("Error scheduling machine controller. No more retries.") } } } diff --git a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/templates.go b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/templates.go index 873ee264f..afe5cc8ec 100644 --- a/vendor/sigs.k8s.io/cluster-api/cloud/terraform/templates.go +++ b/vendor/sigs.k8s.io/cluster-api/cloud/terraform/templates.go @@ -203,7 +203,7 @@ KUBELET=$(getversion kubelet ${KUBELET_VERSION}-) KUBEADM=$(getversion kubeadm ${KUBELET_VERSION}-) KUBECTL=$(getversion kubectl ${KUBELET_VERSION}-) # Explicit cni version is a temporary workaround till the right version can be automatically detected correctly -apt-get install -y kubelet=${KUBELET} kubeadm=${KUBEADM} kubectl=${KUBECTL} kubernetes-cni=0.5.1-00 +apt-get install -y kubelet=${KUBELET} kubeadm=${KUBEADM} kubectl=${KUBECTL} systemctl enable docker || true systemctl start docker || true @@ -213,11 +213,15 @@ sysctl net.bridge.bridge-nf-call-iptables=1 # kubeadm uses 10th IP as DNS server CLUSTER_DNS_SERVER=$(prips ${SERVICE_CIDR} | head -n 11 | tail -n 1) -sed -i "s/KUBELET_DNS_ARGS=[^\"]*/KUBELET_DNS_ARGS=--cluster-dns=${CLUSTER_DNS_SERVER} --cluster-domain=${CLUSTER_DNS_DOMAIN}/" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf +cat > /etc/systemd/system/kubelet.service.d/20-cloud.conf << EOF +[Service] +Environment="KUBELET_DNS_ARGS=--cluster-dns=${CLUSTER_DNS_SERVER} --cluster-domain=${CLUSTER_DNS_DOMAIN}" +Environment="KUBELET_EXTRA_ARGS=--cloud-provider=vsphere" +EOF systemctl daemon-reload systemctl restart kubelet.service -kubeadm join --token "${TOKEN}" "${MASTER}" --skip-preflight-checks +kubeadm join --token "${TOKEN}" "${MASTER}" --skip-preflight-checks --discovery-token-unsafe-skip-ca-verification for tries in $(seq 1 60); do kubectl --kubeconfig /etc/kubernetes/kubelet.conf annotate --overwrite node $(hostname) machine=${MACHINE} && break @@ -288,15 +292,18 @@ KUBEADM=$(getversion kubeadm ${KUBELET_VERSION}-) # Explicit cni version is a temporary workaround till the right version can be automatically detected correctly apt-get install -y \ kubelet=${KUBELET} \ - kubeadm=${KUBEADM} \ - kubernetes-cni=0.5.1-00 + kubeadm=${KUBEADM} mv /usr/bin/kubeadm.dl /usr/bin/kubeadm chmod a+rx /usr/bin/kubeadm systemctl enable docker systemctl start docker -sed -i "s/KUBELET_DNS_ARGS=[^\"]*/KUBELET_DNS_ARGS=--cluster-dns=${CLUSTER_DNS_SERVER} --cluster-domain=${CLUSTER_DNS_DOMAIN}/" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf +cat > /etc/systemd/system/kubelet.service.d/20-cloud.conf << EOF +[Service] +Environment="KUBELET_DNS_ARGS=--cluster-dns=${CLUSTER_DNS_SERVER} --cluster-domain=${CLUSTER_DNS_DOMAIN}" +Environment="KUBELET_EXTRA_ARGS=--cloud-provider=vsphere --cloud-config=/etc/kubernetes/cloud-config/cloud-config.yaml" +EOF systemctl daemon-reload systemctl restart kubelet.service ` + @@ -305,10 +312,40 @@ echo $PRIVATEIP > /tmp/.ip ` + "PUBLICIP=`ip route get 8.8.8.8 | awk '{printf \"%s\", $NF; exit}'`" + ` +# Set up kubeadm config file to pass parameters to kubeadm init. +cat > /etc/kubernetes/kubeadm_config.yaml </cluster-api.git +$ cd $GOPATH/src/sigs.k8s.io/cluster-api/clusterctl/ +$ go build +``` + +### Limitations + + +### Creating a cluster + +**NOT YET SUPPORTED!** - Use [provider-specific deployer](../README.md) to create clusters till cluster creation is supported. + +1. Create a `cluster.yaml` and `machines.yaml` files configured for your cluster. See the provider specific templates and generation tools at `$GOPATH/src/sigs.k8s.io/cluster-api/clusterctl/examples/`. +2. Create a cluster +``` +clusterctl create cluster -c cluster.yaml -m machines.yaml +``` +Additional advanced flags can be found via help +``` +clusterctl create cluster --help +``` + +### Interacting with your cluster + +Once you have created a cluster, you can interact with the cluster and machine +resources using kubectl: + +``` +$ kubectl get clusters +$ kubectl get machines +$ kubectl get machines -o yaml +``` + +#### Scaling your cluster + +**NOT YET SUPPORTED!** + +#### Upgrading your cluster + +**NOT YET SUPPORTED!** + +#### Node repair + +**NOT YET SUPPORTED!** + +### Deleting a cluster + +**NOT YET SUPPORTED!** diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer.go new file mode 100644 index 000000000..057403657 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer.go @@ -0,0 +1,38 @@ +package clusterdeployer + +import ( + "fmt" + + "sigs.k8s.io/cluster-api/errors" + clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" +) + +// Can provision a kubernetes cluster +type ClusterProvisioner interface { + Create() error + Delete() error + GetKubeconfig() (string, error) +} + +type ClusterDeployer struct { + externalProvisioner ClusterProvisioner + cleanupExternalCluster bool +} + +func New(externalProvisioner ClusterProvisioner, cleanupExternalCluster bool) *ClusterDeployer { + return &ClusterDeployer{ + externalProvisioner: externalProvisioner, + cleanupExternalCluster: cleanupExternalCluster, + } +} + +// Creates the a cluster from the provided cluster definition and machine list. +func (d *ClusterDeployer) Create(_ *clusterv1.Cluster, _ []*clusterv1.Machine) error { + if err := d.externalProvisioner.Create(); err != nil { + return fmt.Errorf("could not create external control plane: %v", err) + } + if d.cleanupExternalCluster { + defer d.externalProvisioner.Delete() + } + return errors.NotImplementedError // Not fully functional yet. +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer_test.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer_test.go new file mode 100644 index 000000000..f8005d753 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/clusterdeployer_test.go @@ -0,0 +1,81 @@ +package clusterdeployer_test + +import ( + "fmt" + "sigs.k8s.io/cluster-api/clusterctl/clusterdeployer" + "testing" +) + +type testClusterProvisioner struct { + err error + clusterCreated bool + clusterExists bool +} + +func (p *testClusterProvisioner) Create() error { + if p.err != nil { + return p.err + } + p.clusterCreated = true + p.clusterExists = true + return nil +} + +func (p *testClusterProvisioner) Delete() error { + if p.err != nil { + return p.err + } + p.clusterExists = false + return nil +} + +func (p *testClusterProvisioner) GetKubeconfig() (string, error) { + return "", p.err +} + +func TestCreate(t *testing.T) { + var testcases = []struct { + name string + provisionExternalErr error + cleanupExternal bool + expectErr bool + expectExternalExists bool + expectExternalCreated bool + }{ + { + name: "success", + cleanupExternal: true, + expectExternalExists: false, + expectExternalCreated: true, + expectErr: true, + }, + { + name: "success no cleaning external", + cleanupExternal: false, + expectExternalExists: true, + expectExternalCreated: true, + expectErr: true, + }, + { + name: "fail provision external cluster", + provisionExternalErr: fmt.Errorf("Test failure"), + expectErr: true, + }, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + p := &testClusterProvisioner{err: testcase.provisionExternalErr} + d := clusterdeployer.New(p, testcase.cleanupExternal) + err := d.Create(nil, nil) + if (testcase.expectErr && err == nil) || (!testcase.expectErr && err != nil) { + t.Fatalf("Unexpected returned error. Got: %v, Want Err: %v", err, testcase.expectErr) + } + if testcase.expectExternalExists != p.clusterExists { + t.Errorf("Unexpected external cluster existance. Got: %v, Want: %v", p.clusterExists, testcase.expectExternalExists) + } + if testcase.expectExternalCreated != p.clusterCreated { + t.Errorf("Unexpected external cluster provisioning. Got: %v, Want: %v", p.clusterCreated, testcase.expectExternalCreated) + } + }) + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube.go new file mode 100644 index 000000000..b3bbc5458 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube.go @@ -0,0 +1,68 @@ +package minikube + +import ( + "fmt" + "github.com/golang/glog" + "os" + "os/exec" + "io/ioutil" +) + +type Minikube struct { + kubeconfigpath string + vmDriver string + // minikubeExec implemented as function variable for testing hooks + minikubeExec func(env []string, args ...string) (string, error) +} + +func New(vmDriver string) *Minikube { + return &Minikube{ + minikubeExec: minikubeExec, + vmDriver: vmDriver, + // Arbitrary file name. Can potentially be randomly generated. + kubeconfigpath: "minikube.config", + } +} + +var minikubeExec = func(env []string, args ...string) (string, error) { + const executable = "minikube" + glog.V(5).Infof("Running: %v %v", executable, args) + cmd := exec.Command(executable, args...) + cmd.Env = env + cmdOut, err := cmd.CombinedOutput() + glog.V(4).Infof("Ran: %v %v Output: %v", executable, args, string(cmdOut)) + return string(cmdOut), err +} + +func (m *Minikube) Create() error { + args := []string {"start", "--bootstrapper=kubeadm"} + if m.vmDriver != ""{ + args = append(args, fmt.Sprintf("--vm-driver=%v", m.vmDriver)) + } + _, err := m.exec(args...) + return err +} + +func (m *Minikube) Delete() error { + _, err := m.exec("delete") + os.Remove(m.kubeconfigpath) + return err +} + +func (m *Minikube) GetKubeconfig() (string, error) { + b, err := ioutil.ReadFile(m.kubeconfigpath) + if err!= nil { + return "", err + } + return string(b), nil +} + +func (m *Minikube) exec(args ...string) (string, error) { + // Override kubeconfig environment variable in call + // so that minikube will generate and reference + // the kubeconfig in the desired location. + // Note that the last value set for a key is the final value. + const kubeconfigEnvVar = "KUBECONFIG" + env := append(os.Environ(), fmt.Sprintf("%v=%v", kubeconfigEnvVar, m.kubeconfigpath)) + return m.minikubeExec(env, args...) +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube_test.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube_test.go new file mode 100644 index 000000000..0a284357f --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube/minikube_test.go @@ -0,0 +1,106 @@ +package minikube + +import ( + "fmt" + "io/ioutil" + "os" + "testing" +) + +func TestCreate(t *testing.T) { + var testcases = []struct { + name string + execError error + expectErr bool + }{ + { + name: "success", + }, + { + name: "exec fail", + execError: fmt.Errorf("test error"), + expectErr: true, + }, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + m := New("") + m.minikubeExec = func(env []string, args ...string) (string, error) { + return "", testcase.execError + } + err := m.Create() + if (testcase.expectErr && err == nil) || (!testcase.expectErr && err != nil) { + t.Fatalf("Unexpected returned error. Got: %v, Want Err: %v", err, testcase.expectErr) + } + }) + } +} + +func TestDelete(t *testing.T) { + var testcases = []struct { + name string + execError error + expectErr bool + }{ + { + name: "success", + }, + { + name: "exec fail", + execError: fmt.Errorf("test error"), + expectErr: true, + }, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + m := New("") + m.minikubeExec = func(env []string, args ...string) (string, error) { + return "", testcase.execError + } + err := m.Delete() + if (testcase.expectErr && err == nil) || (!testcase.expectErr && err != nil) { + t.Fatalf("Unexpected returned error. Got: %v, Want Err: %v", err, testcase.expectErr) + } + }) + } +} + +func TestGetKubeconfig(t *testing.T) { + const contents = "dfserfafaew" + m := New("") + f, err := createTempFile(contents) + if err != nil { + t.Fatal("Unable to create test file.") + } + defer os.Remove(f) + t.Run("file does not exist", func(t *testing.T) { + c, err := m.GetKubeconfig() + if err == nil { + t.Fatal("Able to read a file that does not exist") + } + if c != "" { + t.Fatal("Able to return contents for file that does not exist.") + } + }) + t.Run("file exists", func(t *testing.T) { + m.kubeconfigpath = f + c, err := m.GetKubeconfig() + if err != nil { + t.Fatalf("Unexpected err. Got: %v", err) + return + } + if c != contents { + t.Fatalf("Unexpected contents. Got: %v, Want: %v", c, contents) + } + }) +} + +func createTempFile(contents string) (string, error) { + f, err := ioutil.TempFile("", "") + if err != nil { + return "", err + } + defer f.Close() + f.WriteString(contents) + return f.Name(), nil +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create.go new file mode 100644 index 000000000..2eaa1e7e2 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 cmd + +import ( + "github.com/spf13/cobra" +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create a cluster API resource", + Long: `Create a cluster API resource with one command`, +} + +func init() { + RootCmd.AddCommand(createCmd) +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster.go new file mode 100644 index 000000000..09aa12264 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster.go @@ -0,0 +1,115 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 cmd + +import ( + "github.com/ghodss/yaml" + "github.com/golang/glog" + "github.com/spf13/cobra" + "io/ioutil" + "sigs.k8s.io/cluster-api/clusterctl/clusterdeployer" + "sigs.k8s.io/cluster-api/clusterctl/clusterdeployer/minikube" + clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/util" +) + +type CreateOptions struct { + Cluster string + Machine string + CleanupExternalCluster bool + VmDriver string +} + +var co = &CreateOptions{} + +var createClusterCmd = &cobra.Command{ + Use: "cluster", + Short: "Create kubernetes cluster", + Long: `Create a kubernetes cluster with one command`, + Run: func(cmd *cobra.Command, args []string) { + if co.Cluster == "" { + exitWithHelp(cmd, "Please provide yaml file for cluster definition.") + } + if co.Machine == "" { + exitWithHelp(cmd, "Please provide yaml file for machine definition.") + } + if err := RunCreate(co); err != nil { + glog.Exit(err) + } + }, +} + +func RunCreate(co *CreateOptions) error { + c, err := parseClusterYaml(co.Cluster) + if err != nil { + return err + } + m, err := parseMachinesYaml(co.Machine) + if err != nil { + return err + } + + mini := minikube.New(co.VmDriver) + d := clusterdeployer.New(mini, co.CleanupExternalCluster) + err = d.Create(c, m) + return err +} + +func init() { + // Required flags + createClusterCmd.Flags().StringVarP(&co.Cluster, "cluster", "c", "", "A yaml file containing cluster object definition") + createClusterCmd.Flags().StringVarP(&co.Machine, "machines", "m", "", "A yaml file containing machine object definition(s)") + + // Optional flags + createClusterCmd.Flags().BoolVarP(&co.CleanupExternalCluster, "cleanup-external-cluster", "", true, "Whether to cleanup the external cluster after bootstrap") + createClusterCmd.Flags().StringVarP(&co.VmDriver, "vm-driver", "", "", "Which vm driver to use for minikube") + createCmd.AddCommand(createClusterCmd) +} + +func parseClusterYaml(file string) (*clusterv1.Cluster, error) { + bytes, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + cluster := &clusterv1.Cluster{} + err = yaml.Unmarshal(bytes, cluster) + if err != nil { + return nil, err + } + + return cluster, nil +} + +func parseMachinesYaml(file string) ([]*clusterv1.Machine, error) { + bytes, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + list := &clusterv1.MachineList{} + err = yaml.Unmarshal(bytes, &list) + if err != nil { + return nil, err + } + + if list == nil { + return []*clusterv1.Machine{}, nil + } + + return util.MachineP(list.Items), nil +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster_test.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster_test.go new file mode 100644 index 000000000..967aa4155 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/create_cluster_test.go @@ -0,0 +1,130 @@ +package cmd + +import ( + "io/ioutil" + "os" + "testing" +) + +const validCluster = ` +apiVersion: "cluster.k8s.io/v1alpha1" +kind: Cluster +metadata: + name: cluster1 +spec:` + +const validMachines = ` +items: +- apiVersion: "cluster.k8s.io/v1alpha1" + kind: Machine + metadata: + name: machine1 + spec:` + +func TestParseClusterYaml(t *testing.T) { + t.Run("File does not exist", func(t *testing.T) { + _, err := parseClusterYaml("fileDoesNotExist") + if err == nil { + t.Fatal("Was able to parse a file that does not exist") + } + }) + var testcases = []struct { + name string + contents string + expectedName string + expectErr bool + }{ + { + name: "valid file", + contents: validCluster, + expectedName: "cluster1", + }, + { + name: "gibberish in file", + contents: `blah ` + validCluster + ` blah`, + expectErr: true, + }, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + file, err := createTempFile(testcase.contents) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file) + + c, err := parseClusterYaml(file) + if (testcase.expectErr && err == nil) || (!testcase.expectErr && err != nil) { + t.Fatalf("Unexpected returned error. Got: %v, Want Err: %v", err, testcase.expectErr) + } + if err != nil { + return + } + if c == nil { + t.Fatalf("No cluster returned in success case.") + } + if c.Name != testcase.expectedName { + t.Fatalf("Unexpected name. Got: %v, Want:%v", c.Name, testcase.expectedName) + } + }) + } +} + +func TestParseMachineYaml(t *testing.T) { + t.Run("File does not exist", func(t *testing.T) { + _, err := parseMachinesYaml("fileDoesNotExist") + if err == nil { + t.Fatal("Was able to parse a file that does not exist") + } + }) + var testcases = []struct { + name string + contents string + expectErr bool + expectedMachineCount int + }{ + { + name: "valid file", + contents: validMachines, + expectedMachineCount: 1, + }, + { + name: "gibberish in file", + contents: `blah ` + validMachines + ` blah`, + expectErr: true, + }, + } + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + file, err := createTempFile(testcase.contents) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file) + + m, err := parseMachinesYaml(file) + if (testcase.expectErr && err == nil) || (!testcase.expectErr && err != nil) { + t.Fatalf("Unexpected returned error. Got: %v, Want Err: %v", err, testcase.expectErr) + } + if err != nil { + return + } + if m == nil { + t.Fatalf("No machines returned in success case.") + } + if len(m) != testcase.expectedMachineCount { + t.Fatalf("Unexpected machine count. Got: %v, Want: %v", len(m), testcase.expectedMachineCount) + } + }) + } +} + +func createTempFile(contents string) (string, error) { + f, err := ioutil.TempFile("", "") + if err != nil { + return "", err + } + defer f.Close() + f.WriteString(contents) + return f.Name(), nil +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete.go new file mode 100644 index 000000000..ca49d8e1a --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 cmd + +import ( + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a cluster API resource", + Long: `Delete a cluster API resource with one command`, +} + +func init() { + RootCmd.AddCommand(deleteCmd) +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete_cluster.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete_cluster.go new file mode 100644 index 000000000..76177ba06 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/delete_cluster.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 cmd + +import ( + "github.com/golang/glog" + "github.com/spf13/cobra" + "sigs.k8s.io/cluster-api/errors" +) + +type DeleteOptions struct { + ClusterName string +} + +var do = &DeleteOptions{} + +func NewCmdDeleteCluster() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete kubernetes cluster", + Long: `Delete a kubernetes cluster with one command`, + Run: func(cmd *cobra.Command, args []string) { + if do.ClusterName == "" { + exitWithHelp(cmd, "Please provide cluster name.") + } + if err := RunDelete(); err != nil { + glog.Exit(err) + } + }, + } + + return cmd +} + +func init() { + deleteCmd.AddCommand(NewCmdDeleteCluster()) +} + +func RunDelete() error { + return errors.NotImplementedError +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/root.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/root.go new file mode 100644 index 000000000..b6a20e0f3 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/cmd/root.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 cmd + +import ( + "flag" + "os" + "github.com/golang/glog" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/apiserver/pkg/util/logs" +) + +var RootCmd = &cobra.Command{ + Use: "clusterctl", + Short: "cluster management", + Long: `Simple kubernetes cluster management`, + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + cmd.Help() + }, +} + +func Execute() { + if err := RootCmd.Execute(); err != nil { + glog.Exit(err) + } +} + +func exitWithHelp(cmd *cobra.Command, err string) { + glog.Error(err) + cmd.Help() + os.Exit(1) +} + +func init() { + flag.CommandLine.Parse([]string{}) + + // Honor glog flags for verbosity control + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + + logs.InitLogs() +} diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/.gitignore b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/.gitignore new file mode 100644 index 000000000..98ae8a57d --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/.gitignore @@ -0,0 +1 @@ +machines.yaml diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/README.md b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/README.md new file mode 100644 index 000000000..a90a7b51a --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/README.md @@ -0,0 +1,24 @@ +# Google Example Files +## Contents +*.yaml files - concrete example files that can be used as is. +*.yaml.template files - template example files that need values filled in before use. + +## Generation +For convenience, a generation script which populates templates based on gcloud +configuration is provided. + +1. Run the generation script. +``` +./generate-yaml.sh +``` + +If gcloud isn't configured, you will see an error like the one below: + +``` +$ ./generate-yaml.sh +ERROR: (gcloud.config.get-value) Section [core] has no property [project]. +``` + +## Manual Modification +You may always manually curate files based on the examples provided. + diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cluster.yaml b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/cluster.yaml similarity index 100% rename from vendor/sigs.k8s.io/cluster-api/gcp-deployer/cluster.yaml rename to vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/cluster.yaml diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/generate-yaml.sh b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/generate-yaml.sh new file mode 100755 index 000000000..494d0d1f1 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/generate-yaml.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -e + +GCLOUD_PROJECT=$(gcloud config get-value project) +ZONE=$(gcloud config get-value compute/zone) +ZONE="${ZONE:-us-central1-f}" + +TEMPLATE_FILE=machines.yaml.template +GENERATED_FILE=machines.yaml +OVERWRITE=0 + +SCRIPT=$(basename $0) +while test $# -gt 0; do + case "$1" in + -h|--help) + echo "$SCRIPT - generates input yaml files for Cluster API on Google Cloud Platform" + echo " " + echo "$SCRIPT [options]" + echo " " + echo "options:" + echo "-h, --help show brief help" + echo "-f, --force-overwrite if file to be generated already exists, force script to overwrite it" + exit 0 + ;; + -f) + OVERWRITE=1 + shift + ;; + --force-overwrite) + OVERWRITE=1 + shift + ;; + *) + break + ;; + esac +done + +if [ $OVERWRITE -ne 1 ] && [ -f $GENERATED_FILE ]; then + echo File $GENERATED_FILE already exists. Delete it manually before running this script. + exit 1 +fi + +sed -e "s/\$GCLOUD_PROJECT/$GCLOUD_PROJECT/" $TEMPLATE_FILE | sed -e "s/\$ZONE/$ZONE/" > $GENERATED_FILE diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/machines.yaml.template b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/machines.yaml.template new file mode 100644 index 000000000..b43d46768 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/examples/google/machines.yaml.template @@ -0,0 +1,54 @@ +items: +- apiVersion: "cluster.k8s.io/v1alpha1" + kind: Machine + metadata: + generateName: gce-master- + labels: + set: master + spec: + providerConfig: + value: + apiVersion: "gceproviderconfig/v1alpha1" + kind: "GCEProviderConfig" + project: "$GCLOUD_PROJECT" + zone: "$ZONE" + machineType: "n1-standard-2" + os: "ubuntu-1604-lts" + disks: + - initializeParams: + diskSizeGb: 30 + diskType: "pd-standard" + versions: + kubelet: 1.9.4 + controlPlane: 1.9.4 + containerRuntime: + name: docker + version: 1.12.0 + roles: + - Master +- apiVersion: "cluster.k8s.io/v1alpha1" + kind: Machine + metadata: + generateName: gce-node- + labels: + set: node + spec: + providerConfig: + value: + apiVersion: "gceproviderconfig/v1alpha1" + kind: "GCEProviderConfig" + project: "$GCLOUD_PROJECT" + zone: "$ZONE" + machineType: "n1-standard-1" + os: "ubuntu-1604-lts" + disks: + - initializeParams: + diskSizeGb: 30 + diskType: "pd-standard" + versions: + kubelet: 1.9.4 + containerRuntime: + name: docker + version: 1.12.0 + roles: + - Node diff --git a/vendor/sigs.k8s.io/cluster-api/clusterctl/main.go b/vendor/sigs.k8s.io/cluster-api/clusterctl/main.go new file mode 100644 index 000000000..736fcc0e1 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/clusterctl/main.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 main + +import "sigs.k8s.io/cluster-api/clusterctl/cmd" + +func main() { + cmd.Execute() +} diff --git a/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Dockerfile b/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Dockerfile index f34f894f0..04ce47c1b 100644 --- a/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Dockerfile +++ b/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Dockerfile @@ -23,7 +23,7 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' sigs.k8s.io/cluster-api/cmd/apiserver # Final container -FROM alpine:3.7 -RUN apk --no-cache add ca-certificates bash +FROM debian:stretch-slim +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* COPY --from=builder /go/bin/apiserver . diff --git a/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Makefile b/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Makefile index bf8e88af8..902a39dbe 100644 --- a/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Makefile +++ b/vendor/sigs.k8s.io/cluster-api/cmd/apiserver/Makefile @@ -18,7 +18,7 @@ GCR_BUCKET = k8s-cluster-api PREFIX = gcr.io/$(GCR_BUCKET) DEV_PREFIX ?= gcr.io/$(shell gcloud config get-value project) NAME = cluster-apiserver -TAG = 0.0.3 +TAG = 0.0.4 image: docker build -t "$(PREFIX)/$(NAME):$(TAG)" -f ./Dockerfile ../.. diff --git a/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Dockerfile b/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Dockerfile index e1c1392ec..546a903ed 100644 --- a/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Dockerfile +++ b/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Dockerfile @@ -23,7 +23,7 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags "-static"' sigs.k8s.io/cluster-api/cmd/controller-manager # Final container -FROM alpine:3.7 -RUN apk --no-cache add ca-certificates bash +FROM debian:stretch-slim +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* COPY --from=builder /go/bin/controller-manager . diff --git a/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Makefile b/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Makefile index 7d9059741..15929f383 100644 --- a/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Makefile +++ b/vendor/sigs.k8s.io/cluster-api/cmd/controller-manager/Makefile @@ -18,7 +18,7 @@ GCR_BUCKET = k8s-cluster-api PREFIX = gcr.io/$(GCR_BUCKET) DEV_PREFIX ?= gcr.io/$(shell gcloud config get-value project) NAME = controller-manager -TAG = 0.0.2 +TAG = 0.0.4 image: docker build -t "$(PREFIX)/$(NAME):$(TAG)" -f ./Dockerfile ../.. diff --git a/vendor/sigs.k8s.io/cluster-api/docs/examples/machinedeployment/machinedeployment.yaml b/vendor/sigs.k8s.io/cluster-api/docs/examples/machinedeployment/machinedeployment.yaml new file mode 100644 index 000000000..c092b6acb --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/docs/examples/machinedeployment/machinedeployment.yaml @@ -0,0 +1,7 @@ +note: MachineDeployment Example +sample: | + apiVersion: cluster.k8s.io/v1alpha1 + kind: MachineDeployment + metadata: + name: machinedeployment-example + spec: diff --git a/vendor/sigs.k8s.io/cluster-api/errors/deployer.go b/vendor/sigs.k8s.io/cluster-api/errors/deployer.go new file mode 100644 index 000000000..8c7041dbf --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/errors/deployer.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 errors + +import ( + "fmt" +) + +var NotImplementedError = fmt.Errorf("not implemented") diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/.gitignore b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/.gitignore index b6281a4f9..69546c0cd 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/.gitignore +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/.gitignore @@ -1,2 +1,3 @@ gcp-deployer machines.yaml +cluster.yaml diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/CONTRIBUTING.md b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/CONTRIBUTING.md index e86882343..0c9be1aa5 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/CONTRIBUTING.md +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/CONTRIBUTING.md @@ -60,10 +60,6 @@ This message shows that your installation appears to be working correctly. ... ``` -### Install OpenSSL - -Install [OpenSSL](https://www.openssl.org/source/) on your machine. Please note that this is just temporary. We are working to remove this dependency. See [Issue](https://github.com/kubernetes/kube-deploy/issues/591) - ### Install APIServer-Builder (Optional) If you need to rebuild container image for the extension APIServer and Controller Manager, you will need to install [APIServer-builder](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/installing.md) @@ -100,8 +96,8 @@ After making changes to the controllers or the actuator, you need to follow thes 1. Rebuild the machine-controller image. Also change `machineControllerImage` in `cloud/google/pods.go` to the new image path (make sure the version in the Makefile and `pods.go` match if you want to use the new image). Then, rebuild and push the image. ```bash - $ cd $GOPATH/src/k8s.io/kube-deploy/cluster-api - $ apiserver-boot build container --image gcr.io/$(GCLOUD_PROJECT)/apiserver-controller:$(VERSION) --generate=false + $ cd $GOPATH/src/sigs.k8s.io/cluster-api/cloud/google/cmd/gce-machine-controller + $ make dev_push ``` NOTE: that the image will be pushed to `gcr.io/$(GCLOUD_PROJECT)/apiserver-controller`. Image storage is a billable resource. @@ -139,12 +135,13 @@ ERROR: (gcloud.config.get-value) Section [core] has no property [project]. ```bash $ ./gcp-deployer create -c cluster.yaml -m machines.yaml -s machine_setup_configs.yaml ``` -[Optional]To verify API server has been deployed successfully, you can the following command to double check. - ```bash + [Optional]To verify API server has been deployed successfully, you can the following command to double check. + + ```bash $ kubectl get apiservices v1alpha1.cluster.k8s.io -o yaml - ``` - + ``` + 2. Edit the machine to trigger an update ```bash diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/README.md b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/README.md index 0e4f2013b..534454263 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/README.md +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/README.md @@ -20,8 +20,10 @@ gcp-deployer tool only supports Kubernetes version 1.9 or newer. 1. *Optional* update `machines.yaml` to give your preferred GCP zone in each machine's `providerConfig` field. 1. *Optional*: Update `cluster.yaml` to set a custom cluster name. -1. Create a cluster: `./gcp-deployer create -c cluster.yaml -m machines.yaml -s machine_setup_configs.yaml`. +1. Create a cluster: `./gcp-deployer create -c cluster.yaml -m machines.yaml -s machine_setup_configs.yaml -a /path/to/certificate/authority`. 1. **Note**: The `--machinesetup` or `-s` flag is set to `machine_setup_configs.yaml` by default. + 1. Note: The `--certificate-authority-path` or `a` flag is optional. If not + supplied, then a certificate authority will be generated. During cluster creation, you can watch the machine resources get created in Kubernetes, see the corresponding virtual machines created in GCP, and then finally see nodes @@ -74,6 +76,10 @@ to find the broken node (using the dry run flag) and fix it. ### Deleting a cluster +***NOTE***: Before deleting your cluster, it is recommended that you delete any Kubernetes +objects which have created resources external to the cluster, like services with type LoadBalancer, +some types of persistent volume claims, and ingress resources. + To delete your cluster run `./gcp-deployer delete` diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cluster.yaml.template b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cluster.yaml.template new file mode 100644 index 000000000..128884a53 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cluster.yaml.template @@ -0,0 +1,16 @@ +apiVersion: "cluster.k8s.io/v1alpha1" +kind: Cluster +metadata: + name: test1 +spec: + clusterNetwork: + services: + cidrBlocks: ["10.96.0.0/12"] + pods: + cidrBlocks: ["192.168.0.0/16"] + serviceDomain: "cluster.local" + providerConfig: + value: + apiVersion: "gceproviderconfig/v1alpha1" + kind: "GCEClusterProviderConfig" + project: "$GCLOUD_PROJECT" diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/add.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/add.go index 4051735a6..e575bf660 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/add.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/add.go @@ -52,7 +52,7 @@ func RunAdd(ao *AddOptions) error { return err } - d := deploy.NewDeployer(provider, kubeConfig, "") + d := deploy.NewDeployer(provider, kubeConfig, "", nil) return d.AddNodes(machines) } diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/create.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/create.go index 2a2be289c..84b2ee0d6 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/create.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/create.go @@ -22,12 +22,14 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" "sigs.k8s.io/cluster-api/gcp-deployer/deploy" + "sigs.k8s.io/cluster-api/pkg/cert" ) type CreateOptions struct { - Cluster string - Machine string - MachineSetup string + Cluster string + Machine string + MachineSetup string + CertificateAuthorityPath string } var co = &CreateOptions{} @@ -69,14 +71,32 @@ func RunCreate(co *CreateOptions) error { return err } - d := deploy.NewDeployer(provider, kubeConfig, co.MachineSetup) + ca, err := loadCA() + if err != nil { + return err + } + + d := deploy.NewDeployer(provider, kubeConfig, co.MachineSetup, ca) return d.CreateCluster(cluster, machines) } + func init() { createCmd.Flags().StringVarP(&co.Cluster, "cluster", "c", "", "cluster yaml file") createCmd.Flags().StringVarP(&co.Machine, "machines", "m", "", "machine yaml file") createCmd.Flags().StringVarP(&co.MachineSetup, "machinesetup", "s", "machine_setup_configs.yaml", "machine setup configs yaml file") + caHelpMessage := `optional path to a custom certificate authority to be used on a new cluster, path can be one of the following: + 1. directory: a path to a directory, the directory must contain two files named ca.crt and ca.key containing the certificate and private key respectively. + 2. certificate: a path to a certificate file, ${filename}.crt, the file must end with extension '.crt' and there must be a file named ${filename}.key in the same directory. + 3. key: a path to a key file, ${filename}.key, the file must end with extension '.key' and there must be a file named ${filename}.crt in the same directory.` + createCmd.Flags().StringVarP(&co.CertificateAuthorityPath, "certificate-authority-path", "a", "", caHelpMessage) RootCmd.AddCommand(createCmd) } + +func loadCA() (*cert.CertificateAuthority, error) { + if co.CertificateAuthorityPath == "" { + return nil, nil + } + return cert.Load(co.CertificateAuthorityPath) +} diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/delete.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/delete.go index e8fa29d81..64195df66 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/delete.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/cmd/delete.go @@ -34,7 +34,7 @@ var deleteCmd = &cobra.Command{ } func RunDelete() error { - d := deploy.NewDeployer(provider, kubeConfig, "") + d := deploy.NewDeployer(provider, kubeConfig, "", nil) return d.DeleteCluster() } diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy.go index ae87f20ff..7ab16b8d8 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy.go @@ -24,7 +24,9 @@ import ( "k8s.io/client-go/kubernetes" "sigs.k8s.io/cluster-api/cloud/google" + "sigs.k8s.io/cluster-api/cloud/google/machinesetup" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/cert" "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" "sigs.k8s.io/cluster-api/util" @@ -42,7 +44,7 @@ type deployer struct { // NewDeployer returns a cloud provider specific deployer and // sets kubeconfig path for the cluster to be deployed -func NewDeployer(provider string, kubeConfigPath string, machineSetupConfigPath string) *deployer { +func NewDeployer(provider string, kubeConfigPath string, machineSetupConfigPath string, ca *cert.CertificateAuthority) *deployer { token := util.RandomToken() if kubeConfigPath == "" { kubeConfigPath = os.Getenv("KUBECONFIG") @@ -56,7 +58,16 @@ func NewDeployer(provider string, kubeConfigPath string, machineSetupConfigPath glog.Exit(fmt.Sprintf("Failed to set Kubeconfig path err %v\n", err)) } } - ma, err := google.NewMachineActuator(token, nil, machineSetupConfigPath) + configWatch, err := newConfigWatchOrNil(machineSetupConfigPath) + if err != nil { + glog.Exit(fmt.Sprintf("Could not create config watch: %v\n", err)) + } + params := google.MachineActuatorParams{ + CertificateAuthority: ca, + MachineSetupConfigGetter: configWatch, + KubeadmToken: token, + } + ma, err := google.NewMachineActuator(params) if err != nil { glog.Exit(err) } @@ -71,7 +82,7 @@ func (d *deployer) CreateCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac vmCreated := false if err := d.createCluster(c, machines, &vmCreated); err != nil { if vmCreated { - d.deleteMasterVM(machines) + d.deleteMasterVM(c, machines) } d.machineDeployer.PostDelete(c, machines) return err @@ -109,7 +120,8 @@ func (d *deployer) DeleteCluster() error { return err } - if err := d.deleteMasterVM(machines); err != nil { + glog.Info("Deleting master VM") + if err := d.deleteMasterVM(cluster, machines); err != nil { glog.Errorf("Error deleting master vm", err) } @@ -121,15 +133,22 @@ func (d *deployer) DeleteCluster() error { return nil } -func (d *deployer) deleteMasterVM(machines []*clusterv1.Machine) error { +func (d *deployer) deleteMasterVM(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { master := util.GetMaster(machines) if master == nil { return fmt.Errorf("error deleting master vm, no master found") } glog.Infof("Deleting master vm %s", master.Name) - if err := d.machineDeployer.Delete(master); err != nil { + if err := d.machineDeployer.Delete(cluster, master); err != nil { return err } return nil } + +func newConfigWatchOrNil(machineSetupConfigPath string) (*machinesetup.ConfigWatch, error) { + if machineSetupConfigPath == "" { + return nil, nil + } + return machinesetup.NewConfigWatch(machineSetupConfigPath) +} diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy_helper.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy_helper.go index 337a1e366..c92dbb50c 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy_helper.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/deploy_helper.go @@ -55,6 +55,12 @@ func (d *deployer) createCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac master.Name = master.GetGenerateName() + c.GetName() } + glog.Infof("Starting cluster dependency creation %s", c.GetName()) + + if err := d.machineDeployer.ProvisionClusterDependencies(c, machines); err != nil { + return err + } + glog.Infof("Starting cluster creation %s", c.GetName()) glog.Infof("Starting master creation %s", master.GetName()) @@ -66,12 +72,12 @@ func (d *deployer) createCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac *vmCreated = true glog.Infof("Created master %s", master.GetName()) - masterIP, err := d.getMasterIP(master) + masterIP, err := d.getMasterIP(c, master) if err != nil { return fmt.Errorf("unable to get master IP: %v", err) } - if err := d.copyKubeConfig(master); err != nil { + if err := d.copyKubeConfig(c, master); err != nil { return fmt.Errorf("unable to write kubeconfig: %v", err) } @@ -92,13 +98,18 @@ func (d *deployer) createCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac return fmt.Errorf("can't create machine controller: %v", err) } + glog.Info("Creating additional cluster resources...") + if err := d.machineDeployer.PostCreate(c, machines); err != nil { + return fmt.Errorf("can't create additional cluster resources: %v", err) + } + if err := d.waitForClusterResourceReady(); err != nil { - return err + return fmt.Errorf("cluster resource isn't ready: %v", err) } c, err = d.client.Clusters(apiv1.NamespaceDefault).Create(c) if err != nil { - return err + return fmt.Errorf("can't create cluster: %v", err) } c.Status.APIEndpoints = append(c.Status.APIEndpoints, @@ -107,11 +118,11 @@ func (d *deployer) createCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac Port: 443, }) if _, err := d.client.Clusters(apiv1.NamespaceDefault).UpdateStatus(c); err != nil { - return err + return fmt.Errorf("can't update status: %v", err) } if err := d.createMachines(machines); err != nil { - return err + return fmt.Errorf("can't create machines: %v", err) } return nil } @@ -148,14 +159,24 @@ func (d *deployer) deleteAllMachines() error { if err != nil { return err } + glog.Infof("Deleting non-master machines...") + var deletedMachineNames []string for _, m := range machines.Items { if !util.IsMaster(&m) { - if err := d.delete(m.Name); err != nil { + err = d.client.Machines(apiv1.NamespaceDefault).Delete(m.Name, &metav1.DeleteOptions{}) + if err != nil { return err } + deletedMachineNames = append(deletedMachineNames, m.Name) glog.Infof("Deleted machine object %s", m.Name) } } + for _, name := range deletedMachineNames { + err = d.ensureDeletionCompleted(name) + if err != nil { + return err + } + } return nil } @@ -164,12 +185,20 @@ func (d *deployer) delete(name string) error { if err != nil { return err } - err = util.Poll(500*time.Millisecond, 120*time.Second, func() (bool, error) { - if _, err = d.client.Machines(apiv1.NamespaceDefault).Get(name, metav1.GetOptions{}); err == nil { + err = d.ensureDeletionCompleted(name) + return err +} + +func (d *deployer) ensureDeletionCompleted(machineName string) error { + err := util.Poll(500*time.Millisecond, 240*time.Second, func() (bool, error) { + if _, err := d.client.Machines(apiv1.NamespaceDefault).Get(machineName, metav1.GetOptions{}); err == nil { return false, nil } return true, nil }) + if err != nil { + return fmt.Errorf("unable to ensure machine %v has been deleted: %v", machineName, err) + } return err } @@ -192,9 +221,9 @@ func (d *deployer) getCluster() (*clusterv1.Cluster, error) { return &clusters.Items[0], nil } -func (d *deployer) getMasterIP(master *clusterv1.Machine) (string, error) { +func (d *deployer) getMasterIP(cluster *clusterv1.Cluster, master *clusterv1.Machine) (string, error) { for i := 0; i < MasterIPAttempts; i++ { - ip, err := d.machineDeployer.GetIP(master) + ip, err := d.machineDeployer.GetIP(cluster, master) if err != nil || ip == "" { glog.Info("Hanging for master IP...") time.Sleep(time.Duration(SleepSecondsPerAttempt) * time.Second) @@ -205,10 +234,10 @@ func (d *deployer) getMasterIP(master *clusterv1.Machine) (string, error) { return "", fmt.Errorf("unable to find Master IP after defined wait") } -func (d *deployer) copyKubeConfig(master *clusterv1.Machine) error { +func (d *deployer) copyKubeConfig(cluster *clusterv1.Cluster, master *clusterv1.Machine) error { writeErr := util.Retry(func() (bool, error) { glog.Infof("Waiting for Kubernetes to come up...") - config, err := d.machineDeployer.GetKubeConfig(master) + config, err := d.machineDeployer.GetKubeConfig(cluster, master) if err != nil { glog.Errorf("Error while retriving kubeconfig %s", err) return false, err diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/machinedeployer.go b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/machinedeployer.go index 449494a3e..c7d714775 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/machinedeployer.go +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/deploy/machinedeployer.go @@ -9,14 +9,19 @@ import ( // Provider-specific machine logic the deployer needs. type machineDeployer interface { machine.Actuator - GetIP(machine *clusterv1.Machine) (string, error) - GetKubeConfig(master *clusterv1.Machine) (string, error) + GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) + GetKubeConfig(cluster *clusterv1.Cluster, master *clusterv1.Machine) (string, error) + // Provision infrastructure that the cluster needs before it + // can be created + ProvisionClusterDependencies(*clusterv1.Cluster, []*clusterv1.Machine) error // Create and start the machine controller. The list of initial // machines don't have to be reconciled as part of this function, but // are provided in case the function wants to refer to them (and their // ProviderConfigs) to know how to configure the machine controller. // Not idempotent. CreateMachineController(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine, clientSet kubernetes.Clientset) error + // Create GCE and kubernetes resources after the cluster is created + PostCreate(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error PostDelete(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error } diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/generate-yaml.sh b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/generate-yaml.sh index 494d0d1f1..920a78524 100755 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/generate-yaml.sh +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/generate-yaml.sh @@ -5,8 +5,10 @@ GCLOUD_PROJECT=$(gcloud config get-value project) ZONE=$(gcloud config get-value compute/zone) ZONE="${ZONE:-us-central1-f}" -TEMPLATE_FILE=machines.yaml.template -GENERATED_FILE=machines.yaml +MACHINE_TEMPLATE_FILE=machines.yaml.template +MACHINE_GENERATED_FILE=machines.yaml +CLUSTER_TEMPLATE_FILE=cluster.yaml.template +CLUSTER_GENERATED_FILE=cluster.yaml OVERWRITE=0 SCRIPT=$(basename $0) @@ -36,9 +38,16 @@ while test $# -gt 0; do esac done -if [ $OVERWRITE -ne 1 ] && [ -f $GENERATED_FILE ]; then - echo File $GENERATED_FILE already exists. Delete it manually before running this script. +if [ $OVERWRITE -ne 1 ] && [ -f $MACHINE_GENERATED_FILE ]; then + echo File $MACHINE_GENERATED_FILE already exists. Delete it manually before running this script. exit 1 fi -sed -e "s/\$GCLOUD_PROJECT/$GCLOUD_PROJECT/" $TEMPLATE_FILE | sed -e "s/\$ZONE/$ZONE/" > $GENERATED_FILE +if [ $OVERWRITE -ne 1 ] && [ -f $CLUSTER_GENERATED_FILE ]; then + echo File $CLUSTER_GENERATED_FILE already exists. Delete it manually before running this script. + exit 1 +fi + +sed -e "s/\$ZONE/$ZONE/" $MACHINE_TEMPLATE_FILE > $MACHINE_GENERATED_FILE + +sed -e "s/\$GCLOUD_PROJECT/$GCLOUD_PROJECT/" $CLUSTER_TEMPLATE_FILE > $CLUSTER_GENERATED_FILE diff --git a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/machine_setup_configs.yaml b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/machine_setup_configs.yaml index 92e5d0507..c187660bb 100644 --- a/vendor/sigs.k8s.io/cluster-api/gcp-deployer/machine_setup_configs.yaml +++ b/vendor/sigs.k8s.io/cluster-api/gcp-deployer/machine_setup_configs.yaml @@ -16,6 +16,11 @@ items: set -x ( ARCH=amd64 + + function curl_metadata() { + curl --retry 5 --silent --fail --header "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/$@" + } + curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - touch /etc/apt/sources.list.d/kubernetes.list sh -c 'echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list' @@ -64,17 +69,19 @@ items: mv /usr/bin/kubeadm.dl /usr/bin/kubeadm chmod a+rx /usr/bin/kubeadm - # Override network args to use kubenet instead of cni, and override Kubelet DNS args. + # Override network args to use kubenet instead of cni, override Kubelet DNS args and + # add cloud provider args. cat > /etc/systemd/system/kubelet.service.d/20-kubenet.conf < /tmp/.ip - PUBLICIP=`curl --retry 5 -sfH "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"` + PUBLICIP=`curl_metadata "network-interfaces/0/access-configs/0/external-ip"` # Set up the GCE cloud config, which gets picked up by kubeadm init since cloudProvider is set to GCE. cat > /etc/kubernetes/cloud-config < ${CA_CERT_PATH} + chmod 0644 ${CA_CERT_PATH} + CA_KEY_PATH=${PKI_PATH}/ca.key + curl_metadata "attributes/ca-key" | base64 -d > ${CA_KEY_PATH} + chmod 0600 ${CA_KEY_PATH} + } + # Create and set bridge-nf-call-iptables to 1 to pass the kubeadm preflight check. # Workaround was found here: # http://zeeshanali.com/sysadmin/fixed-sysctl-cannot-stat-procsysnetbridgebridge-nf-call-iptables/ modprobe br_netfilter + install_certificates + kubeadm init --config /etc/kubernetes/kubeadm_config.yaml + for tries in $(seq 1 60); do kubectl --kubeconfig /etc/kubernetes/kubelet.conf annotate --overwrite node $(hostname) machine=${MACHINE} && break sleep 1 @@ -152,6 +180,7 @@ items: systemctl enable docker systemctl start docker } + install_configure_docker curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - @@ -159,6 +188,15 @@ items: deb http://apt.kubernetes.io/ kubernetes-xenial main EOF apt-get update + + mkdir -p /etc/kubernetes/ + cat > /etc/kubernetes/cloud-config < /etc/systemd/system/kubelet.service.d/20-kubenet.conf < 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "should not be more than 100%")) + } + } + + if rollingUpdate.MaxSurge != nil { + allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...) + maxSurge, _ = getIntOrPercent(rollingUpdate.MaxSurge, true) + } + + if rollingUpdate.MaxUnavailable != nil && rollingUpdate.MaxSurge != nil && maxUnavailable == 0 && maxSurge == 0 { // Both MaxSurge and MaxUnavailable cannot be zero. allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "may not be 0 when `maxSurge` is 0")) } - // Validate that MaxUnavailable is not more than 100%. - if len(utilvalidation.IsValidPercent(rollingUpdate.MaxUnavailable.StrVal)) == 0 && maxUnavailable > 100 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "should not be more than 100%")) - } return allErrs } @@ -259,9 +273,9 @@ func (MachineDeploymentSchemeFns) DefaultingFunction(o interface{}) { *obj.Spec.Replicas = 1 } - if obj.Spec.ProgressDeadlineSeconds == nil { - obj.Spec.ProgressDeadlineSeconds = new(int32) - *obj.Spec.ProgressDeadlineSeconds = 600 + if obj.Spec.MinReadySeconds == nil { + obj.Spec.MinReadySeconds = new(int32) + *obj.Spec.MinReadySeconds = 0 } if obj.Spec.RevisionHistoryLimit == nil { @@ -269,18 +283,30 @@ func (MachineDeploymentSchemeFns) DefaultingFunction(o interface{}) { *obj.Spec.RevisionHistoryLimit = 1 } - if obj.Spec.Strategy.RollingUpdate == nil { - obj.Spec.Strategy.RollingUpdate = &MachineRollingUpdateDeployment{} + if obj.Spec.ProgressDeadlineSeconds == nil { + obj.Spec.ProgressDeadlineSeconds = new(int32) + *obj.Spec.ProgressDeadlineSeconds = 600 } - if obj.Spec.Strategy.RollingUpdate.MaxSurge == nil { - x := intstr.FromInt(1) - obj.Spec.Strategy.RollingUpdate.MaxSurge = &x + if obj.Spec.Strategy.Type == "" { + obj.Spec.Strategy.Type = common.RollingUpdateMachineDeploymentStrategyType } - if obj.Spec.Strategy.RollingUpdate.MaxUnavailable == nil { - x := intstr.FromInt(0) - obj.Spec.Strategy.RollingUpdate.MaxUnavailable = &x + // Default RollingUpdate strategy only if strategy type is RollingUpdate. + if obj.Spec.Strategy.Type == common.RollingUpdateMachineDeploymentStrategyType { + if obj.Spec.Strategy.RollingUpdate == nil { + obj.Spec.Strategy.RollingUpdate = &MachineRollingUpdateDeployment{} + } + + if obj.Spec.Strategy.RollingUpdate.MaxSurge == nil { + x := intstr.FromInt(1) + obj.Spec.Strategy.RollingUpdate.MaxSurge = &x + } + + if obj.Spec.Strategy.RollingUpdate.MaxUnavailable == nil { + x := intstr.FromInt(0) + obj.Spec.Strategy.RollingUpdate.MaxUnavailable = &x + } } if len(obj.Namespace) == 0 { diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machinedeployment_types_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machinedeployment_types_test.go new file mode 100644 index 000000000..de4938c34 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machinedeployment_types_test.go @@ -0,0 +1,429 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 v1alpha1_test + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/cluster-api/pkg/apis/cluster" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" +) + +func TestMachineDeploymentValidationStrategy(t *testing.T) { + var badReplicaCount int32 = -1 + var goodReplicaCount int32 + var badStrategyType common.MachineDeploymentStrategyType = "BAD-STRATEGY" + var goodStrategyType common.MachineDeploymentStrategyType = "RollingUpdate" + badCount := intstr.FromInt(-3) + badPercent := intstr.FromString("-50%") + goodCount := intstr.FromInt(2) + goodPercent := intstr.FromString("15%") + zeroCount := intstr.FromInt(0) + zeroPercent := intstr.FromString("0%") + over100Percent := intstr.FromString("101%") + + tests := []struct { + name string + machineDeploymentToTest *cluster.MachineDeployment + expectError bool + }{ + { + name: "scenario 1: a machine deployment with empty selector is not valid", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + }, + }, + expectError: true, + }, + { + name: "scenario 2: a machine deployment with valid selector but with empty template.Labels is not valid", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + }, + }, + expectError: true, + }, + { + name: "scenario 3: a machine deployment with valid selector and with corresponding template.Labels is valid", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Strategy: cluster.MachineDeploymentStrategy{ + Type: "RollingUpdate", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + }, + expectError: false, + }, + { + name: "scenario 4: a machine deployment with valid selector but w/o corresponding template.Labels is not valid", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"bar": "foo"}, + }, + }, + }, + }, + expectError: true, + }, + { + name: "scenario 5: a machine deployment with bad replicas", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &badReplicaCount, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + }, + expectError: true, + }, + { + name: "scenario 6: a machine deployment with good replicas", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Strategy: cluster.MachineDeploymentStrategy{ + Type: "RollingUpdate", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + }, + expectError: false, + }, + { + name: "scenario 7: a machine deployment with bad strategy", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Strategy: cluster.MachineDeploymentStrategy{ + Type: badStrategyType, + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + }, + expectError: true, + }, + { + name: "scenario 8: a machine deployment with good strategy", + machineDeploymentToTest: &cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Strategy: cluster.MachineDeploymentStrategy{ + Type: goodStrategyType, + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + }, + expectError: false, + }, + { + name: "scenario 9: a machine deployment with bad MaxUnavailable count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&badCount, nil), + expectError: true, + }, + { + name: "scenario 10: a machine deployment with bad MaxUnavailable percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&badPercent, nil), + expectError: true, + }, + { + name: "scenario 11: a machine deployment with good MaxUnavailable count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodCount, nil), + expectError: false, + }, + { + name: "scenario 12: a machine deployment with good MaxUnavailable percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodPercent, nil), + expectError: false, + }, + { + name: "scenario 13: a machine deployment with over 100 MaxUnavailable percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&over100Percent, nil), + expectError: true, + }, + { + name: "scenario 14: a machine deployment with zero MaxUnavailable count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&zeroCount, nil), + expectError: false, + }, + { + name: "scenario 15: a machine deployment with zero MaxUnavailable percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&zeroPercent, nil), + expectError: false, + }, + { + name: "scenario 16: a machine deployment with bad MaxSurge count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &badCount), + expectError: true, + }, + { + name: "scenario 17: a machine deployment with bad MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &badPercent), + expectError: true, + }, + { + name: "scenario 18: a machine deployment with good MaxSurge count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &goodCount), + expectError: false, + }, + { + name: "scenario 19: a machine deployment with good MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &goodPercent), + expectError: false, + }, + { + name: "scenario 20: a machine deployment with over 100 MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &over100Percent), + expectError: false, + }, + { + name: "scenario 21: a machine deployment with zero MaxSurge count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &zeroCount), + expectError: false, + }, + { + name: "scenario 22: a machine deployment with zero MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(nil, &zeroPercent), + expectError: false, + }, + { + name: "scenario 23: a machine deployment with bad MaxUnavailable/MaxSurge both 0", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&zeroCount, &zeroCount), + expectError: true, + }, + { + name: "scenario 24: a machine deployment with bad MaxUnavailable/MaxSurge both 0%", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&zeroPercent, &zeroPercent), + expectError: true, + }, + { + name: "scenario 25: a machine deployment with good MaxUnavailable count, MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodCount, &goodPercent), + expectError: false, + }, + { + name: "scenario 26: a machine deployment with good MaxUnavailable percent, MaxSurge count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodPercent, &goodCount), + expectError: false, + }, + { + name: "scenario 27: a machine deployment with good MaxUnavailable count, MaxSurge count", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodCount, &goodCount), + expectError: false, + }, + { + name: "scenario 28: a machine deployment with good MaxUnavailable percent, MaxSurge percent", + machineDeploymentToTest: getRollingUpdateMachineDeployment(&goodPercent, &goodPercent), + expectError: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // setup the test scenario + ctx := genericapirequest.NewDefaultContext() + target := v1alpha1.MachineDeploymentValidationStrategy{} + + // act + errors := target.Validate(ctx, test.machineDeploymentToTest) + + // validate + if len(errors) > 0 && !test.expectError { + t.Fatalf("an unexpected error was returned = %v", errors) + } + if test.expectError && len(errors) == 0 { + t.Fatal("expected an error but non was returned") + } + + }) + } +} + +func getRollingUpdateMachineDeployment(unavailable, surge *intstr.IntOrString) *cluster.MachineDeployment { + var goodReplicaCount int32 + d := cluster.MachineDeployment{ + Spec: cluster.MachineDeploymentSpec{ + Replicas: &goodReplicaCount, + Strategy: cluster.MachineDeploymentStrategy{ + Type: "RollingUpdate", + RollingUpdate: &cluster.MachineRollingUpdateDeployment{ + }, + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: cluster.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + } + if unavailable != nil { + d.Spec.Strategy.RollingUpdate.MaxUnavailable = unavailable + } + if surge != nil { + d.Spec.Strategy.RollingUpdate.MaxSurge = surge + } + return &d +} + +func crudAccessToMachineDeploymentClient(t *testing.T, cs *clientset.Clientset) { + instance := v1alpha1.MachineDeployment{ + Spec: v1alpha1.MachineDeploymentSpec{ + + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: v1alpha1.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + }, + }, + } + instance.Name = "instance-1" + + expected := instance.DeepCopy() + // Defaulted fields. + var replicas int32 = 1 + expected.Spec.Replicas = &replicas + var minReadySeconds int32 + expected.Spec.MinReadySeconds = &minReadySeconds + var limit int32 = 1 + expected.Spec.RevisionHistoryLimit = &limit + var deadline int32 = 600 + expected.Spec.ProgressDeadlineSeconds = &deadline + expected.Spec.Strategy.Type = common.RollingUpdateMachineDeploymentStrategyType + unavailable := intstr.FromInt(0) + surge := intstr.FromInt(1) + rollingUpdate := v1alpha1.MachineRollingUpdateDeployment{ + MaxUnavailable: &unavailable, + MaxSurge: &surge, + } + expected.Spec.Strategy.RollingUpdate = &rollingUpdate + + // When sending a storage request for a valid config, + // it should provide CRUD access to the object. + client := cs.ClusterV1alpha1().MachineDeployments("default") + + // Test that the create request returns success. + actual, err := client.Create(&instance) + if err != nil { + t.Fatal(err) + } + defer client.Delete(instance.Name, &metav1.DeleteOptions{}) + if !reflect.DeepEqual(actual.Spec, expected.Spec) { + t.Fatalf( + "Default fields were not set correctly.\nActual:\t%+v\nExpected:\t%+v", + actual.Spec, expected.Spec) + } + + // Test getting the created item for list requests. + result, err := client.List(metav1.ListOptions{}) + if err != nil { + t.Fatal(err) + } + if itemLength := len(result.Items); itemLength != 1 { + t.Fatalf("Number of items in Items list should be 1, but is %d.", itemLength) + } + if resultSpec := result.Items[0].Spec; !reflect.DeepEqual(resultSpec, expected.Spec) { + t.Fatalf( + "Item returned from list is not equal to the expected item.\nActual:\t%+v\nExpected:\t%+v", + resultSpec, expected.Spec) + } + + // Test getting the created item for get requests. + actual, err = client.Get(instance.Name, metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual.Spec, expected.Spec) { + t.Fatalf( + "Item returned from get is not equal to the expected item.\nActual:\t%+v\nExpected:\t%+v", + actual.Spec, expected.Spec) + } + + actual.Finalizers = nil + // Test updating the item, removing the finalizer. + if _, updateErr := client.Update(actual); updateErr != nil { + t.Fatal(updateErr) + } + // Test deleting the item for delete requests. + if deleteErr := client.Delete(instance.Name, &metav1.DeleteOptions{}); deleteErr != nil { + t.Fatal(deleteErr) + } + result, err = client.List(metav1.ListOptions{}) + if err != nil { + t.Fatal(err) + } + if itemLength := len(result.Items); itemLength != 0 { + t.Fatalf("Number of items in Items list should be 0, but is %d.", itemLength) + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machineset_types_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machineset_types_test.go index 76df8be35..483105a47 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machineset_types_test.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/machineset_types_test.go @@ -106,14 +106,28 @@ func TestValidateMachineSetStrategy(t *testing.T) { } func crudAccessToMachineSetClient(t *testing.T, cs *clientset.Clientset) { - instance := v1alpha1.MachineSet{} + instance := v1alpha1.MachineSet{ + Spec: v1alpha1.MachineSetSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{"foo":"bar"}, + }, + Template: v1alpha1.MachineTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo":"bar"}, + }, + }, + }, + } instance.Name = "instance-1" - expected := instance + expected := instance.DeepCopy() + // Defaulted fields. + var replicas int32 = 1 + expected.Spec.Replicas = &replicas // When sending a storage request for a valid config, // it should provide CRUD access to the object. - client := cs.ClusterV1alpha1().MachineSets("machine-test-valid") + client := cs.ClusterV1alpha1().MachineSets("default") // Test that the create request returns success. actual, err := client.Create(&instance) diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/v1alpha1_suite_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/v1alpha1_suite_test.go index 9d82161d2..9c7b23dd5 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/v1alpha1_suite_test.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/v1alpha1_suite_test.go @@ -41,10 +41,10 @@ func TestV1alpha1(t *testing.T) { crudAccessToMachineClient(t, cs) }) t.Run("crudAccessToMachineSetClient", func(t *testing.T) { - // TODO: the following test fails with: - // the namespace of the provided object does not match the namespace sent on the request - // uncomment when fixed - //crudAccessToMachineSetClient(t, cs) + crudAccessToMachineSetClient(t, cs) + }) + t.Run("crudAccessToMachineDeploymentClient", func(t *testing.T) { + crudAccessToMachineDeploymentClient(t, cs) }) testenv.Stop() diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/zz_generated.deepcopy.go b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/zz_generated.deepcopy.go index 3a1a8ee7b..ed4a5ec80 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/zz_generated.deepcopy.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1/zz_generated.deepcopy.go @@ -128,6 +128,7 @@ func (in *ClusterNetworkingConfig) DeepCopy() *ClusterNetworkingConfig { func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = *in in.ClusterNetwork.DeepCopyInto(&out.ClusterNetwork) + in.ProviderConfig.DeepCopyInto(&out.ProviderConfig) return } @@ -149,6 +150,15 @@ func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { *out = make([]APIEndpoint, len(*in)) copy(*out, *in) } + if in.ProviderStatus != nil { + in, out := &in.ProviderStatus, &out.ProviderStatus + if *in == nil { + *out = nil + } else { + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + } return } @@ -610,7 +620,7 @@ func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { **out = **in } } - in.LastUpdated.DeepCopyInto(&out.LastUpdated) + out.LastUpdated = in.LastUpdated if in.Versions != nil { in, out := &in.Versions, &out.Versions if *in == nil { @@ -638,6 +648,15 @@ func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { **out = **in } } + if in.ProviderStatus != nil { + in, out := &in.ProviderStatus, &out.ProviderStatus + if *in == nil { + *out = nil + } else { + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/zz_generated.deepcopy.go b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/zz_generated.deepcopy.go index e865010f1..2d7f19817 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/zz_generated.deepcopy.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/apis/cluster/zz_generated.deepcopy.go @@ -128,6 +128,7 @@ func (in *ClusterNetworkingConfig) DeepCopy() *ClusterNetworkingConfig { func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = *in in.ClusterNetwork.DeepCopyInto(&out.ClusterNetwork) + in.ProviderConfig.DeepCopyInto(&out.ProviderConfig) return } @@ -149,6 +150,15 @@ func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { *out = make([]APIEndpoint, len(*in)) copy(*out, *in) } + if in.ProviderStatus != nil { + in, out := &in.ProviderStatus, &out.ProviderStatus + if *in == nil { + *out = nil + } else { + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + } return } @@ -610,7 +620,7 @@ func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { **out = **in } } - in.LastUpdated.DeepCopyInto(&out.LastUpdated) + out.LastUpdated = in.LastUpdated if in.Versions != nil { in, out := &in.Versions, &out.Versions if *in == nil { @@ -638,6 +648,15 @@ func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { **out = **in } } + if in.ProviderStatus != nil { + in, out := &in.ProviderStatus, &out.ProviderStatus + if *in == nil { + *out = nil + } else { + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority.go b/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority.go new file mode 100644 index 000000000..aabb12448 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority.go @@ -0,0 +1,73 @@ +package cert + +import ( + "fmt" + "github.com/golang/glog" + "io/ioutil" + "os" + "path/filepath" +) + +type CertificateAuthority struct { + Certificate []byte + PrivateKey []byte +} + +// Load takes a given path to what is presumed to be a valid certificate authority and attempts to load both the +// certificate and private key. If the path is a directory then it is assumed that there are two files "ca.crt" and +// "ca.key" which represent the certificate and private key respectively. +// +// If the argument is not a directory, it must be a file ending in either of the .crt or .key extensions. Load will +// read the given file and attempt to load the other associated file. For example, if the path given is /path/to/my-ca.crt, +// then load will attempt to load the private key file at /path/to/my-ca.key. +func Load(caPath string) (*CertificateAuthority, error) { + glog.Infof("Loading certificate authority from %v", caPath) + certPath, keyPath, err := certPathToCertAndKeyPaths(caPath) + if err != nil { + return nil, err + } + certMaterial, err := ioutil.ReadFile(certPath) + if err != nil { + return nil, fmt.Errorf("unable to read cert %v: %v", certPath, err) + } + keyMaterial, err := ioutil.ReadFile(keyPath) + if err != nil { + return nil, fmt.Errorf("unable to read key %v: %v", keyPath, err) + } + ca := CertificateAuthority{ + Certificate: certMaterial, + PrivateKey: keyMaterial, + } + return &ca, nil +} + +func certPathToCertAndKeyPaths(caPath string) (string, string, error) { + fi, err := os.Stat(caPath) + if err != nil { + return "", "", err + } + var certPath, keyPath string + if fi.IsDir() { + certPath = filepath.Join(caPath, "ca.crt") + keyPath = filepath.Join(caPath, "ca.key") + } else { + ext := filepath.Ext(caPath) + switch ext { + case ".crt": + certPath = caPath + keyPath = caPath[0:len(caPath)-len(ext)] + ".key" + case ".key": + certPath = caPath[0:len(caPath)-len(ext)] + ".crt" + keyPath = caPath + default: + return "", "", fmt.Errorf("unable to use certificate authority, not directory, .crt, or .key file: %v", caPath) + } + } + if _, err := os.Stat(certPath); err != nil { + return "", "", fmt.Errorf("unable to use certificate file: %v (%v)", certPath, err) + } + if _, err := os.Stat(keyPath); err != nil { + return "", "", fmt.Errorf("unable to use key file: %v (%v)", keyPath, err) + } + return certPath, keyPath, err +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority_test.go new file mode 100644 index 000000000..057ecbb96 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/cert/cert_authority_test.go @@ -0,0 +1,131 @@ +package cert_test + +import ( + "io/ioutil" + "os" + "path" + "sigs.k8s.io/cluster-api/pkg/cert" + "testing" +) + +var ( + defaultCertMaterial = "this is the cert contents" + defaultKeyMaterial = "this is the key contents" +) + +func TestEmptyPath(t *testing.T) { + _, err := cert.Load("") + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func TestInvalidPath(t *testing.T) { + _, err := cert.Load("/my/invalid/path") + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func TestDirWithMissingKey(t *testing.T) { + dir := newTempDir(t) + defer os.RemoveAll(dir) + newCaDirectory(t, dir, &defaultCertMaterial, nil) + _, err := cert.Load(dir) + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func TestDirWithMissingCert(t *testing.T) { + dir := newTempDir(t) + defer os.RemoveAll(dir) + newCaDirectory(t, dir, nil, &defaultKeyMaterial) + _, err := cert.Load(dir) + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func TestDirHappyPath(t *testing.T) { + dir := newTempDir(t) + defer os.RemoveAll(dir) + newCaDirectory(t, dir, &defaultCertMaterial, &defaultKeyMaterial) + ca, err := cert.Load(dir) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actualCertMaterial := string(ca.Certificate) + if actualCertMaterial != defaultCertMaterial { + t.Errorf("expected '%v' got '%v'", defaultCertMaterial, actualCertMaterial) + } + actualKeyMaterial := string(ca.PrivateKey) + if actualKeyMaterial != defaultKeyMaterial { + t.Errorf("expected '%v' got '%v'", defaultKeyMaterial, actualKeyMaterial) + } +} + +func TestCertPath(t *testing.T) { + dir := newTempDir(t) + defer os.RemoveAll(dir) + certPath, _ := newCaDirectory(t, dir, &defaultCertMaterial, &defaultKeyMaterial) + ca, err := cert.Load(certPath) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actualCertMaterial := string(ca.Certificate) + if actualCertMaterial != defaultCertMaterial { + t.Errorf("expected '%v' got '%v'", defaultCertMaterial, actualCertMaterial) + } + actualKeyMaterial := string(ca.PrivateKey) + if actualKeyMaterial != defaultKeyMaterial { + t.Errorf("expected '%v' got '%v'", defaultKeyMaterial, actualKeyMaterial) + } +} + +func TestKeyPath(t *testing.T) { + dir := newTempDir(t) + defer os.RemoveAll(dir) + _, keyPath := newCaDirectory(t, dir, &defaultCertMaterial, &defaultKeyMaterial) + ca, err := cert.Load(keyPath) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actualCertMaterial := string(ca.Certificate) + if actualCertMaterial != defaultCertMaterial { + t.Errorf("expected '%v' got '%v'", defaultCertMaterial, actualCertMaterial) + } + actualKeyMaterial := string(ca.PrivateKey) + if actualKeyMaterial != defaultKeyMaterial { + t.Errorf("expected '%v' got '%v'", defaultKeyMaterial, actualKeyMaterial) + } +} + +func newCaDirectory(t *testing.T, dir string, certMaterial *string, keyMaterial *string) (certPath string, keyPath string) { + certPath, keyPath = getCertAndKeyPaths(dir) + if certMaterial != nil { + err := ioutil.WriteFile(certPath, []byte(*certMaterial), 0644) + if err != nil { + t.Errorf("unable to write cert material to %v, got %v", certPath, err) + } + } + if keyMaterial != nil { + err := ioutil.WriteFile(keyPath, []byte(*keyMaterial), 0644) + if err != nil { + t.Errorf("unable to write key material to %v, got %v", keyPath, err) + } + } + return +} + +func getCertAndKeyPaths(dir string) (certPath string, keyPath string) { + return path.Join(dir, "ca.crt"), path.Join(dir, "ca.key") +} + +func newTempDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Errorf("unable to create temp dir: %v", err) + } + return dir +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/factory.go b/vendor/sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/factory.go index 5f0e064ee..479bdd7be 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/factory.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/factory.go @@ -23,10 +23,10 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" + reflect "reflect" clientset "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" cluster "sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/cluster" internalinterfaces "sigs.k8s.io/cluster-api/pkg/client/informers_generated/externalversions/internalinterfaces" - reflect "reflect" sync "sync" time "time" ) diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/BUILD b/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/BUILD deleted file mode 100644 index 3d851fbc0..000000000 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "cluster.go", - "expansion_generated.go", - "machine.go", - ], - importpath = "sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion", - visibility = ["//visibility:public"], - deps = [ - "//pkg/apis/cluster:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", - "//vendor/k8s.io/client-go/tools/cache:go_default_library", - ], -) diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/cluster.go b/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/cluster.go deleted file mode 100644 index a8437ad2d..000000000 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/cluster.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - cluster "sigs.k8s.io/cluster-api/pkg/apis/cluster" -) - -// ClusterLister helps list Clusters. -type ClusterLister interface { - // List lists all Clusters in the indexer. - List(selector labels.Selector) (ret []*cluster.Cluster, err error) - // Clusters returns an object that can list and get Clusters. - Clusters(namespace string) ClusterNamespaceLister - ClusterListerExpansion -} - -// clusterLister implements the ClusterLister interface. -type clusterLister struct { - indexer cache.Indexer -} - -// NewClusterLister returns a new ClusterLister. -func NewClusterLister(indexer cache.Indexer) ClusterLister { - return &clusterLister{indexer: indexer} -} - -// List lists all Clusters in the indexer. -func (s *clusterLister) List(selector labels.Selector) (ret []*cluster.Cluster, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.Cluster)) - }) - return ret, err -} - -// Clusters returns an object that can list and get Clusters. -func (s *clusterLister) Clusters(namespace string) ClusterNamespaceLister { - return clusterNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// ClusterNamespaceLister helps list and get Clusters. -type ClusterNamespaceLister interface { - // List lists all Clusters in the indexer for a given namespace. - List(selector labels.Selector) (ret []*cluster.Cluster, err error) - // Get retrieves the Cluster from the indexer for a given namespace and name. - Get(name string) (*cluster.Cluster, error) - ClusterNamespaceListerExpansion -} - -// clusterNamespaceLister implements the ClusterNamespaceLister -// interface. -type clusterNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Clusters in the indexer for a given namespace. -func (s clusterNamespaceLister) List(selector labels.Selector) (ret []*cluster.Cluster, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.Cluster)) - }) - return ret, err -} - -// Get retrieves the Cluster from the indexer for a given namespace and name. -func (s clusterNamespaceLister) Get(name string) (*cluster.Cluster, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(cluster.Resource("cluster"), name) - } - return obj.(*cluster.Cluster), nil -} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/expansion_generated.go b/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/expansion_generated.go deleted file mode 100644 index a1707cc5a..000000000 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/expansion_generated.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -// ClusterListerExpansion allows custom methods to be added to -// ClusterLister. -type ClusterListerExpansion interface{} - -// ClusterNamespaceListerExpansion allows custom methods to be added to -// ClusterNamespaceLister. -type ClusterNamespaceListerExpansion interface{} - -// MachineListerExpansion allows custom methods to be added to -// MachineLister. -type MachineListerExpansion interface{} - -// MachineNamespaceListerExpansion allows custom methods to be added to -// MachineNamespaceLister. -type MachineNamespaceListerExpansion interface{} - -// MachineSetListerExpansion allows custom methods to be added to -// MachineSetLister. -type MachineSetListerExpansion interface{} - -// MachineSetNamespaceListerExpansion allows custom methods to be added to -// MachineSetNamespaceLister. -type MachineSetNamespaceListerExpansion interface{} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machine.go b/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machine.go deleted file mode 100644 index 298bad5c5..000000000 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machine.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - cluster "sigs.k8s.io/cluster-api/pkg/apis/cluster" -) - -// MachineLister helps list Machines. -type MachineLister interface { - // List lists all Machines in the indexer. - List(selector labels.Selector) (ret []*cluster.Machine, err error) - // Machines returns an object that can list and get Machines. - Machines(namespace string) MachineNamespaceLister - MachineListerExpansion -} - -// machineLister implements the MachineLister interface. -type machineLister struct { - indexer cache.Indexer -} - -// NewMachineLister returns a new MachineLister. -func NewMachineLister(indexer cache.Indexer) MachineLister { - return &machineLister{indexer: indexer} -} - -// List lists all Machines in the indexer. -func (s *machineLister) List(selector labels.Selector) (ret []*cluster.Machine, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.Machine)) - }) - return ret, err -} - -// Machines returns an object that can list and get Machines. -func (s *machineLister) Machines(namespace string) MachineNamespaceLister { - return machineNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// MachineNamespaceLister helps list and get Machines. -type MachineNamespaceLister interface { - // List lists all Machines in the indexer for a given namespace. - List(selector labels.Selector) (ret []*cluster.Machine, err error) - // Get retrieves the Machine from the indexer for a given namespace and name. - Get(name string) (*cluster.Machine, error) - MachineNamespaceListerExpansion -} - -// machineNamespaceLister implements the MachineNamespaceLister -// interface. -type machineNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Machines in the indexer for a given namespace. -func (s machineNamespaceLister) List(selector labels.Selector) (ret []*cluster.Machine, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.Machine)) - }) - return ret, err -} - -// Get retrieves the Machine from the indexer for a given namespace and name. -func (s machineNamespaceLister) Get(name string) (*cluster.Machine, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(cluster.Resource("machine"), name) - } - return obj.(*cluster.Machine), nil -} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machineset.go b/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machineset.go deleted file mode 100644 index 531dfe2c8..000000000 --- a/vendor/sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/internalversion/machineset.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -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. -*/ - -// This file was automatically generated by lister-gen - -package internalversion - -import ( - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - cluster "sigs.k8s.io/cluster-api/pkg/apis/cluster" -) - -// MachineSetLister helps list MachineSets. -type MachineSetLister interface { - // List lists all MachineSets in the indexer. - List(selector labels.Selector) (ret []*cluster.MachineSet, err error) - // MachineSets returns an object that can list and get MachineSets. - MachineSets(namespace string) MachineSetNamespaceLister - MachineSetListerExpansion -} - -// machineSetLister implements the MachineSetLister interface. -type machineSetLister struct { - indexer cache.Indexer -} - -// NewMachineSetLister returns a new MachineSetLister. -func NewMachineSetLister(indexer cache.Indexer) MachineSetLister { - return &machineSetLister{indexer: indexer} -} - -// List lists all MachineSets in the indexer. -func (s *machineSetLister) List(selector labels.Selector) (ret []*cluster.MachineSet, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.MachineSet)) - }) - return ret, err -} - -// MachineSets returns an object that can list and get MachineSets. -func (s *machineSetLister) MachineSets(namespace string) MachineSetNamespaceLister { - return machineSetNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// MachineSetNamespaceLister helps list and get MachineSets. -type MachineSetNamespaceLister interface { - // List lists all MachineSets in the indexer for a given namespace. - List(selector labels.Selector) (ret []*cluster.MachineSet, err error) - // Get retrieves the MachineSet from the indexer for a given namespace and name. - Get(name string) (*cluster.MachineSet, error) - MachineSetNamespaceListerExpansion -} - -// machineSetNamespaceLister implements the MachineSetNamespaceLister -// interface. -type machineSetNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all MachineSets in the indexer for a given namespace. -func (s machineSetNamespaceLister) List(selector labels.Selector) (ret []*cluster.MachineSet, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*cluster.MachineSet)) - }) - return ret, err -} - -// Get retrieves the MachineSet from the indexer for a given namespace and name. -func (s machineSetNamespaceLister) Get(name string) (*cluster.MachineSet, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(cluster.Resource("machineset"), name) - } - return obj.(*cluster.MachineSet), nil -} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/config/configuration.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/config/configuration.go index 4fdf31517..6e236e469 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/config/configuration.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/config/configuration.go @@ -17,22 +17,102 @@ limitations under the License. package config import ( + "time" + "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/leaderelection/resourcelock" ) +// This is a COPY from kubernetes source tree to avoid importing the entire kubernetes tree. + +// LeaderElectionConfiguration defines the configuration of leader election +// clients for components that can run with leader election enabled. +type LeaderElectionConfiguration struct { + // LeaderElect enables a leader election client to gain leadership + // before executing the main loop. Enable this when running replicated + // components for high availability. + LeaderElect bool + // LeaseDuration is the duration that non-leader candidates will wait + // after observing a leadership renewal until attempting to acquire + // leadership of a led but unrenewed leader slot. This is effectively the + // maximum duration that a leader can be stopped before it is replaced + // by another candidate. This is only applicable if leader election is + // enabled. + LeaseDuration metav1.Duration + // RenewDeadline is the interval between attempts by the acting master to + // renew a leadership slot before it stops leading. This must be less + // than or equal to the lease duration. This is only applicable if leader + // election is enabled. + RenewDeadline metav1.Duration + // RetryPeriod is the duration the clients should wait between attempting + // acquisition and renewal of a leadership. This is only applicable if + // leader election is enabled. + RetryPeriod metav1.Duration + // ResourceLock indicates the resource object type that will be used to lock + // during leader election cycles. + ResourceLock string +} + type Configuration struct { - Kubeconfig string - InCluster bool - WorkerCount int + Kubeconfig string + InCluster bool + WorkerCount int + leaderElectionConfig *LeaderElectionConfiguration } +const ( + // DefaultLeaseDuration is the default lease duration for leader election + DefaultLeaseDuration = 15 * time.Second + // DefaultRenewDeadline is the default renew duration for leader election + DefaultRenewDeadline = 10 * time.Second + // DefaultRetryPeriod is the default retry period for leader election + DefaultRetryPeriod = 2 * time.Second +) + var ControllerConfig = Configuration{ InCluster: true, WorkerCount: 5, // Default 5 worker. + leaderElectionConfig: &LeaderElectionConfiguration{ + LeaderElect: false, + LeaseDuration: metav1.Duration{Duration: DefaultLeaseDuration}, + RenewDeadline: metav1.Duration{Duration: DefaultRenewDeadline}, + RetryPeriod: metav1.Duration{Duration: DefaultRetryPeriod}, + ResourceLock: resourcelock.EndpointsResourceLock, + }, +} + +func GetLeaderElectionConfig() *LeaderElectionConfiguration { + return ControllerConfig.leaderElectionConfig } func (c *Configuration) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&c.Kubeconfig, "kubeconfig", c.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") fs.BoolVar(&c.InCluster, "incluster", c.InCluster, "Controller will be running inside the cluster.") fs.IntVar(&c.WorkerCount, "workers", c.WorkerCount, "The number of workers for controller.") + + AddLeaderElectionFlags(c.leaderElectionConfig, fs) +} + +func AddLeaderElectionFlags(l *LeaderElectionConfiguration, fs *pflag.FlagSet) { + fs.BoolVar(&l.LeaderElect, "leader-elect", l.LeaderElect, ""+ + "Start a leader election client and gain leadership before "+ + "executing the main loop. Enable this when running replicated "+ + "components for high availability.") + fs.DurationVar(&l.LeaseDuration.Duration, "leader-elect-lease-duration", l.LeaseDuration.Duration, ""+ + "The duration that non-leader candidates will wait after observing a leadership "+ + "renewal until attempting to acquire leadership of a led but unrenewed leader "+ + "slot. This is effectively the maximum duration that a leader can be stopped "+ + "before it is replaced by another candidate. This is only applicable if leader "+ + "election is enabled.") + fs.DurationVar(&l.RenewDeadline.Duration, "leader-elect-renew-deadline", l.RenewDeadline.Duration, ""+ + "The interval between attempts by the acting master to renew a leadership slot "+ + "before it stops leading. This must be less than or equal to the lease duration. "+ + "This is only applicable if leader election is enabled.") + fs.DurationVar(&l.RetryPeriod.Duration, "leader-elect-retry-period", l.RetryPeriod.Duration, ""+ + "The duration the clients should wait between attempting acquisition and renewal "+ + "of a leadership. This is only applicable if leader election is enabled.") + fs.StringVar(&l.ResourceLock, "leader-elect-resource-lock", l.ResourceLock, ""+ + "The type of resource object that is used for locking during "+ + "leader election. Supported options are `endpoints` (default) and `configmap`.") } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/actuator.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/actuator.go index 6d380ead1..8451ce4c2 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/actuator.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/actuator.go @@ -26,9 +26,9 @@ type Actuator interface { // Create the machine. Create(*clusterv1.Cluster, *clusterv1.Machine) error // Delete the machine. - Delete(*clusterv1.Machine) error + Delete(*clusterv1.Cluster, *clusterv1.Machine) error // Update the machine to the provided definition. - Update(c *clusterv1.Cluster, machine *clusterv1.Machine) error + Update(*clusterv1.Cluster, *clusterv1.Machine) error // Checks if the machine currently exists. - Exists(*clusterv1.Machine) (bool, error) + Exists(*clusterv1.Cluster, *clusterv1.Machine) (bool, error) } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/controller.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/controller.go index 591f8e41b..87afc0d34 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/controller.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/controller.go @@ -44,10 +44,11 @@ type MachineControllerImpl struct { actuator Actuator - kubernetesClientSet *kubernetes.Clientset + kubernetesClientSet kubernetes.Interface clientSet clientset.Interface machineClient v1alpha1.MachineInterface linkedNodes map[string]bool + cachedReadiness map[string]bool } // Init initializes the controller and is called by the generated code @@ -64,6 +65,7 @@ func (c *MachineControllerImpl) Init(arguments sharedinformers.ControllerInitArg c.kubernetesClientSet = arguments.GetSharedInformers().KubernetesClientSet c.linkedNodes = make(map[string]bool) + c.cachedReadiness = make(map[string]bool) // Create machine actuator. // TODO: Assume default namespace for now. Maybe a separate a controller per namespace? @@ -101,14 +103,19 @@ func (c *MachineControllerImpl) Reconcile(machine *clusterv1.Machine) error { // Remove finalizer on successful deletion. glog.Infof("machine object %v deletion successful, removing finalizer.", name) machine.ObjectMeta.Finalizers = util.Filter(machine.ObjectMeta.Finalizers, clusterv1.MachineFinalizer) - if _, err := c.machineClient.Update(machine); err != nil { + if _, err := c.clientSet.ClusterV1alpha1().Machines(machine.Namespace).Update(machine); err != nil { glog.Errorf("Error removing finalizer from machine object %v; %v", name, err) return err } return nil } - exist, err := c.actuator.Exists(machine) + cluster, err := c.getCluster(machine) + if err != nil { + return err + } + + exist, err := c.actuator.Exists(cluster, machine) if err != nil { glog.Errorf("Error checking existance of machine instance for machine object %v; %v", name, err) return err @@ -147,7 +154,12 @@ func (c *MachineControllerImpl) update(new_machine *clusterv1.Machine) error { } func (c *MachineControllerImpl) delete(machine *clusterv1.Machine) error { - return c.actuator.Delete(machine) + cluster, err := c.getCluster(machine) + if err != nil { + return err + } + + return c.actuator.Delete(cluster, machine) } func (c *MachineControllerImpl) getCluster(machine *clusterv1.Machine) (*clusterv1.Cluster, error) { diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node.go index c48102b5a..c3fcc56fb 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node.go @@ -23,6 +23,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" + + "sigs.k8s.io/cluster-api/pkg/controller/noderefutil" +) + +const ( + machineAnnotationKey = "machine" ) // We are using "machine" annotation to link node and machine resource. The "machine" @@ -34,11 +40,16 @@ import ( // Currently, these annotations are added by the node itself as part of its // bootup script after "kubeadm join" succeeds. func (c *MachineControllerImpl) link(node *corev1.Node) error { + nodeReady := noderefutil.IsNodeReady(node) + + // skip update if cached and no change in readiness. if c.linkedNodes[node.ObjectMeta.Name] { - return nil + if cachedReady, ok := c.cachedReadiness[node.ObjectMeta.Name]; ok && cachedReady == nodeReady { + return nil + } } - val, ok := node.ObjectMeta.Annotations["machine"] + val, ok := node.ObjectMeta.Annotations[machineAnnotationKey] if !ok { return nil } @@ -49,6 +60,7 @@ func (c *MachineControllerImpl) link(node *corev1.Node) error { return err } + machine.Status.LastUpdated = metav1.Now() machine.Status.NodeRef = objectRef(node) if _, err = c.machineClient.UpdateStatus(machine); err != nil { glog.Errorf("Error updating machine to link to node: %v\n", err) @@ -56,12 +68,13 @@ func (c *MachineControllerImpl) link(node *corev1.Node) error { glog.Infof("Successfully linked machine %s to node %s\n", machine.ObjectMeta.Name, node.ObjectMeta.Name) c.linkedNodes[node.ObjectMeta.Name] = true + c.cachedReadiness[node.ObjectMeta.Name] = nodeReady } return err } func (c *MachineControllerImpl) unlink(node *corev1.Node) error { - val, ok := node.ObjectMeta.Annotations["machine"] + val, ok := node.ObjectMeta.Annotations[machineAnnotationKey] if !ok { return nil } @@ -84,6 +97,7 @@ func (c *MachineControllerImpl) unlink(node *corev1.Node) error { return nil } + machine.Status.LastUpdated = metav1.Now() machine.Status.NodeRef = nil if _, err = c.machineClient.UpdateStatus(machine); err != nil { glog.Errorf("Error updating machine %s to unlink node %s: %v\n", @@ -91,6 +105,7 @@ func (c *MachineControllerImpl) unlink(node *corev1.Node) error { } else { glog.Infof("Successfully unlinked node %s from machine %s\n", node.ObjectMeta.Name, machine.ObjectMeta.Name) + delete(c.cachedReadiness, node.ObjectMeta.Name) delete(c.linkedNodes, node.ObjectMeta.Name) } return err @@ -123,8 +138,7 @@ func (c *MachineControllerImpl) reconcileNode(key string) error { func objectRef(node *corev1.Node) *corev1.ObjectReference { return &corev1.ObjectReference{ - Kind: "Node", - Namespace: node.ObjectMeta.Namespace, - Name: node.ObjectMeta.Name, + Kind: "Node", + Name: node.ObjectMeta.Name, } } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node_test.go new file mode 100644 index 000000000..c82648729 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/node_test.go @@ -0,0 +1,253 @@ +package machine + +import ( + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + k8sfake "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/fake" + v1alpha1listers "sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/v1alpha1" +) + +func TestReconcileNode(t *testing.T) { + tests := []struct { + name string + nodeHasMachineAnnotation bool + nodeIsDeleting bool + nodeLinked bool + nodeCached bool + nodeCachedReady bool + nodeReady bool + nodeNotPresent bool + machineNotPresent bool + nodeRefName string + expectedErr bool + expectLinked bool + expectedActions []string + }{ + { + name: "node doesn't exist, noop", + nodeHasMachineAnnotation: true, + nodeNotPresent: true, + }, + { + name: "node with machine annotations, link", + nodeHasMachineAnnotation: true, + expectLinked: true, + expectedActions: []string{"get", "update"}, + }, + { + name: "node with no machine annotations, noop", + }, + { + name: "node with machine annotations, missing machine, err", + nodeHasMachineAnnotation: true, + machineNotPresent: true, + expectedErr: true, + expectedActions: []string{"get"}, + }, + { + name: "node being deleted, unlink", + nodeHasMachineAnnotation: true, + nodeIsDeleting: true, + nodeRefName: "bar", + nodeCached: true, + expectedActions: []string{"get", "update"}, + }, + { + name: "node being deleted, no machine annotations, noop", + nodeIsDeleting: true, + }, + { + name: "node being deleted, missing machine, err", + nodeHasMachineAnnotation: true, + nodeIsDeleting: true, + machineNotPresent: true, + expectedActions: []string{"get"}, + expectedErr: true, + }, + { + name: "node being deleted, no node ref, noop", + nodeHasMachineAnnotation: true, + nodeIsDeleting: true, + expectedActions: []string{"get"}, + }, + { + name: "node being deleted, node ref mismatch, noop", + nodeHasMachineAnnotation: true, + nodeIsDeleting: true, + nodeRefName: "random", + expectedActions: []string{"get"}, + }, + { + name: "node cached and no change to ready state, noop", + nodeCached: true, + nodeReady: true, + }, + { + name: "node cached ready and change to not ready state, update", + nodeCached: true, + nodeCachedReady: true, + nodeReady: false, + nodeHasMachineAnnotation: true, + expectLinked: true, + expectedActions: []string{"get", "update"}, + }, + { + name: "node cached not ready and change to ready state, update", + nodeCached: true, + nodeCachedReady: false, + nodeReady: true, + nodeHasMachineAnnotation: true, + expectLinked: true, + expectedActions: []string{"get", "update"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + m := getMachine("foo", false, false, false) + if test.nodeRefName != "" { + m.Status.NodeRef = &corev1.ObjectReference{ + Kind: "Node", + Name: test.nodeRefName, + } + } + mrObjects := []runtime.Object{} + if !test.machineNotPresent { + mrObjects = append(mrObjects, m) + } + + machinesIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + err := machinesIndexer.Add(m) + if err != nil { + t.Fatal(err) + } + machineLister := v1alpha1listers.NewMachineLister(machinesIndexer) + + fakeClient := fake.NewSimpleClientset(mrObjects...) + fakeMachineClient := fakeClient.Cluster().Machines(metav1.NamespaceDefault) + + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Annotations: make(map[string]string), + }, + } + if test.nodeReady { + node.Status = corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + }, + }, + } + } + if test.nodeHasMachineAnnotation { + node.ObjectMeta.Annotations[machineAnnotationKey] = "foo" + } + if test.nodeIsDeleting { + node.DeletionTimestamp = &metav1.Time{Time: time.Now()} + } + + krObjects := []runtime.Object{} + if !test.nodeNotPresent { + krObjects = append(krObjects, node) + } + fakeK8sClient := k8sfake.NewSimpleClientset(krObjects...) + + target := &MachineControllerImpl{} + target.clientSet = fakeClient + target.machineClient = fakeMachineClient + target.kubernetesClientSet = fakeK8sClient + target.lister = machineLister + target.linkedNodes = make(map[string]bool) + target.cachedReadiness = make(map[string]bool) + if test.nodeCached { + target.linkedNodes["bar"] = true + target.cachedReadiness["bar"] = test.nodeCachedReady + } + + nodeKey, err := cache.MetaNamespaceKeyFunc(node) + if err != nil { + t.Fatalf("unable to get key for test node, %v", err) + } + err = target.reconcileNode(nodeKey) + + if (err != nil) != test.expectedErr { + t.Fatalf("got %v error, expected %v error", err, test.expectedErr) + } + + cached := target.linkedNodes["bar"] + if cached != test.expectLinked && !test.nodeCached { + t.Errorf("got %v node cache result, expected %v node cache result", cached, test.expectLinked) + } + // Successful unlink should clear cache. + if test.nodeIsDeleting && len(test.expectedActions) == 2 { + if cached { + t.Errorf("got %v node cache result, expected no node cache result", cached) + } + } + if cached { + isReady := target.cachedReadiness["bar"] + // If successful, isRead + if len(test.expectedActions) == 2 { + if isReady != test.nodeReady { + t.Errorf("got %v cached node ready, expected %v cached node ready", isReady, test.nodeReady) + } + } else { + if isReady != test.nodeCachedReady { + t.Errorf("got %v cached node ready, expected %v cached node ready", isReady, test.nodeCachedReady) + } + } + } + + var actualMachine *v1alpha1.Machine + actions := fakeClient.Actions() + if len(actions) != len(test.expectedActions) { + t.Fatalf("got %v actions, expected %v actions; got actions %v, expected actions %v", len(actions), len(test.expectedActions), actions, test.expectedActions) + } + + for i, action := range test.expectedActions { + if actions[i].GetVerb() != action { + t.Errorf("got %v action verb, expected %v action verb", actions[i].GetVerb(), action) + } + if actions[i].GetVerb() == "update" { + updateAction, ok := actions[i].(clienttesting.UpdateAction) + if !ok { + t.Fatalf("unexpected action %#v", action) + } + actualMachine, ok = updateAction.GetObject().(*v1alpha1.Machine) + if !ok { + t.Fatalf("unexpected object %#v", actualMachine) + } + } + } + if actualMachine != nil && actualMachine.Status.LastUpdated.IsZero() { + t.Errorf("got %v last updated time, expected non-zero last updated time.", actualMachine.Status.LastUpdated) + } + if test.expectLinked { + if actualMachine.Status.NodeRef == nil { + t.Fatalf("got nil node ref, expected non-nil node ref") + } + if actualMachine.Status.NodeRef.Name != "bar" { + t.Errorf("got %v node ref name, expected bar node ref name.", actualMachine.Status.NodeRef.Name) + } + } + if !test.expectLinked { + if actualMachine != nil && actualMachine.Status.NodeRef != nil { + t.Fatalf("got non-nil node ref, expected nil node ref") + } + } + }) + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/testactuator.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/testactuator.go index 359383af3..523e3fb56 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/testactuator.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machine/testactuator.go @@ -1,12 +1,9 @@ /* Copyright 2018 The Kubernetes Authors. - 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. @@ -49,7 +46,7 @@ func (a *TestActuator) Create(*v1alpha1.Cluster, *v1alpha1.Machine) error { return nil } -func (a *TestActuator) Delete(*v1alpha1.Machine) error { +func (a *TestActuator) Delete(*v1alpha1.Cluster, *v1alpha1.Machine) error { defer func() { if a.BlockOnDelete { <-a.unblock @@ -75,7 +72,7 @@ func (a *TestActuator) Update(c *v1alpha1.Cluster, machine *v1alpha1.Machine) er return nil } -func (a *TestActuator) Exists(*v1alpha1.Machine) (bool, error) { +func (a *TestActuator) Exists(*v1alpha1.Cluster, *v1alpha1.Machine) (bool, error) { defer func() { if a.BlockOnExists { <-a.unblock @@ -96,4 +93,4 @@ func NewTestActuator() *TestActuator { func (a *TestActuator) Unblock() { close(a.unblock) -} +} \ No newline at end of file diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/controller.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/controller.go new file mode 100644 index 000000000..50147b25b --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/controller.go @@ -0,0 +1,55 @@ + +/* +Copyright 2018 The Kubernetes Authors. + +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 machinedeployment + +import ( + "log" + + "github.com/kubernetes-incubator/apiserver-builder/pkg/builders" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" + listers "sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/v1alpha1" +) + +// +controller:group=cluster,version=v1alpha1,kind=MachineDeployment,resource=machinedeployments +type MachineDeploymentControllerImpl struct { + builders.DefaultControllerFns + + // lister indexes properties about MachineDeployment + lister listers.MachineDeploymentLister +} + +// Init initializes the controller and is called by the generated code +// Register watches for additional resource types here. +func (c *MachineDeploymentControllerImpl) Init(arguments sharedinformers.ControllerInitArguments) { + // Use the lister for indexing machinedeployments labels + c.lister = arguments.GetSharedInformers().Factory.Cluster().V1alpha1().MachineDeployments().Lister() +} + +// Reconcile handles enqueued messages +func (c *MachineDeploymentControllerImpl) Reconcile(u *v1alpha1.MachineDeployment) error { + // Implement controller logic here + log.Printf("Running reconcile MachineDeployment for %s\n", u.Name) + return nil +} + +func (c *MachineDeploymentControllerImpl) Get(namespace, name string) (*v1alpha1.MachineDeployment, error) { + return c.lister.MachineDeployments(namespace).Get(name) +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/zz_generated.api.register.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/zz_generated.api.register.go new file mode 100644 index 000000000..44a7aa97b --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machinedeployment/zz_generated.api.register.go @@ -0,0 +1,124 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +// This file was autogenerated by apiregister-gen. Do not edit it manually! + +package machinedeployment + +import ( + "github.com/golang/glog" + "github.com/kubernetes-incubator/apiserver-builder/pkg/controller" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" +) + +// MachineDeploymentController implements the controller.MachineDeploymentController interface +type MachineDeploymentController struct { + queue *controller.QueueWorker + + // Handles messages + controller *MachineDeploymentControllerImpl + + Name string + + BeforeReconcile func(key string) + AfterReconcile func(key string, err error) + + Informers *sharedinformers.SharedInformers +} + +// NewController returns a new MachineDeploymentController for responding to MachineDeployment events +func NewMachineDeploymentController(config *rest.Config, si *sharedinformers.SharedInformers) *MachineDeploymentController { + q := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "MachineDeployment") + + queue := &controller.QueueWorker{q, 10, "MachineDeployment", nil} + c := &MachineDeploymentController{queue, nil, "MachineDeployment", nil, nil, si} + + // For non-generated code to add events + uc := &MachineDeploymentControllerImpl{} + var ci sharedinformers.Controller = uc + + // Call the Init method that is implemented. + // Support multiple Init methods for backwards compatibility + if i, ok := ci.(sharedinformers.LegacyControllerInit); ok { + i.Init(config, si, c.LookupAndReconcile) + } else if i, ok := ci.(sharedinformers.ControllerInit); ok { + i.Init(&sharedinformers.ControllerInitArgumentsImpl{si, config, c.LookupAndReconcile}) + } + + c.controller = uc + + queue.Reconcile = c.reconcile + if c.Informers.WorkerQueues == nil { + c.Informers.WorkerQueues = map[string]*controller.QueueWorker{} + } + c.Informers.WorkerQueues["MachineDeployment"] = queue + si.Factory.Cluster().V1alpha1().MachineDeployments().Informer(). + AddEventHandler(&controller.QueueingEventHandler{q, nil, false}) + return c +} + +func (c *MachineDeploymentController) GetName() string { + return c.Name +} + +func (c *MachineDeploymentController) LookupAndReconcile(key string) (err error) { + return c.reconcile(key) +} + +func (c *MachineDeploymentController) reconcile(key string) (err error) { + var namespace, name string + + if c.BeforeReconcile != nil { + c.BeforeReconcile(key) + } + if c.AfterReconcile != nil { + // Wrap in a function so err is evaluated after it is set + defer func() { c.AfterReconcile(key, err) }() + } + + namespace, name, err = cache.SplitMetaNamespaceKey(key) + if err != nil { + return + } + + u, err := c.controller.Get(namespace, name) + if errors.IsNotFound(err) { + glog.Infof("Not doing work for MachineDeployment %v because it has been deleted", key) + // Set error so it is picked up by AfterReconcile and the return function + err = nil + return + } + if err != nil { + glog.Errorf("Unable to retrieve MachineDeployment %v from store: %v", key, err) + return + } + + // Set error so it is picked up by AfterReconcile and the return function + err = c.controller.Reconcile(u) + + return +} + +func (c *MachineDeploymentController) Run(stopCh <-chan struct{}) { + for _, q := range c.Informers.WorkerQueues { + q.Run(stopCh) + } + controller.GetDefaults(c.controller).Run(stopCh) +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/controller.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/controller.go index 545647c97..bcb54e5c0 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/controller.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/controller.go @@ -18,12 +18,20 @@ package machineset import ( "fmt" + "strings" + "sync" + "time" + "github.com/golang/glog" "github.com/kubernetes-incubator/apiserver-builder/pkg/builders" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" - machineclientset "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" + clusterapiclientset "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset" listers "sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/v1alpha1" "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" ) @@ -35,27 +43,62 @@ var controllerKind = v1alpha1.SchemeGroupVersion.WithKind("MachineSet") type MachineSetControllerImpl struct { builders.DefaultControllerFns - // machineClient a client that knows how to consume Machine resources - machineClient machineclientset.Interface + // kubernetesClient a client that knows how to consume Node resources + kubernetesClient kubernetes.Interface - // machineSetsLister indexes properties about MachineSet - machineSetsLister listers.MachineSetLister + // clusterAPIClient a client that knows how to consume Cluster API resources + clusterAPIClient clusterapiclientset.Interface + // machineSetsLister indexes properties about MachineSet + machineSetLister listers.MachineSetLister // machineLister holds a lister that knows how to list Machines from a cache machineLister listers.MachineLister + + informers *sharedinformers.SharedInformers + + // msKeyMuxMap holds a mutex lock for reconcilation keyed on the machineset key + msKeyMuxMap map[string]sync.Mutex } // Init initializes the controller and is called by the generated code // Register watches for additional resource types here. func (c *MachineSetControllerImpl) Init(arguments sharedinformers.ControllerInitArguments) { - c.machineSetsLister = arguments.GetSharedInformers().Factory.Cluster().V1alpha1().MachineSets().Lister() + c.kubernetesClient = arguments.GetSharedInformers().KubernetesClientSet + + c.machineSetLister = arguments.GetSharedInformers().Factory.Cluster().V1alpha1().MachineSets().Lister() c.machineLister = arguments.GetSharedInformers().Factory.Cluster().V1alpha1().Machines().Lister() var err error - c.machineClient, err = machineclientset.NewForConfig(arguments.GetRestConfig()) + c.clusterAPIClient, err = clusterapiclientset.NewForConfig(arguments.GetRestConfig()) if err != nil { - glog.Fatalf("error building clientset for machineClient: %v", err) + glog.Fatalf("error building clientset for clusterAPIClient: %v", err) + } + + // Start watching for Machine resource. It will effectively create a new worker queue, and + // reconcileMachine() will be invoked in a loop to handle the reconciling. + mi := arguments.GetSharedInformers().Factory.Cluster().V1alpha1().Machines().Informer() + arguments.GetSharedInformers().Watch("MachineWatcher", mi, nil, c.reconcileMachine) + + c.informers = arguments.GetSharedInformers() + + c.msKeyMuxMap = make(map[string]sync.Mutex) + + c.waitForCacheSync() +} + +func (c *MachineSetControllerImpl) waitForCacheSync() { + glog.Infof("Waiting for caches to sync for machine set controller") + + stopCh := make(chan struct{}) + + msListerSynced := c.informers.Factory.Cluster().V1alpha1().MachineSets().Informer().HasSynced + mListerSynced := c.informers.Factory.Cluster().V1alpha1().Machines().Informer().HasSynced + + if !cache.WaitForCacheSync(stopCh, mListerSynced, msListerSynced) { + glog.Warningf("Unable to sync caches for machineset controller") + return } + glog.Infof("Caches are synced for machineset controller") } // Reconcile holds the controller's business logic. @@ -63,66 +106,144 @@ func (c *MachineSetControllerImpl) Init(arguments sharedinformers.ControllerInit // note that the current state of the cluster is calculated based on the number of machines // that are owned by the given machineSet (key). func (c *MachineSetControllerImpl) Reconcile(machineSet *v1alpha1.MachineSet) error { - filteredMachines, err := c.getMachines(machineSet) + key, err := cache.MetaNamespaceKeyFunc(machineSet) if err != nil { + glog.Errorf("Couldn't get key for object %+v.", machineSet) return err } - return c.syncReplicas(machineSet, filteredMachines) + // Lock on Reconcile, this is to avoid the change of a machine object to cause the same machineset to Reconcile + // during the creation/deletion of machines, causing the incorrect number of machines to created/deleted + // TODO: Find a less heavy handed approach to avoid concurrent machineset reconcilation. + mux := c.msKeyMuxMap[key] + mux.Lock() + defer mux.Unlock() + + glog.V(4).Infof("Reconcile machineset %v", machineSet.Name) + allMachines, err := c.machineLister.Machines(machineSet.Namespace).List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list machines, %v", err) + } + + // Filter out irrelevant machines (deleting/mismatch labels) and claim orphaned machines. + var filteredMachines []*v1alpha1.Machine + for _, machine := range allMachines { + if shouldExcludeMachine(machineSet, machine) { + continue + } + // Attempt to adopt machine if it meets previous conditions and it has no controller ref. + if metav1.GetControllerOf(machine) == nil { + if err := c.adoptOrphan(machineSet, machine); err != nil { + glog.Warningf("failed to adopt machine %v into machineset %v. %v", machine.Name, machineSet.Name, err) + continue + } + } + filteredMachines = append(filteredMachines, machine) + } + + syncErr := c.syncReplicas(machineSet, filteredMachines) + + ms := machineSet.DeepCopy() + newStatus := c.calculateStatus(ms, filteredMachines) + + // Always updates status as machines come up or die. + updatedMS, err := updateMachineSetStatus(c.clusterAPIClient.ClusterV1alpha1().MachineSets(machineSet.Namespace), machineSet, newStatus) + if err != nil { + if syncErr != nil { + return fmt.Errorf("failed to sync machines. %v. failed to update machine set status. %v", syncErr, err) + } + return fmt.Errorf("failed to update machine set status. %v", err) + } + if updatedMS.Spec.Replicas == nil { + return fmt.Errorf("the Replicas field in Spec for machineset %v is nil, this should not be allowed.", ms.Name) + } + + // Resync the MachineSet after MinReadySeconds as a last line of defense to guard against clock-skew. + // Clock-skew is an issue as it may impact whether an available replica is counted as a ready replica. + // A replica is available if the amount of time since last transition exceeds MinReadySeconds. + // If there was a clock skew, checking whether the amount of time since last transition to ready state + // exceeds MinReadySeconds could be incorrect. + // To avoid an available replica stuck in the ready state, we force a reconcile after MinReadySeconds, + // at which point it should confirm any available replica to be available. + if syncErr == nil && updatedMS.Spec.MinReadySeconds > 0 && + updatedMS.Status.ReadyReplicas == *(updatedMS.Spec.Replicas) && + updatedMS.Status.AvailableReplicas != *(updatedMS.Spec.Replicas) { + + if err := c.enqueueAfter(updatedMS, time.Duration(updatedMS.Spec.MinReadySeconds)*time.Second); err != nil { + return fmt.Errorf("failed to enqueue %v machineset for later. %v", updatedMS.Name, err) + } + } + return syncErr } func (c *MachineSetControllerImpl) Get(namespace, name string) (*v1alpha1.MachineSet, error) { - return c.machineSetsLister.MachineSets(namespace).Get(name) + return c.machineSetLister.MachineSets(namespace).Get(name) } // syncReplicas essentially scales machine resources up and down. -func (c *MachineSetControllerImpl) syncReplicas(machineSet *v1alpha1.MachineSet, machines []*v1alpha1.Machine) error { - // Take ownership of machines if not already owned. - for _, machine := range machines { - if shouldAdopt(machineSet, machine) { - c.adoptOrphan(machineSet, machine) - } +func (c *MachineSetControllerImpl) syncReplicas(ms *v1alpha1.MachineSet, machines []*v1alpha1.Machine) error { + if ms.Spec.Replicas == nil { + return fmt.Errorf("the Replicas field in Spec for machineset %v is nil, this should not be allowed.", ms.Name) } - - var result error - currentMachineCount := int32(len(machines)) - desiredReplicas := *machineSet.Spec.Replicas - diff := int(currentMachineCount - desiredReplicas) - + diff := len(machines) - int(*(ms.Spec.Replicas)) if diff < 0 { diff *= -1 + glog.Infof("Too few replicas for %v %s/%s, need %d, creating %d", controllerKind, ms.Namespace, ms.Name, *(ms.Spec.Replicas), diff) + + var errstrings []string for i := 0; i < diff; i++ { - glog.V(2).Infof("creating a machine ( spec.replicas(%d) > currentMachineCount(%d) )", desiredReplicas, currentMachineCount) - machine, err := c.createMachine(machineSet) - if err != nil { - return err - } - _, err = c.machineClient.ClusterV1alpha1().Machines(machineSet.Namespace).Create(machine) + glog.Infof("creating machine %d of %d, ( spec.replicas(%d) > currentMachineCount(%d) )", i+1, diff, *(ms.Spec.Replicas), len(machines)) + machine := c.createMachine(ms) + _, err := c.clusterAPIClient.ClusterV1alpha1().Machines(ms.Namespace).Create(machine) if err != nil { glog.Errorf("unable to create a machine = %s, due to %v", machine.Name, err) - result = err + errstrings = append(errstrings, err.Error()) } } + + if len(errstrings) > 0 { + return fmt.Errorf(strings.Join(errstrings, "; ")) + } + + return nil } else if diff > 0 { - for i := 0; i < diff; i++ { - glog.V(2).Infof("deleting a machine ( spec.replicas(%d) < currentMachineCount(%d) )", desiredReplicas, currentMachineCount) - // TODO: Define machines deletion policies. - // see: https://github.com/kubernetes/kube-deploy/issues/625 - machineToDelete := machines[i] - err := c.machineClient.ClusterV1alpha1().Machines(machineSet.Namespace).Delete(machineToDelete.Name, &metav1.DeleteOptions{}) + glog.Infof("Too many replicas for %v %s/%s, need %d, deleting %d", controllerKind, ms.Namespace, ms.Name, *(ms.Spec.Replicas), diff) + + // Choose which Machines to delete. + machinesToDelete := getMachinesToDelete(machines, diff) + + // TODO: Add cap to limit concurrent delete calls. + errCh := make(chan error, diff) + var wg sync.WaitGroup + wg.Add(diff) + for _, machine := range machinesToDelete { + go func(targetMachine *v1alpha1.Machine) { + defer wg.Done() + err := c.clusterAPIClient.ClusterV1alpha1().Machines(ms.Namespace).Delete(targetMachine.Name, &metav1.DeleteOptions{}) + if err != nil { + glog.Errorf("unable to delete a machine = %s, due to %v", machine.Name, err) + errCh <- err + } + }(machine) + } + wg.Wait() + + select { + case err := <-errCh: + // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit. if err != nil { - glog.Errorf("unable to delete a machine = %s, due to %v", machineToDelete.Name, err) - result = err + return err } + default: } } - return result + return nil } // createMachine creates a machine resource. // the name of the newly created resource is going to be created by the API server, we set the generateName field -func (c *MachineSetControllerImpl) createMachine(machineSet *v1alpha1.MachineSet) (*v1alpha1.Machine, error) { +func (c *MachineSetControllerImpl) createMachine(machineSet *v1alpha1.MachineSet) *v1alpha1.Machine { gv := v1alpha1.SchemeGroupVersion machine := &v1alpha1.Machine{ TypeMeta: metav1.TypeMeta{ @@ -133,46 +254,48 @@ func (c *MachineSetControllerImpl) createMachine(machineSet *v1alpha1.MachineSet Spec: machineSet.Spec.Template.Spec, } machine.ObjectMeta.GenerateName = fmt.Sprintf("%s-", machineSet.Name) - machine.ObjectMeta.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(machineSet, controllerKind),} + machine.ObjectMeta.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(machineSet, controllerKind)} - return machine, nil + return machine } -// getMachines returns a list of machines that match on machineSet.Spec.Selector -func (c *MachineSetControllerImpl) getMachines(machineSet *v1alpha1.MachineSet) ([]*v1alpha1.Machine, error) { - selector, err := metav1.LabelSelectorAsSelector(&machineSet.Spec.Selector) - if err != nil { - return nil, err +// shoudExcludeMachine returns true if the machine should be filtered out, false otherwise. +func shouldExcludeMachine(machineSet *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { + // Ignore inactive machines. + if machine.DeletionTimestamp != nil || !machine.DeletionTimestamp.IsZero() { + glog.V(4).Infof("Skipping machine (%v), as it is being deleted.", machine.Name) + return true } - filteredMachines, err := c.machineLister.List(selector) - if err != nil { - return nil, err + + if metav1.GetControllerOf(machine) != nil && !metav1.IsControlledBy(machine, machineSet) { + glog.V(4).Infof("%s not controlled by %v", machine.Name, machineSet.Name) + return true + } + if !hasMatchingLabels(machineSet, machine) { + return true } - return filteredMachines, err + return false } -func shouldAdopt(machineSet *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { - // Do nothing if the machine is being deleted. - if !machine.ObjectMeta.DeletionTimestamp.IsZero() { - glog.V(2).Infof("Skipping machine (%v), as it is being deleted.", machine.Name) +func hasMatchingLabels(machineSet *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { + selector, err := metav1.LabelSelectorAsSelector(&machineSet.Spec.Selector) + if err != nil { + glog.Warningf("unable to convert selector: %v", err) return false } - - // Machine owned by another controller. - if metav1.GetControllerOf(machine) != nil && !metav1.IsControlledBy(machine, machineSet) { - glog.Warningf("Skipping machine (%v), as it is owned by someone else.", machine.Name) + // If a deployment with a nil or empty selector creeps in, it should match nothing, not everything. + if selector.Empty() { + glog.V(2).Infof("%v machineset has empty selector", machineSet.Name) return false } - - // Machine we control. - if metav1.IsControlledBy(machine, machineSet) { + if !selector.Matches(labels.Set(machine.Labels)) { + glog.V(4).Infof("%v machine has mismatch labels", machine.Name) return false } - return true } -func (c *MachineSetControllerImpl) adoptOrphan(machineSet *v1alpha1.MachineSet, machine *v1alpha1.Machine) { +func (c *MachineSetControllerImpl) adoptOrphan(machineSet *v1alpha1.MachineSet, machine *v1alpha1.Machine) error { // Add controller reference. ownerRefs := machine.ObjectMeta.GetOwnerReferences() if ownerRefs == nil { @@ -182,7 +305,35 @@ func (c *MachineSetControllerImpl) adoptOrphan(machineSet *v1alpha1.MachineSet, newRef := *metav1.NewControllerRef(machineSet, controllerKind) ownerRefs = append(ownerRefs, newRef) machine.ObjectMeta.SetOwnerReferences(ownerRefs) - if _, err := c.machineClient.ClusterV1alpha1().Machines(machineSet.Namespace).Update(machine); err != nil { + if _, err := c.clusterAPIClient.ClusterV1alpha1().Machines(machineSet.Namespace).Update(machine); err != nil { glog.Warningf("Failed to update machine owner reference. %v", err) + return err } + return nil +} + +func (c *MachineSetControllerImpl) enqueue(machineSet *v1alpha1.MachineSet) error { + key, err := cache.MetaNamespaceKeyFunc(machineSet) + if err != nil { + glog.Errorf("Couldn't get key for object %+v.", machineSet) + return err + } + c.informers.WorkerQueues["MachineSet"].Queue.Add(key) + return nil +} + +func (c *MachineSetControllerImpl) enqueueAfter(machineSet *v1alpha1.MachineSet, after time.Duration) error { + key, err := cache.MetaNamespaceKeyFunc(machineSet) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", machineSet, after) + return err + } + c.informers.WorkerQueues["MachineSet"].Queue.AddAfter(key, after) + return nil +} + +func getMachinesToDelete(filteredMachines []*v1alpha1.Machine, diff int) []*v1alpha1.Machine { + // TODO: Define machines deletion policies. + // see: https://github.com/kubernetes/kube-deploy/issues/625 + return filteredMachines[:diff] } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine.go new file mode 100644 index 000000000..a2a722614 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine.go @@ -0,0 +1,118 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 machineset + +import ( + "fmt" + "strings" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" +) + +func (c *MachineSetControllerImpl) reconcileMachine(key string) error { + glog.V(4).Infof("Reconcile machine from machineset: %v", key) + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + glog.Errorf("Failed to split key: %v. %v", key, err) + return err + } + + m, err := c.clusterAPIClient.ClusterV1alpha1().Machines(namespace).Get(name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + glog.Errorf("Unable to retrieve Machine %v from store: %v", key, err) + return err + } + + // If it has a ControllerRef, enqueue the controller ref machine set + if controllerRef := metav1.GetControllerOf(m); controllerRef != nil { + ms := c.resolveControllerRef(namespace, controllerRef) + if ms == nil { + glog.V(4).Infof("Found no machineset from controller ref for machine %v", m.Name) + return nil + } + if err := c.enqueue(ms); err != nil { + return fmt.Errorf("failed to enqueue machine set %v due to change to machine %v. %v", ms.Name, m.Name, err) + } + return nil + } + + mss := c.getMachineSetsForMachine(m) + if len(mss) == 0 { + glog.V(4).Infof("Found no machine set for machine: %v", m.Name) + return nil + } + var errstrings []string + for _, ms := range mss { + if err := c.enqueue(ms); err != nil { + errstrings = append(errstrings, err.Error()) + } + } + if len(errstrings) > 0 { + return fmt.Errorf("failed to enqueue machine sets due to change to machine %v. %v", m.Name, strings.Join(errstrings, "; ")) + } + return nil +} + +func (c *MachineSetControllerImpl) resolveControllerRef(namespace string, controllerRef *metav1.OwnerReference) *v1alpha1.MachineSet { + if controllerRef.Kind != controllerKind.Kind { + glog.Warningf("Found unexpected controller ref kind, got %v, expected %v", controllerRef.Kind, controllerKind.Kind) + return nil + } + ms, err := c.machineSetLister.MachineSets(namespace).Get(controllerRef.Name) + if err != nil { + glog.Warningf("Failed to get machine set with name %v.", controllerRef.Name) + return nil + } + if ms.UID != controllerRef.UID { + // The controller we found with this Name is not the same one that the ControllerRef points to. + glog.Warningf("Found unexpected UID, got %v, expected %v.", ms.UID, controllerRef.UID) + return nil + } + return ms +} + +func (c *MachineSetControllerImpl) getMachineSetsForMachine(m *v1alpha1.Machine) []*v1alpha1.MachineSet { + if len(m.Labels) == 0 { + glog.Warningf("No machine sets found for Machine %v because it has no labels", m.Name) + return nil + } + + msList, err := c.machineSetLister.MachineSets(m.Namespace).List(labels.Everything()) + if err != nil { + glog.Errorf("Failed to list machine sets, %v", err) + return nil + } + + var mss []*v1alpha1.MachineSet + for _, ms := range msList { + if hasMatchingLabels(ms, m) { + mss = append(mss, ms) + } + } + + return mss +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine_test.go new file mode 100644 index 000000000..55b143d63 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/machine_test.go @@ -0,0 +1,176 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 machineset + +import ( + "reflect" + "sort" + "testing" + + "github.com/kubernetes-incubator/apiserver-builder/pkg/controller" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/fake" + v1alpha1listers "sigs.k8s.io/cluster-api/pkg/client/listers_generated/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" +) + +func TestMachineSetController_reconcileMachine(t *testing.T) { + tests := []struct { + name string + machineNotPresent bool + machineSetNotPresent bool + machineSetDiffUID bool + machineNoCtrlRef bool + machineNoLabels bool + sameMSLabels bool + expectQueued int + }{ + { + name: "machine doesn't exist, noop", + machineNotPresent: true, + }, + { + name: "machine with controller ref, found machine set, 1 queued.", + expectQueued: 1, + }, + { + name: "machine with controller ref, machine set not found, noop", + machineSetNotPresent: true, + }, + { + name: "machine with controller ref, machine set UID mismatch, noop", + machineSetDiffUID: true, + }, + { + name: "machine without controller ref, found 1 machine set with matching labels, 1 queued", + machineNoCtrlRef: true, + expectQueued: 1, + }, + { + name: "machine without controller ref, no labels, noop", + machineNoCtrlRef: true, + machineNoLabels: true, + }, + { + name: "machine without controller ref, found 2 machine set with matching labels, 2 queued", + machineNoCtrlRef: true, + sameMSLabels: true, + expectQueued: 2, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ms := createMachineSet(1, "foo", "bar1", "acme") + msSameLabel := createMachineSet(1, "ms2", "bar1", "acme") + msDiffLabel := createMachineSet(1, "ms3", "ms-bar", "acme") + msDiffLabel.Spec.Selector.MatchLabels = map[string]string{labelKey: "DIFFLABELS"} + msDiffLabel.Spec.Template.Labels = map[string]string{labelKey: "DIFFLABELS"} + msDiffUID := ms.DeepCopy() + msDiffUID.UID = "NotMe" + + m := machineFromMachineSet(ms, "bar1") + + rObjects := []runtime.Object{} + if !test.machineNotPresent { + rObjects = append(rObjects, m) + } + if test.machineNoCtrlRef { + m.ObjectMeta.OwnerReferences = []metav1.OwnerReference{} + } + if test.machineNoLabels { + m.ObjectMeta.Labels = map[string]string{} + } + + fakeClient := fake.NewSimpleClientset(rObjects...) + + machineSetIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + machineSetLister := v1alpha1listers.NewMachineSetLister(machineSetIndexer) + + if !test.machineSetNotPresent { + if test.machineSetDiffUID { + machineSetIndexer.Add(msDiffUID) + } else { + machineSetIndexer.Add(ms) + } + } + machineSetIndexer.Add(msDiffLabel) + if test.sameMSLabels { + machineSetIndexer.Add(msSameLabel) + } + + target := &MachineSetControllerImpl{} + target.clusterAPIClient = fakeClient + target.machineSetLister = machineSetLister + target.informers = &sharedinformers.SharedInformers{} + target.informers.WorkerQueues = map[string]*controller.QueueWorker{} + target_queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "MachineSet") + target.informers.WorkerQueues["MachineSet"] = &controller.QueueWorker{target_queue, 10, "MachineSet", nil} + + mKey, err := cache.MetaNamespaceKeyFunc(m) + if err != nil { + t.Fatalf("unable to get key for test machine, %v", err) + } + err = target.reconcileMachine(mKey) + + if err != nil { + t.Fatalf("got %v error, expected %v error; %v", err != nil, false, err) + } + + if target_queue.Len() != test.expectQueued { + t.Fatalf("got %v queued items, expected %v queued items", target_queue.Len(), test.expectQueued) + } + + if test.expectQueued == 1 { + verifyQueuedKey(t, target_queue, []*v1alpha1.MachineSet{ms}) + } + if test.expectQueued == 2 { + verifyQueuedKey(t, target_queue, []*v1alpha1.MachineSet{ms, msSameLabel}) + } + }) + } +} + +func verifyQueuedKey(t *testing.T, queue workqueue.RateLimitingInterface, mss []*v1alpha1.MachineSet) { + queuedLength := queue.Len() + var queuedKeys, expectedKeys []string + for i := 0; i < queuedLength; i++ { + key, done := queue.Get() + if key == nil || done { + t.Fatalf("failed to enqueue controller.") + } + queuedKeys = append(queuedKeys, key.(string)) + queue.Done(key) + } + for _, ms := range mss { + key, err := cache.MetaNamespaceKeyFunc(ms) + if err != nil { + t.Fatalf("failed to get key for deployment.") + } + expectedKeys = append(expectedKeys, key) + } + sort.Strings(queuedKeys) + sort.Strings(expectedKeys) + if !reflect.DeepEqual(queuedKeys, expectedKeys) { + t.Fatalf("got %v keys, expected %v keys", queuedKeys, expectedKeys) + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/reconcile_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/reconcile_test.go index 809ee5db0..46a5f60b0 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/reconcile_test.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/reconcile_test.go @@ -106,20 +106,20 @@ func TestMachineSetControllerReconcileHandler(t *testing.T) { expectedMachine: machineFromMachineSet(createMachineSet(1, "foo", "bar2", "acme"), "bar1"), }, { - name: "scenario 8: the current machine has different controller ref, do nothing.", + name: "scenario 8: the current machine has different controller ref, thus a machine is created.", startingMachineSets: []*v1alpha1.MachineSet{createMachineSet(1, "foo", "bar2", "acme")}, startingMachines: []*v1alpha1.Machine{setDifferentOwnerUID(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"))}, machineSetToSync: "foo", namespaceToSync: "acme", - expectedActions: []string{}, + expectedActions: []string{"create"}, }, { - name: "scenario 9: the current machine is being deleted, do nothing.", + name: "scenario 9: the current machine is being deleted, thus a machine is created.", startingMachineSets: []*v1alpha1.MachineSet{createMachineSet(1, "foo", "bar2", "acme")}, startingMachines: []*v1alpha1.Machine{setMachineDeleting(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"))}, machineSetToSync: "foo", namespaceToSync: "acme", - expectedActions: []string{}, + expectedActions: []string{"create"}, }, { name: "scenario 10: the current machine has no controller refs, owner refs preserved, machine should be adopted.", @@ -150,13 +150,14 @@ func TestMachineSetControllerReconcileHandler(t *testing.T) { if err != nil { t.Fatal(err) } + rObjects = append(rObjects, amachineset) } fakeClient := fake.NewSimpleClientset(rObjects...) machineLister := v1alpha1listers.NewMachineLister(machinesIndexer) machineSetLister := v1alpha1listers.NewMachineSetLister(machineSetIndexer) target := &MachineSetControllerImpl{} - target.machineClient = fakeClient - target.machineSetsLister = machineSetLister + target.clusterAPIClient = fakeClient + target.machineSetLister = machineSetLister target.machineLister = machineLister // act @@ -171,8 +172,9 @@ func TestMachineSetControllerReconcileHandler(t *testing.T) { // validate actions := fakeClient.Actions() + actions = getFilteredActions(actions, "machines") if len(actions) != len(test.expectedActions) { - t.Fatalf("unexpected actions: %v, expected %d actions got %d", actions, len(test.expectedActions), len(actions)) + t.Fatalf("got %d actions, expected %d actions; got %v actions, expected %v actions", len(actions), len(test.expectedActions), actions, test.expectedActions) } for i, verb := range test.expectedActions { if actions[i].GetVerb() != verb { @@ -222,15 +224,16 @@ func createMachineSet(replicas int, machineSetName string, machineName string, n Namespace: namespace, }, Spec: v1alpha1.MachineSetSpec{ - Replicas: &replicasInt32, - Selector:metav1.LabelSelector{ - MatchLabels: map[string]string{labelKey:"strongMachine"}, + Replicas: &replicasInt32, + MinReadySeconds: 600, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{labelKey: "strongMachine"}, }, Template: v1alpha1.MachineTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: machineName, Namespace: namespace, - Labels: map[string]string{labelKey:"strongMachine"}, + Labels: map[string]string{labelKey: "strongMachine"}, }, Spec: v1alpha1.MachineSpec{ ProviderConfig: v1alpha1.ProviderConfig{ @@ -298,3 +301,13 @@ func setNonControllerRef(m *v1alpha1.Machine) *v1alpha1.Machine { m.ObjectMeta.OwnerReferences[0].Controller = &controller return m } + +func getFilteredActions(actions []clienttesting.Action, resource string) []clienttesting.Action { + var filteredActions []clienttesting.Action + for _, action := range actions { + if action.GetResource().Resource == resource { + filteredActions = append(filteredActions, action) + } + } + return filteredActions +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status.go new file mode 100644 index 000000000..4ba33bf78 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status.go @@ -0,0 +1,129 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 machineset + +import ( + "fmt" + + "github.com/golang/glog" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + machinesetclientset "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/controller/noderefutil" +) + +const ( + // The number of times we retry updating a MachineSet's status. + statusUpdateRetries = 1 +) + +func (c *MachineSetControllerImpl) calculateStatus(ms *v1alpha1.MachineSet, filteredMachines []*v1alpha1.Machine) v1alpha1.MachineSetStatus { + newStatus := ms.Status + // Count the number of machines that have labels matching the labels of the machine + // template of the replica set, the matching machines may have more + // labels than are in the template. Because the label of machineTemplateSpec is + // a superset of the selector of the replica set, so the possible + // matching machines must be part of the filteredMachines. + fullyLabeledReplicasCount := 0 + readyReplicasCount := 0 + availableReplicasCount := 0 + templateLabel := labels.Set(ms.Spec.Template.Labels).AsSelectorPreValidated() + for _, machine := range filteredMachines { + if templateLabel.Matches(labels.Set(machine.Labels)) { + fullyLabeledReplicasCount++ + } + node, err := c.getMachineNode(machine) + if err != nil { + glog.V(4).Infof("Unable to get node for machine %v, %v", machine.Name, err) + continue + } + if noderefutil.IsNodeReady(node) { + readyReplicasCount++ + if noderefutil.IsNodeAvailable(node, ms.Spec.MinReadySeconds, metav1.Now()) { + availableReplicasCount++ + } + } + } + + newStatus.Replicas = int32(len(filteredMachines)) + newStatus.FullyLabeledReplicas = int32(fullyLabeledReplicasCount) + newStatus.ReadyReplicas = int32(readyReplicasCount) + newStatus.AvailableReplicas = int32(availableReplicasCount) + return newStatus +} + +// updateMachineSetStatus attempts to update the Status.Replicas of the given MachineSet, with a single GET/PUT retry. +func updateMachineSetStatus(c machinesetclientset.MachineSetInterface, ms *v1alpha1.MachineSet, newStatus v1alpha1.MachineSetStatus) (*v1alpha1.MachineSet, error) { + // This is the steady state. It happens when the MachineSet doesn't have any expectations, since + // we do a periodic relist every 30s. If the generations differ but the replicas are + // the same, a caller might've resized to the same replica count. + if ms.Status.Replicas == newStatus.Replicas && + ms.Status.FullyLabeledReplicas == newStatus.FullyLabeledReplicas && + ms.Status.ReadyReplicas == newStatus.ReadyReplicas && + ms.Status.AvailableReplicas == newStatus.AvailableReplicas && + ms.Generation == ms.Status.ObservedGeneration { + return ms, nil + } + + // Save the generation number we acted on, otherwise we might wrongfully indicate + // that we've seen a spec update when we retry. + // TODO: This can clobber an update if we allow multiple agents to write to the + // same status. + newStatus.ObservedGeneration = ms.Generation + + var getErr, updateErr error + var updatedMS *v1alpha1.MachineSet + for i := 0; ; i++ { + glog.V(4).Infof(fmt.Sprintf("Updating status for %v: %s/%s, ", ms.Kind, ms.Namespace, ms.Name) + + fmt.Sprintf("replicas %d->%d (need %d), ", ms.Status.Replicas, newStatus.Replicas, *(ms.Spec.Replicas)) + + fmt.Sprintf("fullyLabeledReplicas %d->%d, ", ms.Status.FullyLabeledReplicas, newStatus.FullyLabeledReplicas) + + fmt.Sprintf("readyReplicas %d->%d, ", ms.Status.ReadyReplicas, newStatus.ReadyReplicas) + + fmt.Sprintf("availableReplicas %d->%d, ", ms.Status.AvailableReplicas, newStatus.AvailableReplicas) + + fmt.Sprintf("sequence No: %v->%v", ms.Status.ObservedGeneration, newStatus.ObservedGeneration)) + + ms.Status = newStatus + updatedMS, updateErr = c.UpdateStatus(ms) + if updateErr == nil { + return updatedMS, nil + } + // Stop retrying if we exceed statusUpdateRetries - the machineSet will be requeued with a rate limit. + if i >= statusUpdateRetries { + break + } + // Update the MachineSet with the latest resource version for the next poll + if ms, getErr = c.Get(ms.Name, metav1.GetOptions{}); getErr != nil { + // If the GET fails we can't trust status.Replicas anymore. This error + // is bound to be more interesting than the update failure. + return nil, getErr + } + } + + return nil, updateErr +} + +func (c *MachineSetControllerImpl) getMachineNode(machine *v1alpha1.Machine) (*corev1.Node, error) { + nodeRef := machine.Status.NodeRef + if nodeRef == nil { + return nil, fmt.Errorf("machine has no node ref") + } + + return c.kubernetesClient.CoreV1().Nodes().Get(nodeRef.Name, metav1.GetOptions{}) +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status_test.go new file mode 100644 index 000000000..d3c81fc22 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/machineset/status_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 machineset + +import ( + "fmt" + "reflect" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kubefake "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/fake" +) + +func TestMachineSetController_calculateStatus(t *testing.T) { + readyNode := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "rNode"}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now()}, + }, + }, + }, + } + notReadyNode := readyNode.DeepCopy() + notReadyNode.Name = "nrNode" + notReadyNode.Status.Conditions[0].Status = corev1.ConditionFalse + unknownStatusNode := readyNode.DeepCopy() + unknownStatusNode.Name = "usNode" + unknownStatusNode.Status.Conditions[0].Status = corev1.ConditionUnknown + noConditionNode := readyNode.DeepCopy() + noConditionNode.Name = "ncNode" + noConditionNode.Status.Conditions = []corev1.NodeCondition{} + availableNode := readyNode.DeepCopy() + availableNode.Name = "aNode" + availableNode.Status.Conditions[0].LastTransitionTime.Time = time.Now().Add(time.Duration(-6) * time.Minute) + + tests := []struct { + name string + machines []*v1alpha1.Machine + setMinReadySeconds bool + minReadySeconds int32 + expectedReplicas int32 + expectedLabeledReplicas int32 + expectedReadyReplicas int32 + expectedAvailableReplicas int32 + }{ + { + name: "scenario 1: empty machinset.", + }, + { + name: "scenario 2: 1 replica, 1 labeled machine", + machines: []*v1alpha1.Machine{ + machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), + }, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + }, + { + name: "scenario 3: 1 replica, 0 labeled machine", + machines: []*v1alpha1.Machine{ + setDifferentLabels(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1")), + }, + expectedReplicas: 1, + }, + { + name: "scenario 4: 1 replica, 1 ready machine", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), readyNode), + }, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + expectedReadyReplicas: 1, + }, + { + name: "scenario 5: 1 replica, 0 ready machine, not ready node", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), notReadyNode), + }, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + }, + { + name: "scenario 6: 1 replica, 0 ready machine, unknown node", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), unknownStatusNode), + }, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + }, + { + name: "scenario 7: 1 replica, 0 ready machine, missing condition node", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), noConditionNode), + }, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + }, + { + name: "scenario 8: 1 replica, 1 available machine, minReadySeconds = 0", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), readyNode), + }, + setMinReadySeconds: true, + minReadySeconds: 0, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + expectedReadyReplicas: 1, + expectedAvailableReplicas: 1, + }, + { + name: "scenario 9: 1 replica, 1 available machine, 360s elapsed, need 300s", + machines: []*v1alpha1.Machine{ + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), availableNode), + }, + setMinReadySeconds: true, + minReadySeconds: 300, + expectedReplicas: 1, + expectedLabeledReplicas: 1, + expectedReadyReplicas: 1, + expectedAvailableReplicas: 1, + }, + { + name: "scenario 10: 4 replicas, 3 labeled, 2 ready, 1 available machine", + machines: []*v1alpha1.Machine{ + setDifferentLabels(setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), noConditionNode)), + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), notReadyNode), + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), readyNode), + setNode(machineFromMachineSet(createMachineSet(1, "foo", "bar1", "acme"), "bar1"), availableNode), + }, + setMinReadySeconds: true, + minReadySeconds: 300, + expectedReplicas: 4, + expectedLabeledReplicas: 3, + expectedReadyReplicas: 2, + expectedAvailableReplicas: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + rObjects := []runtime.Object{readyNode, notReadyNode, unknownStatusNode, noConditionNode, availableNode} + k8sClient := kubefake.NewSimpleClientset(rObjects...) + + c := &MachineSetControllerImpl{} + c.kubernetesClient = k8sClient + + ms := createMachineSet(len(test.machines), "foo", "bar1", "acme") + if test.setMinReadySeconds { + ms.Spec.MinReadySeconds = test.minReadySeconds + } + + status := c.calculateStatus(ms, test.machines) + + if status.Replicas != test.expectedReplicas { + t.Errorf("got %v replicas, expected %v replicas", status.Replicas, test.expectedReplicas) + } + + if status.FullyLabeledReplicas != test.expectedLabeledReplicas { + t.Errorf("got %v fully labeled replicas, expected %v fully labeled replicas", status.FullyLabeledReplicas, test.expectedLabeledReplicas) + } + + if status.ReadyReplicas != test.expectedReadyReplicas { + t.Errorf("got %v ready replicas, expected %v ready replicas", status.ReadyReplicas, test.expectedReadyReplicas) + } + + if status.AvailableReplicas != test.expectedAvailableReplicas { + t.Errorf("got %v available replicas, expected %v available replicas", status.AvailableReplicas, test.expectedAvailableReplicas) + } + + }) + } +} + +func setNode(machine *v1alpha1.Machine, node *corev1.Node) *v1alpha1.Machine { + machine.Status.NodeRef = getNodeRef(node) + return machine +} + +func getNodeRef(node *corev1.Node) *corev1.ObjectReference { + return &corev1.ObjectReference{ + Kind: "Node", + Name: node.ObjectMeta.Name, + } +} + +func TestMachineSetController_updateMachineSetStatus(t *testing.T) { + tests := []struct { + name string + attemptOutcomes []bool + newStatus v1alpha1.MachineSetStatus + expectErr bool + }{ + { + name: "no change in status, noop", + newStatus: getMachineSetStatus(1, 1, 1, 1), + }, + { + name: "machine set status update success first try", + attemptOutcomes: []bool{true}, + newStatus: getMachineSetStatus(2, 1, 1, 1), + }, + { + name: "machine set status update fail first try, success second try", + attemptOutcomes: []bool{false, true}, + newStatus: getMachineSetStatus(1, 2, 1, 1), + }, + { + name: "machine set status update fail second try", + attemptOutcomes: []bool{false, false}, + newStatus: getMachineSetStatus(1, 1, 2, 1), + expectErr: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ms := createMachineSet(1, "foo", "bar1", "acme") + ms.Status = getMachineSetStatus(1, 1, 1, 1) + + rObjects := []runtime.Object{ms} + fakeClient := fake.NewSimpleClientset(rObjects...) + + numCalls := 0 + fakeClient.PrependReactor("update", "machinesets", func(action core.Action) (bool, runtime.Object, error) { + success := test.attemptOutcomes[numCalls] + numCalls++ + if !success { + return true, nil, fmt.Errorf("error") + } + newMS := ms.DeepCopy() + newMS.Status = test.newStatus + return true, newMS, nil + }) + + updatedMS, err := updateMachineSetStatus(fakeClient.ClusterV1alpha1().MachineSets(ms.Namespace), ms, test.newStatus) + + if numCalls != len(test.attemptOutcomes) { + t.Fatalf("got %v update calls, expected %v update calls", numCalls, len(test.attemptOutcomes)) + } + + if (err != nil) != test.expectErr { + t.Fatalf("got %v err, expected %v err, %v", err != nil, test.expectErr, err) + } + if !test.expectErr && len(test.attemptOutcomes) > 0 { + if !reflect.DeepEqual(updatedMS.Status, test.newStatus) { + t.Fatalf("got %v status, expected %v status", updatedMS.Status, test.newStatus) + } + } + }) + } +} + +func getMachineSetStatus(replicas, labeled, ready, available int32) v1alpha1.MachineSetStatus { + return v1alpha1.MachineSetStatus{ + Replicas: replicas, + FullyLabeledReplicas: labeled, + ReadyReplicas: ready, + AvailableReplicas: available, + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util.go new file mode 100644 index 000000000..a5bf0b842 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util.go @@ -0,0 +1,72 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 noderefutil + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// IsNodeAvailable returns true if the node is ready and minReadySeconds have elapsed or is 0. False otherwise. +func IsNodeAvailable(node *corev1.Node, minReadySeconds int32, now metav1.Time) bool { + if !IsNodeReady(node) { + return false + } + + if minReadySeconds == 0 { + return true + } + + minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second + readyCondition := GetReadyCondition(&node.Status) + + if !readyCondition.LastTransitionTime.IsZero() && + readyCondition.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) { + return true + } + + return false +} + +// GetReadyCondition extracts the ready condition from the given status and returns that. +// Returns nil and -1 if the condition is not present, and the index of the located condition. +func GetReadyCondition(status *corev1.NodeStatus) *corev1.NodeCondition { + if status == nil { + return nil + } + for i := range status.Conditions { + if status.Conditions[i].Type == corev1.NodeReady { + return &status.Conditions[i] + } + } + return nil +} + +// IsNodeReady returns true if a node is ready; false otherwise. +func IsNodeReady(node *corev1.Node) bool { + if node == nil || &node.Status == nil { + return false + } + for _, c := range node.Status.Conditions { + if c.Type == corev1.NodeReady { + return c.Status == corev1.ConditionTrue + } + } + return false +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util_test.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util_test.go new file mode 100644 index 000000000..644b5ebb4 --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/noderefutil/util_test.go @@ -0,0 +1,294 @@ +package noderefutil + +import ( + "reflect" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestIsNodeAvaialble(t *testing.T) { + tests := []struct { + name string + node *corev1.Node + minReadySeconds int32 + expectedAvailable bool + }{ + { + name: "no node", + expectedAvailable: false, + }, + { + name: "no status", + node: &corev1.Node{}, + expectedAvailable: false, + }, + { + name: "no condition", + node: &corev1.Node{Status: corev1.NodeStatus{}}, + expectedAvailable: false, + }, + { + name: "no ready condition", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeOutOfDisk, + Status: corev1.ConditionTrue, + }, + }}, + }, + expectedAvailable: false, + }, + { + name: "ready condition true, minReadySeconds = 0, lastTransitionTime now", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + }, + }}, + }, + expectedAvailable: true, + }, + { + name: "ready condition true, minReadySeconds = 0, lastTransitionTime past", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now().Add(time.Duration(-700) * time.Second)}, + }, + }}, + }, + expectedAvailable: true, + }, + { + name: "ready condition true, minReadySeconds = 300, lastTransitionTime now", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + }, + }}, + }, + minReadySeconds: 300, + expectedAvailable: false, + }, + { + name: "ready condition true, minReadySeconds = 300, lastTransitionTime past", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: time.Now().Add(time.Duration(-700) * time.Second)}, + }, + }}, + }, + minReadySeconds: 300, + expectedAvailable: true, + }, + { + name: "ready condition false", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionFalse, + }, + }}, + }, + expectedAvailable: false, + }, + { + name: "ready condition unknown", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionUnknown, + }, + }}, + }, + expectedAvailable: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + isAvailable := IsNodeAvailable(test.node, test.minReadySeconds, metav1.Now()) + if isAvailable != test.expectedAvailable { + t.Fatalf("got %v available, expected %v available", isAvailable, test.expectedAvailable) + } + }) + } +} + +func TestGetReadyCondition(t *testing.T) { + tests := []struct { + name string + nodeStatus *corev1.NodeStatus + expectedCondition *corev1.NodeCondition + }{ + { + name: "no status", + }, + { + name: "no condition", + nodeStatus: &corev1.NodeStatus{}, + }, + { + name: "no ready condition", + nodeStatus: &corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeOutOfDisk, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + { + name: "ready condition true", + nodeStatus: &corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }, + }, + expectedCondition: &corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }, + { + name: "ready condition false", + nodeStatus: &corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionFalse, + }, + }, + }, + expectedCondition: &corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionFalse, + }, + }, + { + name: "ready condition unknown", + nodeStatus: &corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionUnknown, + }, + }, + }, + expectedCondition: &corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionUnknown, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + c := GetReadyCondition(test.nodeStatus) + if !reflect.DeepEqual(c, test.expectedCondition) { + t.Fatalf("got %v condition, expected %v condition", c, test.expectedCondition) + } + }) + } +} + +func TestIsNodeReady(t *testing.T) { + tests := []struct { + name string + node *corev1.Node + expectedReady bool + }{ + { + name: "no node", + expectedReady: false, + }, + { + name: "no status", + node: &corev1.Node{}, + expectedReady: false, + }, + { + name: "no condition", + node: &corev1.Node{Status: corev1.NodeStatus{}}, + expectedReady: false, + }, + { + name: "no ready condition", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeOutOfDisk, + Status: corev1.ConditionTrue, + }, + }}, + }, + expectedReady: false, + }, + { + name: "ready condition true", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }}, + }, + expectedReady: true, + }, + { + name: "ready condition false", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionFalse, + }, + }}, + }, + expectedReady: false, + }, + { + name: "ready condition unknown", + node: &corev1.Node{Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + corev1.NodeCondition{ + Type: corev1.NodeReady, + Status: corev1.ConditionUnknown, + }, + }}, + }, + expectedReady: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + isReady := IsNodeReady(test.node) + if isReady != test.expectedReady { + t.Fatalf("got %v ready, expected %v ready", isReady, test.expectedReady) + } + }) + } +} diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/sharedinformers/zz_generated.api.register.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/sharedinformers/zz_generated.api.register.go index 091fc5f67..644b2834a 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/sharedinformers/zz_generated.api.register.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/sharedinformers/zz_generated.api.register.go @@ -53,6 +53,7 @@ func NewSharedInformers(config *rest.Config, shutdown <-chan struct{}) *SharedIn func (si *SharedInformers) startInformers(shutdown <-chan struct{}) { go si.Factory.Cluster().V1alpha1().Clusters().Informer().Run(shutdown) go si.Factory.Cluster().V1alpha1().Machines().Informer().Run(shutdown) + go si.Factory.Cluster().V1alpha1().MachineDeployments().Informer().Run(shutdown) go si.Factory.Cluster().V1alpha1().MachineSets().Informer().Run(shutdown) } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/controller/zz_generated.api.register.go b/vendor/sigs.k8s.io/cluster-api/pkg/controller/zz_generated.api.register.go index 40e248c5e..3a196b6a0 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/controller/zz_generated.api.register.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/controller/zz_generated.api.register.go @@ -22,6 +22,7 @@ import ( "github.com/kubernetes-incubator/apiserver-builder/pkg/controller" "k8s.io/client-go/rest" "sigs.k8s.io/cluster-api/pkg/controller/cluster" + "sigs.k8s.io/cluster-api/pkg/controller/machinedeployment" "sigs.k8s.io/cluster-api/pkg/controller/machineset" "sigs.k8s.io/cluster-api/pkg/controller/sharedinformers" ) @@ -31,6 +32,7 @@ func GetAllControllers(config *rest.Config) ([]controller.Controller, chan struc si := sharedinformers.NewSharedInformers(config, shutdown) return []controller.Controller{ cluster.NewClusterController(config, si), + machinedeployment.NewMachineDeploymentController(config, si), machineset.NewMachineSetController(config, si), }, shutdown } diff --git a/vendor/sigs.k8s.io/cluster-api/pkg/openapi/openapi_generated.go b/vendor/sigs.k8s.io/cluster-api/pkg/openapi/openapi_generated.go index 31d2b1e6b..95fd58773 100644 --- a/vendor/sigs.k8s.io/cluster-api/pkg/openapi/openapi_generated.go +++ b/vendor/sigs.k8s.io/cluster-api/pkg/openapi/openapi_generated.go @@ -22181,15 +22181,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "Duration is a wrapper around time.Duration which supports correct marshaling to YAML and JSON. In particular, it marshals into strings, which can be used as map keys in json.", - Properties: map[string]spec.Schema{ - "Duration": { - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int64", - }, - }, - }, - Required: []string{"Duration"}, + Properties: map[string]spec.Schema{}, }, }, Dependencies: []string{}, @@ -23577,16 +23569,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "providerConfig": { SchemaProps: spec.SchemaProps{ Description: "Provider-specific serialized configuration to use during cluster creation. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field.", - Type: []string{"string"}, - Format: "", + Ref: ref("sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ProviderConfig"), }, }, }, - Required: []string{"clusterNetwork", "providerConfig"}, + Required: []string{"clusterNetwork"}, }, }, Dependencies: []string{ - "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ClusterNetworkingConfig"}, + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ClusterNetworkingConfig", "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ProviderConfig"}, }, "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ClusterStatus": { Schema: spec.Schema{ @@ -23622,9 +23613,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA }, "providerStatus": { SchemaProps: spec.SchemaProps{ - Description: "Provider-specific serialized status to use during cluster creation. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field.", - Type: []string{"string"}, - Format: "", + Description: "Provider-specific status. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), }, }, }, @@ -23632,7 +23622,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA }, }, Dependencies: []string{ - "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.APIEndpoint"}, + "k8s.io/apimachinery/pkg/runtime.RawExtension", "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.APIEndpoint"}, }, "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ClusterStatusStrategy": { Schema: spec.Schema{ @@ -24410,11 +24400,18 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Format: "", }, }, + "providerStatus": { + SchemaProps: spec.SchemaProps{ + Description: "Provider-specific status. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, }, + Required: []string{"providerStatus"}, }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time", "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.MachineVersionInfo"}, + "k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time", "k8s.io/apimachinery/pkg/runtime.RawExtension", "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.MachineVersionInfo"}, }, "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.MachineStatusStrategy": { Schema: spec.Schema{ @@ -24533,7 +24530,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Properties: map[string]spec.Schema{ "value": { SchemaProps: spec.SchemaProps{ - Description: "Value is an inlined, serialized representation of the node configuration. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field, akin to component config.", + Description: "Value is an inlined, serialized representation of the resource configuration. It is recommended that providers maintain their own versioned API types that should be serialized/deserialized from this field, akin to component config.", Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), }, }, @@ -24552,7 +24549,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1.ProviderConfigSource": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ProviderConfigSource represents a source for the provider-specific node configuration.", + Description: "ProviderConfigSource represents a source for the provider-specific resource configuration.", Properties: map[string]spec.Schema{}, }, }, diff --git a/vendor/sigs.k8s.io/cluster-api/sample/machinedeployment.yaml b/vendor/sigs.k8s.io/cluster-api/sample/machinedeployment.yaml new file mode 100644 index 000000000..9a688d46c --- /dev/null +++ b/vendor/sigs.k8s.io/cluster-api/sample/machinedeployment.yaml @@ -0,0 +1,5 @@ +apiVersion: cluster.k8s.io/v1alpha1 +kind: MachineDeployment +metadata: + name: machinedeployment-example +spec: diff --git a/vendor/sigs.k8s.io/cluster-api/sample/machineset.yaml b/vendor/sigs.k8s.io/cluster-api/sample/machineset.yaml index 8f8db44ec..6165d1cfa 100644 --- a/vendor/sigs.k8s.io/cluster-api/sample/machineset.yaml +++ b/vendor/sigs.k8s.io/cluster-api/sample/machineset.yaml @@ -15,8 +15,7 @@ spec: providerConfig: value: apiVersion: "gceproviderconfig/v1alpha1" - kind: "GCEProviderConfig" - project: "$GCLOUD_PROJECT" + kind: "GCEMachineProviderConfig" zone: "us-central1-f" machineType: "n1-standard-1" os: "ubuntu-1604-lts" diff --git a/vendor/sigs.k8s.io/cluster-api/tf-deployer/CONTRIBUTING.md b/vendor/sigs.k8s.io/cluster-api/tf-deployer/CONTRIBUTING.md index 91fb08275..8e364f89d 100644 --- a/vendor/sigs.k8s.io/cluster-api/tf-deployer/CONTRIBUTING.md +++ b/vendor/sigs.k8s.io/cluster-api/tf-deployer/CONTRIBUTING.md @@ -58,12 +58,12 @@ Install [OpenSSL](https://www.openssl.org/source/) on your machine. Please note ## Fetch Source Code -1. Fork [kube-deploy repo](https://github.com/kubernetes/kube-deploy). If it's your first time forking, please take a look at [GitHub Repo instructions](https://help.github.com/articles/fork-a-repo/). The general [Kubernetes GitHub workflow](https://github.com/kubernetes/community/blob/master/contributors/guide/github-workflow.md) is helpful here too if you're getting started. +1. Fork [cluster-api repo](https://github.com/kubernetes-sigs/cluster-api). If it's your first time forking, please take a look at [GitHub Repo instructions](https://help.github.com/articles/fork-a-repo/). The general [Kubernetes GitHub workflow](https://github.com/kubernetes/community/blob/master/contributors/guide/github-workflow.md) is helpful here too if you're getting started. 2. Clone Repo Locally ```bash -$ cd $GOPATH/src/k8s.io/ -$ git clone https://github.com//kube-deploy.git +$ cd $GOPATH/src/sigs.k8s.io/ +$ git clone https://github.com//cluster-api.git ``` ## Build @@ -79,6 +79,8 @@ This will create a binary `tf-deployer` in the same directory. You can use that When making changes to the machine controller, it's generally a good idea to delete any existing cluster created with an older version of the cluster-api. +Note: the below command is not yet implemented. + ```bash $ ./tf-deployer delete ``` @@ -96,10 +98,10 @@ NOTE: that the image will be pushed to `gcr.io/$GCP_PROJECT/terraform-machine-co 2. Rebuild tf-deployer - ```bash + ```bash $ cd $GOPATH/src/sigs.k8s.io/cluster-api/tf-deployer/ - $ go build - ``` + $ go build + ``` The new `†f-deployer` will have your changes. @@ -112,7 +114,7 @@ We do not have unit tests or integration tests currently. For any changes, it is 1. Create a cluster ```bash - $ ./tf-deployer create -c cluster.yaml -m machines.yaml + $ ./tf-deployer create -c cluster.yaml -m machines.yaml -n vsphere_named_machines.yaml ``` [Optional]To verify API server has been deployed successfully, you can the following command to double check. @@ -131,4 +133,4 @@ We do not have unit tests or integration tests currently. For any changes, it is ```bash $ ./tf-deployer delete - ``` \ No newline at end of file + ``` diff --git a/vendor/sigs.k8s.io/cluster-api/tf-deployer/README.md b/vendor/sigs.k8s.io/cluster-api/tf-deployer/README.md index cc67f5d1a..e68b38164 100644 --- a/vendor/sigs.k8s.io/cluster-api/tf-deployer/README.md +++ b/vendor/sigs.k8s.io/cluster-api/tf-deployer/README.md @@ -1,16 +1,28 @@ # Cluster API Terraform Prototype for vSphere 6.5 -The Cluster API Terraform prototype implements the [Cluster API](https://github.com/kubernetes/kube-deploy/blob/master/cluster-api/README.md) for Terraform. The target provider for this prototype is vSphere 6.5. +The Cluster API Terraform prototype implements the [Cluster API](https://github.com/kubernetes-sigs/cluster-api/blob/master/README.md) for Terraform. The target provider for this prototype is vSphere 6.5. ## Getting Started ### Prerequisites -Follow the steps listed at [CONTRIBUTING.md](https://github.com/kubernetes/kube-deploy/blob/master/cluster-api/tf-deployer/CONTRIBUTING.md) to: +Follow the steps listed at [CONTRIBUTING.md](https://github.com/kubernetes-sigs/cluster-api/blob/master/tf-deployer/CONTRIBUTING.md) to: 1. Build the `tf-deployer` tool -2. Create a vSphere template following [this guide](https://blog.inkubate.io/deploy-a-vmware-vsphere-virtual-machine-with-terraform/), and make sure you have sshd running, and that it had a public key (for which you have the private key). Additionally, make sure a VM running that image is capable of networking (`etc/networking/interfaces`). -3. Create a `machines.yaml` file configured for your cluster. See the provided template for an example. + ``` + cd $GOPATH/src/sigs.k8s.io/cluster-api/tf-deployer/ + go build + ``` +1. Create a vSphere template following [this guide](https://blog.inkubate.io/deploy-a-vmware-vsphere-virtual-machine-with-terraform/) up to **Create the vSphere Ubuntu 16.04 template** section. +1. Create a resource pool for your cluster. +1. Create a pair of ssh keys that will be used to connect to the VMs. Put your + keys in `~/.ssh/vsphere_tmp` and `~/.ssh/vsphere_tmp.pub`. + ``` + ssh-keygen -b 2048 -t rsa -f ~/.ssh/vsphere_tmp -q -N "" + ``` +1. Create a `machines.yaml` file configured for your cluster. See the provided template + for an example, if you use it, make sure to fill in all missing `terraformVariables` + in `providerConfig`. ### Limitation @@ -18,7 +30,10 @@ See [here](https://github.com/karan/kube-deploy/issues?utf8=%E2%9C%93&q=is%3Aiss ### Creating a cluster -1. Create a cluster: `./tf-deployer create -c cluster.yaml -m machines.yaml` +1. Create a cluster: + ``` + ./tf-deployer create -c cluster.yaml -m machines.yaml -n vsphere_named_machines.yaml + ``` During cluster creation, you can watch the machine resources get created in Kubernetes, see the corresponding virtual machines created in your provider, and then finally see nodes @@ -26,7 +41,6 @@ join the cluster: ```bash $ watch -n 5 "kubectl get machines" -$ watch -n 5 "gcloud compute instances list" $ watch -n 5 "kubectl get nodes" ``` @@ -65,4 +79,4 @@ connected with main apiserver through api aggregation. We deploy the extension A controller manager as a pod inside the cluster. Like other resources in Kubernetes, machine controller as part of controller manager is responsible to reconcile the actual vs. desired machine state. Bootstrapping and in-place upgrading is handled by -[kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/). \ No newline at end of file +[kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/). diff --git a/vendor/sigs.k8s.io/cluster-api/tf-deployer/deploy/deploy.go b/vendor/sigs.k8s.io/cluster-api/tf-deployer/deploy/deploy.go index 6885d86b0..08ec48f50 100644 --- a/vendor/sigs.k8s.io/cluster-api/tf-deployer/deploy/deploy.go +++ b/vendor/sigs.k8s.io/cluster-api/tf-deployer/deploy/deploy.go @@ -70,7 +70,7 @@ func (d *deployer) CreateCluster(c *clusterv1.Cluster, machines []*clusterv1.Mac vmCreated := false if err := d.createCluster(c, machines, &vmCreated); err != nil { if vmCreated { - d.deleteMasterVM(machines) + d.deleteMasterVM(c, machines) } d.machineDeployer.PostDelete(c, machines) return err @@ -108,7 +108,7 @@ func (d *deployer) DeleteCluster() error { return err } - if err := d.deleteMasterVM(machines); err != nil { + if err := d.deleteMasterVM(cluster, machines); err != nil { glog.Errorf("Error deleting master vm", err) } @@ -120,14 +120,14 @@ func (d *deployer) DeleteCluster() error { return nil } -func (d *deployer) deleteMasterVM(machines []*clusterv1.Machine) error { +func (d *deployer) deleteMasterVM(cluster *clusterv1.Cluster, machines []*clusterv1.Machine) error { master := util.GetMaster(machines) if master == nil { return fmt.Errorf("error deleting master vm, no master found") } glog.Infof("Deleting master vm %s", master.Name) - if err := d.machineDeployer.Delete(master); err != nil { + if err := d.machineDeployer.Delete(cluster, master); err != nil { return err } return nil diff --git a/vendor/sigs.k8s.io/cluster-api/tf-deployer/machines.yaml.template b/vendor/sigs.k8s.io/cluster-api/tf-deployer/machines.yaml.template index 8ba54f8cf..70f95173d 100644 --- a/vendor/sigs.k8s.io/cluster-api/tf-deployer/machines.yaml.template +++ b/vendor/sigs.k8s.io/cluster-api/tf-deployer/machines.yaml.template @@ -2,7 +2,7 @@ items: - apiVersion: "cluster.k8s.io/v1alpha1" kind: Machine metadata: - generateName: karangoel- + generateName: tf-master- labels: set: master spec: @@ -12,13 +12,23 @@ items: kind: "TerraformProviderConfig" terraformMachine: "standard-master" terraformVariables: [ - "user = \"foo\"", - "password = \"bar\"", - "vsphere_server = \"192.169.1.1\"", - ] + "user = \"\"", + "password = \"\"", + "vsphere_server = \"", + "datacenter = \"\"", + "datastore = \"\"", + "resource_pool = \"\"", + "network = \"\"", + "num_cpus = \"2\"", + "memory = \"2048\"", + "vm_template = \"\"", + "disk_label = \"\"", + "disk_size = \"\"", + "virtual_machine_domain = \"\"", + ] versions: - kubelet: 1.8.3 - controlPlane: 1.8.3 + kubelet: 1.10.1 + controlPlane: 1.10.1 containerRuntime: name: docker version: 1.12.0 @@ -27,7 +37,7 @@ items: - apiVersion: "cluster.k8s.io/v1alpha1" kind: Machine metadata: - generateName: karangoel- + generateName: tf-node- labels: set: master spec: @@ -37,15 +47,25 @@ items: kind: "TerraformProviderConfig" terraformMachine: "standard-node" terraformVariables: [ - "user = \"foo\"", - "password = \"bar\"", - "vsphere_server = \"192.169.1.1\"", - ] + "user = \"\"", + "password = \"\"", + "vsphere_server = \"", + "datacenter = \"\"", + "datastore = \"\"", + "resource_pool = \"\"", + "network = \"\"", + "num_cpus = \"2\"", + "memory = \"2048\"", + "vm_template = \"\"", + "disk_label = \"\"", + "disk_size = \"\"", + "virtual_machine_domain = \"\"", + ] versions: - kubelet: 1.8.3 - controlPlane: 1.8.3 + kubelet: 1.10.1 + controlPlane: 1.10.1 containerRuntime: name: docker version: 1.12.0 roles: - - Node \ No newline at end of file + - Node diff --git a/vendor/sigs.k8s.io/cluster-api/tf-deployer/vsphere_named_machines.yaml b/vendor/sigs.k8s.io/cluster-api/tf-deployer/vsphere_named_machines.yaml index 238d5bfa7..a79afcc5b 100644 --- a/vendor/sigs.k8s.io/cluster-api/tf-deployer/vsphere_named_machines.yaml +++ b/vendor/sigs.k8s.io/cluster-api/tf-deployer/vsphere_named_machines.yaml @@ -53,19 +53,60 @@ items: datacenter_id = "${data.vsphere_datacenter.dc.id}" } + data "template_file" "cloud_provider_config" { + template = <