From 7b890f625438418b040163f34a5fe54ff407fb71 Mon Sep 17 00:00:00 2001 From: Michael Shitrit Date: Sun, 4 Apr 2021 17:04:41 +0300 Subject: [PATCH 1/3] - Run go mod tidy & vendor Signed-off-by: Michael Shitrit --- go.mod | 4 + go.sum | 9 + vendor/github.com/MakeNowJust/heredoc/LICENSE | 2 +- .../github.com/MakeNowJust/heredoc/README.md | 13 +- vendor/github.com/MakeNowJust/heredoc/go.mod | 3 + .../github.com/MakeNowJust/heredoc/heredoc.go | 11 +- .../gobuffalo/flect/azure-pipelines.yml | 71 ------ .../gobuffalo/flect/azure-tests.yml | 19 -- .../github.com/gobuffalo/flect/capitalize.go | 9 +- vendor/github.com/gobuffalo/flect/go.mod | 7 +- vendor/github.com/gobuffalo/flect/go.sum | 10 +- vendor/github.com/gobuffalo/flect/humanize.go | 10 +- vendor/github.com/gobuffalo/flect/ident.go | 42 ++-- .../gobuffalo/flect/plural_rules.go | 11 +- .../github.com/gobuffalo/flect/pluralize.go | 16 +- .../github.com/gobuffalo/flect/singularize.go | 10 + .../github.com/gobuffalo/flect/underscore.go | 11 +- vendor/github.com/mattn/go-isatty/.travis.yml | 15 +- vendor/github.com/mattn/go-isatty/README.md | 2 +- vendor/github.com/mattn/go-isatty/go.mod | 4 +- vendor/github.com/mattn/go-isatty/go.sum | 4 +- vendor/github.com/mattn/go-isatty/go.test.sh | 12 ++ .../mattn/go-isatty/isatty_android.go | 23 -- .../github.com/mattn/go-isatty/isatty_bsd.go | 12 +- .../mattn/go-isatty/isatty_plan9.go | 22 ++ .../mattn/go-isatty/isatty_tcgets.go | 1 - .../mattn/go-isatty/isatty_windows.go | 39 +++- .../github.com/mattn/go-isatty/renovate.json | 8 + vendor/k8s.io/apiserver/LICENSE | 202 ++++++++++++++++++ .../apiserver/pkg/storage/names/generate.go | 54 +++++ vendor/modules.txt | 8 +- 31 files changed, 468 insertions(+), 196 deletions(-) create mode 100644 vendor/github.com/MakeNowJust/heredoc/go.mod delete mode 100644 vendor/github.com/gobuffalo/flect/azure-pipelines.yml delete mode 100644 vendor/github.com/gobuffalo/flect/azure-tests.yml create mode 100644 vendor/github.com/mattn/go-isatty/go.test.sh delete mode 100644 vendor/github.com/mattn/go-isatty/isatty_android.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_plan9.go create mode 100644 vendor/github.com/mattn/go-isatty/renovate.json create mode 100644 vendor/k8s.io/apiserver/LICENSE create mode 100644 vendor/k8s.io/apiserver/pkg/storage/names/generate.go diff --git a/go.mod b/go.mod index 98d7094470..b6579d6f68 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,13 @@ module github.com/openshift/machine-api-operator go 1.13 require ( + github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/blang/semver v3.5.1+incompatible + github.com/gobuffalo/flect v0.2.2 // indirect github.com/go-logr/logr v0.3.0 github.com/google/gofuzz v1.1.0 github.com/google/uuid v1.1.2 + github.com/mattn/go-isatty v0.0.12 // indirect github.com/onsi/ginkgo v1.14.1 github.com/onsi/gomega v1.10.2 github.com/openshift/api v0.0.0-20201216151826-78a19e96f9eb @@ -23,6 +26,7 @@ require ( gopkg.in/gcfg.v1 v1.2.3 k8s.io/api v0.20.0 k8s.io/apimachinery v0.20.0 + k8s.io/apiserver v0.20.0 k8s.io/client-go v0.20.0 k8s.io/code-generator v0.20.0 k8s.io/klog/v2 v2.4.0 diff --git a/go.sum b/go.sum index bc1e61c40d..0d4314c687 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -249,6 +251,8 @@ github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -429,6 +433,8 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -602,6 +608,7 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -809,6 +816,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1085,6 +1093,7 @@ k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk= k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= +k8s.io/apiserver v0.20.0 h1:0MwO4xCoqZwhoLbFyyBSJdu55CScp4V4sAgX6z4oPBY= k8s.io/apiserver v0.20.0/go.mod h1:6gRIWiOkvGvQt12WTYmsiYoUyYW0FXSiMdNl4m+sxY8= k8s.io/cli-runtime v0.18.0-rc.1/go.mod h1:yuKZYDG8raONmwjwIkT77lCfIuPwX+Bsp88MKYf1TlU= k8s.io/cli-runtime v0.19.0 h1:wLe+osHSqcItyS3MYQXVyGFa54fppORVA8Jn7DBGSWw= diff --git a/vendor/github.com/MakeNowJust/heredoc/LICENSE b/vendor/github.com/MakeNowJust/heredoc/LICENSE index 8a58c22208..6d0eb9d5d6 100644 --- a/vendor/github.com/MakeNowJust/heredoc/LICENSE +++ b/vendor/github.com/MakeNowJust/heredoc/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2017 TSUYUSATO Kitsune +Copyright (c) 2014-2019 TSUYUSATO Kitsune Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/MakeNowJust/heredoc/README.md b/vendor/github.com/MakeNowJust/heredoc/README.md index a3a65faba1..e9924d2974 100644 --- a/vendor/github.com/MakeNowJust/heredoc/README.md +++ b/vendor/github.com/MakeNowJust/heredoc/README.md @@ -1,4 +1,6 @@ -# heredoc [![CircleCI](https://circleci.com/gh/MakeNowJust/heredoc.svg?style=svg)](https://circleci.com/gh/MakeNowJust/heredoc) [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/MakeNowJust/heredoc) +# heredoc + +[![Build Status](https://circleci.com/gh/MakeNowJust/heredoc.svg?style=svg)](https://circleci.com/gh/MakeNowJust/heredoc) [![GoDoc](https://godoc.org/github.com/MakeNowJusti/heredoc?status.svg)](https://godoc.org/github.com/MakeNowJust/heredoc) ## About @@ -15,8 +17,6 @@ $ go get github.com/MakeNowJust/heredoc ```go // usual import "github.com/MakeNowJust/heredoc" -// shortcuts -import . "github.com/MakeNowJust/heredoc/dot" ``` ## Example @@ -26,11 +26,11 @@ package main import ( "fmt" - . "github.com/MakeNowJust/heredoc/dot" + "github.com/MakeNowJust/heredoc" ) func main() { - fmt.Println(D(` + fmt.Println(heredoc.Doc(` Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, ... @@ -45,8 +45,7 @@ func main() { ## API Document - - [Go Walker - github.com/MakeNowJust/heredoc](https://gowalker.org/github.com/MakeNowJust/heredoc) - - [Go Walker - github.com/MakeNowJust/heredoc/dot](https://gowalker.org/github.com/MakeNowJust/heredoc/dot) + - [heredoc - GoDoc](https://godoc.org/github.com/MakeNowJust/heredoc) ## License diff --git a/vendor/github.com/MakeNowJust/heredoc/go.mod b/vendor/github.com/MakeNowJust/heredoc/go.mod new file mode 100644 index 0000000000..52b0dfd49c --- /dev/null +++ b/vendor/github.com/MakeNowJust/heredoc/go.mod @@ -0,0 +1,3 @@ +module github.com/MakeNowJust/heredoc + +go 1.12 diff --git a/vendor/github.com/MakeNowJust/heredoc/heredoc.go b/vendor/github.com/MakeNowJust/heredoc/heredoc.go index fea12e622f..1fc0469555 100644 --- a/vendor/github.com/MakeNowJust/heredoc/heredoc.go +++ b/vendor/github.com/MakeNowJust/heredoc/heredoc.go @@ -1,24 +1,31 @@ -// Copyright (c) 2014-2017 TSUYUSATO Kitsune +// Copyright (c) 2014-2019 TSUYUSATO Kitsune // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php // Package heredoc provides creation of here-documents from raw strings. // // Golang supports raw-string syntax. +// // doc := ` // Foo // Bar // ` +// // But raw-string cannot recognize indentation. Thus such content is an indented string, equivalent to +// // "\n\tFoo\n\tBar\n" +// // I dont't want this! // // However this problem is solved by package heredoc. +// // doc := heredoc.Doc(` // Foo // Bar // `) +// // Is equivalent to +// // "Foo\nBar\n" package heredoc @@ -33,7 +40,7 @@ const maxInt = int(^uint(0) >> 1) // Doc returns un-indented string as here-document. func Doc(raw string) string { skipFirstLine := false - if raw[0] == '\n' { + if len(raw) > 0 && raw[0] == '\n' { raw = raw[1:] } else { skipFirstLine = true diff --git a/vendor/github.com/gobuffalo/flect/azure-pipelines.yml b/vendor/github.com/gobuffalo/flect/azure-pipelines.yml deleted file mode 100644 index 417e2c5792..0000000000 --- a/vendor/github.com/gobuffalo/flect/azure-pipelines.yml +++ /dev/null @@ -1,71 +0,0 @@ -variables: - GOBIN: "$(GOPATH)/bin" # Go binaries path - GOPATH: "$(system.defaultWorkingDirectory)/gopath" # Go workspace path - modulePath: "$(GOPATH)/src/github.com/$(build.repository.name)" # Path to the module"s code - -jobs: -- job: Windows - pool: - vmImage: "vs2017-win2016" - strategy: - matrix: - go 1.10: - go_version: "1.10" - go 1.11 (on): - go_version: "1.11.5" - GO111MODULE: "on" - go 1.11 (off): - go_version: "1.11.5" - GO111MODULE: "off" - go 1.12 (on): - go_version: "1.12" - GO111MODULE: "on" - go 1.12 (off): - go_version: "1.12" - GO111MODULE: "off" - steps: - - template: azure-tests.yml - -- job: macOS - pool: - vmImage: "macOS-10.13" - strategy: - matrix: - go 1.10: - go_version: "1.10" - go 1.11 (on): - go_version: "1.11.5" - GO111MODULE: "on" - go 1.11 (off): - go_version: "1.11.5" - GO111MODULE: "off" - go 1.12 (on): - go_version: "1.12" - GO111MODULE: "on" - go 1.12 (off): - go_version: "1.12" - GO111MODULE: "off" - steps: - - template: azure-tests.yml - -- job: Linux - pool: - vmImage: "ubuntu-16.04" - strategy: - matrix: - go 1.10: - go_version: "1.10" - go 1.11 (on): - go_version: "1.11.5" - GO111MODULE: "on" - go 1.11 (off): - go_version: "1.11.5" - GO111MODULE: "off" - go 1.12 (on): - go_version: "1.12" - GO111MODULE: "on" - go 1.12 (off): - go_version: "1.12" - GO111MODULE: "off" - steps: - - template: azure-tests.yml diff --git a/vendor/github.com/gobuffalo/flect/azure-tests.yml b/vendor/github.com/gobuffalo/flect/azure-tests.yml deleted file mode 100644 index eea5822fad..0000000000 --- a/vendor/github.com/gobuffalo/flect/azure-tests.yml +++ /dev/null @@ -1,19 +0,0 @@ -steps: - - task: GoTool@0 - inputs: - version: $(go_version) - - task: Bash@3 - inputs: - targetType: inline - script: | - mkdir -p "$(GOBIN)" - mkdir -p "$(GOPATH)/pkg" - mkdir -p "$(modulePath)" - shopt -s extglob - mv !(gopath) "$(modulePath)" - displayName: "Setup Go Workspace" - - script: | - go get -t -v ./... - go test -race ./... - workingDirectory: "$(modulePath)" - displayName: "Tests" diff --git a/vendor/github.com/gobuffalo/flect/capitalize.go b/vendor/github.com/gobuffalo/flect/capitalize.go index 42ecc166cb..78334fc0f9 100644 --- a/vendor/github.com/gobuffalo/flect/capitalize.go +++ b/vendor/github.com/gobuffalo/flect/capitalize.go @@ -15,13 +15,10 @@ func Capitalize(s string) string { // bob dylan = Bob dylan // widget_id = Widget_id func (i Ident) Capitalize() Ident { - var x string if len(i.Parts) == 0 { return New("") } - x = string(unicode.ToTitle(rune(i.Original[0]))) - if len(i.Original) > 1 { - x += i.Original[1:] - } - return New(x) + runes := []rune(i.Original) + runes[0] = unicode.ToTitle(runes[0]) + return New(string(runes)) } diff --git a/vendor/github.com/gobuffalo/flect/go.mod b/vendor/github.com/gobuffalo/flect/go.mod index cd02d074b9..7c8d049aba 100644 --- a/vendor/github.com/gobuffalo/flect/go.mod +++ b/vendor/github.com/gobuffalo/flect/go.mod @@ -1,8 +1,5 @@ module github.com/gobuffalo/flect -go 1.12 +go 1.13 -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/stretchr/testify v1.3.0 -) +require github.com/stretchr/testify v1.4.0 diff --git a/vendor/github.com/gobuffalo/flect/go.sum b/vendor/github.com/gobuffalo/flect/go.sum index 4f76e62c1f..8fdee5854f 100644 --- a/vendor/github.com/gobuffalo/flect/go.sum +++ b/vendor/github.com/gobuffalo/flect/go.sum @@ -1,9 +1,11 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/gobuffalo/flect/humanize.go b/vendor/github.com/gobuffalo/flect/humanize.go index ded0939703..6a0b75af7b 100644 --- a/vendor/github.com/gobuffalo/flect/humanize.go +++ b/vendor/github.com/gobuffalo/flect/humanize.go @@ -7,11 +7,11 @@ import ( // Humanize returns first letter of sentence capitalized. // Common acronyms are capitalized as well. // Other capital letters in string are left as provided. -// employee_salary = Employee salary -// employee_id = employee ID -// employee_mobile_number = Employee mobile number -// first_Name = First Name -// firstName = First Name +// employee_salary = Employee salary +// employee_id = employee ID +// employee_mobile_number = Employee mobile number +// first_Name = First Name +// firstName = First Name func Humanize(s string) string { return New(s).Humanize().String() } diff --git a/vendor/github.com/gobuffalo/flect/ident.go b/vendor/github.com/gobuffalo/flect/ident.go index 78b51d4576..9189e9a39b 100644 --- a/vendor/github.com/gobuffalo/flect/ident.go +++ b/vendor/github.com/gobuffalo/flect/ident.go @@ -38,9 +38,9 @@ func toParts(s string) []string { return []string{strings.ToUpper(s)} } var prev rune - var x string + var x strings.Builder + x.Grow(len(s)) for _, c := range s { - cs := string(c) // fmt.Println("### cs ->", cs) // fmt.Println("### unicode.IsControl(c) ->", unicode.IsControl(c)) // fmt.Println("### unicode.IsDigit(c) ->", unicode.IsDigit(c)) @@ -58,35 +58,38 @@ func toParts(s string) []string { } if isSpace(c) { - parts = xappend(parts, x) - x = cs + parts = xappend(parts, x.String()) + x.Reset() + x.WriteRune(c) prev = c continue } if unicode.IsUpper(c) && !unicode.IsUpper(prev) { - parts = xappend(parts, x) - x = cs + parts = xappend(parts, x.String()) + x.Reset() + x.WriteRune(c) prev = c continue } - if unicode.IsUpper(c) && baseAcronyms[strings.ToUpper(x)] { - parts = xappend(parts, x) - x = cs + if unicode.IsUpper(c) && baseAcronyms[strings.ToUpper(x.String())] { + parts = xappend(parts, x.String()) + x.Reset() + x.WriteRune(c) prev = c continue } if unicode.IsLetter(c) || unicode.IsDigit(c) || unicode.IsPunct(c) || c == '`' { prev = c - x += cs + x.WriteRune(c) continue } - parts = xappend(parts, x) - x = "" + parts = xappend(parts, x.String()) + x.Reset() prev = c } - parts = xappend(parts, x) + parts = xappend(parts, x.String()) return parts } @@ -94,6 +97,19 @@ func toParts(s string) []string { var _ encoding.TextUnmarshaler = &Ident{} var _ encoding.TextMarshaler = &Ident{} +// LastPart returns the last part/word of the original string +func (i *Ident) LastPart() string { + if len(i.Parts) == 0 { + return "" + } + return i.Parts[len(i.Parts)-1] +} + +// ReplaceSuffix creates a new Ident with the original suffix replaced by new +func (i Ident) ReplaceSuffix(orig, new string) Ident { + return New(strings.TrimSuffix(i.Original, orig) + new) +} + //UnmarshalText unmarshalls byte array into the Ident func (i *Ident) UnmarshalText(data []byte) error { (*i) = New(string(data)) diff --git a/vendor/github.com/gobuffalo/flect/plural_rules.go b/vendor/github.com/gobuffalo/flect/plural_rules.go index 86fca8c5f5..8cd3ba72e7 100644 --- a/vendor/github.com/gobuffalo/flect/plural_rules.go +++ b/vendor/github.com/gobuffalo/flect/plural_rules.go @@ -40,6 +40,7 @@ var singleToPlural = map[string]string{ "bus": "buses", "campus": "campuses", "caucus": "caucuses", + "child": "children", "château": "châteaux", "circus": "circuses", "codex": "codices", @@ -48,7 +49,6 @@ var singleToPlural = map[string]string{ "crisis": "crises", "curriculum": "curriculums", "datum": "data", - "dear": "dear", "deer": "deer", "diagnosis": "diagnoses", "die": "dice", @@ -105,11 +105,13 @@ var singleToPlural = map[string]string{ "prognosis": "prognoses", "prometheus": "prometheuses", "quiz": "quizzes", + "quota": "quotas", "radius": "radiuses", "referendum": "referendums", "ress": "resses", "rice": "rice", "salmon": "salmon", + "sex": "sexes", "series": "series", "sheep": "sheep", "shoe": "shoes", @@ -120,6 +122,7 @@ var singleToPlural = map[string]string{ "swine": "swine", "syllabus": "syllabi", "symposium": "symposiums", + "synapse": "synapses", "synopsis": "synopses", "tableau": "tableaus", "testis": "testes", @@ -128,12 +131,14 @@ var singleToPlural = map[string]string{ "tooth": "teeth", "trout": "trout", "tuna": "tuna", + "vedalia": "vedalias", "vertebra": "vertebrae", "vertix": "vertices", "vita": "vitae", "vortex": "vortices", "wharf": "wharves", "wife": "wives", + "woman": "women", "wolf": "wolves", "you": "you", } @@ -160,7 +165,6 @@ var singularToPluralSuffixList = []singularToPluralSuffix{ {"randum", "randa"}, {"actus", "acti"}, {"adium", "adia"}, - {"alias", "aliases"}, {"basis", "basis"}, {"child", "children"}, {"chive", "chives"}, @@ -168,6 +172,7 @@ var singularToPluralSuffixList = []singularToPluralSuffix{ {"hello", "hellos"}, {"jeans", "jeans"}, {"louse", "lice"}, + {"media", "media"}, {"mouse", "mice"}, {"movie", "movies"}, {"oasis", "oasis"}, @@ -251,12 +256,10 @@ var singularToPluralSuffixList = []singularToPluralSuffix{ {"io", "ios"}, {"jy", "jies"}, {"ky", "kies"}, - {"ld", "ldren"}, {"lf", "lves"}, {"ly", "lies"}, {"my", "mies"}, {"ny", "nies"}, - {"ox", "oxen"}, {"py", "pies"}, {"qy", "qies"}, {"rf", "rves"}, diff --git a/vendor/github.com/gobuffalo/flect/pluralize.go b/vendor/github.com/gobuffalo/flect/pluralize.go index 1b9d43e462..e265f84e91 100644 --- a/vendor/github.com/gobuffalo/flect/pluralize.go +++ b/vendor/github.com/gobuffalo/flect/pluralize.go @@ -15,12 +15,22 @@ func Pluralize(s string) string { return New(s).Pluralize().String() } +// PluralizeWithSize will pluralize a string taking a number number into account. +// PluralizeWithSize("user", 1) = user +// PluralizeWithSize("user", 2) = users +func PluralizeWithSize(s string, i int) string { + if i == 1 || i == -1 { + return New(s).Singularize().String() + } + return New(s).Pluralize().String() +} + // Pluralize returns a plural version of the string // user = users // person = people // datum = data func (i Ident) Pluralize() Ident { - s := i.Original + s := i.LastPart() if len(s) == 0 { return New("") } @@ -33,11 +43,11 @@ func (i Ident) Pluralize() Ident { return i } if p, ok := singleToPlural[ls]; ok { - return New(p) + return i.ReplaceSuffix(s, p) } for _, r := range pluralRules { if strings.HasSuffix(ls, r.suffix) { - return New(r.fn(s)) + return i.ReplaceSuffix(s, r.fn(s)) } } diff --git a/vendor/github.com/gobuffalo/flect/singularize.go b/vendor/github.com/gobuffalo/flect/singularize.go index a0f8545ef2..1ed4995053 100644 --- a/vendor/github.com/gobuffalo/flect/singularize.go +++ b/vendor/github.com/gobuffalo/flect/singularize.go @@ -15,6 +15,16 @@ func Singularize(s string) string { return New(s).Singularize().String() } +// SingularizeWithSize will singular a string taking a number number into account. +// SingularizeWithSize("user", 1) = user +// SingularizeWithSize("user", 2) = users +func SingularizeWithSize(s string, i int) string { + if i == 1 || i == -1 { + return New(s).Singularize().String() + } + return New(s).Pluralize().String() +} + // Singularize returns a singular version of the string // users = user // data = datum diff --git a/vendor/github.com/gobuffalo/flect/underscore.go b/vendor/github.com/gobuffalo/flect/underscore.go index b92488aa00..e1466d99b9 100644 --- a/vendor/github.com/gobuffalo/flect/underscore.go +++ b/vendor/github.com/gobuffalo/flect/underscore.go @@ -18,16 +18,17 @@ func Underscore(s string) string { // Nice to see you! = nice_to_see_you // widgetID = widget_id func (i Ident) Underscore() Ident { - var out []string + out := make([]string, 0, len(i.Parts)) for _, part := range i.Parts { - var x string + var x strings.Builder + x.Grow(len(part)) for _, c := range part { if unicode.IsLetter(c) || unicode.IsDigit(c) { - x += string(c) + x.WriteRune(c) } } - if x != "" { - out = append(out, x) + if x.Len() > 0 { + out = append(out, x.String()) } } return New(strings.ToLower(strings.Join(out, "_"))) diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml index 5597e026dd..604314dd44 100644 --- a/vendor/github.com/mattn/go-isatty/.travis.yml +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -1,13 +1,14 @@ language: go +sudo: false go: + - 1.13.x - tip -os: - - linux - - osx - before_install: - - go get github.com/mattn/goveralls - - go get golang.org/x/tools/cmd/cover + - go get -t -v ./... + script: - - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 + - ./go.test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md index 1e69004bb0..38418353e3 100644 --- a/vendor/github.com/mattn/go-isatty/README.md +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -1,7 +1,7 @@ # go-isatty [![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) -[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty) [![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) [![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) diff --git a/vendor/github.com/mattn/go-isatty/go.mod b/vendor/github.com/mattn/go-isatty/go.mod index f310320c33..605c4c2210 100644 --- a/vendor/github.com/mattn/go-isatty/go.mod +++ b/vendor/github.com/mattn/go-isatty/go.mod @@ -1,3 +1,5 @@ module github.com/mattn/go-isatty -require golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 +go 1.12 + +require golang.org/x/sys v0.0.0-20200116001909-b77594299b42 diff --git a/vendor/github.com/mattn/go-isatty/go.sum b/vendor/github.com/mattn/go-isatty/go.sum index 426c8973c0..912e29cbc1 100644 --- a/vendor/github.com/mattn/go-isatty/go.sum +++ b/vendor/github.com/mattn/go-isatty/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/mattn/go-isatty/go.test.sh b/vendor/github.com/mattn/go-isatty/go.test.sh new file mode 100644 index 0000000000..012162b077 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/go.test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -race -coverprofile=profile.out -covermode=atomic "$d" + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/vendor/github.com/mattn/go-isatty/isatty_android.go b/vendor/github.com/mattn/go-isatty/isatty_android.go deleted file mode 100644 index d3567cb5bf..0000000000 --- a/vendor/github.com/mattn/go-isatty/isatty_android.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build android - -package isatty - -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TCGETS - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} - -// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go index 07e93039db..711f288085 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -3,18 +3,12 @@ package isatty -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TIOCGETA +import "golang.org/x/sys/unix" // IsTerminal return true if the file descriptor is terminal. func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 + _, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA) + return err == nil } // IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 diff --git a/vendor/github.com/mattn/go-isatty/isatty_plan9.go b/vendor/github.com/mattn/go-isatty/isatty_plan9.go new file mode 100644 index 0000000000..c5b6e0c084 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_plan9.go @@ -0,0 +1,22 @@ +// +build plan9 + +package isatty + +import ( + "syscall" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd uintptr) bool { + path, err := syscall.Fd2path(int(fd)) + if err != nil { + return false + } + return path == "/dev/cons" || path == "/mnt/term/dev/cons" +} + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go index 453b025d0d..31a1ca973c 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go +++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go @@ -1,6 +1,5 @@ // +build linux aix // +build !appengine -// +build !android package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go index af51cbcaa4..1fa8691540 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_windows.go +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -4,6 +4,7 @@ package isatty import ( + "errors" "strings" "syscall" "unicode/utf16" @@ -11,15 +12,18 @@ import ( ) const ( - fileNameInfo uintptr = 2 - fileTypePipe = 3 + objectNameInfo uintptr = 1 + fileNameInfo = 2 + fileTypePipe = 3 ) var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") + ntdll = syscall.NewLazyDLL("ntdll.dll") procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") procGetFileType = kernel32.NewProc("GetFileType") + procNtQueryObject = ntdll.NewProc("NtQueryObject") ) func init() { @@ -45,7 +49,10 @@ func isCygwinPipeName(name string) bool { return false } - if token[0] != `\msys` && token[0] != `\cygwin` { + if token[0] != `\msys` && + token[0] != `\cygwin` && + token[0] != `\Device\NamedPipe\msys` && + token[0] != `\Device\NamedPipe\cygwin` { return false } @@ -68,11 +75,35 @@ func isCygwinPipeName(name string) bool { return true } +// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler +// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion +// guys are using Windows XP, this is a workaround for those guys, it will also work on system from +// Windows vista to 10 +// see https://stackoverflow.com/a/18792477 for details +func getFileNameByHandle(fd uintptr) (string, error) { + if procNtQueryObject == nil { + return "", errors.New("ntdll.dll: NtQueryObject not supported") + } + + var buf [4 + syscall.MAX_PATH]uint16 + var result int + r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5, + fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0) + if r != 0 { + return "", e + } + return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil +} + // IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 // terminal. func IsCygwinTerminal(fd uintptr) bool { if procGetFileInformationByHandleEx == nil { - return false + name, err := getFileNameByHandle(fd) + if err != nil { + return false + } + return isCygwinPipeName(name) } // Cygwin/msys's pty is a pipe. diff --git a/vendor/github.com/mattn/go-isatty/renovate.json b/vendor/github.com/mattn/go-isatty/renovate.json new file mode 100644 index 0000000000..5ae9d96b74 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/renovate.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ] +} diff --git a/vendor/k8s.io/apiserver/LICENSE b/vendor/k8s.io/apiserver/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/k8s.io/apiserver/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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/k8s.io/apiserver/pkg/storage/names/generate.go b/vendor/k8s.io/apiserver/pkg/storage/names/generate.go new file mode 100644 index 0000000000..aad9a07f9a --- /dev/null +++ b/vendor/k8s.io/apiserver/pkg/storage/names/generate.go @@ -0,0 +1,54 @@ +/* +Copyright 2014 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 names + +import ( + "fmt" + + utilrand "k8s.io/apimachinery/pkg/util/rand" +) + +// NameGenerator generates names for objects. Some backends may have more information +// available to guide selection of new names and this interface hides those details. +type NameGenerator interface { + // GenerateName generates a valid name from the base name, adding a random suffix to the + // the base. If base is valid, the returned name must also be valid. The generator is + // responsible for knowing the maximum valid name length. + GenerateName(base string) string +} + +// simpleNameGenerator generates random names. +type simpleNameGenerator struct{} + +// SimpleNameGenerator is a generator that returns the name plus a random suffix of five alphanumerics +// when a name is requested. The string is guaranteed to not exceed the length of a standard Kubernetes +// name (63 characters) +var SimpleNameGenerator NameGenerator = simpleNameGenerator{} + +const ( + // TODO: make this flexible for non-core resources with alternate naming rules. + maxNameLength = 63 + randomLength = 5 + maxGeneratedNameLength = maxNameLength - randomLength +) + +func (simpleNameGenerator) GenerateName(base string) string { + if len(base) > maxGeneratedNameLength { + base = base[:maxGeneratedNameLength] + } + return fmt.Sprintf("%s%s", base, utilrand.String(randomLength)) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 36a4b06696..aa6e61af69 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -3,7 +3,7 @@ cloud.google.com/go/compute/metadata # github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 github.com/Azure/go-ansiterm github.com/Azure/go-ansiterm/winterm -# github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd +# github.com/MakeNowJust/heredoc v1.0.0 github.com/MakeNowJust/heredoc # github.com/PuerkitoBio/purell v1.1.1 github.com/PuerkitoBio/purell @@ -43,7 +43,7 @@ github.com/go-openapi/jsonreference github.com/go-openapi/spec # github.com/go-openapi/swag v0.19.5 github.com/go-openapi/swag -# github.com/gobuffalo/flect v0.2.0 +# github.com/gobuffalo/flect v0.2.2 github.com/gobuffalo/flect # github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf/proto @@ -93,7 +93,7 @@ github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter # github.com/mattn/go-colorable v0.1.2 github.com/mattn/go-colorable -# github.com/mattn/go-isatty v0.0.8 +# github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-isatty # github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 github.com/matttproud/golang_protobuf_extensions/pbutil @@ -522,6 +522,8 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect +# k8s.io/apiserver v0.20.0 +k8s.io/apiserver/pkg/storage/names # k8s.io/cli-runtime v0.20.0 k8s.io/cli-runtime/pkg/genericclioptions k8s.io/cli-runtime/pkg/kustomize From c41c00f493ec507bf79afe271ae6a0f65f3ea93e Mon Sep 17 00:00:00 2001 From: Michael Shitrit Date: Sun, 4 Apr 2021 22:21:41 +0300 Subject: [PATCH 2/3] Tidy: removing unnecessary variables, removing unnecessary conversions etc.. Signed-off-by: Michael Shitrit --- pkg/apis/machine/v1beta1/consts.go | 70 +------------------ .../machine/v1beta1/machineset_types_test.go | 6 +- .../machinehealthcheck_controller.go | 4 +- 3 files changed, 8 insertions(+), 72 deletions(-) diff --git a/pkg/apis/machine/v1beta1/consts.go b/pkg/apis/machine/v1beta1/consts.go index 363716809a..a0e977816c 100644 --- a/pkg/apis/machine/v1beta1/consts.go +++ b/pkg/apis/machine/v1beta1/consts.go @@ -16,10 +16,6 @@ limitations under the License. package v1beta1 -// Constants aren't automatically generated for unversioned packages. -// Instead share the same constant for all versioned packages -type MachineStatusError string - const ( // Represents that the combination of configuration in the MachineSpec // is not supported by this cluster. This is not a transient error, but @@ -28,20 +24,6 @@ const ( // Example: the ProviderSpec specifies an instance type that doesn't exist, InvalidConfigurationMachineError MachineStatusError = "InvalidConfiguration" - // This indicates that the MachineSpec has been updated in a way that - // is not supported for reconciliation on this cluster. The spec may be - // completely valid from a configuration standpoint, but the controller - // does not support changing the real world state to match the new - // spec. - // - // Example: the responsible controller is not capable of changing the - // container runtime from docker to rkt. - UnsupportedChangeMachineError MachineStatusError = "UnsupportedChange" - - // This generally refers to exceeding one's quota in a cloud provider, - // or running out of physical machines in an on-premise environment. - InsufficientResourcesMachineError MachineStatusError = "InsufficientResources" - // There was an error while trying to create a Node to match this // Machine. This may indicate a transient problem that will be fixed // automatically with time, such as a service outage, or a terminal @@ -66,56 +48,10 @@ const ( // Example: cannot resolve EC2 IP address. DeleteMachineError MachineStatusError = "DeleteError" - // This error indicates that the machine did not join the cluster - // as a new node within the expected timeframe after instance - // creation at the provider succeeded - // - // Example use case: A controller that deletes Machines which do - // not result in a Node joining the cluster within a given timeout - // and that are managed by a MachineSet - JoinClusterTimeoutMachineError = "JoinClusterTimeoutError" ) -type ClusterStatusError string - -const ( - // InvalidConfigurationClusterError indicates that the cluster - // configuration is invalid. - InvalidConfigurationClusterError ClusterStatusError = "InvalidConfiguration" - - // UnsupportedChangeClusterError indicates that the cluster - // spec has been updated in an unsupported way. That cannot be - // reconciled. - UnsupportedChangeClusterError ClusterStatusError = "UnsupportedChange" - - // CreateClusterError indicates that an error was encountered - // when trying to create the cluster. - CreateClusterError ClusterStatusError = "CreateError" - - // UpdateClusterError indicates that an error was encountered - // when trying to update the cluster. - UpdateClusterError ClusterStatusError = "UpdateError" - - // DeleteClusterError indicates that an error was encountered - // when trying to delete the cluster. - DeleteClusterError ClusterStatusError = "DeleteError" -) +// Constants aren't automatically generated for unversioned packages. +// Instead share the same constant for all versioned packages +type MachineStatusError string type MachineSetStatusError string - -const ( - // Represents that the combination of configuration in the MachineTemplateSpec - // is not supported by this cluster. This is not a transient error, but - // indicates a state that must be fixed before progress can be made. - // - // Example: the ProviderSpec specifies an instance type that doesn't exist. - InvalidConfigurationMachineSetError MachineSetStatusError = "InvalidConfiguration" -) - -type MachineDeploymentStrategyType string - -const ( - // Replace the old MachineSet by new one using rolling update - // i.e. gradually scale down the old MachineSet and scale up the new one. - RollingUpdateMachineDeploymentStrategyType MachineDeploymentStrategyType = "RollingUpdate" -) diff --git a/pkg/apis/machine/v1beta1/machineset_types_test.go b/pkg/apis/machine/v1beta1/machineset_types_test.go index 53d6f3eca5..79ea98498b 100644 --- a/pkg/apis/machine/v1beta1/machineset_types_test.go +++ b/pkg/apis/machine/v1beta1/machineset_types_test.go @@ -88,7 +88,7 @@ func TestDefaults(t *testing.T) { func TestRoundTripMachineSet(t *testing.T) { codecs := serializer.NewCodecFactory(scheme.Scheme) seed := time.Now().UnixNano() - fuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, machineFuzzerFuncs), rand.NewSource(seed), codecs) + machineFuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, machineFuzzerFuncs), rand.NewSource(seed), codecs) ctx := context.Background() g := NewWithT(t) @@ -103,8 +103,8 @@ func TestRoundTripMachineSet(t *testing.T) { // losing data spec := &MachineSetSpec{} status := &MachineSetStatus{} - fuzzer.Fuzz(spec) - fuzzer.Fuzz(status) + machineFuzzer.Fuzz(spec) + machineFuzzer.Fuzz(status) machineSet.Spec = *spec.DeepCopy() g.Expect(c.Create(ctx, machineSet)).To(Succeed()) diff --git a/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go b/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go index 6392ba0286..6b1ae6a657 100644 --- a/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go +++ b/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go @@ -686,7 +686,7 @@ func minDuration(durations []time.Duration) time.Duration { return time.Duration(0) } - minDuration := time.Duration(1 * time.Hour) + minDuration := time.Hour for _, nc := range durations { if nc < minDuration { minDuration = nc @@ -764,7 +764,7 @@ func getIntOrPercentValue(intOrStr *intstr.IntOrString) (int, bool, error) { if err != nil { return 0, isPercent, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err) } - return int(v), isPercent, nil + return v, isPercent, nil } return 0, false, fmt.Errorf("invalid type: neither int nor percentage") } From 338eab54e7ecae11b1c4dcede91266c28b58e300 Mon Sep 17 00:00:00 2001 From: Michael Shitrit Date: Sun, 4 Apr 2021 22:30:58 +0300 Subject: [PATCH 3/3] Feature Code: mhc crd changes, RBAC changes, controller logic and tests Signed-off-by: Michael Shitrit --- ...pi-operator_07_machinehealthcheck.crd.yaml | 25 + .../0000_30_machine-api-operator_09_rbac.yaml | 29 ++ pkg/apis/machine/v1beta1/condition_consts.go | 15 +- pkg/apis/machine/v1beta1/consts.go | 7 + .../v1beta1/machinehealthcheck_types.go | 9 + .../machine/v1beta1/zz_generated.deepcopy.go | 5 + .../machinehealthcheck_controller.go | 153 +++++- .../machinehealthcheck_controller_test.go | 453 ++++++++++++------ pkg/util/conditions/setter.go | 5 + pkg/util/external/util.go | 116 +++++ pkg/util/testing/testing.go | 32 ++ 11 files changed, 684 insertions(+), 165 deletions(-) create mode 100644 pkg/util/external/util.go diff --git a/install/0000_30_machine-api-operator_07_machinehealthcheck.crd.yaml b/install/0000_30_machine-api-operator_07_machinehealthcheck.crd.yaml index 75bbb4a768..83266746c1 100644 --- a/install/0000_30_machine-api-operator_07_machinehealthcheck.crd.yaml +++ b/install/0000_30_machine-api-operator_07_machinehealthcheck.crd.yaml @@ -64,6 +64,31 @@ spec: description: Machines older than this duration without a node will be considered to have failed and will be remediated. Expects an unsigned duration string of decimal numbers each with optional fraction and a unit suffix, eg "300ms", "1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ type: string + remediationTemplate: + description: "RemediationTemplate is a reference to a remediation template provided by an infrastructure provider. \n This field is completely optional, when filled, the MachineHealthCheck controller creates a new object from the template referenced and hands off remediation of the machine to a controller that lives outside of Machine API Operator." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object selector: description: 'Label selector to match machines whose health will be exercised. Note: An empty selector will match all machines.' properties: diff --git a/install/0000_30_machine-api-operator_09_rbac.yaml b/install/0000_30_machine-api-operator_09_rbac.yaml index 0fe7c1e7c8..8f5c712364 100644 --- a/install/0000_30_machine-api-operator_09_rbac.yaml +++ b/install/0000_30_machine-api-operator_09_rbac.yaml @@ -394,6 +394,35 @@ rules: - create - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: machine-api-operator-ext-remediation +aggregationRule: + clusterRoleSelectors: + - matchLabels: # Allowing external remediations to add their permissions + rbac.ext-remediation/aggregate-to-ext-remediation: "true" + +rules: [] # The control plane automatically fills in the rules + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: machine-api-operator-ext-remediation + annotations: + include.release.openshift.io/self-managed-high-availability: "true" + include.release.openshift.io/single-node-developer: "true" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: machine-api-operator-ext-remediation +subjects: + - kind: ServiceAccount + name: machine-api-controllers + namespace: openshift-machine-api + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/pkg/apis/machine/v1beta1/condition_consts.go b/pkg/apis/machine/v1beta1/condition_consts.go index 0ca7a139e8..9b579de3fb 100644 --- a/pkg/apis/machine/v1beta1/condition_consts.go +++ b/pkg/apis/machine/v1beta1/condition_consts.go @@ -17,7 +17,6 @@ limitations under the License. package v1beta1 // Conditions and condition Reasons for the MachineHealthCheck object - const ( // RemediationAllowedCondition is set on MachineHealthChecks to show the status of whether the MachineHealthCheck is // allowed to remediate any Machines or whether it is blocked from remediating any further. @@ -26,6 +25,20 @@ const ( // TooManyUnhealthy is the reason used when too many Machines are unhealthy and the MachineHealthCheck is blocked // from making any further remediations. TooManyUnhealthyReason = "TooManyUnhealthy" + + // ExternalRemediationTemplateAvailable is set on machinehealthchecks when MachineHealthCheck controller uses external remediation. + // ExternalRemediationTemplateAvailable is set to false if external remediation template is not found. + ExternalRemediationTemplateAvailable ConditionType = "ExternalRemediationTemplateAvailable" + + // ExternalRemediationTemplateNotFound is the reason used when a machine health check fails to find external remediation template. + ExternalRemediationTemplateNotFound = "ExternalRemediationTemplateNotFound" + + // ExternalRemediationRequestAvailable is set on machinehealthchecks when MachineHealthCheck controller uses external remediation. + // ExternalRemediationRequestAvailable is set to false if creating external remediation request fails. + ExternalRemediationRequestAvailable ConditionType = "ExternalRemediationRequestAvailable" + + // ExternalRemediationRequestCreationFailed is the reason used when a machine health check fails to create external remediation request. + ExternalRemediationRequestCreationFailed = "ExternalRemediationRequestCreationFailed" ) const ( diff --git a/pkg/apis/machine/v1beta1/consts.go b/pkg/apis/machine/v1beta1/consts.go index a0e977816c..e5f81aee82 100644 --- a/pkg/apis/machine/v1beta1/consts.go +++ b/pkg/apis/machine/v1beta1/consts.go @@ -48,6 +48,13 @@ const ( // Example: cannot resolve EC2 IP address. DeleteMachineError MachineStatusError = "DeleteError" + // TemplateClonedFromGroupKindAnnotation is the infrastructure machine annotation that stores the group-kind of the infrastructure template resource + // that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. + TemplateClonedFromGroupKindAnnotation = "machine.openshift.io/cloned-from-groupkind" + + // TemplateClonedFromNameAnnotation is the infrastructure machine annotation that stores the name of the infrastructure template resource + // that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. + TemplateClonedFromNameAnnotation = "machine.openshift.io/cloned-from-name" ) // Constants aren't automatically generated for unversioned packages. diff --git a/pkg/apis/machine/v1beta1/machinehealthcheck_types.go b/pkg/apis/machine/v1beta1/machinehealthcheck_types.go index 6324e0f745..28bfe29c35 100644 --- a/pkg/apis/machine/v1beta1/machinehealthcheck_types.go +++ b/pkg/apis/machine/v1beta1/machinehealthcheck_types.go @@ -80,6 +80,15 @@ type MachineHealthCheckSpec struct { // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" // +kubebuilder:validation:Type:=string NodeStartupTimeout metav1.Duration `json:"nodeStartupTimeout,omitempty"` + + // RemediationTemplate is a reference to a remediation template + // provided by an infrastructure provider. + // + // This field is completely optional, when filled, the MachineHealthCheck controller + // creates a new object from the template referenced and hands off remediation of the machine to + // a controller that lives outside of Machine API Operator. + // +optional + RemediationTemplate *corev1.ObjectReference `json:"remediationTemplate,omitempty"` } // UnhealthyCondition represents a Node condition type and value with a timeout diff --git a/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go b/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go index 29075384e9..7b85b78750 100644 --- a/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go @@ -200,6 +200,11 @@ func (in *MachineHealthCheckSpec) DeepCopyInto(out *MachineHealthCheckSpec) { **out = **in } out.NodeStartupTimeout = in.NodeStartupTimeout + if in.RemediationTemplate != nil { + in, out := &in.RemediationTemplate, &out.RemediationTemplate + *out = new(corev1.ObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineHealthCheckSpec. diff --git a/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go b/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go index 6b1ae6a657..eed0656031 100644 --- a/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go +++ b/pkg/controller/machinehealthcheck/machinehealthcheck_controller.go @@ -9,6 +9,9 @@ import ( "strings" "time" + "github.com/openshift/machine-api-operator/pkg/util/external" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" @@ -179,9 +182,10 @@ func (r *ReconcileMachineHealthCheck) Reconcile(ctx context.Context, request rec // health check all targets and reconcile mhc status currentHealthy, needRemediationTargets, nextCheckTimes, errList := r.healthCheckTargets(targets, mhc.Spec.NodeStartupTimeout.Duration) - mhc.Status.CurrentHealthy = ¤tHealthy + healthyCount := len(currentHealthy) + mhc.Status.CurrentHealthy = &healthyCount mhc.Status.ExpectedMachines = &totalTargets - unhealthyCount := totalTargets - currentHealthy + unhealthyCount := totalTargets - healthyCount // check MHC current health against MaxUnhealthy if !isAllowedRemediation(mhc) { @@ -189,7 +193,7 @@ func (r *ReconcileMachineHealthCheck) Reconcile(ctx context.Context, request rec request.String(), totalTargets, mhc.Spec.MaxUnhealthy, - totalTargets-currentHealthy, + unhealthyCount, ) message := fmt.Sprintf("Remediation is not allowed, the number of not started or unhealthy machines exceeds maxUnhealthy (total: %v, unhealthy: %v, maxUnhealthy: %v)", @@ -238,16 +242,9 @@ func (r *ReconcileMachineHealthCheck) Reconcile(ctx context.Context, request rec klog.Errorf("Reconciling %s: error patching status: %v", request.String(), err) return reconcile.Result{}, err } - - // remediate - for _, t := range needRemediationTargets { - klog.V(3).Infof("Reconciling %s: meet unhealthy criteria, triggers remediation", t.string()) - if err := t.remediate(r); err != nil { - klog.Errorf("Reconciling %s: error remediating: %v", t.string(), err) - errList = append(errList, err) - } - } - + errList = append(errList, r.remediate(ctx, needRemediationTargets, mhc)...) + // deletes External Machine Remediation for healthy machines - indicating remediation was successful + r.cleanEMR(ctx, currentHealthy, mhc) // return values if len(errList) > 0 { requeueError := apimachineryutilerrors.NewAggregate(errList) @@ -264,6 +261,124 @@ func (r *ReconcileMachineHealthCheck) Reconcile(ctx context.Context, request rec return reconcile.Result{}, nil } +func (r *ReconcileMachineHealthCheck) remediate(ctx context.Context, needRemediationTargets []target, m *mapiv1.MachineHealthCheck) []error { + var errList []error + // remediate unhealthy + for _, t := range needRemediationTargets { + klog.V(3).Infof("Reconciling %s: meet unhealthy criteria, triggers remediation", t.string()) + if m.Spec.RemediationTemplate != nil { + if err := r.externalRemediation(ctx, m, t); err != nil { + klog.Errorf("Reconciling %s: error external remediating: %v", t.string(), err) + errList = append(errList, err) + } + } else { + if err := r.internalRemediation(t); err != nil { + klog.Errorf("Reconciling %s: error remediating: %v", t.string(), err) + errList = append(errList, err) + } + } + } + return errList +} + +// deletes EMR (External Machine Remediation) for healthy machines +func (r *ReconcileMachineHealthCheck) cleanEMR(ctx context.Context, currentHealthy []target, m *mapiv1.MachineHealthCheck) { + if m.Spec.RemediationTemplate == nil { + return + } + for _, t := range currentHealthy { + + // Get remediation request object + obj, err := r.getExternalRemediationRequest(ctx, m, t.Machine.Name) + if err != nil { + if !apierrors.IsNotFound(err) { + klog.Errorf("failed to fetch remediation request for machine %q in namespace %q: %v", t.Machine.Name, t.Machine.Namespace, err) + } + continue + } + // Check that obj has no DeletionTimestamp to avoid hot loop + if obj.GetDeletionTimestamp() == nil { + klog.V(3).Infof("Target has passed health check, deleting the external remediation request", "remediation request name", obj.GetName(), "target", t.string()) + // Issue a delete for remediation request. + if err := r.client.Delete(ctx, obj); err != nil && !apierrors.IsNotFound(err) { + klog.Errorf("failed to delete %v %q for Machine %q: %v", obj.GroupVersionKind(), obj.GetName(), t.Machine.Name, err) + } + } + } +} + +func (r *ReconcileMachineHealthCheck) externalRemediation(ctx context.Context, m *mapiv1.MachineHealthCheck, t target) error { + klog.V(3).Infof(" %s: start external remediation logic", t.string()) + re, err := r.externalRemediationRequestExists(ctx, m, t.Machine.Name) + if err != nil { + return fmt.Errorf("error retrieving external remediation %v %q for machine %q in namespace %q: %v", m.Spec.RemediationTemplate.GroupVersionKind(), m.Spec.RemediationTemplate.Name, t.Machine.Name, t.Machine.Namespace, err) + } + // If external remediation request already exists, + // return early + if re { + return nil + } + + cloneOwnerRef := &metav1.OwnerReference{ + APIVersion: mapiv1.SchemeGroupVersion.String(), + Kind: "Machine", + Name: t.Machine.Name, + UID: t.Machine.UID, + } + from, err := external.Get(ctx, r.client, m.Spec.RemediationTemplate, t.Machine.Namespace) + if err != nil { + conditions.MarkFalse(m, mapiv1.ExternalRemediationTemplateAvailable, mapiv1.ExternalRemediationTemplateNotFound, mapiv1.ConditionSeverityError, err.Error()) + return fmt.Errorf("error retrieving remediation template %v %q for machine %q in namespace %q: %v", m.Spec.RemediationTemplate.GroupVersionKind(), m.Spec.RemediationTemplate.Name, t.Machine.Name, t.Machine.Namespace, err) + } + + generateTemplateInput := &external.GenerateTemplateInput{ + Template: from, + TemplateRef: m.Spec.RemediationTemplate, + Namespace: t.Machine.Namespace, + OwnerRef: cloneOwnerRef, + } + to, err := external.GenerateTemplate(generateTemplateInput) + if err != nil { + return fmt.Errorf("failed to create template for remediation request %v %q for machine %q in namespace %q: %v", m.Spec.RemediationTemplate.GroupVersionKind(), m.Spec.RemediationTemplate.Name, t.Machine.Name, t.Machine.Namespace, err) + } + + // Set the Remediation Request to match the Machine name, the name is used to + // guarantee uniqueness between runs. A Machine should only ever have a single + // remediation object of a specific GVK created. + // + // NOTE: This doesn't guarantee uniqueness across different MHC objects watching + // the same Machine, users are in charge of setting health checks and remediation properly. + to.SetName(t.Machine.Name) + + klog.V(3).Infof("Target has failed health check, creating an external remediation request", "remediation request name", to.GetName(), "target", t.string()) + // Create the external clone. + if err := r.client.Create(ctx, to); err != nil { + conditions.MarkFalse(m, mapiv1.ExternalRemediationRequestAvailable, mapiv1.ExternalRemediationRequestCreationFailed, mapiv1.ConditionSeverityError, err.Error()) + return fmt.Errorf("error creating remediation request for machine %q in namespace %q: %v", t.Machine.Name, t.Machine.Namespace, err) + } + return nil +} + +// getExternalRemediationRequest gets reference to External Remediation Request, unstructured object. +func (r *ReconcileMachineHealthCheck) getExternalRemediationRequest(ctx context.Context, m *mapiv1.MachineHealthCheck, machineName string) (*unstructured.Unstructured, error) { + remediationRef := &corev1.ObjectReference{ + APIVersion: m.Spec.RemediationTemplate.APIVersion, + Kind: strings.TrimSuffix(m.Spec.RemediationTemplate.Kind, external.TemplateSuffix), + Name: machineName, + } + return external.Get(ctx, r.client, remediationRef, m.Namespace) +} + +// externalRemediationRequestExists checks if the External Remediation Request is created +// for the machine. +func (r *ReconcileMachineHealthCheck) externalRemediationRequestExists(ctx context.Context, m *mapiv1.MachineHealthCheck, machineName string) (bool, error) { + remediationReq, err := r.getExternalRemediationRequest(ctx, m, machineName) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } + return remediationReq != nil, nil +} + func isAllowedRemediation(mhc *mapiv1.MachineHealthCheck) bool { maxUnhealthy, err := getMaxUnhealthy(mhc) if err != nil { @@ -323,11 +438,10 @@ func (r *ReconcileMachineHealthCheck) reconcileStatus(baseToPatch client.Patch, // healthCheckTargets health checks a slice of targets // and gives a data to measure the average health -func (r *ReconcileMachineHealthCheck) healthCheckTargets(targets []target, timeoutForMachineToHaveNode time.Duration) (int, []target, []time.Duration, []error) { - var nextCheckTimes []time.Duration +func (r *ReconcileMachineHealthCheck) healthCheckTargets(targets []target, timeoutForMachineToHaveNode time.Duration) ([]target, []target, []time.Duration, []error) { var errList []error - var needRemediationTargets []target - var currentHealthy int + var needRemediationTargets, currentHealthy []target + var nextCheckTimes []time.Duration for _, t := range targets { klog.V(3).Infof("Reconciling %s: health checking", t.string()) needsRemediation, nextCheck, err := t.needsRemediation(timeoutForMachineToHaveNode) @@ -357,7 +471,7 @@ func (r *ReconcileMachineHealthCheck) healthCheckTargets(targets []target, timeo } if t.Machine.DeletionTimestamp == nil { - currentHealthy++ + currentHealthy = append(currentHealthy, t) } } return currentHealthy, needRemediationTargets, nextCheckTimes, errList @@ -488,9 +602,8 @@ func (r *ReconcileMachineHealthCheck) mhcRequestsFromMachine(o client.Object) [] return requests } -func (t *target) remediate(r *ReconcileMachineHealthCheck) error { +func (r *ReconcileMachineHealthCheck) internalRemediation(t target) error { klog.Infof(" %s: start remediation logic", t.string()) - if derefStringPointer(t.Machine.Status.Phase) != machinePhaseFailed { if remediationStrategy, ok := t.MHC.Annotations[remediationStrategyAnnotation]; ok { if mapiv1.RemediationStrategyType(remediationStrategy) == remediationStrategyExternal { diff --git a/pkg/controller/machinehealthcheck/machinehealthcheck_controller_test.go b/pkg/controller/machinehealthcheck/machinehealthcheck_controller_test.go index f7d8b71370..c6e01310a7 100644 --- a/pkg/controller/machinehealthcheck/machinehealthcheck_controller_test.go +++ b/pkg/controller/machinehealthcheck/machinehealthcheck_controller_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/openshift/machine-api-operator/pkg/util/external" + . "github.com/onsi/gomega" mapiv1beta1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" "github.com/openshift/machine-api-operator/pkg/util/conditions" @@ -17,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" @@ -33,12 +36,34 @@ const ( ) var ( - ctx = context.Background() + remediationAllowedCondition = mapiv1beta1.Condition{ + Type: mapiv1beta1.RemediationAllowedCondition, + Status: corev1.ConditionTrue, + } ) +type testCase struct { + name string + machine *mapiv1beta1.Machine + node *corev1.Node + mhc *mapiv1beta1.MachineHealthCheck + expected expectedReconcile + expectedEvents []string + expectedStatus *mapiv1beta1.MachineHealthCheckStatus + externalRemediationMachine *unstructured.Unstructured + externalRemediationTemplate *unstructured.Unstructured +} + +type expectedReconcile struct { + result reconcile.Result + error bool +} + func init() { // Add types to scheme - mapiv1beta1.AddToScheme(scheme.Scheme) + if err := mapiv1beta1.AddToScheme(scheme.Scheme); err != nil { + panic(err) + } } func TestHasMatchingLabels(t *testing.T) { @@ -151,48 +176,6 @@ func TestGetNodeCondition(t *testing.T) { } } -func assertEvents(t *testing.T, testCase string, expectedEvents []string, realEvents chan string) { - if len(expectedEvents) != len(realEvents) { - t.Errorf( - "Test case: %s. Number of expected events (%v) differs from number of real events (%v)", - testCase, - len(expectedEvents), - len(realEvents), - ) - } else { - for _, eventType := range expectedEvents { - select { - case event := <-realEvents: - if !strings.Contains(event, fmt.Sprintf(" %s ", eventType)) { - t.Errorf("Test case: %s. Expected %v event, got: %v", testCase, eventType, event) - } - default: - t.Errorf("Test case: %s. Expected %v event, but no event occured", testCase, eventType) - } - } - } -} - -// newFakeReconciler returns a new reconcile.Reconciler with a fake client -func newFakeReconciler(initObjects ...runtime.Object) *ReconcileMachineHealthCheck { - return newFakeReconcilerWithCustomRecorder(nil, initObjects...) -} - -func newFakeReconcilerWithCustomRecorder(recorder record.EventRecorder, initObjects ...runtime.Object) *ReconcileMachineHealthCheck { - fakeClient := fake.NewFakeClient(initObjects...) - return &ReconcileMachineHealthCheck{ - client: fakeClient, - scheme: scheme.Scheme, - namespace: namespace, - recorder: recorder, - } -} - -type expectedReconcile struct { - result reconcile.Result - error bool -} - func TestReconcile(t *testing.T) { ctx := context.Background() @@ -257,25 +240,12 @@ func TestReconcile(t *testing.T) { machineAlreadyDeleted := maotesting.NewMachine("machineAlreadyDeleted", nodeAlreadyDeleted.Name) machineAlreadyDeleted.SetDeletionTimestamp(&metav1.Time{Time: time.Now()}) - remediationAllowedCondition := mapiv1beta1.Condition{ - Type: mapiv1beta1.RemediationAllowedCondition, - Status: corev1.ConditionTrue, - } - - testCases := []struct { - testCase string - machine *mapiv1beta1.Machine - node *corev1.Node - mhc *mapiv1beta1.MachineHealthCheck - expected expectedReconcile - expectedEvents []string - expectedStatus *mapiv1beta1.MachineHealthCheckStatus - }{ + testCases := []testCase{ { - testCase: "machine unhealthy", - machine: machineUnhealthyForTooLong, - node: nodeUnhealthyForTooLong, - mhc: machineHealthCheck, + name: "machine unhealthy", + machine: machineUnhealthyForTooLong, + node: nodeUnhealthyForTooLong, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -291,10 +261,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine with node healthy", - machine: machineWithNodeHealthy, - node: nodeHealthy, - mhc: machineHealthCheck, + name: "machine with node healthy", + machine: machineWithNodeHealthy, + node: nodeHealthy, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -310,10 +280,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine with node likely to go unhealthy", - machine: machineWithNodeRecentlyUnhealthy, - node: nodeRecentlyUnhealthy, - mhc: machineHealthCheck, + name: "machine with node likely to go unhealthy", + machine: machineWithNodeRecentlyUnhealthy, + node: nodeRecentlyUnhealthy, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{ Requeue: true, @@ -332,10 +302,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "no target: no machine and bad node annotation", - machine: nil, - node: nodeWithoutMachineAnnotation, - mhc: machineHealthCheck, + name: "no target: no machine and bad node annotation", + machine: nil, + node: nodeWithoutMachineAnnotation, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -351,10 +321,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "no target: no machine", - machine: nil, - node: nodeAnnotatedWithNoExistentMachine, - mhc: machineHealthCheck, + name: "no target: no machine", + machine: nil, + node: nodeAnnotatedWithNoExistentMachine, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -370,10 +340,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine no controller owner", - machine: machineWithoutOwnerController, - node: nodeAnnotatedWithMachineWithoutOwnerReference, - mhc: machineHealthCheck, + name: "machine no controller owner", + machine: machineWithoutOwnerController, + node: nodeAnnotatedWithMachineWithoutOwnerReference, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -389,10 +359,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine no noderef", - machine: machineWithoutNodeRef, - node: nodeAnnotatedWithMachineWithoutNodeReference, - mhc: machineHealthCheck, + name: "machine no noderef", + machine: machineWithoutNodeRef, + node: nodeAnnotatedWithMachineWithoutNodeReference, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{ RequeueAfter: nodeStartupTimeout, @@ -410,10 +380,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine already deleted", - machine: machineAlreadyDeleted, - node: nodeAlreadyDeleted, - mhc: machineHealthCheck, + name: "machine already deleted", + machine: machineAlreadyDeleted, + node: nodeAlreadyDeleted, + mhc: machineHealthCheck, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -429,10 +399,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine healthy with MHC negative maxUnhealthy", - machine: machineWithNodeHealthy, - node: nodeHealthy, - mhc: machineHealthCheckNegativeMaxUnhealthy, + name: "machine healthy with MHC negative maxUnhealthy", + machine: machineWithNodeHealthy, + node: nodeHealthy, + mhc: machineHealthCheckNegativeMaxUnhealthy, expected: expectedReconcile{ result: reconcile.Result{}, error: false, @@ -448,10 +418,10 @@ func TestReconcile(t *testing.T) { }, }, { - testCase: "machine unhealthy with MHC negative maxUnhealthy", - machine: machineUnhealthyForTooLong, - node: nodeUnhealthyForTooLong, - mhc: machineHealthCheckNegativeMaxUnhealthy, + name: "machine unhealthy with MHC negative maxUnhealthy", + machine: machineUnhealthyForTooLong, + node: nodeUnhealthyForTooLong, + mhc: machineHealthCheckNegativeMaxUnhealthy, expected: expectedReconcile{ result: reconcile.Result{ Requeue: true, @@ -477,50 +447,104 @@ func TestReconcile(t *testing.T) { } for _, tc := range testCases { - t.Run(tc.testCase, func(t *testing.T) { - var objects []runtime.Object - objects = append(objects, tc.mhc) - if tc.machine != nil { - objects = append(objects, tc.machine) - } - objects = append(objects, tc.node) + t.Run(tc.name, func(t *testing.T) { recorder := record.NewFakeRecorder(2) - r := newFakeReconcilerWithCustomRecorder(recorder, objects...) + r := newFakeReconcilerWithCustomRecorder(recorder, buildRunTimeObjects(tc)...) + assertBaseReconcile(t, tc, ctx, r) + }) + } +} - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: tc.mhc.GetNamespace(), - Name: tc.mhc.GetName(), +func TestReconcileExternalRemediationTemplate(t *testing.T) { + ctx := context.Background() + + nodeHealthy := maotesting.NewNode("NodeHealthy", true) + machineWithNodeHealthy := maotesting.NewMachine("Machine", nodeHealthy.Name) + + nodeUnHealthy := maotesting.NewNode("NodeUnhealthy", false) + machineWithNodeUnHealthy := maotesting.NewMachine("Machine", nodeUnHealthy.Name) + machineWithNodeUnHealthy.APIVersion = mapiv1beta1.SchemeGroupVersion.String() + //external remediation machine template crd + ermTemplate := maotesting.NewExternalRemediationTemplate() + mhcWithRemediationTemplate := newMachineHealthCheckWithRemediationTemplate(ermTemplate) + erm := maotesting.NewExternalRemediationMachine() + + testCases := []testCase{ + + { //When remediationTemplate is set and node transitions back to healthy, new Remediation Request should be deleted + name: "external remediation is done", + machine: machineWithNodeHealthy, + node: nodeHealthy, + mhc: mhcWithRemediationTemplate, + externalRemediationMachine: erm, + externalRemediationTemplate: ermTemplate, + expected: expectedReconcile{ + result: reconcile.Result{}, + error: false, + }, + expectedEvents: []string{}, + expectedStatus: &mapiv1beta1.MachineHealthCheckStatus{ + ExpectedMachines: IntPtr(1), + CurrentHealthy: IntPtr(1), + RemediationsAllowed: 1, + Conditions: mapiv1beta1.Conditions{ + remediationAllowedCondition, }, - } - result, err := r.Reconcile(ctx, request) - assertEvents(t, tc.testCase, tc.expectedEvents, recorder.Events) - if tc.expected.error != (err != nil) { - var errorExpectation string - if !tc.expected.error { - errorExpectation = "no" - } - t.Errorf("Test case: %s. Expected: %s error, got: %v", tc.node.Name, errorExpectation, err) - } + }, + }, - if result != tc.expected.result { - if tc.expected.result.Requeue { - before := tc.expected.result.RequeueAfter - after := tc.expected.result.RequeueAfter + time.Second - if after < result.RequeueAfter || before > result.RequeueAfter { - t.Errorf("Test case: %s. Expected RequeueAfter between: %v and %v, got: %v", tc.node.Name, before, after, result) - } - } else { - t.Errorf("Test case: %s. Expected: %v, got: %v", tc.node.Name, tc.expected.result, result) - } - } + { //When remediationTemplate is set and node transitions to unhealthy, new Remediation Request should be created + name: "create new external remediation", + machine: machineWithNodeUnHealthy, + node: nodeUnHealthy, + mhc: mhcWithRemediationTemplate, + externalRemediationMachine: nil, + externalRemediationTemplate: ermTemplate, + expected: expectedReconcile{ + result: reconcile.Result{}, + error: false, + }, + expectedEvents: []string{}, + expectedStatus: &mapiv1beta1.MachineHealthCheckStatus{ + ExpectedMachines: IntPtr(1), + CurrentHealthy: IntPtr(0), + RemediationsAllowed: 0, + Conditions: mapiv1beta1.Conditions{ + remediationAllowedCondition, + }, + }, + }, + + { //When remediationTemplate is set and node transitions to unhealthy, and a a Remediation Request already exist + name: "external remediation is in process", + machine: machineWithNodeUnHealthy, + node: nodeUnHealthy, + mhc: mhcWithRemediationTemplate, + externalRemediationMachine: erm, + externalRemediationTemplate: ermTemplate, + expected: expectedReconcile{ + result: reconcile.Result{}, + error: false, + }, + expectedEvents: []string{}, + expectedStatus: &mapiv1beta1.MachineHealthCheckStatus{ + ExpectedMachines: IntPtr(1), + CurrentHealthy: IntPtr(0), + RemediationsAllowed: 0, + Conditions: mapiv1beta1.Conditions{ + remediationAllowedCondition, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + recorder := record.NewFakeRecorder(2) + r := newFakeReconcilerWithCustomRecorder(recorder, buildRunTimeObjects(tc)...) + assertBaseReconcile(t, tc, ctx, r) + assertExternalRemediation(t, tc, ctx, r) - if tc.expectedStatus != nil { - g := NewWithT(t) - mhc := &mapiv1beta1.MachineHealthCheck{} - g.Expect(r.client.Get(ctx, request.NamespacedName, mhc)).To(Succeed()) - g.Expect(&mhc.Status).To(MatchMachineHealthCheckStatus(tc.expectedStatus)) - } }) } } @@ -1588,7 +1612,7 @@ func TestNeedsRemediation(t *testing.T) { }, Spec: mapiv1beta1.MachineSpec{}, Status: mapiv1beta1.MachineStatus{ - LastUpdated: &metav1.Time{Time: time.Now().Add(time.Duration(-defaultNodeStartupTimeout) - 1*time.Second)}, + LastUpdated: &metav1.Time{Time: time.Now().Add(-defaultNodeStartupTimeout - 1*time.Second)}, }, }, Node: nil, @@ -1641,7 +1665,7 @@ func TestNeedsRemediation(t *testing.T) { }, Spec: mapiv1beta1.MachineSpec{}, Status: mapiv1beta1.MachineStatus{ - LastUpdated: &metav1.Time{Time: time.Now().Add(time.Duration(-defaultNodeStartupTimeout) - 1*time.Second)}, + LastUpdated: &metav1.Time{Time: time.Now().Add(-defaultNodeStartupTimeout - 1*time.Second)}, }, }, Node: &corev1.Node{ @@ -1769,7 +1793,7 @@ func TestNeedsRemediation(t *testing.T) { }, Spec: mapiv1beta1.MachineSpec{}, Status: mapiv1beta1.MachineStatus{ - LastUpdated: &metav1.Time{Time: time.Now().Add(time.Duration(-defaultNodeStartupTimeout) - 1*time.Second)}, + LastUpdated: &metav1.Time{Time: time.Now().Add(-defaultNodeStartupTimeout - 1*time.Second)}, }, }, Node: &corev1.Node{ @@ -1827,7 +1851,7 @@ func TestNeedsRemediation(t *testing.T) { }, timeoutForMachineToHaveNode: defaultNodeStartupTimeout, expectedNeedsRemediation: false, - expectedNextCheck: time.Duration(1 * time.Minute), // 300-200 rounded + expectedNextCheck: 1 * time.Minute, // 300-200 rounded expectedError: false, }, } @@ -2046,7 +2070,7 @@ func TestRemediate(t *testing.T) { objects = append(objects, runtime.Object(&tc.target.Machine)) recorder := record.NewFakeRecorder(2) r := newFakeReconcilerWithCustomRecorder(recorder, objects...) - if err := tc.target.remediate(r); (err != nil) != tc.expectedError { + if err := r.internalRemediation(*tc.target); (err != nil) != tc.expectedError { t.Errorf("Case: %v. Got: %v, expected error: %v", tc.testCase, err, tc.expectedError) } assertEvents(t, tc.testCase, tc.expectedEvents, recorder.Events) @@ -2404,7 +2428,7 @@ func TestHealthCheckTargets(t *testing.T) { }, Spec: mapiv1beta1.MachineSpec{}, Status: mapiv1beta1.MachineStatus{ - LastUpdated: &metav1.Time{Time: now.Add(time.Duration(-defaultNodeStartupTimeout) + 1*time.Minute)}, + LastUpdated: &metav1.Time{Time: now.Add(-defaultNodeStartupTimeout + 1*time.Minute)}, }, }, Node: nil, @@ -2516,7 +2540,7 @@ func TestHealthCheckTargets(t *testing.T) { r := newFakeReconcilerWithCustomRecorder(recorder) t.Run(tc.testCase, func(t *testing.T) { currentHealhty, needRemediationTargets, nextCheckTimes, errList := r.healthCheckTargets(tc.targets, tc.timeoutForMachineToHaveNode) - if currentHealhty != tc.currentHealthy { + if len(currentHealhty) != tc.currentHealthy { t.Errorf("Case: %v. Got: %v, expected: %v", tc.testCase, currentHealhty, tc.currentHealthy) } if !equality.Semantic.DeepEqual(needRemediationTargets, tc.needRemediationTargets) { @@ -2893,3 +2917,144 @@ func TestGetIntOrPercentValue(t *testing.T) { func IntPtr(i int) *int { return &i } + +func assertEvents(t *testing.T, testCase string, expectedEvents []string, realEvents chan string) { + if len(expectedEvents) != len(realEvents) { + t.Errorf( + "Test case: %s. Number of expected events (%v) differs from number of real events (%v)", + testCase, + len(expectedEvents), + len(realEvents), + ) + } else { + for _, eventType := range expectedEvents { + select { + case event := <-realEvents: + if !strings.Contains(event, fmt.Sprintf(" %s ", eventType)) { + t.Errorf("Test case: %s. Expected %v event, got: %v", testCase, eventType, event) + } + default: + t.Errorf("Test case: %s. Expected %v event, but no event occured", testCase, eventType) + } + } + } +} + +// newFakeReconciler returns a new reconcile.Reconciler with a fake client +func newFakeReconciler(initObjects ...runtime.Object) *ReconcileMachineHealthCheck { + return newFakeReconcilerWithCustomRecorder(nil, initObjects...) +} + +func newFakeReconcilerWithCustomRecorder(recorder record.EventRecorder, initObjects ...runtime.Object) *ReconcileMachineHealthCheck { + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(initObjects...).Build() + return &ReconcileMachineHealthCheck{ + client: fakeClient, + scheme: scheme.Scheme, + namespace: namespace, + recorder: recorder, + } +} + +func assertBaseReconcile(t *testing.T, tc testCase, ctx context.Context, r *ReconcileMachineHealthCheck) { + recorder := r.recorder.(*record.FakeRecorder) + + request := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: tc.mhc.GetNamespace(), + Name: tc.mhc.GetName(), + }, + } + result, err := r.Reconcile(ctx, request) + if &result == nil { + t.Errorf("Test case: %s. Expected: non nil result error, got: nil", tc.node.Name) + } + assertEvents(t, tc.name, tc.expectedEvents, recorder.Events) + if tc.expected.error != (err != nil) { + var errorExpectation string + if !tc.expected.error { + errorExpectation = "no" + } + t.Errorf("Test case: %s. Expected: %s error, got: %v", tc.node.Name, errorExpectation, err) + } + + if result != tc.expected.result { + if tc.expected.result.Requeue { + before := tc.expected.result.RequeueAfter + after := tc.expected.result.RequeueAfter + time.Second + if after < result.RequeueAfter || before > result.RequeueAfter { + t.Errorf("Test case: %s. Expected RequeueAfter between: %v and %v, got: %v", tc.node.Name, before, after, result) + } + } else { + t.Errorf("Test case: %s. Expected: %v, got: %v", tc.node.Name, tc.expected.result, result) + } + } + g := NewWithT(t) + if tc.expectedStatus != nil { + mhc := &mapiv1beta1.MachineHealthCheck{} + g.Expect(r.client.Get(ctx, request.NamespacedName, mhc)).To(Succeed()) + g.Expect(tc.expectedStatus).To(MatchMachineHealthCheckStatus(&mhc.Status)) + } +} + +func assertExternalRemediation(t *testing.T, tc testCase, ctx context.Context, r *ReconcileMachineHealthCheck) { + //When remediationTemplate is set and node transitions to unhealthy, new Remediation Request should be created + nodeReadyStatus := tc.node.Status.Conditions[0].Status + if tc.externalRemediationMachine == nil { + //Trying to get External Machine Remediation + verifyErm(t, tc, ctx, r.client, true) + } else if nodeReadyStatus == corev1.ConditionTrue { //When remediationTemplate is set and node transitions back to healthy, new Remediation Request should be deleted + //Trying to get External Machine Remediation + verifyErm(t, tc, ctx, r.client, false) + } else { //When remediationTemplate is already in process + //Trying to get External Machine Remediation + verifyErm(t, tc, ctx, r.client, true) + } +} + +func newMachineHealthCheckWithRemediationTemplate(infraRemediationTmpl *unstructured.Unstructured) *mapiv1beta1.MachineHealthCheck { + + mhc := maotesting.NewMachineHealthCheck("machineHealthCheck") + remediationTemplateObjRef := &corev1.ObjectReference{ + APIVersion: "infrastructure.machine.openshift.io/v1alpha3", + Kind: "InfrastructureRemediationTemplate", + Name: infraRemediationTmpl.GetName(), + } + + mhc.Spec.RemediationTemplate = remediationTemplateObjRef + return mhc +} + +func buildRunTimeObjects(tc testCase) []runtime.Object { + var objects []runtime.Object + objects = append(objects, tc.mhc) + if tc.machine != nil { + objects = append(objects, tc.machine) + } + objects = append(objects, tc.node) + if tc.externalRemediationTemplate != nil { + objects = append(objects, tc.externalRemediationTemplate) + } + if tc.externalRemediationMachine != nil { + objects = append(objects, tc.externalRemediationMachine) + } + + return objects +} + +func verifyErm(t *testing.T, tc testCase, ctx context.Context, client client.Client, isExist bool) { + g := NewWithT(t) + erm := new(unstructured.Unstructured) + erm.SetAPIVersion(tc.externalRemediationTemplate.GetAPIVersion()) + erm.SetKind(strings.TrimSuffix(tc.externalRemediationTemplate.GetKind(), external.TemplateSuffix)) + erm.SetName(tc.machine.GetName()) + + nameSpace := types.NamespacedName{ + Namespace: tc.externalRemediationTemplate.GetNamespace(), + Name: tc.machine.GetName(), + } + if isExist { + g.Expect(client.Get(ctx, nameSpace, erm)).To(Succeed()) + } else { + g.Expect(client.Get(ctx, nameSpace, erm)).NotTo(Succeed()) + } +} diff --git a/pkg/util/conditions/setter.go b/pkg/util/conditions/setter.go index 73e9a6b4f5..26e4c7db6a 100644 --- a/pkg/util/conditions/setter.go +++ b/pkg/util/conditions/setter.go @@ -110,6 +110,11 @@ func MarkTrue(to Setter, t mapiv1.ConditionType) { Set(to, TrueCondition(t)) } +// MarkFalse sets Status=False for the condition with the given type. +func MarkFalse(to Setter, t mapiv1.ConditionType, reason string, severity mapiv1.ConditionSeverity, messageFormat string, messageArgs ...interface{}) { + Set(to, FalseCondition(t, reason, severity, messageFormat, messageArgs...)) +} + // lexicographicLess returns true if a condition is less than another with regards to the // to order of conditions designed for convenience of the consumer, i.e. kubectl. func lexicographicLess(i, j *mapiv1.Condition) bool { diff --git a/pkg/util/external/util.go b/pkg/util/external/util.go new file mode 100644 index 0000000000..6f0ce10e40 --- /dev/null +++ b/pkg/util/external/util.go @@ -0,0 +1,116 @@ +/* +Copyright 2019 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 external + +import ( + "context" + "fmt" + + mapiv1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apiserver/pkg/storage/names" + + "strings" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // TemplateSuffix is the object kind suffix used by infrastructure references associated + // with MachineSet or MachineDeployments. + TemplateSuffix = "Template" +) + +// Get uses the client and reference to get an external, unstructured object. +func Get(ctx context.Context, c client.Client, ref *corev1.ObjectReference, namespace string) (*unstructured.Unstructured, error) { + obj := new(unstructured.Unstructured) + obj.SetAPIVersion(ref.APIVersion) + obj.SetKind(ref.Kind) + obj.SetName(ref.Name) + key := client.ObjectKey{Name: obj.GetName(), Namespace: namespace} + if err := c.Get(ctx, key, obj); err != nil { + return nil, fmt.Errorf("failed to retrieve %s external object %q/%q: %w", obj.GetKind(), key.Namespace, key.Name, err) + } + return obj, nil +} + +// GenerateTemplate input is everything needed to generate a new template. +type GenerateTemplateInput struct { + // Template is the TemplateRef turned into an unstructured. + // +required + Template *unstructured.Unstructured + + // TemplateRef is a reference to the template that needs to be cloned. + // +required + TemplateRef *corev1.ObjectReference + + // Namespace is the Kubernetes namespace the cloned object should be created into. + // +required + Namespace string + + // OwnerRef is an optional OwnerReference to attach to the cloned object. + // +optional + OwnerRef *metav1.OwnerReference + + // Labels is an optional map of labels to be added to the object. + // +optional + Labels map[string]string +} + +func GenerateTemplate(in *GenerateTemplateInput) (*unstructured.Unstructured, error) { + template, found, err := unstructured.NestedMap(in.Template.Object, "spec", "template") + if !found { + return nil, fmt.Errorf("missing Spec.Template on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) + } else if err != nil { + return nil, fmt.Errorf("failed to retrieve Spec.Template map on %v %q", in.Template.GroupVersionKind(), in.Template.GetName()) + } + + // Create the unstructured object from the template. + to := &unstructured.Unstructured{Object: template} + to.SetResourceVersion("") + to.SetFinalizers(nil) + to.SetUID("") + to.SetSelfLink("") + to.SetName(names.SimpleNameGenerator.GenerateName(in.Template.GetName() + "-")) + to.SetNamespace(in.Namespace) + + if to.GetAnnotations() == nil { + to.SetAnnotations(map[string]string{}) + } + annotations := to.GetAnnotations() + annotations[mapiv1.TemplateClonedFromNameAnnotation] = in.TemplateRef.Name + annotations[mapiv1.TemplateClonedFromGroupKindAnnotation] = in.TemplateRef.GroupVersionKind().GroupKind().String() + to.SetAnnotations(annotations) + + // Set the owner reference. + if in.OwnerRef != nil { + to.SetOwnerReferences([]metav1.OwnerReference{*in.OwnerRef}) + } + + // Set the object APIVersion. + if to.GetAPIVersion() == "" { + to.SetAPIVersion(in.Template.GetAPIVersion()) + } + + // Set the object Kind and strip the word "Template" if it's a suffix. + if to.GetKind() == "" { + to.SetKind(strings.TrimSuffix(in.Template.GetKind(), TemplateSuffix)) + } + return to, nil +} diff --git a/pkg/util/testing/testing.go b/pkg/util/testing/testing.go index 778554a10b..c610ef9bcb 100644 --- a/pkg/util/testing/testing.go +++ b/pkg/util/testing/testing.go @@ -7,6 +7,7 @@ import ( mapiv1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/utils/pointer" ) @@ -38,6 +39,37 @@ func NewSelectorFooBar() *metav1.LabelSelector { return NewSelector(FooBar()) } +func NewExternalRemediationTemplate() *unstructured.Unstructured { + // Create remediation template resource. + infraRemediationTmpl := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "InfrastructureRemediationTemplate", + "apiVersion": "infrastructure.machine.openshift.io/v1alpha3", + "spec": map[string]interface{}{ + "template": map[string]interface{}{}, + }, + "metadata": map[string]interface{}{ + "namespace": Namespace, + }, + }, + } + + return infraRemediationTmpl +} + +func NewExternalRemediationMachine() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "InfrastructureRemediation", + "apiVersion": "infrastructure.machine.openshift.io/v1alpha3", + "metadata": map[string]interface{}{ + "name": "Machine", + "namespace": Namespace, + }, + }, + } +} + // NewNode returns new node object that can be used for testing func NewNode(name string, ready bool) *corev1.Node { nodeReadyStatus := corev1.ConditionTrue